Merge branch 'main' into PAM-10
@@ -150,8 +150,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
|
||||
"integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
"version": "5.2.0",
|
||||
@@ -196,6 +195,7 @@
|
||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.3",
|
||||
@@ -488,6 +488,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.7.2.tgz",
|
||||
"integrity": "sha512-KjKXlcjKbUz8dKw7PY56F7qlfOFgxTU6tnlJ8YrbDyWkJMIlHa6VRWzCD8RU20zbJUC1hExhOFggZjm6tf1mUw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ucast/mongo2js": "^1.3.0"
|
||||
},
|
||||
@@ -546,6 +547,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -1323,6 +1325,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.1.tgz",
|
||||
"integrity": "sha512-8dBIHbfsKlCk2jHQ9PoRBg2Z+4TwyE3vZICSnoDlnsHA6SiMlTwfmW6yX0lHsRmWJugkeb92sA0hZdkXJhuz+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.7.1"
|
||||
},
|
||||
@@ -2013,6 +2016,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz",
|
||||
"integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^5.0.0",
|
||||
"@octokit/graphql": "^8.0.0",
|
||||
@@ -4408,6 +4412,7 @@
|
||||
"integrity": "sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/utils": "^8.13.0",
|
||||
"eslint-visitor-keys": "^4.2.0",
|
||||
@@ -5033,6 +5038,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.95.1.tgz",
|
||||
"integrity": "sha512-P5x4yNhcdkYsCEoYeGZP8Q9Jlxf0WXJa4G/xvbmM905seZc9FqJqvCSRvX3dWTPOXRABhl4g+8DHqfft0c/AvQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/history": "1.95.0",
|
||||
"@tanstack/react-store": "^0.7.0",
|
||||
@@ -5244,7 +5250,6 @@
|
||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
@@ -5265,7 +5270,6 @@
|
||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
}
|
||||
@@ -5276,7 +5280,6 @@
|
||||
"integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": "^4.4.0",
|
||||
"aria-query": "^5.0.0",
|
||||
@@ -5296,8 +5299,7 @@
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
|
||||
"integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@testing-library/user-event": {
|
||||
"version": "14.6.1",
|
||||
@@ -5305,7 +5307,6 @@
|
||||
"integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
@@ -5326,8 +5327,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
@@ -5380,7 +5380,6 @@
|
||||
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/deep-eql": "*",
|
||||
"assertion-error": "^2.0.1"
|
||||
@@ -5454,8 +5453,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
|
||||
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/doctrine": {
|
||||
"version": "0.0.9",
|
||||
@@ -5591,6 +5589,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.16.tgz",
|
||||
"integrity": "sha512-oh8AMIC4Y2ciKufU8hnKgs+ufgbA/dhPTACaZPM86AbwX9QwnFtSoPWEeRUj8fge+v6kFt78BXcDhAU1SrrAsw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
@@ -5602,6 +5601,7 @@
|
||||
"integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
}
|
||||
@@ -5649,6 +5649,7 @@
|
||||
"integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.0",
|
||||
@@ -5689,6 +5690,7 @@
|
||||
"integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.34.0",
|
||||
"@typescript-eslint/types": "8.34.0",
|
||||
@@ -5960,7 +5962,6 @@
|
||||
"integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "3.2.4",
|
||||
@@ -5978,7 +5979,6 @@
|
||||
"integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/spy": "3.2.4",
|
||||
"estree-walker": "^3.0.3",
|
||||
@@ -6006,7 +6006,6 @@
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
@@ -6017,7 +6016,6 @@
|
||||
"integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^2.0.0"
|
||||
},
|
||||
@@ -6031,7 +6029,6 @@
|
||||
"integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tinyspy": "^4.0.3"
|
||||
},
|
||||
@@ -6045,7 +6042,6 @@
|
||||
"integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "3.2.4",
|
||||
"loupe": "^3.1.4",
|
||||
@@ -6119,6 +6115,7 @@
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -6235,7 +6232,6 @@
|
||||
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -6284,7 +6280,6 @@
|
||||
"integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
@@ -6365,7 +6360,6 @@
|
||||
"integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
@@ -6453,7 +6447,6 @@
|
||||
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@@ -6464,7 +6457,6 @@
|
||||
"integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
@@ -6477,8 +6469,7 @@
|
||||
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
|
||||
"integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
@@ -6528,7 +6519,6 @@
|
||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -6630,7 +6620,6 @@
|
||||
"integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"open": "^8.0.4"
|
||||
},
|
||||
@@ -6867,6 +6856,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.9",
|
||||
"caniuse-lite": "^1.0.30001746",
|
||||
@@ -7048,7 +7038,6 @@
|
||||
"integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"assertion-error": "^2.0.1",
|
||||
"check-error": "^2.1.1",
|
||||
@@ -7119,7 +7108,6 @@
|
||||
"integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
@@ -7505,8 +7493,7 @@
|
||||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
@@ -7525,7 +7512,8 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cva": {
|
||||
"name": "class-variance-authority",
|
||||
@@ -7597,6 +7585,7 @@
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@@ -7650,8 +7639,7 @@
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
"version": "1.0.1",
|
||||
@@ -7761,7 +7749,6 @@
|
||||
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -7796,7 +7783,6 @@
|
||||
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -7918,8 +7904,7 @@
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
@@ -8129,7 +8114,6 @@
|
||||
"integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
@@ -8213,6 +8197,7 @@
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
@@ -8254,7 +8239,6 @@
|
||||
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
@@ -8291,6 +8275,7 @@
|
||||
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -8347,6 +8332,7 @@
|
||||
"integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"object.assign": "^4.1.2",
|
||||
@@ -8369,6 +8355,7 @@
|
||||
"integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"confusing-browser-globals": "^1.0.10",
|
||||
"object.assign": "^4.1.2",
|
||||
@@ -8399,6 +8386,7 @@
|
||||
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
@@ -8498,6 +8486,7 @@
|
||||
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.8",
|
||||
@@ -8627,7 +8616,6 @@
|
||||
"integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"array-includes": "^3.1.8",
|
||||
"array.prototype.findlast": "^1.2.5",
|
||||
@@ -8661,6 +8649,7 @@
|
||||
"integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -8684,7 +8673,6 @@
|
||||
"integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esutils": "^2.0.2"
|
||||
},
|
||||
@@ -8698,7 +8686,6 @@
|
||||
"integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
"path-parse": "^1.0.7",
|
||||
@@ -8717,7 +8704,6 @@
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -8879,7 +8865,6 @@
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
"esvalidate": "bin/esvalidate.js"
|
||||
@@ -9910,6 +9895,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
},
|
||||
@@ -10003,7 +9989,6 @@
|
||||
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -10280,7 +10265,6 @@
|
||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"is-docker": "cli.js"
|
||||
},
|
||||
@@ -10602,7 +10586,6 @@
|
||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0"
|
||||
},
|
||||
@@ -10638,7 +10621,6 @@
|
||||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
|
||||
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "GitHub Sponsors ❤",
|
||||
"url": "https://github.com/sponsors/dmonad"
|
||||
@@ -10650,7 +10632,6 @@
|
||||
"integrity": "sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-object-atoms": "^1.0.0",
|
||||
@@ -10800,7 +10781,6 @@
|
||||
"integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"array-includes": "^3.1.6",
|
||||
"array.prototype.flat": "^1.3.1",
|
||||
@@ -10835,8 +10815,7 @@
|
||||
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
|
||||
"integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
|
||||
"dev": true,
|
||||
"license": "CC0-1.0",
|
||||
"peer": true
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/language-tags": {
|
||||
"version": "1.0.9",
|
||||
@@ -10844,7 +10823,6 @@
|
||||
"integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"language-subtag-registry": "^0.3.20"
|
||||
},
|
||||
@@ -10877,7 +10855,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.102.tgz",
|
||||
"integrity": "sha512-g70kydI0I1sZU0ChO8mBbhw0oUW/8U0GHzygpvEIx8k+jgOpqnTSb/E+70toYVqHxBhrERD21TwD5QcZJQ40ZQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"isomorphic.js": "^0.2.4"
|
||||
},
|
||||
@@ -11202,8 +11179,7 @@
|
||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
|
||||
"integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
@@ -11230,7 +11206,6 @@
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
@@ -11922,7 +11897,6 @@
|
||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
@@ -12271,7 +12245,6 @@
|
||||
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"define-lazy-prop": "^2.0.0",
|
||||
"is-docker": "^2.1.1",
|
||||
@@ -12541,7 +12514,6 @@
|
||||
"integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 14.16"
|
||||
}
|
||||
@@ -12693,6 +12665,7 @@
|
||||
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -12809,7 +12782,6 @@
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
@@ -12825,7 +12797,6 @@
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -12838,8 +12809,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.30.0",
|
||||
@@ -13040,6 +13010,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
@@ -13067,6 +13038,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
||||
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@@ -13158,6 +13130,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
@@ -13208,6 +13181,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.3.tgz",
|
||||
"integrity": "sha512-IK18V6GVbab4TAo1/cz3kqajxbDPGofdF0w7VHdCo0Nt8PrPlOZcuuDq9YYIV1BtjcX78x0XsldbQRQnQXWXmw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@@ -13458,7 +13432,6 @@
|
||||
"integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ast-types": "^0.16.1",
|
||||
"esprima": "~4.0.0",
|
||||
@@ -13476,7 +13449,6 @@
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -13493,7 +13465,6 @@
|
||||
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"indent-string": "^4.0.0",
|
||||
"strip-indent": "^3.0.0"
|
||||
@@ -13508,7 +13479,6 @@
|
||||
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"min-indent": "^1.0.0"
|
||||
},
|
||||
@@ -13723,6 +13693,7 @@
|
||||
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
@@ -14233,7 +14204,6 @@
|
||||
"integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
@@ -14249,7 +14219,6 @@
|
||||
"integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"define-properties": "^1.2.1",
|
||||
@@ -14277,7 +14246,6 @@
|
||||
"integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.17.5"
|
||||
@@ -14498,7 +14466,8 @@
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
|
||||
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
@@ -14602,7 +14571,6 @@
|
||||
"integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
@@ -14613,7 +14581,6 @@
|
||||
"integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
@@ -14898,6 +14865,7 @@
|
||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -15283,6 +15251,7 @@
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -15700,7 +15669,6 @@
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@@ -15745,6 +15713,7 @@
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
|
||||
"integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
@@ -15895,6 +15864,7 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
|
||||
"integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 86 KiB |
@@ -67,7 +67,8 @@ export const createNotification = (
|
||||
...toastProps,
|
||||
autoClose: toastProps.autoClose || 15000,
|
||||
theme: "dark",
|
||||
type: myProps?.type || "info"
|
||||
type: myProps?.type || "info",
|
||||
className: `pointer-events-auto ${toastProps.className}`
|
||||
});
|
||||
|
||||
export const NotificationContainer = () => (
|
||||
|
||||
@@ -9,7 +9,7 @@ import { cva, type VariantProps } from "cva";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
const alertVariants = cva(
|
||||
"w-full bg-mineshaft-800 rounded-lg border border-bunker-400 px-4 py-3 text-sm flex items-center gap-x-4",
|
||||
"w-full bg-mineshaft-800 rounded-lg border border-bunker-400 px-4 py-3 text-sm flex items-center gap-x-3",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
@@ -28,6 +28,7 @@ type AlertProps = {
|
||||
title?: string;
|
||||
hideTitle?: boolean;
|
||||
icon?: React.ReactNode;
|
||||
iconClassName?: string;
|
||||
};
|
||||
|
||||
const variantTitleMap = {
|
||||
@@ -45,36 +46,41 @@ const variantIconMap = {
|
||||
const Alert = forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> & AlertProps
|
||||
>(({ className, variant, title, icon, hideTitle = false, children, ...props }, ref) => {
|
||||
const defaultTitle = title ?? variantTitleMap[variant ?? "default"];
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
role="alert"
|
||||
className={twMerge(alertVariants({ variant }), className)}
|
||||
{...props}
|
||||
>
|
||||
<div>
|
||||
{typeof icon !== "undefined" ? (
|
||||
<>{icon} </>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
className="text-lg text-primary"
|
||||
icon={variantIconMap[variant ?? "default"]}
|
||||
/>
|
||||
)}
|
||||
>(
|
||||
(
|
||||
{ className, variant, title, icon, hideTitle = false, children, iconClassName, ...props },
|
||||
ref
|
||||
) => {
|
||||
const defaultTitle = title ?? variantTitleMap[variant ?? "default"];
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
role="alert"
|
||||
className={twMerge(alertVariants({ variant }), className)}
|
||||
{...props}
|
||||
>
|
||||
<div>
|
||||
{typeof icon !== "undefined" ? (
|
||||
<>{icon} </>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
className={twMerge("text-lg text-primary", iconClassName)}
|
||||
icon={variantIconMap[variant ?? "default"]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
{hideTitle ? null : (
|
||||
<h5 className="leading-6 font-medium tracking-tight" {...props}>
|
||||
{defaultTitle}
|
||||
</h5>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
{hideTitle ? null : (
|
||||
<h5 className="leading-6 font-medium tracking-tight" {...props}>
|
||||
{defaultTitle}
|
||||
</h5>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
);
|
||||
}
|
||||
);
|
||||
Alert.displayName = "Alert";
|
||||
|
||||
const AlertDescription = forwardRef<
|
||||
|
||||
@@ -55,6 +55,19 @@ export const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
|
||||
{...props}
|
||||
ref={forwardedRef}
|
||||
className={twMerge(drawerContentVariation({ direction, className }))}
|
||||
onPointerDownOutside={(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const toastElement = target.closest('[class*="Toastify"]');
|
||||
if (toastElement) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
props.onPointerDownOutside?.(e);
|
||||
}}
|
||||
>
|
||||
<Card isRounded={false} className="dark h-full w-full">
|
||||
{title && (
|
||||
|
||||
@@ -2,10 +2,15 @@ import { forwardRef, TextareaHTMLAttributes, useCallback, useMemo, useRef, useSt
|
||||
import { faFolder, faKey, faLayerGroup, faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
import { useProject } from "@app/context";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { useProject, useProjectPermission } from "@app/context";
|
||||
import { ProjectPermissionSecretActions } from "@app/context/ProjectPermissionContext/types";
|
||||
import { useDebounce, useToggle } from "@app/hooks";
|
||||
import { useGetProjectFolders, useGetProjectSecrets } from "@app/hooks/api";
|
||||
import { hasSecretReadValueOrDescribePermission } from "@app/lib/fn/permission";
|
||||
|
||||
import { SecretInput } from "../SecretInput";
|
||||
|
||||
@@ -80,6 +85,8 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
) => {
|
||||
const { currentProject } = useProject();
|
||||
const projectId = currentProject?.id || "";
|
||||
const navigate = useNavigate({ from: ROUTE_PATHS.SecretManager.SecretDashboardPage.path });
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
const [debouncedValue] = useDebounce(value, 100);
|
||||
|
||||
@@ -307,6 +314,120 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClickSegment = useCallback(
|
||||
(segment: string, allSegments: string[]) => {
|
||||
if (!projectId) {
|
||||
createNotification({
|
||||
text: "Project ID is not set",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (allSegments.length === 0) {
|
||||
createNotification({
|
||||
text: "Invalid secret reference",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (allSegments.length === 1) {
|
||||
const canReadSecretValue = hasSecretReadValueOrDescribePermission(
|
||||
permission,
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
{
|
||||
environment: propEnvironment ?? "*",
|
||||
secretPath: propSecretPath ?? "/",
|
||||
secretName: segment,
|
||||
secretTags: ["*"]
|
||||
}
|
||||
);
|
||||
|
||||
if (!canReadSecretValue) {
|
||||
createNotification({
|
||||
text: "You do not have permission to access this secret",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
navigate({
|
||||
search: (prev) => ({
|
||||
...prev,
|
||||
search: segment,
|
||||
filterBy: "secret",
|
||||
tags: ""
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const environmentSlug = allSegments[0];
|
||||
const secretName = allSegments[allSegments.length - 1];
|
||||
let folderPath = "/";
|
||||
|
||||
if (allSegments.length > 2) {
|
||||
const pathSegments = allSegments.slice(1, -1);
|
||||
for (let i = 0; i < pathSegments.length; i += 1) {
|
||||
if (!pathSegments[i]) {
|
||||
createNotification({
|
||||
text: "Invalid secret reference",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const pathSegment = pathSegments[i];
|
||||
folderPath += `${pathSegment}`;
|
||||
if (pathSegment === segment) {
|
||||
folderPath += "/";
|
||||
break;
|
||||
}
|
||||
folderPath += "/";
|
||||
}
|
||||
}
|
||||
|
||||
// Only validate secret permission, users can always view environments and folders
|
||||
if (segment === secretName) {
|
||||
const canReadSecretValue = hasSecretReadValueOrDescribePermission(
|
||||
permission,
|
||||
ProjectPermissionSecretActions.ReadValue,
|
||||
{
|
||||
environment: environmentSlug,
|
||||
secretPath: folderPath,
|
||||
secretName,
|
||||
secretTags: ["*"]
|
||||
}
|
||||
);
|
||||
|
||||
if (!canReadSecretValue) {
|
||||
createNotification({
|
||||
text: "You do not have permission to access this secret",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
navigate({
|
||||
to: ROUTE_PATHS.SecretManager.SecretDashboardPage.path,
|
||||
params: {
|
||||
projectId,
|
||||
envSlug: environmentSlug
|
||||
},
|
||||
search: (prev) => ({
|
||||
...prev,
|
||||
secretPath: segment === environmentSlug ? "/" : folderPath,
|
||||
search: segment === secretName ? secretName : prev.search,
|
||||
filterBy: segment === secretName ? "secret" : prev.filterBy,
|
||||
tags: ""
|
||||
})
|
||||
});
|
||||
},
|
||||
[navigate, projectId, permission, propEnvironment, propSecretPath]
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover.Root open={isPopupOpen} onOpenChange={handlePopUpOpen}>
|
||||
<Popover.Trigger asChild>
|
||||
@@ -329,6 +450,7 @@ export const InfisicalSecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
}}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
containerClassName={containerClassName}
|
||||
onClickSegment={handleClickSegment}
|
||||
/>
|
||||
</Popover.Trigger>
|
||||
<Popover.Content
|
||||
|
||||
@@ -15,6 +15,7 @@ export type ModalContentProps = Omit<DialogPrimitive.DialogContentProps, "title"
|
||||
onClose?: () => void;
|
||||
overlayClassName?: string;
|
||||
showCloseButton?: boolean;
|
||||
closeOnOutsideClick?: boolean;
|
||||
};
|
||||
|
||||
export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
|
||||
@@ -29,6 +30,7 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
|
||||
bodyClassName,
|
||||
onClose,
|
||||
showCloseButton = true,
|
||||
closeOnOutsideClick = true,
|
||||
...props
|
||||
},
|
||||
forwardedRef
|
||||
@@ -38,7 +40,23 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
|
||||
className={twMerge("animate-fade-in fixed inset-0 z-30 h-full w-full", overlayClassName)}
|
||||
style={{ backgroundColor: "rgba(0, 0, 0, 0.7)" }}
|
||||
/>
|
||||
<DialogPrimitive.Content {...props} ref={forwardedRef}>
|
||||
<DialogPrimitive.Content
|
||||
{...props}
|
||||
ref={forwardedRef}
|
||||
onPointerDownOutside={(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const toastElement = target.closest('[class*="Toastify"]');
|
||||
if (toastElement) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (closeOnOutsideClick && onClose) {
|
||||
onClose();
|
||||
}
|
||||
props.onPointerDownOutside?.(e);
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
isRounded
|
||||
className={twMerge(
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
/* eslint-disable react/no-danger */
|
||||
import { forwardRef, TextareaHTMLAttributes } from "react";
|
||||
import { forwardRef, TextareaHTMLAttributes, useEffect, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { HIDDEN_SECRET_VALUE } from "@app/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem";
|
||||
|
||||
const REGEX = /(\${([a-zA-Z0-9-_.]+)})/g;
|
||||
const REGEX = /(\${([a-zA-Z0-9-_. ]+)})/g;
|
||||
|
||||
const syntaxHighlight = (
|
||||
content?: string | null,
|
||||
isVisible?: boolean,
|
||||
isImport?: boolean,
|
||||
isLoadingValue?: boolean,
|
||||
isErrorLoadingValue?: boolean
|
||||
isErrorLoadingValue?: boolean,
|
||||
onHoverPart?: (part: string) => void,
|
||||
hoveredPart?: string,
|
||||
isCmdOrCtrlPressed?: boolean,
|
||||
onClickSegment?: (segment: string, allSegments: string[]) => void,
|
||||
placeholder?: string
|
||||
) => {
|
||||
if (isLoadingValue) return HIDDEN_SECRET_VALUE;
|
||||
if (isErrorLoadingValue)
|
||||
return <span className="ph-no-capture text-red/75">Error loading secret value.</span>;
|
||||
if (isImport && !content) return "IMPORTED";
|
||||
if (placeholder && (content === "" || !content)) return placeholder;
|
||||
if (content === "") return "EMPTY";
|
||||
if (!content) return "EMPTY";
|
||||
if (!isVisible) return HIDDEN_SECRET_VALUE;
|
||||
@@ -27,10 +33,57 @@ const syntaxHighlight = (
|
||||
const isInterpolationSyntax = el.startsWith("${") && el.endsWith("}");
|
||||
if (isInterpolationSyntax) {
|
||||
skipNext = true;
|
||||
const part = el;
|
||||
const innerContent = el.slice(2, -1); // Remove ${ and }
|
||||
const parts = innerContent.split(".");
|
||||
|
||||
return (
|
||||
<span className="ph-no-capture text-yellow" key={`secret-value-${i + 1}`}>
|
||||
<span className="ph-no-capture relative z-10 text-yellow" key={`secret-value-${i + 1}`}>
|
||||
${
|
||||
<span className="ph-no-capture text-yellow-200/80">{el.slice(2, -1)}</span>
|
||||
{parts.map((segment, segmentIndex) => {
|
||||
const segmentKey = `${part}-segment-${segmentIndex}`;
|
||||
const isHovered = hoveredPart === segmentKey;
|
||||
const shouldShowHoverStyle = isHovered && isCmdOrCtrlPressed;
|
||||
|
||||
return (
|
||||
<span key={segmentKey}>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={isCmdOrCtrlPressed ? 0 : -1}
|
||||
className={`ph-no-capture text-yellow-200/80 ${
|
||||
isCmdOrCtrlPressed ? "pointer-events-auto" : "pointer-events-none"
|
||||
} ${shouldShowHoverStyle ? "cursor-pointer underline decoration-yellow-400" : ""}`}
|
||||
onMouseEnter={() => onHoverPart?.(segmentKey)}
|
||||
onMouseLeave={() => onHoverPart?.("")}
|
||||
onMouseDown={(e) => {
|
||||
if (isCmdOrCtrlPressed) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (isCmdOrCtrlPressed) {
|
||||
e.preventDefault();
|
||||
onClickSegment?.(segment, parts);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (isCmdOrCtrlPressed && (e.key === "Enter" || e.key === " ")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onClickSegment?.(segment, parts);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{segment}
|
||||
</span>
|
||||
{segmentIndex < parts.length - 1 && (
|
||||
<span className="ph-no-capture pointer-events-none text-yellow-200/80">.</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
}
|
||||
</span>
|
||||
);
|
||||
@@ -60,6 +113,7 @@ type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
||||
canEditButNotView?: boolean;
|
||||
isLoadingValue?: boolean;
|
||||
isErrorLoadingValue?: boolean;
|
||||
onClickSegment?: (segment: string, allSegments: string[]) => void;
|
||||
};
|
||||
|
||||
const commonClassName = "font-mono text-sm caret-white border-none outline-hidden w-full break-all";
|
||||
@@ -79,11 +133,43 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
canEditButNotView,
|
||||
isLoadingValue,
|
||||
isErrorLoadingValue,
|
||||
onClickSegment,
|
||||
placeholder,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [isSecretFocused, setIsSecretFocused] = useToggle();
|
||||
const [hoveredPart, setHoveredPart] = useState<string | undefined>();
|
||||
const [isCmdOrCtrlPressed, setIsCmdOrCtrlPressed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
setIsCmdOrCtrlPressed(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyUp = (e: KeyboardEvent) => {
|
||||
if (!e.metaKey && !e.ctrlKey) {
|
||||
setIsCmdOrCtrlPressed(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setIsCmdOrCtrlPressed(false);
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
window.addEventListener("keyup", handleKeyUp);
|
||||
window.addEventListener("blur", handleBlur);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
window.removeEventListener("keyup", handleKeyUp);
|
||||
window.removeEventListener("blur", handleBlur);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -91,20 +177,33 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
style={{ maxHeight: `${21 * 7}px` }}
|
||||
>
|
||||
<div className="relative overflow-hidden">
|
||||
<pre aria-hidden className="m-0">
|
||||
<pre aria-hidden className="pointer-events-none relative z-10 m-0">
|
||||
<code className={`inline-block w-full ${commonClassName}`}>
|
||||
<span style={{ whiteSpace: "break-spaces" }}>
|
||||
<span
|
||||
className={twMerge(
|
||||
"whitespace-break-spaces",
|
||||
placeholder && !value && "text-gray-500/50"
|
||||
)}
|
||||
>
|
||||
{syntaxHighlight(
|
||||
value,
|
||||
isVisible || (isSecretFocused && !valueAlwaysHidden),
|
||||
isImport,
|
||||
isLoadingValue,
|
||||
isErrorLoadingValue
|
||||
isErrorLoadingValue,
|
||||
(part) => {
|
||||
setHoveredPart(part);
|
||||
},
|
||||
hoveredPart,
|
||||
isCmdOrCtrlPressed,
|
||||
onClickSegment,
|
||||
placeholder
|
||||
)}
|
||||
</span>
|
||||
</code>
|
||||
</pre>
|
||||
<textarea
|
||||
placeholder={placeholder}
|
||||
style={{ whiteSpace: "break-spaces" }}
|
||||
aria-label="secret value"
|
||||
ref={ref}
|
||||
@@ -128,6 +227,9 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
onBlur?.(evt);
|
||||
setIsSecretFocused.off();
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHoveredPart(undefined);
|
||||
}}
|
||||
value={value || ""}
|
||||
{...props}
|
||||
readOnly={isReadOnly || isLoadingValue || isErrorLoadingValue}
|
||||
|
||||
@@ -78,7 +78,11 @@ export enum ProjectPermissionIdentityActions {
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
GrantPrivileges = "grant-privileges",
|
||||
AssumePrivileges = "assume-privileges"
|
||||
AssumePrivileges = "assume-privileges",
|
||||
RevokeAuth = "revoke-auth",
|
||||
CreateToken = "create-token",
|
||||
GetToken = "get-token",
|
||||
DeleteToken = "delete-token"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionMemberActions {
|
||||
@@ -129,7 +133,8 @@ export enum ProjectPermissionCertificateProfileActions {
|
||||
Create = "create",
|
||||
Edit = "edit",
|
||||
Delete = "delete",
|
||||
IssueCert = "issue-cert"
|
||||
IssueCert = "issue-cert",
|
||||
RevealAcmeEabSecret = "reveal-acme-eab-secret"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionSecretRotationActions {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { faGithub, IconDefinition } from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faBullseye,
|
||||
faCertificate,
|
||||
faKey,
|
||||
faLink,
|
||||
faLock,
|
||||
@@ -211,6 +212,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
||||
case AzureKeyVaultConnectionMethod.ClientSecret:
|
||||
case AzureDevOpsConnectionMethod.ClientSecret:
|
||||
return { name: "Client Secret", icon: faKey };
|
||||
case AzureClientSecretsConnectionMethod.Certificate:
|
||||
return { name: "Certificate", icon: faCertificate };
|
||||
default:
|
||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-con
|
||||
|
||||
export enum AzureClientSecretsConnectionMethod {
|
||||
OAuth = "oauth",
|
||||
ClientSecret = "client-secret"
|
||||
ClientSecret = "client-secret",
|
||||
Certificate = "certificate"
|
||||
}
|
||||
|
||||
export type TAzureClientSecretsConnection = TRootAppConnection & {
|
||||
@@ -24,4 +25,13 @@ export type TAzureClientSecretsConnection = TRootAppConnection & {
|
||||
tenantId: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
method: AzureClientSecretsConnectionMethod.Certificate;
|
||||
credentials: {
|
||||
clientId: string;
|
||||
tenantId: string;
|
||||
certificateBody: string;
|
||||
privateKey: string;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -251,6 +251,17 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.UPDATE_ORG_ROLE]: "Update Org Role",
|
||||
[EventType.DELETE_ORG_ROLE]: "Delete Org Role",
|
||||
|
||||
[EventType.CREATE_SUB_ORGANIZATION]: "Create Sub Organization",
|
||||
[EventType.UPDATE_SUB_ORGANIZATION]: "Update Sub Organization",
|
||||
|
||||
[EventType.CREATE_IDENTITY_ORG_MEMBERSHIP]: "Create Identity Org Membership",
|
||||
[EventType.UPDATE_IDENTITY_ORG_MEMBERSHIP]: "Update Identity Org Membership",
|
||||
[EventType.DELETE_IDENTITY_ORG_MEMBERSHIP]: "Delete Identity Org Membership",
|
||||
|
||||
[EventType.CREATE_IDENTITY_PROJECT_MEMBERSHIP]: "Create Identity Project Membership",
|
||||
[EventType.UPDATE_IDENTITY_PROJECT_MEMBERSHIP]: "Update Identity Project Membership",
|
||||
[EventType.DELETE_IDENTITY_PROJECT_MEMBERSHIP]: "Delete Identity Project Membership",
|
||||
|
||||
[EventType.PAM_SESSION_START]: "PAM Session Start",
|
||||
[EventType.PAM_SESSION_LOGS_UPDATE]: "PAM Session Logs Update",
|
||||
[EventType.PAM_SESSION_END]: "PAM Session End",
|
||||
@@ -268,7 +279,18 @@ export const eventToNameMap: { [K in EventType]: string } = {
|
||||
[EventType.PAM_RESOURCE_GET]: "PAM Resource Get",
|
||||
[EventType.PAM_RESOURCE_CREATE]: "PAM Resource Create",
|
||||
[EventType.PAM_RESOURCE_UPDATE]: "PAM Resource Update",
|
||||
[EventType.PAM_RESOURCE_DELETE]: "PAM Resource Delete"
|
||||
[EventType.PAM_RESOURCE_DELETE]: "PAM Resource Delete",
|
||||
|
||||
[EventType.CREATE_CERTIFICATE_PROFILE]: "Create Certificate Profile",
|
||||
[EventType.UPDATE_CERTIFICATE_PROFILE]: "Update Certificate Profile",
|
||||
[EventType.DELETE_CERTIFICATE_PROFILE]: "Delete Certificate Profile",
|
||||
[EventType.GET_CERTIFICATE_PROFILE]: "Get Certificate Profile",
|
||||
[EventType.LIST_CERTIFICATE_PROFILES]: "List Certificate Profiles",
|
||||
[EventType.ISSUE_CERTIFICATE_FROM_PROFILE]: "Issue Certificate From Profile",
|
||||
[EventType.SIGN_CERTIFICATE_FROM_PROFILE]: "Sign Certificate From Profile",
|
||||
[EventType.ORDER_CERTIFICATE_FROM_PROFILE]: "Order Certificate From Profile",
|
||||
[EventType.GET_CERTIFICATE_PROFILE_LATEST_ACTIVE_BUNDLE]:
|
||||
"Get Certificate Profile Latest Active Bundle"
|
||||
};
|
||||
|
||||
export const userAgentTypeToNameMap: { [K in UserAgentType]: string } = {
|
||||
|
||||
@@ -243,6 +243,17 @@ export enum EventType {
|
||||
UPDATE_ORG_ROLE = "update-org-role",
|
||||
DELETE_ORG_ROLE = "delete-org-role",
|
||||
|
||||
CREATE_SUB_ORGANIZATION = "create-sub-organization",
|
||||
UPDATE_SUB_ORGANIZATION = "update-sub-organization",
|
||||
|
||||
CREATE_IDENTITY_ORG_MEMBERSHIP = "create-identity-org-membership",
|
||||
UPDATE_IDENTITY_ORG_MEMBERSHIP = "update-identity-org-membership",
|
||||
DELETE_IDENTITY_ORG_MEMBERSHIP = "delete-identity-org-membership",
|
||||
|
||||
CREATE_IDENTITY_PROJECT_MEMBERSHIP = "create-identity-project-membership",
|
||||
UPDATE_IDENTITY_PROJECT_MEMBERSHIP = "update-identity-project-membership",
|
||||
DELETE_IDENTITY_PROJECT_MEMBERSHIP = "delete-identity-project-membership",
|
||||
|
||||
PAM_SESSION_START = "pam-session-start",
|
||||
PAM_SESSION_LOGS_UPDATE = "pam-session-logs-update",
|
||||
PAM_SESSION_END = "pam-session-end",
|
||||
@@ -260,5 +271,15 @@ export enum EventType {
|
||||
PAM_RESOURCE_GET = "pam-resource-get",
|
||||
PAM_RESOURCE_CREATE = "pam-resource-create",
|
||||
PAM_RESOURCE_UPDATE = "pam-resource-update",
|
||||
PAM_RESOURCE_DELETE = "pam-resource-delete"
|
||||
PAM_RESOURCE_DELETE = "pam-resource-delete",
|
||||
|
||||
CREATE_CERTIFICATE_PROFILE = "create-certificate-profile",
|
||||
UPDATE_CERTIFICATE_PROFILE = "update-certificate-profile",
|
||||
DELETE_CERTIFICATE_PROFILE = "delete-certificate-profile",
|
||||
GET_CERTIFICATE_PROFILE = "get-certificate-profile",
|
||||
LIST_CERTIFICATE_PROFILES = "list-certificate-profiles",
|
||||
ISSUE_CERTIFICATE_FROM_PROFILE = "issue-certificate-from-profile",
|
||||
SIGN_CERTIFICATE_FROM_PROFILE = "sign-certificate-from-profile",
|
||||
ORDER_CERTIFICATE_FROM_PROFILE = "order-certificate-from-profile",
|
||||
GET_CERTIFICATE_PROFILE_LATEST_ACTIVE_BUNDLE = "get-certificate-profile-latest-active-bundle"
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export const useCreateCertificateV3 = (options?: { projectId?: string }) => {
|
||||
return useMutation<TCreateCertificateV3Response, object, TCreateCertificateV3DTO>({
|
||||
mutationFn: async (body) => {
|
||||
const { data } = await apiRequest.post<TCreateCertificateV3Response>(
|
||||
"/api/v3/certificates/issue-certificate",
|
||||
"/api/v3/pki/certificates/issue-certificate",
|
||||
body
|
||||
);
|
||||
return data;
|
||||
@@ -185,7 +185,7 @@ export const useOrderCertificateWithProfile = () => {
|
||||
return useMutation<TOrderCertificateResponse, object, TOrderCertificateDTO>({
|
||||
mutationFn: async (body) => {
|
||||
const { data } = await apiRequest.post<TOrderCertificateResponse>(
|
||||
"/api/v3/certificates/order-certificate",
|
||||
"/api/v3/pki/certificates/order-certificate",
|
||||
body
|
||||
);
|
||||
return data;
|
||||
|
||||
@@ -159,8 +159,8 @@ export type TCreateCertificateDTO = {
|
||||
ttl: string; // string compatible with ms
|
||||
notBefore?: string;
|
||||
notAfter?: string;
|
||||
keyUsages: CertKeyUsage[];
|
||||
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||
keyUsages: string[];
|
||||
extendedKeyUsages: string[];
|
||||
};
|
||||
|
||||
export type TCreateCertificateResponse = {
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
TGetProfileCertificatesDTO,
|
||||
TGetProfileMetricsDTO,
|
||||
TListCertificateProfilesDTO,
|
||||
TProfileCertificate
|
||||
TProfileCertificate,
|
||||
TRevealAcmeEabSecretDTO
|
||||
} from "./types";
|
||||
|
||||
export const certificateProfileKeys = {
|
||||
@@ -41,6 +42,11 @@ export const certificateProfileKeys = {
|
||||
"metrics",
|
||||
profileId,
|
||||
params
|
||||
],
|
||||
revealAcmeEabSecret: (profileId: string) => [
|
||||
"certificate-profiles",
|
||||
"reveal-acme-eab-secret",
|
||||
profileId
|
||||
]
|
||||
};
|
||||
|
||||
@@ -112,6 +118,20 @@ export const useGetCertificateProfileBySlug = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const useRevealAcmeEabSecret = ({ profileId }: TRevealAcmeEabSecretDTO) => {
|
||||
return useQuery({
|
||||
queryKey: certificateProfileKeys.revealAcmeEabSecret(profileId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{
|
||||
eabKid: string;
|
||||
eabSecret: string;
|
||||
}>(`/api/v1/pki/certificate-profiles/${profileId}/acme/eab-secret/reveal`);
|
||||
return data;
|
||||
},
|
||||
enabled: Boolean(profileId)
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetProfileCertificates = ({
|
||||
profileId,
|
||||
offset = 0,
|
||||
|
||||
@@ -5,7 +5,7 @@ export type TCertificateProfile = {
|
||||
certificateTemplateId: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
enrollmentType: "api" | "est";
|
||||
enrollmentType: "api" | "est" | "acme";
|
||||
estConfigId?: string;
|
||||
apiConfigId?: string;
|
||||
createdAt: string;
|
||||
@@ -36,6 +36,10 @@ export type TCertificateProfileWithDetails = TCertificateProfile & {
|
||||
autoRenew: boolean;
|
||||
renewBeforeDays?: number;
|
||||
};
|
||||
acmeConfig?: {
|
||||
id: string;
|
||||
directoryUrl: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type TCreateCertificateProfileDTO = {
|
||||
@@ -44,7 +48,7 @@ export type TCreateCertificateProfileDTO = {
|
||||
certificateTemplateId: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
enrollmentType: "api" | "est";
|
||||
enrollmentType: "api" | "est" | "acme";
|
||||
estConfig?: {
|
||||
disableBootstrapCaValidation?: boolean;
|
||||
passphrase: string;
|
||||
@@ -54,6 +58,7 @@ export type TCreateCertificateProfileDTO = {
|
||||
autoRenew?: boolean;
|
||||
renewBeforeDays?: number;
|
||||
};
|
||||
acmeConfig?: unknown;
|
||||
};
|
||||
|
||||
export type TUpdateCertificateProfileDTO = {
|
||||
@@ -69,6 +74,7 @@ export type TUpdateCertificateProfileDTO = {
|
||||
autoRenew?: boolean;
|
||||
renewBeforeDays?: number;
|
||||
};
|
||||
acmeConfig?: unknown;
|
||||
};
|
||||
|
||||
export type TDeleteCertificateProfileDTO = {
|
||||
@@ -81,7 +87,7 @@ export type TListCertificateProfilesDTO = {
|
||||
offset?: number;
|
||||
search?: string;
|
||||
includeConfigs?: boolean;
|
||||
enrollmentType?: "api" | "est";
|
||||
enrollmentType?: "api" | "est" | "acme";
|
||||
};
|
||||
|
||||
export type TGetCertificateProfileByIdDTO = {
|
||||
@@ -93,6 +99,10 @@ export type TGetCertificateProfileBySlugDTO = {
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type TRevealAcmeEabSecretDTO = {
|
||||
profileId: string;
|
||||
};
|
||||
|
||||
export type TProfileCertificate = {
|
||||
id: string;
|
||||
serialNumber: string;
|
||||
|
||||
@@ -90,7 +90,12 @@ export const useCreateCertTemplateV2 = () => {
|
||||
return data.certificateTemplate;
|
||||
},
|
||||
onSuccess: (_, { projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) });
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (query) => {
|
||||
const [firstKey, queryProjectId] = query.queryKey;
|
||||
return firstKey === "list-template" && queryProjectId === projectId;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -107,7 +112,12 @@ export const useUpdateCertTemplateV2 = () => {
|
||||
return data.certificateTemplate;
|
||||
},
|
||||
onSuccess: (_, { projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) });
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (query) => {
|
||||
const [firstKey, queryProjectId] = query.queryKey;
|
||||
return firstKey === "list-template" && queryProjectId === projectId;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -127,7 +137,12 @@ export const useDeleteCertTemplateV2 = () => {
|
||||
return data.certificateTemplate;
|
||||
},
|
||||
onSuccess: (_, { projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) });
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (query) => {
|
||||
const [firstKey, queryProjectId] = query.queryKey;
|
||||
return firstKey === "list-template" && queryProjectId === projectId;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export { CertStatus } from "./enums";
|
||||
export {
|
||||
useDeleteCert,
|
||||
useDownloadCertPkcs12,
|
||||
useImportCertificate,
|
||||
useRenewCertificate,
|
||||
useRevokeCert,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { projectKeys } from "../projects";
|
||||
import {
|
||||
TCertificate,
|
||||
TDeleteCertDTO,
|
||||
TDownloadPkcs12DTO,
|
||||
TImportCertificateDTO,
|
||||
TImportCertificateResponse,
|
||||
TRenewCertificateDTO,
|
||||
@@ -86,7 +87,7 @@ export const useRenewCertificate = () => {
|
||||
return useMutation<TRenewCertificateResponse, object, TRenewCertificateDTO>({
|
||||
mutationFn: async ({ certificateId }) => {
|
||||
const { data } = await apiRequest.post<TRenewCertificateResponse>(
|
||||
`/api/v3/certificates/${certificateId}/renew`,
|
||||
`/api/v3/pki/certificates/${certificateId}/renew`,
|
||||
{}
|
||||
);
|
||||
return data;
|
||||
@@ -119,7 +120,7 @@ export const useUpdateRenewalConfig = () => {
|
||||
>({
|
||||
mutationFn: async ({ certificateId, renewBeforeDays, enableAutoRenewal }) => {
|
||||
const { data } = await apiRequest.patch<{ message: string; renewBeforeDays?: number }>(
|
||||
`/api/v3/certificates/${certificateId}/config`,
|
||||
`/api/v3/pki/certificates/${certificateId}/config`,
|
||||
{ renewBeforeDays, enableAutoRenewal }
|
||||
);
|
||||
return data;
|
||||
@@ -134,3 +135,42 @@ export const useUpdateRenewalConfig = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDownloadCertPkcs12 = () => {
|
||||
return useMutation<void, object, TDownloadPkcs12DTO>({
|
||||
mutationFn: async ({ serialNumber, projectSlug, password, alias }) => {
|
||||
try {
|
||||
const response = await apiRequest.post(
|
||||
`/api/v1/pki/certificates/${serialNumber}/pkcs12`,
|
||||
{
|
||||
password,
|
||||
alias
|
||||
},
|
||||
{
|
||||
params: { projectSlug },
|
||||
responseType: "arraybuffer"
|
||||
}
|
||||
);
|
||||
|
||||
// Create blob and trigger download
|
||||
const blob = new Blob([response.data], { type: "application/octet-stream" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = `certificate-${serialNumber}.p12`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error: any) {
|
||||
if (error.response?.data instanceof ArrayBuffer) {
|
||||
const decoder = new TextDecoder();
|
||||
const errorText = decoder.decode(error.response.data);
|
||||
const errorData = JSON.parse(errorText);
|
||||
throw new Error(errorData.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -72,3 +72,10 @@ export type TUpdateRenewalConfigDTO = {
|
||||
enableAutoRenewal?: boolean;
|
||||
projectSlug: string;
|
||||
};
|
||||
|
||||
export type TDownloadPkcs12DTO = {
|
||||
serialNumber: string;
|
||||
projectSlug: string;
|
||||
password: string;
|
||||
alias: string;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { projectIdentityQuery, projectKeys } from "@app/hooks/api";
|
||||
|
||||
import { organizationKeys } from "../organization/queries";
|
||||
import { subscriptionQueryKeys } from "../subscriptions/queries";
|
||||
import { identitiesKeys } from "./queries";
|
||||
import {
|
||||
AddIdentityAliCloudAuthDTO,
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
ClearIdentityLdapAuthLockoutsDTO,
|
||||
ClearIdentityUniversalAuthLockoutsDTO,
|
||||
ClientSecretData,
|
||||
CreateIdentityDTO,
|
||||
CreateIdentityUniversalAuthClientSecretDTO,
|
||||
CreateIdentityUniversalAuthClientSecretRes,
|
||||
CreateTokenIdentityTokenAuthDTO,
|
||||
@@ -29,7 +28,6 @@ import {
|
||||
DeleteIdentityAliCloudAuthDTO,
|
||||
DeleteIdentityAwsAuthDTO,
|
||||
DeleteIdentityAzureAuthDTO,
|
||||
DeleteIdentityDTO,
|
||||
DeleteIdentityGcpAuthDTO,
|
||||
DeleteIdentityJwtAuthDTO,
|
||||
DeleteIdentityKubernetesAuthDTO,
|
||||
@@ -40,7 +38,6 @@ import {
|
||||
DeleteIdentityTokenAuthDTO,
|
||||
DeleteIdentityUniversalAuthClientSecretDTO,
|
||||
DeleteIdentityUniversalAuthDTO,
|
||||
Identity,
|
||||
IdentityAccessToken,
|
||||
IdentityAliCloudAuth,
|
||||
IdentityAwsAuth,
|
||||
@@ -59,7 +56,6 @@ import {
|
||||
UpdateIdentityAliCloudAuthDTO,
|
||||
UpdateIdentityAwsAuthDTO,
|
||||
UpdateIdentityAzureAuthDTO,
|
||||
UpdateIdentityDTO,
|
||||
UpdateIdentityGcpAuthDTO,
|
||||
UpdateIdentityJwtAuthDTO,
|
||||
UpdateIdentityKubernetesAuthDTO,
|
||||
@@ -72,73 +68,6 @@ import {
|
||||
UpdateTokenIdentityTokenAuthDTO
|
||||
} from "./types";
|
||||
|
||||
export const useCreateIdentity = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<Identity, object, CreateIdentityDTO>({
|
||||
mutationFn: async (body) => {
|
||||
const {
|
||||
data: { identity }
|
||||
} = await apiRequest.post("/api/v1/identities/", body);
|
||||
return identity;
|
||||
},
|
||||
onSuccess: (_, { organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateIdentity = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<Identity, object, UpdateIdentityDTO>({
|
||||
mutationFn: async ({ identityId, name, role, hasDeleteProtection, metadata }) => {
|
||||
const {
|
||||
data: { identity }
|
||||
} = await apiRequest.patch(`/api/v1/identities/${identityId}`, {
|
||||
name,
|
||||
role,
|
||||
hasDeleteProtection,
|
||||
metadata
|
||||
});
|
||||
|
||||
return identity;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIdentity = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<Identity, object, DeleteIdentityDTO>({
|
||||
mutationFn: async ({ identityId }) => {
|
||||
const {
|
||||
data: { identity }
|
||||
} = await apiRequest.delete(`/api/v1/identities/${identityId}`);
|
||||
return identity;
|
||||
},
|
||||
onSuccess: (_, { organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: move these to /auth
|
||||
|
||||
export const useAddIdentityUniversalAuth = () => {
|
||||
@@ -171,10 +100,20 @@ export const useAddIdentityUniversalAuth = () => {
|
||||
});
|
||||
return identityUniversalAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityUniversalAuth(identityId)
|
||||
@@ -215,10 +154,20 @@ export const useUpdateIdentityUniversalAuth = () => {
|
||||
});
|
||||
return identityUniversalAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityUniversalAuth(identityId)
|
||||
@@ -236,10 +185,20 @@ export const useDeleteIdentityUniversalAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/universal-auth/identities/${identityId}`);
|
||||
return identityUniversalAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityUniversalAuth(identityId)
|
||||
@@ -344,10 +303,20 @@ export const useAddIdentityGcpAuth = () => {
|
||||
|
||||
return identityGcpAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityGcpAuth(identityId) });
|
||||
}
|
||||
@@ -386,10 +355,20 @@ export const useUpdateIdentityGcpAuth = () => {
|
||||
|
||||
return identityGcpAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityGcpAuth(identityId) });
|
||||
}
|
||||
@@ -405,10 +384,20 @@ export const useDeleteIdentityGcpAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/gcp-auth/identities/${identityId}`);
|
||||
return identityGcpAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityGcpAuth(identityId) });
|
||||
}
|
||||
@@ -445,10 +434,20 @@ export const useAddIdentityAwsAuth = () => {
|
||||
|
||||
return identityAwsAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAwsAuth(identityId) });
|
||||
}
|
||||
@@ -485,10 +484,20 @@ export const useUpdateIdentityAwsAuth = () => {
|
||||
|
||||
return identityAwsAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAwsAuth(identityId) });
|
||||
}
|
||||
@@ -504,10 +513,20 @@ export const useDeleteIdentityAwsAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/aws-auth/identities/${identityId}`);
|
||||
return identityAwsAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAwsAuth(identityId) });
|
||||
}
|
||||
@@ -542,10 +561,20 @@ export const useAddIdentityOciAuth = () => {
|
||||
|
||||
return identityOciAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOciAuth(identityId) });
|
||||
}
|
||||
@@ -580,10 +609,20 @@ export const useUpdateIdentityOciAuth = () => {
|
||||
|
||||
return identityOciAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOciAuth(identityId) });
|
||||
}
|
||||
@@ -599,10 +638,20 @@ export const useDeleteIdentityOciAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/oci-auth/identities/${identityId}`);
|
||||
return identityOciAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOciAuth(identityId) });
|
||||
}
|
||||
@@ -635,10 +684,20 @@ export const useAddIdentityAliCloudAuth = () => {
|
||||
|
||||
return identityAliCloudAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityAliCloudAuth(identityId)
|
||||
@@ -673,10 +732,20 @@ export const useUpdateIdentityAliCloudAuth = () => {
|
||||
|
||||
return identityAliCloudAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityAliCloudAuth(identityId)
|
||||
@@ -694,10 +763,20 @@ export const useDeleteIdentityAliCloudAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/alicloud-auth/identities/${identityId}`);
|
||||
return identityAliCloudAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityAliCloudAuth(identityId)
|
||||
@@ -734,10 +813,20 @@ export const useAddIdentityTlsCertAuth = () => {
|
||||
|
||||
return identityTlsCertAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityTlsCertAuth(identityId)
|
||||
@@ -774,10 +863,20 @@ export const useUpdateIdentityTlsCertAuth = () => {
|
||||
|
||||
return identityTlsCertAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityTlsCertAuth(identityId)
|
||||
@@ -795,10 +894,20 @@ export const useDeleteIdentityTlsCertAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/tls-cert-auth/identities/${identityId}`);
|
||||
return identityTlsCertAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityTlsCertAuth(identityId)
|
||||
@@ -845,10 +954,20 @@ export const useUpdateIdentityOidcAuth = () => {
|
||||
|
||||
return identityOidcAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOidcAuth(identityId) });
|
||||
}
|
||||
@@ -893,10 +1012,20 @@ export const useAddIdentityOidcAuth = () => {
|
||||
|
||||
return identityOidcAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOidcAuth(identityId) });
|
||||
}
|
||||
@@ -912,10 +1041,20 @@ export const useDeleteIdentityOidcAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/oidc-auth/identities/${identityId}`);
|
||||
return identityOidcAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityOidcAuth(identityId) });
|
||||
}
|
||||
@@ -961,10 +1100,20 @@ export const useUpdateIdentityJwtAuth = () => {
|
||||
|
||||
return identityJwtAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityJwtAuth(identityId) });
|
||||
}
|
||||
@@ -1011,10 +1160,20 @@ export const useAddIdentityJwtAuth = () => {
|
||||
|
||||
return identityJwtAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityJwtAuth(identityId) });
|
||||
}
|
||||
@@ -1030,10 +1189,20 @@ export const useDeleteIdentityJwtAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/jwt-auth/identities/${identityId}`);
|
||||
return identityJwtAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityJwtAuth(identityId) });
|
||||
}
|
||||
@@ -1070,10 +1239,20 @@ export const useAddIdentityAzureAuth = () => {
|
||||
|
||||
return identityAzureAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityKubernetesAuth(identityId)
|
||||
@@ -1122,10 +1301,20 @@ export const useAddIdentityKubernetesAuth = () => {
|
||||
|
||||
return identityKubernetesAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAzureAuth(identityId) });
|
||||
}
|
||||
@@ -1162,10 +1351,20 @@ export const useUpdateIdentityAzureAuth = () => {
|
||||
|
||||
return identityAzureAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAzureAuth(identityId) });
|
||||
}
|
||||
@@ -1181,10 +1380,20 @@ export const useDeleteIdentityAzureAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/azure-auth/identities/${identityId}`);
|
||||
return identityAzureAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityAzureAuth(identityId) });
|
||||
}
|
||||
@@ -1231,10 +1440,20 @@ export const useUpdateIdentityKubernetesAuth = () => {
|
||||
|
||||
return identityKubernetesAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityKubernetesAuth(identityId)
|
||||
@@ -1252,10 +1471,20 @@ export const useDeleteIdentityKubernetesAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/kubernetes-auth/identities/${identityId}`);
|
||||
return identityKubernetesAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityKubernetesAuth(identityId)
|
||||
@@ -1288,10 +1517,20 @@ export const useAddIdentityTokenAuth = () => {
|
||||
|
||||
return identityTokenAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityTokenAuth(identityId)
|
||||
@@ -1324,10 +1563,20 @@ export const useUpdateIdentityTokenAuth = () => {
|
||||
|
||||
return identityTokenAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityTokenAuth(identityId)
|
||||
@@ -1345,10 +1594,20 @@ export const useDeleteIdentityTokenAuth = () => {
|
||||
} = await apiRequest.delete(`/api/v1/auth/token-auth/identities/${identityId}`);
|
||||
return identityTokenAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityTokenAuth(identityId) });
|
||||
}
|
||||
@@ -1462,10 +1721,20 @@ export const useAddIdentityLdapAuth = () => {
|
||||
);
|
||||
return data.identityLdapAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
|
||||
@@ -1519,10 +1788,20 @@ export const useUpdateIdentityLdapAuth = () => {
|
||||
);
|
||||
return data.identityLdapAuth;
|
||||
},
|
||||
onSuccess: (_, { identityId, organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { identityId, organizationId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
|
||||
@@ -1538,10 +1817,20 @@ export const useDeleteIdentityLdapAuth = () => {
|
||||
const { data } = await apiRequest.delete(`/api/v1/auth/ldap-auth/identities/${identityId}`);
|
||||
return data.identityLdapAuth;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
onSuccess: (_, { organizationId, identityId, projectId }) => {
|
||||
if (organizationId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
}
|
||||
if (projectId) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectIdentityQuery.getByIdKey({ identityId, projectId })
|
||||
});
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
|
||||
|
||||
@@ -13,10 +13,10 @@ import {
|
||||
IdentityJwtAuth,
|
||||
IdentityKubernetesAuth,
|
||||
IdentityLdapAuth,
|
||||
IdentityMembership,
|
||||
IdentityMembershipOrg,
|
||||
IdentityOciAuth,
|
||||
IdentityOidcAuth,
|
||||
IdentityProjectMembershipV1,
|
||||
IdentityTlsCertAuth,
|
||||
IdentityTokenAuth,
|
||||
IdentityUniversalAuth,
|
||||
@@ -52,7 +52,7 @@ export const identitiesKeys = {
|
||||
[{ identityId }, "identity-project-memberships"] as const
|
||||
};
|
||||
|
||||
export const useGetIdentityById = (identityId: string) => {
|
||||
export const useGetOrgIdentityMembershipById = (identityId: string) => {
|
||||
return useQuery({
|
||||
enabled: Boolean(identityId),
|
||||
queryKey: identitiesKeys.getIdentityById(identityId),
|
||||
@@ -67,7 +67,7 @@ export const useGetIdentityById = (identityId: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useSearchIdentities = (dto: TSearchIdentitiesDTO) => {
|
||||
export const useSearchOrgIdentityMemberships = (dto: TSearchIdentitiesDTO) => {
|
||||
const { limit, search, offset, orderBy, orderDirection } = dto;
|
||||
return useQuery({
|
||||
queryKey: identitiesKeys.searchIdentities(dto),
|
||||
@@ -95,7 +95,7 @@ export const useGetIdentityProjectMemberships = (identityId: string) => {
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { identityMemberships }
|
||||
} = await apiRequest.get<{ identityMemberships: IdentityMembership[] }>(
|
||||
} = await apiRequest.get<{ identityMemberships: IdentityProjectMembershipV1[] }>(
|
||||
`/api/v1/identities/${identityId}/identity-memberships`
|
||||
);
|
||||
return identityMemberships;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { TemporaryPermissionMode } from "@app/hooks/api/shared";
|
||||
|
||||
import { OrderByDirection } from "../generic/types";
|
||||
import { OrgIdentityOrderBy } from "../organization/types";
|
||||
import { Project, ProjectUserMembershipTemporaryMode } from "../projects/types";
|
||||
import { Project } from "../projects/types";
|
||||
import { TOrgRole } from "../roles/types";
|
||||
import { IdentityAuthMethod, IdentityJwtConfigurationType } from "./enums";
|
||||
|
||||
@@ -21,6 +23,8 @@ export type Identity = {
|
||||
updatedAt: string;
|
||||
isInstanceAdmin?: boolean;
|
||||
orgId: string;
|
||||
projectId?: string | null;
|
||||
metadata?: { key: string; value: string; id: string }[];
|
||||
};
|
||||
|
||||
export type IdentityAccessToken = {
|
||||
@@ -52,7 +56,7 @@ export type IdentityMembershipOrg = {
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type IdentityMembership = {
|
||||
export type IdentityProjectMembershipV1 = {
|
||||
id: string;
|
||||
identity: Identity;
|
||||
project: Pick<Project, "id" | "name" | "type">;
|
||||
@@ -74,7 +78,7 @@ export type IdentityMembership = {
|
||||
| {
|
||||
isTemporary: true;
|
||||
temporaryRange: string;
|
||||
temporaryMode: ProjectUserMembershipTemporaryMode;
|
||||
temporaryMode: TemporaryPermissionMode;
|
||||
temporaryAccessEndTime: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
@@ -82,6 +86,41 @@ export type IdentityMembership = {
|
||||
>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastLoginTime?: string;
|
||||
lastLoginAuthMethod?: IdentityAuthMethod;
|
||||
};
|
||||
|
||||
export type IdentityProjectMembershipV2 = {
|
||||
id: string;
|
||||
identity: Identity;
|
||||
roles: Array<
|
||||
{
|
||||
id: string;
|
||||
role: "owner" | "admin" | "member" | "no-access" | "custom";
|
||||
customRoleId: string;
|
||||
customRoleName: string;
|
||||
customRoleSlug: string;
|
||||
} & (
|
||||
| {
|
||||
isTemporary: false;
|
||||
temporaryRange: null;
|
||||
temporaryMode: null;
|
||||
temporaryAccessEndTime: null;
|
||||
temporaryAccessStartTime: null;
|
||||
}
|
||||
| {
|
||||
isTemporary: true;
|
||||
temporaryRange: string;
|
||||
temporaryMode: TemporaryPermissionMode;
|
||||
temporaryAccessEndTime: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
)
|
||||
>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastLoginTime?: string;
|
||||
lastLoginAuthMethod?: IdentityAuthMethod;
|
||||
};
|
||||
|
||||
export type CreateIdentityDTO = {
|
||||
@@ -122,7 +161,8 @@ export type IdentityUniversalAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityUniversalAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
clientSecretTrustedIps: {
|
||||
ipAddress: string;
|
||||
@@ -138,10 +178,11 @@ export type AddIdentityUniversalAuthDTO = {
|
||||
lockoutThreshold: number;
|
||||
lockoutDurationSeconds: number;
|
||||
lockoutCounterResetSeconds: number;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityUniversalAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
clientSecretTrustedIps?: {
|
||||
ipAddress: string;
|
||||
@@ -157,12 +198,13 @@ export type UpdateIdentityUniversalAuthDTO = {
|
||||
lockoutThreshold?: number;
|
||||
lockoutDurationSeconds?: number;
|
||||
lockoutCounterResetSeconds?: number;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityUniversalAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type IdentityGcpAuth = {
|
||||
identityId: string;
|
||||
@@ -177,7 +219,8 @@ export type IdentityGcpAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityGcpAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
type: "iam" | "gce";
|
||||
allowedServiceAccounts: string;
|
||||
@@ -189,10 +232,11 @@ export type AddIdentityGcpAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityGcpAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
type?: "iam" | "gce";
|
||||
allowedServiceAccounts?: string;
|
||||
@@ -204,12 +248,13 @@ export type UpdateIdentityGcpAuthDTO = {
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityGcpAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type IdentityOidcAuth = {
|
||||
identityId: string;
|
||||
@@ -227,7 +272,8 @@ export type IdentityOidcAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityOidcAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
oidcDiscoveryUrl: string;
|
||||
caCert: string;
|
||||
@@ -242,10 +288,11 @@ export type AddIdentityOidcAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityOidcAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
oidcDiscoveryUrl?: string;
|
||||
caCert?: string;
|
||||
@@ -260,12 +307,13 @@ export type UpdateIdentityOidcAuthDTO = {
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityOidcAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type IdentityAwsAuth = {
|
||||
identityId: string;
|
||||
@@ -280,7 +328,8 @@ export type IdentityAwsAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityAwsAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
stsEndpoint: string;
|
||||
allowedPrincipalArns: string;
|
||||
@@ -291,10 +340,11 @@ export type AddIdentityAwsAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityAwsAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
stsEndpoint?: string;
|
||||
allowedPrincipalArns?: string;
|
||||
@@ -308,9 +358,10 @@ export type UpdateIdentityAwsAuthDTO = {
|
||||
};
|
||||
|
||||
export type DeleteIdentityAwsAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type IdentityAliCloudAuth = {
|
||||
identityId: string;
|
||||
@@ -323,7 +374,8 @@ export type IdentityAliCloudAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityAliCloudAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
allowedArns: string;
|
||||
accessTokenTTL: number;
|
||||
@@ -332,10 +384,11 @@ export type AddIdentityAliCloudAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityAliCloudAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
allowedArns: string;
|
||||
accessTokenTTL?: number;
|
||||
@@ -344,12 +397,13 @@ export type UpdateIdentityAliCloudAuthDTO = {
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityAliCloudAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type IdentityOciAuth = {
|
||||
identityId: string;
|
||||
@@ -363,7 +417,8 @@ export type IdentityOciAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityOciAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
tenancyOcid: string;
|
||||
allowedUsernames?: string | null;
|
||||
@@ -373,10 +428,11 @@ export type AddIdentityOciAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityOciAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
tenancyOcid?: string;
|
||||
allowedUsernames?: string | null;
|
||||
@@ -386,12 +442,13 @@ export type UpdateIdentityOciAuthDTO = {
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityOciAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type IdentityAzureAuth = {
|
||||
identityId: string;
|
||||
@@ -405,7 +462,8 @@ export type IdentityAzureAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityAzureAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
tenantId: string;
|
||||
resource: string;
|
||||
@@ -416,10 +474,11 @@ export type AddIdentityAzureAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityAzureAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
tenantId?: string;
|
||||
resource?: string;
|
||||
@@ -430,12 +489,13 @@ export type UpdateIdentityAzureAuthDTO = {
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityAzureAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export enum IdentityKubernetesAuthTokenReviewMode {
|
||||
Api = "api",
|
||||
@@ -459,7 +519,8 @@ export type IdentityKubernetesAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityKubernetesAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
kubernetesHost: string | null;
|
||||
tokenReviewerJwt?: string;
|
||||
@@ -475,10 +536,11 @@ export type AddIdentityKubernetesAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityKubernetesAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
kubernetesHost?: string | null;
|
||||
tokenReviewerJwt?: string | null;
|
||||
@@ -494,12 +556,13 @@ export type UpdateIdentityKubernetesAuthDTO = {
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityKubernetesAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type IdentityTlsCertAuth = {
|
||||
identityId: string;
|
||||
@@ -512,7 +575,8 @@ export type IdentityTlsCertAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityTlsCertAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
caCertificate: string;
|
||||
allowedCommonNames?: string;
|
||||
@@ -522,10 +586,11 @@ export type AddIdentityTlsCertAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityTlsCertAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
caCertificate: string;
|
||||
allowedCommonNames?: string | null;
|
||||
@@ -535,12 +600,13 @@ export type UpdateIdentityTlsCertAuthDTO = {
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityTlsCertAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type CreateIdentityUniversalAuthClientSecretDTO = {
|
||||
identityId: string;
|
||||
@@ -585,7 +651,8 @@ export type IdentityTokenAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityLdapAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
templateId?: string;
|
||||
url?: string;
|
||||
@@ -609,11 +676,12 @@ export type AddIdentityLdapAuthDTO = {
|
||||
lockoutThreshold: number;
|
||||
lockoutDurationSeconds: number;
|
||||
lockoutCounterResetSeconds: number;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityLdapAuthDTO = {
|
||||
identityId: string;
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
templateId?: string;
|
||||
url?: string;
|
||||
bindDN?: string;
|
||||
@@ -636,12 +704,13 @@ export type UpdateIdentityLdapAuthDTO = {
|
||||
lockoutThreshold?: number;
|
||||
lockoutDurationSeconds?: number;
|
||||
lockoutCounterResetSeconds?: number;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityLdapAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type IdentityLdapAuth = {
|
||||
url?: string;
|
||||
@@ -673,7 +742,8 @@ export type ClearIdentityLdapAuthLockoutsDTO = {
|
||||
};
|
||||
|
||||
export type AddIdentityTokenAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
@@ -681,10 +751,11 @@ export type AddIdentityTokenAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityTokenAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
accessTokenTTL?: number;
|
||||
accessTokenMaxTTL?: number;
|
||||
@@ -692,12 +763,13 @@ export type UpdateIdentityTokenAuthDTO = {
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityTokenAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type IdentityJwtAuth = {
|
||||
identityId: string;
|
||||
@@ -716,7 +788,8 @@ export type IdentityJwtAuth = {
|
||||
};
|
||||
|
||||
export type AddIdentityJwtAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
configurationType: string;
|
||||
jwksUrl?: string;
|
||||
@@ -732,10 +805,11 @@ export type AddIdentityJwtAuthDTO = {
|
||||
accessTokenTrustedIps: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type UpdateIdentityJwtAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
configurationType?: string;
|
||||
jwksUrl?: string;
|
||||
@@ -751,12 +825,13 @@ export type UpdateIdentityJwtAuthDTO = {
|
||||
accessTokenTrustedIps?: {
|
||||
ipAddress: string;
|
||||
}[];
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type DeleteIdentityJwtAuthDTO = {
|
||||
organizationId: string;
|
||||
organizationId?: string;
|
||||
projectId?: string;
|
||||
identityId: string;
|
||||
};
|
||||
} & ({ organizationId: string } | { projectId: string });
|
||||
|
||||
export type CreateTokenIdentityTokenAuthDTO = {
|
||||
identityId: string;
|
||||
@@ -785,8 +860,13 @@ export type RevokeTokenRes = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type TProjectIdentitiesList = {
|
||||
identityMemberships: IdentityMembership[];
|
||||
export type TProjectIdentityMembershipsList = {
|
||||
identityMemberships: IdentityProjectMembershipV1[];
|
||||
totalCount: number;
|
||||
};
|
||||
|
||||
export type TProjectIdentityMembershipsListV2 = {
|
||||
identityMemberships: IdentityProjectMembershipV2[];
|
||||
totalCount: number;
|
||||
};
|
||||
|
||||
|
||||
@@ -25,10 +25,14 @@ export * from "./ldapConfig";
|
||||
export * from "./oidcConfig";
|
||||
export * from "./orgAdmin";
|
||||
export * from "./organization";
|
||||
export * from "./orgIdentity";
|
||||
export * from "./orgIdentityMembership";
|
||||
export * from "./pkiAlerts";
|
||||
export * from "./pkiCollections";
|
||||
export * from "./pkiSubscriber";
|
||||
export * from "./pkiSyncs";
|
||||
export * from "./projectIdentity";
|
||||
export * from "./projectIdentityMembership";
|
||||
export * from "./projects";
|
||||
export * from "./projectUserAdditionalPrivilege";
|
||||
export * from "./rateLimit";
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./mutations";
|
||||
export * from "./queries";
|
||||
export type * from "./types";
|
||||
@@ -0,0 +1,116 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { identitiesKeys } from "@app/hooks/api";
|
||||
import { CreateIdentityDTO, Identity, UpdateIdentityDTO } from "@app/hooks/api/identities/types";
|
||||
import { organizationKeys } from "@app/hooks/api/organization/queries";
|
||||
import { subscriptionQueryKeys } from "@app/hooks/api/subscriptions/queries";
|
||||
|
||||
import { orgIdentityQuery } from "./queries";
|
||||
import { TDeleteOrgIdentityDTO, TOrgIdentity } from "./types";
|
||||
|
||||
// TODO (scott/akhi): eventually move to the new api commented out below; the current ones use old api
|
||||
|
||||
export const useCreateOrgIdentity = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<Identity, object, CreateIdentityDTO>({
|
||||
mutationFn: async (body) => {
|
||||
const {
|
||||
data: { identity }
|
||||
} = await apiRequest.post("/api/v1/identities/", body);
|
||||
return identity;
|
||||
},
|
||||
onSuccess: (_, { organizationId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: subscriptionQueryKeys.getOrgSubsription(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateOrgIdentity = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation<Identity, object, UpdateIdentityDTO>({
|
||||
mutationFn: async ({ identityId, name, role, hasDeleteProtection, metadata }) => {
|
||||
const {
|
||||
data: { identity }
|
||||
} = await apiRequest.patch(`/api/v1/identities/${identityId}`, {
|
||||
name,
|
||||
role,
|
||||
hasDeleteProtection,
|
||||
metadata
|
||||
});
|
||||
|
||||
return identity;
|
||||
},
|
||||
onSuccess: (_, { organizationId, identityId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// export const useCreateOrgIdentity = () => {
|
||||
// const queryClient = useQueryClient();
|
||||
// return useMutation({
|
||||
// mutationFn: async (dto: TCreateOrgIdentityDTO) => {
|
||||
// const { data } = await apiRequest.post<{ identity: TOrgIdentity }>(
|
||||
// "/api/v1/organization/identities",
|
||||
// dto
|
||||
// );
|
||||
// return data;
|
||||
// },
|
||||
// onSuccess: () => {
|
||||
// queryClient.invalidateQueries({ queryKey: orgIdentityQuery.allKey() });
|
||||
// queryClient.invalidateQueries({
|
||||
// queryKey: subscriptionQueryKeys.all()
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// export const useUpdateOrgIdentity = () => {
|
||||
// const queryClient = useQueryClient();
|
||||
// return useMutation({
|
||||
// mutationFn: async ({ identityId, ...updates }: TUpdateOrgIdentityDTO) => {
|
||||
// const { data } = await apiRequest.patch<{ identity: TOrgIdentity }>(
|
||||
// `/api/v1/organization/identities/${identityId}`,
|
||||
// updates
|
||||
// );
|
||||
// return data;
|
||||
// },
|
||||
// onSuccess: () => {
|
||||
// queryClient.invalidateQueries({ queryKey: orgIdentityQuery.allKey() });
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
|
||||
export const useDeleteOrgIdentity = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ identityId }: TDeleteOrgIdentityDTO) => {
|
||||
const { data } = await apiRequest.delete<{ identity: TOrgIdentity }>(
|
||||
`/api/v1/identities/${identityId}`
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: (_, { orgId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: organizationKeys.getOrgIdentityMemberships(orgId)
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.searchIdentitiesRoot });
|
||||
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(orgId) });
|
||||
queryClient.invalidateQueries({ queryKey: orgIdentityQuery.allKey() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: subscriptionQueryKeys.all()
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TGetOrgIdentityByIdDTO, TListOrgIdentitiesDTO, TOrgIdentity } from "./types";
|
||||
|
||||
export const orgIdentityQuery = {
|
||||
allKey: () => ["organization-identities"] as const,
|
||||
getByIdKey: (params: TGetOrgIdentityByIdDTO) =>
|
||||
[...orgIdentityQuery.allKey(), "by-id", params] as const,
|
||||
listKey: (params?: TListOrgIdentitiesDTO) =>
|
||||
[...orgIdentityQuery.allKey(), "list", params] as const,
|
||||
getById: (params: TGetOrgIdentityByIdDTO) =>
|
||||
queryOptions({
|
||||
queryKey: orgIdentityQuery.getByIdKey(params),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ identity: TOrgIdentity }>(
|
||||
`/api/v1/organization/identities/${params.identityId}`
|
||||
);
|
||||
return data.identity;
|
||||
}
|
||||
}),
|
||||
list: (params: TListOrgIdentitiesDTO = {}) =>
|
||||
queryOptions({
|
||||
queryKey: orgIdentityQuery.listKey(params),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{
|
||||
identities: TOrgIdentity[];
|
||||
totalCount: number;
|
||||
}>("/api/v1/organization/identities", {
|
||||
params: {
|
||||
offset: params.offset,
|
||||
limit: params.limit,
|
||||
search: params.search
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { TIdentity, TMetadata } from "@app/hooks/api/shared";
|
||||
|
||||
export type TOrgIdentity = TIdentity;
|
||||
|
||||
export type TCreateOrgIdentityDTO = {
|
||||
name: string;
|
||||
hasDeleteProtection?: boolean;
|
||||
metadata?: TMetadata;
|
||||
};
|
||||
|
||||
export type TUpdateOrgIdentityDTO = {
|
||||
identityId: string;
|
||||
name?: string;
|
||||
hasDeleteProtection?: boolean;
|
||||
metadata?: TMetadata;
|
||||
};
|
||||
|
||||
export type TGetOrgIdentityByIdDTO = {
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type TListOrgIdentitiesDTO = {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
};
|
||||
|
||||
export type TDeleteOrgIdentityDTO = {
|
||||
identityId: string;
|
||||
orgId: string;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import {
|
||||
TAvailableOrganizationIdentities,
|
||||
TListAvailableOrganizationIdentitiesDTO,
|
||||
TListOrgIdentityMembershipsDTO
|
||||
} from "./types";
|
||||
|
||||
export const orgIdentityMembershipQuery = {
|
||||
allKey: () => ["organization-identity-memberships"] as const,
|
||||
listAvailableKey: (params?: TListOrgIdentityMembershipsDTO) =>
|
||||
[...orgIdentityMembershipQuery.allKey(), "list-available", params] as const,
|
||||
listAvailable: (params: TListAvailableOrganizationIdentitiesDTO = {}) =>
|
||||
queryOptions({
|
||||
queryKey: orgIdentityMembershipQuery.listAvailableKey(params),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{
|
||||
identities: TAvailableOrganizationIdentities;
|
||||
}>("/api/v1/organization/available-identities", {
|
||||
params: {
|
||||
offset: params.offset,
|
||||
limit: params.limit,
|
||||
identityName: params.identityName
|
||||
}
|
||||
});
|
||||
return data.identities;
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -1,6 +1,4 @@
|
||||
export enum TemporaryPermissionMode {
|
||||
Relative = "relative"
|
||||
}
|
||||
import { TRoles } from "@app/hooks/api/shared";
|
||||
|
||||
export type TOrgIdentityMembership = {
|
||||
id: string;
|
||||
@@ -12,21 +10,24 @@ export type TOrgIdentityMembership = {
|
||||
|
||||
export type TCreateOrgIdentityMembershipDTO = {
|
||||
identityId: string;
|
||||
roles: Array<
|
||||
| {
|
||||
role: string;
|
||||
isTemporary?: false;
|
||||
}
|
||||
| {
|
||||
role: string;
|
||||
isTemporary: true;
|
||||
temporaryMode: TemporaryPermissionMode;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
>;
|
||||
roles: TRoles;
|
||||
};
|
||||
|
||||
export type TDeleteOrgIdentityMembershipDTO = {
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type TListOrgIdentityMembershipsDTO = {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
identityName?: string;
|
||||
roles?: string[];
|
||||
};
|
||||
|
||||
export type TListAvailableOrganizationIdentitiesDTO = {
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
identityName?: string;
|
||||
};
|
||||
|
||||
export type TAvailableOrganizationIdentities = Array<{ id: string; name: string }>;
|
||||
|
||||
@@ -6,7 +6,6 @@ export {
|
||||
useDeleteOrgById,
|
||||
useDeleteOrgPmtMethod,
|
||||
useDeleteOrgTaxId,
|
||||
useGetAvailableOrgIdentities,
|
||||
useGetIdentityMembershipOrgs,
|
||||
useGetOrganizationGroups,
|
||||
useGetOrganizations,
|
||||
|
||||
@@ -579,19 +579,6 @@ export const useGetOrgIntegrationAuths = <TData = IntegrationAuth[],>(
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetAvailableOrgIdentities = (enabled = true) =>
|
||||
useQuery({
|
||||
queryKey: organizationKeys.getAvailableIdentities(),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ identities: { name: string; id: string }[] }>(
|
||||
"/api/v1/organization/identities/available"
|
||||
);
|
||||
|
||||
return data.identities;
|
||||
},
|
||||
enabled
|
||||
});
|
||||
|
||||
export const useGetAvailableOrgUsers = (enabled = true) =>
|
||||
useQuery({
|
||||
queryKey: organizationKeys.getAvailableUsers(),
|
||||
|
||||
@@ -4,7 +4,19 @@ export enum PamResourceType {
|
||||
MySQL = "mysql",
|
||||
RDP = "rdp",
|
||||
SSH = "ssh",
|
||||
Kubernetes = "kubernetes"
|
||||
Kubernetes = "kubernetes",
|
||||
OracleDB = "oracledb",
|
||||
SQLite = "sqlite",
|
||||
MsSQL = "mssql",
|
||||
MCP = "mcp",
|
||||
Redis = "redis",
|
||||
MongoDB = "mongodb",
|
||||
WebApp = "webapp",
|
||||
Cassandra = "cassandra",
|
||||
CockroachDB = "cockroachdb",
|
||||
Elasticsearch = "elasticsearch",
|
||||
Snowflake = "snowflake",
|
||||
DynamoDB = "dynamodb"
|
||||
}
|
||||
|
||||
export enum PamResourceOrderBy {
|
||||
|
||||
@@ -8,5 +8,17 @@ export const PAM_RESOURCE_TYPE_MAP: Record<
|
||||
[PamResourceType.MySQL]: { name: "MySQL", image: "MySql.png" },
|
||||
[PamResourceType.RDP]: { name: "RDP", image: "RDP.png" },
|
||||
[PamResourceType.SSH]: { name: "SSH", image: "SSH.png" },
|
||||
[PamResourceType.Kubernetes]: { name: "Kubernetes", image: "Kubernetes.png" }
|
||||
[PamResourceType.Kubernetes]: { name: "Kubernetes", image: "Kubernetes.png" },
|
||||
[PamResourceType.OracleDB]: { name: "OracleDB", image: "Oracle.png", size: 55 },
|
||||
[PamResourceType.SQLite]: { name: "SQLite", image: "SQLite.png" },
|
||||
[PamResourceType.MsSQL]: { name: "Microsoft SQL Server", image: "MsSql.png" },
|
||||
[PamResourceType.MCP]: { name: "MCP", image: "MCP.png" },
|
||||
[PamResourceType.Redis]: { name: "Redis", image: "Redis.png" },
|
||||
[PamResourceType.MongoDB]: { name: "MongoDB", image: "MongoDB.png" },
|
||||
[PamResourceType.WebApp]: { name: "Web Application", image: "Web.png" },
|
||||
[PamResourceType.Cassandra]: { name: "Cassandra", image: "Cassandra.png", size: 55 },
|
||||
[PamResourceType.CockroachDB]: { name: "CockroachDB", image: "CockroachDB.png" },
|
||||
[PamResourceType.Elasticsearch]: { name: "Elasticsearch", image: "Elastic.png" },
|
||||
[PamResourceType.Snowflake]: { name: "Snowflake", image: "Snowflake.png" },
|
||||
[PamResourceType.DynamoDB]: { name: "DynamoDB", image: "DynamoDB.png", size: 55 }
|
||||
};
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
export { useCreatePkiAlertV2, useDeletePkiAlertV2, useUpdatePkiAlertV2 } from "./mutations";
|
||||
export {
|
||||
pkiAlertsV2Keys,
|
||||
useGetPkiAlertsV2,
|
||||
useGetPkiAlertV2ById,
|
||||
useGetPkiAlertV2CurrentMatchingCertificates,
|
||||
useGetPkiAlertV2MatchingCertificates
|
||||
} from "./queries";
|
||||
export type {
|
||||
TCreatePkiAlertV2,
|
||||
TDeletePkiAlertV2,
|
||||
TGetPkiAlertsV2,
|
||||
TGetPkiAlertV2ById,
|
||||
TGetPkiAlertV2CurrentMatchingCertificates,
|
||||
TGetPkiAlertV2CurrentMatchingCertificatesResponse,
|
||||
TGetPkiAlertV2MatchingCertificates,
|
||||
TPkiAlertChannelConfigEmail,
|
||||
TPkiAlertChannelV2,
|
||||
TPkiAlertV2,
|
||||
TPkiFilterRuleV2,
|
||||
TUpdatePkiAlertV2
|
||||
} from "./types";
|
||||
export {
|
||||
createPkiAlertV2Schema,
|
||||
PkiAlertChannelTypeV2,
|
||||
PkiAlertEventTypeV2,
|
||||
PkiFilterFieldV2,
|
||||
PkiFilterOperatorV2,
|
||||
updatePkiAlertV2Schema
|
||||
} from "./types";
|
||||
@@ -0,0 +1,68 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { pkiAlertsV2Keys } from "./queries";
|
||||
import { TCreatePkiAlertV2, TDeletePkiAlertV2, TPkiAlertV2, TUpdatePkiAlertV2 } from "./types";
|
||||
|
||||
export const useCreatePkiAlertV2 = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<TPkiAlertV2, unknown, TCreatePkiAlertV2>({
|
||||
mutationFn: async (data) => {
|
||||
const { data: response } = await apiRequest.post<{ alert: TPkiAlertV2 }>(
|
||||
"/api/v2/pki/alerts",
|
||||
data
|
||||
);
|
||||
return response.alert;
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiAlertsV2Keys.allPkiAlertsV2({ projectId: variables.projectId })
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdatePkiAlertV2 = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<TPkiAlertV2, unknown, TUpdatePkiAlertV2>({
|
||||
mutationFn: async ({ alertId, ...data }) => {
|
||||
const { data: response } = await apiRequest.patch<{ alert: TPkiAlertV2 }>(
|
||||
`/api/v2/pki/alerts/${alertId}`,
|
||||
data
|
||||
);
|
||||
return response.alert;
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiAlertsV2Keys.specificPkiAlertV2(variables.alertId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiAlertsV2Keys.all
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeletePkiAlertV2 = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<TPkiAlertV2, unknown, TDeletePkiAlertV2>({
|
||||
mutationFn: async ({ alertId }) => {
|
||||
const { data } = await apiRequest.delete<{ alert: TPkiAlertV2 }>(
|
||||
`/api/v2/pki/alerts/${alertId}`
|
||||
);
|
||||
return data.alert;
|
||||
},
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pkiAlertsV2Keys.all
|
||||
});
|
||||
queryClient.removeQueries({
|
||||
queryKey: pkiAlertsV2Keys.specificPkiAlertV2(variables.alertId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import {
|
||||
TGetPkiAlertsV2,
|
||||
TGetPkiAlertsV2Response,
|
||||
TGetPkiAlertV2ById,
|
||||
TGetPkiAlertV2CurrentMatchingCertificates,
|
||||
TGetPkiAlertV2CurrentMatchingCertificatesResponse,
|
||||
TGetPkiAlertV2MatchingCertificates,
|
||||
TGetPkiAlertV2MatchingCertificatesResponse,
|
||||
TPkiAlertV2
|
||||
} from "./types";
|
||||
|
||||
export const pkiAlertsV2Keys = {
|
||||
all: ["pki-alerts-v2"] as const,
|
||||
allPkiAlertsV2: (filters?: TGetPkiAlertsV2) => [pkiAlertsV2Keys.all[0], filters] as const,
|
||||
specificPkiAlertV2: (alertId: string) => [...pkiAlertsV2Keys.all, alertId] as const,
|
||||
pkiAlertV2MatchingCertificates: (alertId: string, filters?: TGetPkiAlertV2MatchingCertificates) =>
|
||||
[...pkiAlertsV2Keys.specificPkiAlertV2(alertId), "certificates", filters] as const,
|
||||
pkiAlertV2CurrentMatchingCertificates: (filters?: TGetPkiAlertV2CurrentMatchingCertificates) =>
|
||||
[...pkiAlertsV2Keys.all, "current-certificates", filters] as const
|
||||
};
|
||||
|
||||
const fetchPkiAlertsV2 = async (params: TGetPkiAlertsV2): Promise<TGetPkiAlertsV2Response> => {
|
||||
const { data } = await apiRequest.get<TGetPkiAlertsV2Response>("/api/v2/pki/alerts", {
|
||||
params
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
const fetchPkiAlertV2ById = async ({ alertId }: TGetPkiAlertV2ById): Promise<TPkiAlertV2> => {
|
||||
const { data } = await apiRequest.get<{ alert: TPkiAlertV2 }>(`/api/v2/pki/alerts/${alertId}`);
|
||||
return data.alert;
|
||||
};
|
||||
|
||||
const fetchPkiAlertV2MatchingCertificates = async (
|
||||
params: TGetPkiAlertV2MatchingCertificates
|
||||
): Promise<TGetPkiAlertV2MatchingCertificatesResponse> => {
|
||||
const { alertId, ...queryParams } = params;
|
||||
const { data } = await apiRequest.get<TGetPkiAlertV2MatchingCertificatesResponse>(
|
||||
`/api/v2/pki/alerts/${alertId}/certificates`,
|
||||
{ params: queryParams }
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
const fetchPkiAlertV2CurrentMatchingCertificates = async (
|
||||
params: TGetPkiAlertV2CurrentMatchingCertificates
|
||||
): Promise<TGetPkiAlertV2CurrentMatchingCertificatesResponse> => {
|
||||
const { data } = await apiRequest.post<TGetPkiAlertV2CurrentMatchingCertificatesResponse>(
|
||||
"/api/v2/pki/alerts/preview/certificates",
|
||||
params
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const useGetPkiAlertsV2 = (
|
||||
params: TGetPkiAlertsV2,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TGetPkiAlertsV2Response,
|
||||
unknown,
|
||||
TGetPkiAlertsV2Response,
|
||||
ReturnType<typeof pkiAlertsV2Keys.allPkiAlertsV2>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: pkiAlertsV2Keys.allPkiAlertsV2(params),
|
||||
queryFn: () => fetchPkiAlertsV2(params),
|
||||
enabled: !!params.projectId,
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetPkiAlertV2ById = (
|
||||
params: TGetPkiAlertV2ById,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TPkiAlertV2,
|
||||
unknown,
|
||||
TPkiAlertV2,
|
||||
ReturnType<typeof pkiAlertsV2Keys.specificPkiAlertV2>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: pkiAlertsV2Keys.specificPkiAlertV2(params.alertId),
|
||||
queryFn: () => fetchPkiAlertV2ById(params),
|
||||
enabled: !!params.alertId,
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetPkiAlertV2MatchingCertificates = (
|
||||
params: TGetPkiAlertV2MatchingCertificates,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TGetPkiAlertV2MatchingCertificatesResponse,
|
||||
unknown,
|
||||
TGetPkiAlertV2MatchingCertificatesResponse,
|
||||
ReturnType<typeof pkiAlertsV2Keys.pkiAlertV2MatchingCertificates>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: pkiAlertsV2Keys.pkiAlertV2MatchingCertificates(params.alertId, params),
|
||||
queryFn: () => fetchPkiAlertV2MatchingCertificates(params),
|
||||
enabled: !!params.alertId,
|
||||
placeholderData: (previousData) => previousData,
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetPkiAlertV2CurrentMatchingCertificates = (
|
||||
params: TGetPkiAlertV2CurrentMatchingCertificates,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TGetPkiAlertV2CurrentMatchingCertificatesResponse,
|
||||
unknown,
|
||||
TGetPkiAlertV2CurrentMatchingCertificatesResponse,
|
||||
ReturnType<typeof pkiAlertsV2Keys.pkiAlertV2CurrentMatchingCertificates>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: pkiAlertsV2Keys.pkiAlertV2CurrentMatchingCertificates(params),
|
||||
queryFn: () => fetchPkiAlertV2CurrentMatchingCertificates(params),
|
||||
enabled: !!params.projectId && params.filters !== undefined,
|
||||
placeholderData: (previousData) => previousData,
|
||||
...options
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,192 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export enum PkiAlertEventTypeV2 {
|
||||
EXPIRATION = "expiration",
|
||||
RENEWAL = "renewal",
|
||||
ISSUANCE = "issuance",
|
||||
REVOCATION = "revocation"
|
||||
}
|
||||
|
||||
export enum PkiAlertChannelTypeV2 {
|
||||
EMAIL = "email"
|
||||
}
|
||||
|
||||
export enum PkiFilterFieldV2 {
|
||||
COMMON_NAME = "common_name",
|
||||
PROFILE_NAME = "profile_name",
|
||||
SAN = "san",
|
||||
INCLUDE_CAS = "include_cas"
|
||||
}
|
||||
|
||||
export enum PkiFilterOperatorV2 {
|
||||
EQUALS = "equals",
|
||||
CONTAINS = "contains",
|
||||
STARTS_WITH = "starts_with",
|
||||
ENDS_WITH = "ends_with",
|
||||
MATCHES = "matches"
|
||||
}
|
||||
|
||||
export interface TPkiFilterRuleV2 {
|
||||
field: PkiFilterFieldV2;
|
||||
operator: PkiFilterOperatorV2;
|
||||
value: string | string[] | boolean;
|
||||
}
|
||||
|
||||
export interface TPkiAlertChannelConfigEmail {
|
||||
recipients: string[];
|
||||
}
|
||||
|
||||
// In the future other channels like webhooks will be supported here
|
||||
export type TPkiAlertChannelConfig = TPkiAlertChannelConfigEmail;
|
||||
|
||||
export interface TPkiAlertChannelV2 {
|
||||
id: string;
|
||||
channelType: PkiAlertChannelTypeV2;
|
||||
config: TPkiAlertChannelConfig;
|
||||
enabled: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface TPkiAlertV2 {
|
||||
id: string;
|
||||
projectId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
eventType: PkiAlertEventTypeV2;
|
||||
alertBefore?: string;
|
||||
filters: TPkiFilterRuleV2[];
|
||||
enabled: boolean;
|
||||
channels: TPkiAlertChannelV2[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface TPkiCertificateMatchV2 {
|
||||
id: string;
|
||||
serialNumber: string;
|
||||
commonName?: string;
|
||||
san?: string[];
|
||||
profileName?: string;
|
||||
enrollmentType?: string;
|
||||
notBefore: string;
|
||||
notAfter: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface TGetPkiAlertsV2 {
|
||||
projectId: string;
|
||||
search?: string;
|
||||
eventType?: PkiAlertEventTypeV2;
|
||||
enabled?: boolean;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface TGetPkiAlertsV2Response {
|
||||
alerts: TPkiAlertV2[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface TGetPkiAlertV2ById {
|
||||
alertId: string;
|
||||
}
|
||||
|
||||
export interface TCreatePkiAlertV2 {
|
||||
projectId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
eventType: PkiAlertEventTypeV2;
|
||||
alertBefore?: string;
|
||||
filters: TPkiFilterRuleV2[];
|
||||
enabled?: boolean;
|
||||
channels: Omit<TPkiAlertChannelV2, "id" | "createdAt" | "updatedAt">[];
|
||||
}
|
||||
|
||||
export interface TUpdatePkiAlertV2 {
|
||||
alertId: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
eventType?: PkiAlertEventTypeV2;
|
||||
alertBefore?: string;
|
||||
filters?: TPkiFilterRuleV2[];
|
||||
enabled?: boolean;
|
||||
channels?: Omit<TPkiAlertChannelV2, "id" | "createdAt" | "updatedAt">[];
|
||||
}
|
||||
|
||||
export interface TDeletePkiAlertV2 {
|
||||
alertId: string;
|
||||
}
|
||||
|
||||
export interface TGetPkiAlertV2MatchingCertificates {
|
||||
alertId: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface TGetPkiAlertV2MatchingCertificatesResponse {
|
||||
certificates: TPkiCertificateMatchV2[];
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export interface TGetPkiAlertV2CurrentMatchingCertificates {
|
||||
projectId: string;
|
||||
filters: TPkiFilterRuleV2[];
|
||||
alertBefore: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export interface TGetPkiAlertV2CurrentMatchingCertificatesResponse {
|
||||
certificates: TPkiCertificateMatchV2[];
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export const pkiFilterRuleV2Schema = z.object({
|
||||
field: z.nativeEnum(PkiFilterFieldV2),
|
||||
operator: z.nativeEnum(PkiFilterOperatorV2),
|
||||
value: z.union([z.string(), z.array(z.string()), z.boolean()])
|
||||
});
|
||||
|
||||
const emailChannelConfigSchema = z.object({
|
||||
recipients: z
|
||||
.array(z.string())
|
||||
.transform((emails) => emails.filter(Boolean).map((email) => email.trim()))
|
||||
.refine((emails) => emails.length > 0, "At least one email recipient is required")
|
||||
.refine((emails) => emails.length <= 10, "Maximum 10 email recipients allowed")
|
||||
.refine(
|
||||
(emails) => emails.every((email) => z.string().email().safeParse(email).success),
|
||||
"All recipients must be valid email addresses"
|
||||
)
|
||||
});
|
||||
|
||||
export const pkiAlertChannelV2Schema = z.object({
|
||||
channelType: z.nativeEnum(PkiAlertChannelTypeV2),
|
||||
config: emailChannelConfigSchema,
|
||||
enabled: z.boolean().default(true)
|
||||
});
|
||||
|
||||
export const createPkiAlertV2Schema = z.object({
|
||||
projectId: z.string().uuid(),
|
||||
name: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(255)
|
||||
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Must be a valid name (lowercase, numbers, hyphens only)"),
|
||||
description: z.string().max(1000).optional(),
|
||||
eventType: z.nativeEnum(PkiAlertEventTypeV2),
|
||||
alertBefore: z
|
||||
.string()
|
||||
.regex(/^\d+[dwmy]$/, "Must be in format like '30d', '1w', '3m', '1y'")
|
||||
.refine((val) => val.length <= 32, "Alert timing too long")
|
||||
.optional(),
|
||||
filters: z.array(pkiFilterRuleV2Schema),
|
||||
enabled: z.boolean().default(true),
|
||||
channels: z.array(pkiAlertChannelV2Schema).min(1)
|
||||
});
|
||||
|
||||
export const updatePkiAlertV2Schema = createPkiAlertV2Schema.partial().omit({ projectId: true });
|
||||
@@ -0,0 +1,15 @@
|
||||
export {
|
||||
useCreateProjectIdentity,
|
||||
useDeleteProjectIdentity,
|
||||
useUpdateProjectIdentity
|
||||
} from "./mutations";
|
||||
export { projectIdentityQuery } from "./queries";
|
||||
export type {
|
||||
TCreateProjectIdentityDTO,
|
||||
TDeleteProjectIdentityDTO,
|
||||
TGetProjectIdentityByIdDTO,
|
||||
TListProjectIdentitiesDTO,
|
||||
TProjectIdentity,
|
||||
TProjectIdentityMetadata,
|
||||
TUpdateProjectIdentityDTO
|
||||
} from "./types";
|
||||
@@ -0,0 +1,76 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { identitiesKeys, projectKeys } from "@app/hooks/api";
|
||||
import { subscriptionQueryKeys } from "@app/hooks/api/subscriptions/queries";
|
||||
|
||||
import { projectIdentityQuery } from "./queries";
|
||||
import {
|
||||
TCreateProjectIdentityDTO,
|
||||
TDeleteProjectIdentityDTO,
|
||||
TProjectIdentity,
|
||||
TUpdateProjectIdentityDTO
|
||||
} from "./types";
|
||||
|
||||
export const useCreateProjectIdentity = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ projectId, ...dto }: TCreateProjectIdentityDTO) => {
|
||||
const { data } = await apiRequest.post<{ identity: TProjectIdentity }>(
|
||||
`/api/v1/projects/${projectId}/identities`,
|
||||
dto
|
||||
);
|
||||
return data.identity;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: subscriptionQueryKeys.all()
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateProjectIdentity = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ projectId, identityId, ...updates }: TUpdateProjectIdentityDTO) => {
|
||||
const { data } = await apiRequest.patch<{ identity: TProjectIdentity }>(
|
||||
`/api/v1/projects/${projectId}/identities/${identityId}`,
|
||||
updates
|
||||
);
|
||||
return data.identity;
|
||||
},
|
||||
onSuccess: (_, { projectId, identityId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteProjectIdentity = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ projectId, identityId }: TDeleteProjectIdentityDTO) => {
|
||||
const { data } = await apiRequest.delete<{ identity: TProjectIdentity }>(
|
||||
`/api/v1/projects/${projectId}/identities/${identityId}`
|
||||
);
|
||||
return data.identity;
|
||||
},
|
||||
onSuccess: (_, { projectId, identityId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
|
||||
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: subscriptionQueryKeys.all()
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { queryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import { TGetProjectIdentityByIdDTO, TListProjectIdentitiesDTO, TProjectIdentity } from "./types";
|
||||
|
||||
export const projectIdentityQuery = {
|
||||
allKey: () => ["project-identities"] as const,
|
||||
getByIdKey: (params: TGetProjectIdentityByIdDTO) =>
|
||||
[...projectIdentityQuery.allKey(), "by-id", params] as const,
|
||||
listKey: (params: TListProjectIdentitiesDTO) =>
|
||||
[...projectIdentityQuery.allKey(), "list", params] as const,
|
||||
getById: (params: TGetProjectIdentityByIdDTO) =>
|
||||
queryOptions({
|
||||
queryKey: projectIdentityQuery.getByIdKey(params),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{ identity: TProjectIdentity }>(
|
||||
`/api/v1/projects/${params.projectId}/identities/${params.identityId}`
|
||||
);
|
||||
return data.identity;
|
||||
}
|
||||
}),
|
||||
list: (params: TListProjectIdentitiesDTO) =>
|
||||
queryOptions({
|
||||
queryKey: projectIdentityQuery.listKey(params),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{
|
||||
identities: TProjectIdentity[];
|
||||
totalCount: number;
|
||||
}>(`/api/v1/projects/${params.projectId}/identities`, {
|
||||
params: {
|
||||
offset: params.offset,
|
||||
limit: params.limit,
|
||||
search: params.search
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { TIdentity, TMetadata } from "@app/hooks/api/shared";
|
||||
|
||||
export type TProjectIdentityMetadata = {
|
||||
key: string;
|
||||
value: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type TProjectIdentity = TIdentity;
|
||||
|
||||
export type TCreateProjectIdentityDTO = {
|
||||
projectId: string;
|
||||
name: string;
|
||||
hasDeleteProtection?: boolean;
|
||||
metadata?: TMetadata[];
|
||||
};
|
||||
|
||||
export type TUpdateProjectIdentityDTO = {
|
||||
projectId: string;
|
||||
identityId: string;
|
||||
name?: string;
|
||||
hasDeleteProtection?: boolean;
|
||||
metadata?: TMetadata[];
|
||||
};
|
||||
|
||||
export type TGetProjectIdentityByIdDTO = {
|
||||
projectId: string;
|
||||
identityId: string;
|
||||
};
|
||||
|
||||
export type TListProjectIdentitiesDTO = {
|
||||
projectId: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
};
|
||||
|
||||
export type TDeleteProjectIdentityDTO = {
|
||||
projectId: string;
|
||||
identityId: string;
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./mutations";
|
||||
export * from "./queries";
|
||||
export * from "./types";
|
||||
@@ -0,0 +1,93 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { identitiesKeys, projectKeys } from "@app/hooks/api";
|
||||
import { projectIdentityQuery } from "@app/hooks/api/projectIdentity";
|
||||
|
||||
import {
|
||||
TCreateProjectIdentityMembershipDTO,
|
||||
TDeleteProjectIdentityMembershipDTO,
|
||||
TProjectIdentityMembership,
|
||||
TUpdateProjectIdentityMembershipDTO
|
||||
} from "./types";
|
||||
|
||||
export const useCreateProjectIdentityMembership = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ identityId, projectId, role }: TCreateProjectIdentityMembershipDTO) => {
|
||||
const {
|
||||
data: { identityMembership }
|
||||
} = await apiRequest.post<{ identityMembership: TProjectIdentityMembership }>(
|
||||
`/api/v1/projects/${projectId}/memberships/identities/${identityId}`,
|
||||
{
|
||||
role
|
||||
}
|
||||
);
|
||||
|
||||
return identityMembership;
|
||||
},
|
||||
onSuccess: (_, { identityId, projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateProjectIdentityMembership = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
projectId,
|
||||
identityId,
|
||||
...updates
|
||||
}: TUpdateProjectIdentityMembershipDTO) => {
|
||||
const {
|
||||
data: { identityMembership }
|
||||
} = await apiRequest.patch<{ identityMembership: TProjectIdentityMembership }>(
|
||||
`/api/v1/projects/${projectId}/memberships/identities/${identityId}`,
|
||||
updates
|
||||
);
|
||||
return identityMembership;
|
||||
},
|
||||
onSuccess: (_, { projectId, identityId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteProjectIdentityMembership = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ identityId, projectId }: TDeleteProjectIdentityMembershipDTO) => {
|
||||
const {
|
||||
data: { identityMembership }
|
||||
} = await apiRequest.delete<{ identityMembership: TProjectIdentityMembership }>(
|
||||
`/api/v1/projects/${projectId}/memberships/identities/${identityId}`
|
||||
);
|
||||
return identityMembership;
|
||||
},
|
||||
onSuccess: (_, { identityId, projectId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: projectIdentityQuery.allKey() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
import { queryOptions, useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import {
|
||||
projectKeys,
|
||||
TAvailableProjectIdentities,
|
||||
TListAvailableProjectIdentitiesDTO
|
||||
} from "@app/hooks/api";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import {
|
||||
IdentityProjectMembershipV1,
|
||||
TProjectIdentityMembershipsListV2
|
||||
} from "@app/hooks/api/identities/types";
|
||||
import { ProjectIdentityOrderBy, TListProjectIdentitiesDTO } from "@app/hooks/api/projects/types";
|
||||
|
||||
export const projectIdentityMembershipQuery = {
|
||||
allKey: () => ["project-identity-memberships"] as const,
|
||||
listAvailableKey: (params?: TListAvailableProjectIdentitiesDTO) =>
|
||||
[...projectIdentityMembershipQuery.allKey(), "list-available", params] as const,
|
||||
listAvailable: (params: TListAvailableProjectIdentitiesDTO) =>
|
||||
queryOptions({
|
||||
queryKey: projectIdentityMembershipQuery.listAvailableKey(params),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<{
|
||||
identities: TAvailableProjectIdentities;
|
||||
}>(`/api/v1/projects/${params.projectId}/memberships/available-identities`, {
|
||||
params: {
|
||||
offset: params.offset,
|
||||
limit: params.limit,
|
||||
identityName: params.identityName
|
||||
}
|
||||
});
|
||||
return data.identities;
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// TODO (scott/akhi): move to new projectIdentityMembershipQuery structure
|
||||
|
||||
export const useListProjectIdentityMemberships = (
|
||||
{
|
||||
projectId,
|
||||
offset = 0,
|
||||
limit = 100,
|
||||
orderBy = ProjectIdentityOrderBy.Name,
|
||||
orderDirection = OrderByDirection.ASC,
|
||||
search = ""
|
||||
}: TListProjectIdentitiesDTO,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TProjectIdentityMembershipsListV2,
|
||||
unknown,
|
||||
TProjectIdentityMembershipsListV2,
|
||||
ReturnType<typeof projectKeys.getProjectIdentityMembershipsWithParams>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: projectKeys.getProjectIdentityMembershipsWithParams({
|
||||
projectId,
|
||||
offset,
|
||||
limit,
|
||||
orderBy,
|
||||
orderDirection,
|
||||
search
|
||||
}),
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
orderBy: String(orderBy),
|
||||
orderDirection: String(orderDirection),
|
||||
search: String(search)
|
||||
});
|
||||
|
||||
const { data } = await apiRequest.get<TProjectIdentityMembershipsListV2>(
|
||||
`/api/v1/projects/${projectId}/memberships/identities`,
|
||||
{ params }
|
||||
);
|
||||
return data;
|
||||
},
|
||||
enabled: true,
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetProjectIdentityMembership = (projectId: string, identityId: string) => {
|
||||
return useQuery({
|
||||
enabled: Boolean(projectId && identityId),
|
||||
queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId),
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { identityMembership }
|
||||
} = await apiRequest.get<{ identityMembership: IdentityProjectMembershipV1 }>(
|
||||
`/api/v1/projects/${projectId}/identity-memberships/${identityId}`
|
||||
);
|
||||
return identityMembership;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetProjectIdentityMembershipV2 = (projectId: string, identityId: string) => {
|
||||
return useQuery({
|
||||
enabled: Boolean(projectId && identityId),
|
||||
queryKey: projectKeys.getProjectIdentityMembershipDetailsV2(projectId, identityId),
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { identityMembership }
|
||||
} = await apiRequest.get<{ identityMembership: IdentityProjectMembershipV1 }>(
|
||||
`/api/v1/projects/${projectId}/memberships/identities/${identityId}`
|
||||
);
|
||||
return identityMembership;
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { TRoles } from "@app/hooks/api/shared";
|
||||
|
||||
export type TProjectIdentityMembership = {
|
||||
id: string;
|
||||
projectId: string;
|
||||
identityId: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
// TODO
|
||||
};
|
||||
|
||||
export type TCreateProjectIdentityMembershipDTO = {
|
||||
identityId: string;
|
||||
projectId: string;
|
||||
role?: string;
|
||||
};
|
||||
|
||||
export type TUpdateProjectIdentityMembershipDTO = {
|
||||
identityId: string;
|
||||
projectId: string;
|
||||
roles: TRoles;
|
||||
};
|
||||
|
||||
export type TDeleteProjectIdentityMembershipDTO = {
|
||||
identityId: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TListAvailableProjectIdentitiesDTO = {
|
||||
projectId: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
identityName?: string;
|
||||
};
|
||||
|
||||
export type TAvailableProjectIdentities = Array<{ id: string; name: string }>;
|
||||
@@ -8,10 +8,8 @@ export {
|
||||
useUpdateProjectSshConfig
|
||||
} from "./mutations";
|
||||
export {
|
||||
useAddIdentityToWorkspace,
|
||||
useCreateWorkspace,
|
||||
useCreateWsEnvironment,
|
||||
useDeleteIdentityFromWorkspace,
|
||||
useDeleteUserFromWorkspace,
|
||||
useDeleteWorkspace,
|
||||
useDeleteWsEnvironment,
|
||||
@@ -21,8 +19,6 @@ export {
|
||||
useGetUserWorkspaceMemberships,
|
||||
useGetWorkspaceAuthorizations,
|
||||
useGetWorkspaceById,
|
||||
useGetWorkspaceIdentityMembershipDetails,
|
||||
useGetWorkspaceIdentityMemberships,
|
||||
useGetWorkspaceIndexStatus,
|
||||
useGetWorkspaceIntegrations,
|
||||
useGetWorkspaceUserDetails,
|
||||
@@ -41,7 +37,6 @@ export {
|
||||
useListWorkspaceSshHostGroups,
|
||||
useListWorkspaceSshHosts,
|
||||
useSearchProjects,
|
||||
useUpdateIdentityWorkspaceRole,
|
||||
useUpdateProject,
|
||||
useUpdateUserWorkspaceRole,
|
||||
useUpdateWsEnvironment,
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
|
||||
import { CaStatus } from "../ca/enums";
|
||||
import { TCertificateAuthority } from "../ca/types";
|
||||
import { TCertificate } from "../certificates/types";
|
||||
import { TCertificateTemplate } from "../certificateTemplates/types";
|
||||
import { TGroupMembership } from "../groups/types";
|
||||
import { identitiesKeys } from "../identities/queries";
|
||||
import { IdentityMembership, TProjectIdentitiesList } from "../identities/types";
|
||||
import { IntegrationAuth } from "../integrationAuth/types";
|
||||
import { TIntegration } from "../integrations/types";
|
||||
import { TPkiAlert } from "../pkiAlerts/types";
|
||||
@@ -33,13 +30,10 @@ import {
|
||||
DeleteWorkspaceDTO,
|
||||
Project,
|
||||
ProjectEnv,
|
||||
ProjectIdentityOrderBy,
|
||||
ProjectType,
|
||||
TGetUpgradeProjectStatusDTO,
|
||||
TListProjectIdentitiesDTO,
|
||||
TProjectSshConfig,
|
||||
TSearchProjectsDTO,
|
||||
TUpdateWorkspaceIdentityRoleDTO,
|
||||
TUpdateWorkspaceUserRoleDTO,
|
||||
UpdateAuditLogsRetentionDTO,
|
||||
UpdateEnvironmentDTO,
|
||||
@@ -452,154 +446,6 @@ export const useUpdateUserWorkspaceRole = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddIdentityToWorkspace = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
identityId,
|
||||
projectId,
|
||||
role
|
||||
}: {
|
||||
identityId: string;
|
||||
projectId: string;
|
||||
role?: string;
|
||||
}) => {
|
||||
const {
|
||||
data: { identityMembership }
|
||||
} = await apiRequest.post(
|
||||
`/api/v1/projects/${projectId}/identity-memberships/${identityId}`,
|
||||
{
|
||||
role
|
||||
}
|
||||
);
|
||||
|
||||
return identityMembership;
|
||||
},
|
||||
onSuccess: (_, { identityId, projectId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateIdentityWorkspaceRole = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ identityId, projectId, roles }: TUpdateWorkspaceIdentityRoleDTO) => {
|
||||
const {
|
||||
data: { identityMembership }
|
||||
} = await apiRequest.patch(
|
||||
`/api/v1/projects/${projectId}/identity-memberships/${identityId}`,
|
||||
{
|
||||
roles
|
||||
}
|
||||
);
|
||||
|
||||
return identityMembership;
|
||||
},
|
||||
onSuccess: (_, { identityId, projectId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteIdentityFromWorkspace = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async ({ identityId, projectId }: { identityId: string; projectId: string }) => {
|
||||
const {
|
||||
data: { identityMembership }
|
||||
} = await apiRequest.delete(
|
||||
`/api/v1/projects/${projectId}/identity-memberships/${identityId}`
|
||||
);
|
||||
return identityMembership;
|
||||
},
|
||||
onSuccess: (_, { identityId, projectId }) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: projectKeys.getProjectIdentityMemberships(projectId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: identitiesKeys.getIdentityProjectMemberships(identityId)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetWorkspaceIdentityMemberships = (
|
||||
{
|
||||
projectId,
|
||||
offset = 0,
|
||||
limit = 100,
|
||||
orderBy = ProjectIdentityOrderBy.Name,
|
||||
orderDirection = OrderByDirection.ASC,
|
||||
search = ""
|
||||
}: TListProjectIdentitiesDTO,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
TProjectIdentitiesList,
|
||||
unknown,
|
||||
TProjectIdentitiesList,
|
||||
ReturnType<typeof projectKeys.getProjectIdentityMembershipsWithParams>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: projectKeys.getProjectIdentityMembershipsWithParams({
|
||||
projectId,
|
||||
offset,
|
||||
limit,
|
||||
orderBy,
|
||||
orderDirection,
|
||||
search
|
||||
}),
|
||||
queryFn: async () => {
|
||||
const params = new URLSearchParams({
|
||||
offset: String(offset),
|
||||
limit: String(limit),
|
||||
orderBy: String(orderBy),
|
||||
orderDirection: String(orderDirection),
|
||||
search: String(search)
|
||||
});
|
||||
|
||||
const { data } = await apiRequest.get<TProjectIdentitiesList>(
|
||||
`/api/v1/projects/${projectId}/identity-memberships`,
|
||||
{ params }
|
||||
);
|
||||
return data;
|
||||
},
|
||||
enabled: true,
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetWorkspaceIdentityMembershipDetails = (projectId: string, identityId: string) => {
|
||||
return useQuery({
|
||||
enabled: Boolean(projectId && identityId),
|
||||
queryKey: projectKeys.getProjectIdentityMembershipDetails(projectId, identityId),
|
||||
queryFn: async () => {
|
||||
const {
|
||||
data: { identityMembership }
|
||||
} = await apiRequest.get<{ identityMembership: IdentityMembership }>(
|
||||
`/api/v1/projects/${projectId}/identity-memberships/${identityId}`
|
||||
);
|
||||
return identityMembership;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetWorkspaceGroupMembershipDetails = (projectId: string, groupId: string) => {
|
||||
return useQuery({
|
||||
enabled: Boolean(projectId && groupId),
|
||||
|
||||
@@ -23,6 +23,8 @@ export const projectKeys = {
|
||||
[{ projectId }, "project-identity-memberships"] as const,
|
||||
getProjectIdentityMembershipDetails: (projectId: string, identityId: string) =>
|
||||
[{ projectId, identityId }, "project-identity-membership-details"] as const,
|
||||
getProjectIdentityMembershipDetailsV2: (projectId: string, identityId: string) =>
|
||||
[{ projectId, identityId }, "project-identity-membership-details"] as const,
|
||||
// allows invalidation using above key without knowing params
|
||||
getProjectIdentityMembershipsWithParams: ({ projectId, ...params }: TListProjectIdentitiesDTO) =>
|
||||
[...projectKeys.getProjectIdentityMemberships(projectId), params] as const,
|
||||
|
||||
@@ -138,24 +138,6 @@ export type TUpdateWorkspaceUserRoleDTO = {
|
||||
)[];
|
||||
};
|
||||
|
||||
export type TUpdateWorkspaceIdentityRoleDTO = {
|
||||
identityId: string;
|
||||
projectId: string;
|
||||
roles: (
|
||||
| {
|
||||
role: string;
|
||||
isTemporary?: false;
|
||||
}
|
||||
| {
|
||||
role: string;
|
||||
isTemporary: true;
|
||||
temporaryMode: ProjectUserMembershipTemporaryMode;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
)[];
|
||||
};
|
||||
|
||||
export type TUpdateWorkspaceGroupRoleDTO = {
|
||||
groupId: string;
|
||||
projectId: string;
|
||||
|
||||
@@ -54,6 +54,7 @@ export type TSecretApprovalRequest = {
|
||||
lastName: string;
|
||||
username: string;
|
||||
isOrgMembershipActive: boolean;
|
||||
createdAt: Date;
|
||||
}[];
|
||||
project: string;
|
||||
environment: string;
|
||||
|
||||
@@ -109,6 +109,7 @@ export type SecretVersions = {
|
||||
actorType?: string | null;
|
||||
name?: string | null;
|
||||
membershipId?: string | null;
|
||||
groupId?: string | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./types";
|
||||
@@ -0,0 +1,37 @@
|
||||
import { IdentityAuthMethod } from "@app/hooks/api";
|
||||
|
||||
export enum TemporaryPermissionMode {
|
||||
Relative = "relative"
|
||||
}
|
||||
|
||||
export type TMetadata = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type TIdentity = {
|
||||
id: string;
|
||||
name: string;
|
||||
orgId: string;
|
||||
projectId: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
hasDeleteProtection: boolean;
|
||||
authMethods: IdentityAuthMethod[];
|
||||
activeLockoutAuthMethods: string[];
|
||||
metadata?: Array<TMetadata & { id: string }>;
|
||||
};
|
||||
|
||||
export type TRoles = Array<
|
||||
| {
|
||||
role: string;
|
||||
isTemporary?: false;
|
||||
}
|
||||
| {
|
||||
role: string;
|
||||
isTemporary: true;
|
||||
temporaryMode: TemporaryPermissionMode;
|
||||
temporaryRange: string;
|
||||
temporaryAccessStartTime: string;
|
||||
}
|
||||
>;
|
||||
@@ -7,7 +7,8 @@ import { SubscriptionPlan } from "./types";
|
||||
// import { Workspace } from './types';
|
||||
|
||||
export const subscriptionQueryKeys = {
|
||||
getOrgSubsription: (orgID: string) => ["plan", { orgID }] as const
|
||||
all: () => ["plan"] as const,
|
||||
getOrgSubsription: (orgID: string) => [...subscriptionQueryKeys.all(), { orgID }] as const
|
||||
};
|
||||
|
||||
export const fetchOrgSubscription = async (orgID: string, refreshCache: boolean = false) => {
|
||||
|
||||
@@ -48,6 +48,7 @@ export type SubscriptionPlan = {
|
||||
gateway: boolean;
|
||||
externalKms: boolean;
|
||||
pkiEst: boolean;
|
||||
pkiAcme: boolean;
|
||||
pkiLegacyTemplates: boolean;
|
||||
enforceMfa: boolean;
|
||||
enforceGoogleSSO: boolean;
|
||||
|
||||
@@ -86,6 +86,8 @@ export type ProjectWorkflowIntegrationConfig =
|
||||
accessRequestChannels: string;
|
||||
isSecretRequestNotificationEnabled: boolean;
|
||||
secretRequestChannels: string;
|
||||
isSecretSyncErrorNotificationEnabled: boolean;
|
||||
secretSyncErrorChannels: string;
|
||||
}
|
||||
| {
|
||||
id: string;
|
||||
@@ -112,6 +114,8 @@ export type TUpdateProjectWorkflowIntegrationConfigDTO =
|
||||
accessRequestChannels: string;
|
||||
isSecretRequestNotificationEnabled: boolean;
|
||||
secretRequestChannels: string;
|
||||
isSecretSyncErrorNotificationEnabled: boolean;
|
||||
secretSyncErrorChannels: string;
|
||||
}
|
||||
| {
|
||||
integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS;
|
||||
|
||||
@@ -2,12 +2,13 @@ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Button } from "@app/components/v2";
|
||||
import { useProject, useProjectPermission } from "@app/context";
|
||||
import { useOrganization, useProject, useProjectPermission } from "@app/context";
|
||||
import { getProjectHomePage } from "@app/helpers/project";
|
||||
import { useRemoveAssumeProjectPrivilege } from "@app/hooks/api";
|
||||
import { ActorType } from "@app/hooks/api/auditLogs/enums";
|
||||
|
||||
export const AssumePrivilegeModeBanner = () => {
|
||||
const { isSubOrganization, currentOrg } = useOrganization();
|
||||
const { currentProject } = useProject();
|
||||
const exitAssumePrivilegeMode = useRemoveAssumeProjectPrivilege();
|
||||
const { assumedPrivilegeDetails } = useProjectPermission();
|
||||
@@ -36,7 +37,7 @@ export const AssumePrivilegeModeBanner = () => {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
const url = getProjectHomePage(currentProject.type, currentProject.environments);
|
||||
const url = `${getProjectHomePage(currentProject.type, currentProject.environments)}${isSubOrganization ? `?subOrganization=${currentOrg.slug}` : ""}`;
|
||||
window.location.href = url.replace("$projectId", currentProject.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import { useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub, useProject } from "@app/context";
|
||||
import { useListWorkspacePkiAlerts } from "@app/hooks/api";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
import { PkiAlertsV2Page } from "@app/views/PkiAlertsV2Page";
|
||||
|
||||
import { PkiAlertsSection } from "./components";
|
||||
import { PkiAlertsSection, PkiCollectionSection } from "./components";
|
||||
|
||||
export const AlertingPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentProject } = useProject();
|
||||
const [selectedTab, setSelectedTab] = useState("rule-based");
|
||||
|
||||
const { data: v1AlertsData } = useListWorkspacePkiAlerts({
|
||||
projectId: currentProject?.id || ""
|
||||
});
|
||||
|
||||
const hasV1Alerts = v1AlertsData?.alerts && v1AlertsData.alerts.length > 0;
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex h-full flex-col justify-between bg-bunker-800 text-white">
|
||||
<Helmet>
|
||||
@@ -26,7 +38,33 @@ export const AlertingPage = () => {
|
||||
I={ProjectPermissionActions.Read}
|
||||
a={ProjectPermissionSub.PkiAlerts}
|
||||
>
|
||||
<PkiAlertsSection />
|
||||
{!hasV1Alerts ? (
|
||||
<div>
|
||||
<PkiAlertsV2Page hideContainer />
|
||||
</div>
|
||||
) : (
|
||||
<Tabs orientation="vertical" value={selectedTab} onValueChange={setSelectedTab}>
|
||||
<TabList>
|
||||
<Tab variant="project" value="rule-based">
|
||||
Certificate Alerts
|
||||
</Tab>
|
||||
<Tab variant="project" value="legacy">
|
||||
Collection Alerts (Legacy)
|
||||
</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanel value="rule-based">
|
||||
<PkiAlertsV2Page />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value="legacy">
|
||||
<div className="space-y-6">
|
||||
<PkiAlertsSection />
|
||||
<PkiCollectionSection />
|
||||
</div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["certificateExport"]>;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<["certificateExport"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
onFormatSelected: (
|
||||
format: "pem" | "pkcs12",
|
||||
serialNumber: string,
|
||||
options?: ExportOptions
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type CertificateExportFormat = "pem" | "pkcs12";
|
||||
|
||||
export type ExportOptions = {
|
||||
pkcs12?: {
|
||||
password: string;
|
||||
alias: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const CertificateExportModal = ({ popUp, handlePopUpToggle, onFormatSelected }: Props) => {
|
||||
const [selectedFormat, setSelectedFormat] = useState<CertificateExportFormat>("pem");
|
||||
const [pkcs12Options, setPkcs12Options] = useState({
|
||||
password: "",
|
||||
alias: ""
|
||||
});
|
||||
|
||||
const serialNumber =
|
||||
(popUp?.certificateExport?.data as { serialNumber: string })?.serialNumber || "";
|
||||
|
||||
// Reset form whenever the modal opens
|
||||
useEffect(() => {
|
||||
if (popUp?.certificateExport?.isOpen) {
|
||||
setSelectedFormat("pem");
|
||||
setPkcs12Options({
|
||||
password: "",
|
||||
alias: ""
|
||||
});
|
||||
}
|
||||
}, [popUp?.certificateExport?.isOpen]);
|
||||
|
||||
const isFormValid = () => {
|
||||
if (selectedFormat === "pkcs12") {
|
||||
return pkcs12Options.password.length >= 6 && pkcs12Options.alias.trim() !== "";
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
if (serialNumber && isFormValid()) {
|
||||
const options: ExportOptions = {};
|
||||
|
||||
if (selectedFormat === "pkcs12") {
|
||||
options.pkcs12 = pkcs12Options;
|
||||
}
|
||||
|
||||
onFormatSelected(selectedFormat, serialNumber, options);
|
||||
handlePopUpToggle("certificateExport", false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.certificateExport?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("certificateExport", isOpen);
|
||||
}}
|
||||
>
|
||||
<ModalContent title="Export Certificate">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-400">Choose the format for exporting your certificate</p>
|
||||
|
||||
<FormControl
|
||||
label="Export Format"
|
||||
helperText={
|
||||
selectedFormat === "pem"
|
||||
? "Privacy Enhanced Mail - Text-based certificate format"
|
||||
: "PKCS12 format - Binary keystore format compatible with Java applications"
|
||||
}
|
||||
>
|
||||
<Select
|
||||
className="w-full"
|
||||
value={selectedFormat}
|
||||
onValueChange={(value) => setSelectedFormat(value as CertificateExportFormat)}
|
||||
>
|
||||
<SelectItem value="pem">PEM Format</SelectItem>
|
||||
<SelectItem value="pkcs12">PKCS12 Format</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{selectedFormat === "pkcs12" && (
|
||||
<>
|
||||
<FormControl
|
||||
label="Keystore Password"
|
||||
helperText={
|
||||
pkcs12Options.password.length > 0 && pkcs12Options.password.length < 6
|
||||
? undefined
|
||||
: "Password to protect the PKCS12 keystore (minimum 6 characters)"
|
||||
}
|
||||
isError={pkcs12Options.password.length > 0 && pkcs12Options.password.length < 6}
|
||||
errorText="Password must be at least 6 characters long"
|
||||
>
|
||||
<Input
|
||||
placeholder="Enter keystore password"
|
||||
value={pkcs12Options.password}
|
||||
onChange={(e) =>
|
||||
setPkcs12Options((prev) => ({ ...prev, password: e.target.value }))
|
||||
}
|
||||
type="password"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl
|
||||
label="Certificate Alias"
|
||||
helperText="Friendly name for the certificate in the keystore"
|
||||
>
|
||||
<Input
|
||||
placeholder="Enter certificate alias"
|
||||
value={pkcs12Options.alias}
|
||||
onChange={(e) => setPkcs12Options((prev) => ({ ...prev, alias: e.target.value }))}
|
||||
/>
|
||||
</FormControl>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
onClick={() => handlePopUpToggle("certificateExport", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
leftIcon={<FontAwesomeIcon icon={faDownload} />}
|
||||
onClick={handleExport}
|
||||
disabled={!serialNumber || !isFormValid()}
|
||||
>
|
||||
Export {selectedFormat.toUpperCase()}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -94,9 +94,9 @@ const createSchema = (shouldShowSubjectSection: boolean) => {
|
||||
export type FormData = z.infer<ReturnType<typeof createSchema>>;
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["certificateIssuance"]>;
|
||||
popUp: UsePopUpState<["issueCertificate"]>;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<["certificateIssuance"]>,
|
||||
popUpName: keyof UsePopUpState<["issueCertificate"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
profileId?: string;
|
||||
@@ -115,7 +115,7 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
const { currentProject } = useProject();
|
||||
|
||||
const inputSerialNumber =
|
||||
(popUp?.certificateIssuance?.data as { serialNumber: string })?.serialNumber || "";
|
||||
(popUp?.issueCertificate?.data as { serialNumber: string })?.serialNumber || "";
|
||||
const sanitizedSerialNumber = inputSerialNumber.replace(/[^a-fA-F0-9:]/g, "");
|
||||
|
||||
const { data: cert } = useGetCert(sanitizedSerialNumber);
|
||||
@@ -181,7 +181,7 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
} = useCertificateTemplate(
|
||||
templateData,
|
||||
actualSelectedProfile,
|
||||
popUp?.certificateIssuance?.isOpen || false,
|
||||
popUp?.issueCertificate?.isOpen || false,
|
||||
setValue,
|
||||
watch
|
||||
);
|
||||
@@ -227,10 +227,10 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
}, [cert, reset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (popUp?.certificateIssuance?.isOpen && profileId && !cert) {
|
||||
if (popUp?.issueCertificate?.isOpen && profileId && !cert) {
|
||||
setValue("profileId", profileId);
|
||||
}
|
||||
}, [popUp?.certificateIssuance?.isOpen, profileId, cert, setValue]);
|
||||
}, [popUp?.issueCertificate?.isOpen, profileId, cert, setValue]);
|
||||
|
||||
const onFormSubmit = useCallback(
|
||||
async ({
|
||||
@@ -332,9 +332,9 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.certificateIssuance?.isOpen}
|
||||
isOpen={popUp?.issueCertificate?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("certificateIssuance", isOpen);
|
||||
handlePopUpToggle("issueCertificate", isOpen);
|
||||
if (!isOpen) {
|
||||
resetAllState();
|
||||
}
|
||||
@@ -503,7 +503,7 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => {
|
||||
handlePopUpToggle("certificateIssuance", false);
|
||||
handlePopUpToggle("issueCertificate", false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
|
||||
@@ -75,6 +75,7 @@ export type FormData = z.infer<typeof schema>;
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["certificate"]>;
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["certificate"]>, state?: boolean) => void;
|
||||
preselectedTemplate?: { id: string; name: string };
|
||||
};
|
||||
|
||||
type TCertificateDetails = {
|
||||
@@ -86,7 +87,7 @@ type TCertificateDetails = {
|
||||
|
||||
const CERT_TEMPLATE_NONE_VALUE = "none";
|
||||
|
||||
export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
export const CertificateModal = ({ popUp, handlePopUpToggle, preselectedTemplate }: Props) => {
|
||||
const [certificateDetails, setCertificateDetails] = useState<TCertificateDetails | null>(null);
|
||||
const { currentProject } = useProject();
|
||||
const { data: cert } = useGetCert(
|
||||
@@ -147,13 +148,15 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
(cert.extendedKeyUsages || []).map((name) => [name, true])
|
||||
)
|
||||
});
|
||||
} else {
|
||||
} else if (popUp?.certificate?.isOpen) {
|
||||
const templateId = preselectedTemplate?.id || CERT_TEMPLATE_NONE_VALUE;
|
||||
|
||||
reset({
|
||||
caId: "",
|
||||
commonName: "",
|
||||
subjectAltNames: "",
|
||||
ttl: "",
|
||||
certificateTemplateId: CERT_TEMPLATE_NONE_VALUE,
|
||||
certificateTemplateId: templateId,
|
||||
keyUsages: {
|
||||
[CertKeyUsage.DIGITAL_SIGNATURE]: true,
|
||||
[CertKeyUsage.KEY_ENCIPHERMENT]: true
|
||||
@@ -161,7 +164,7 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
extendedKeyUsages: {}
|
||||
});
|
||||
}
|
||||
}, [cert]);
|
||||
}, [cert, preselectedTemplate, popUp?.certificate?.isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!cert && selectedCertTemplate) {
|
||||
@@ -198,10 +201,14 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
ttl,
|
||||
keyUsages: Object.entries(keyUsages)
|
||||
.filter(([, value]) => value)
|
||||
.map(([key]) => key as CertKeyUsage),
|
||||
.map(([key]) =>
|
||||
key === CertKeyUsage.CRL_SIGN
|
||||
? "cRLSign"
|
||||
: key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
|
||||
),
|
||||
extendedKeyUsages: Object.entries(extendedKeyUsages)
|
||||
.filter(([, value]) => value)
|
||||
.map(([key]) => key as CertExtendedKeyUsage)
|
||||
.map(([key]) => key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()))
|
||||
});
|
||||
|
||||
reset();
|
||||
@@ -269,15 +276,25 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
isRequired
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
value={field.value}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
isDisabled={Boolean(cert)}
|
||||
isDisabled={Boolean(cert) || Boolean(preselectedTemplate)}
|
||||
>
|
||||
<SelectItem value={CERT_TEMPLATE_NONE_VALUE} key="cert-template-none">
|
||||
None
|
||||
</SelectItem>
|
||||
{preselectedTemplate &&
|
||||
!templatesData?.certificateTemplates?.find(
|
||||
(t) => t.id === preselectedTemplate.id
|
||||
) && (
|
||||
<SelectItem
|
||||
value={preselectedTemplate.id}
|
||||
key={`cert-template-preselected-${preselectedTemplate.id}`}
|
||||
>
|
||||
{preselectedTemplate.name}
|
||||
</SelectItem>
|
||||
)}
|
||||
{(templatesData?.certificateTemplates || []).map(({ id, name }) => (
|
||||
<SelectItem value={id} key={`cert-template-${id}`}>
|
||||
{name}
|
||||
|
||||
@@ -9,15 +9,15 @@ import {
|
||||
ProjectPermissionSub,
|
||||
useProject
|
||||
} from "@app/context";
|
||||
import { useDeleteCert } from "@app/hooks/api";
|
||||
import { useDeleteCert, useDownloadCertPkcs12 } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { CertificateCertModal } from "./CertificateCertModal";
|
||||
import { CertificateExportModal, ExportOptions } from "./CertificateExportModal";
|
||||
import { CertificateImportModal } from "./CertificateImportModal";
|
||||
import { CertificateIssuanceModal } from "./CertificateIssuanceModal";
|
||||
import { CertificateManagePkiSyncsModal } from "./CertificateManagePkiSyncsModal";
|
||||
import { CertificateManageRenewalModal } from "./CertificateManageRenewalModal";
|
||||
import { CertificateModal } from "./CertificateModal";
|
||||
import { CertificateRenewalModal } from "./CertificateRenewalModal";
|
||||
import { CertificateRevocationModal } from "./CertificateRevocationModal";
|
||||
import { CertificatesTable } from "./CertificatesTable";
|
||||
@@ -25,15 +25,13 @@ import { CertificatesTable } from "./CertificatesTable";
|
||||
export const CertificatesSection = () => {
|
||||
const { currentProject } = useProject();
|
||||
const { mutateAsync: deleteCert } = useDeleteCert();
|
||||
|
||||
// TODO: Use subscription.pkiLegacyTemplates to block legacy templates creation
|
||||
const isLegacyTemplatesEnabled = true;
|
||||
const { mutateAsync: downloadCertPkcs12 } = useDownloadCertPkcs12();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"certificateIssuance",
|
||||
"certificate",
|
||||
"issueCertificate",
|
||||
"certificateImport",
|
||||
"certificateCert",
|
||||
"certificateExport",
|
||||
"deleteCertificate",
|
||||
"revokeCertificate",
|
||||
"manageRenewal",
|
||||
@@ -54,6 +52,45 @@ export const CertificatesSection = () => {
|
||||
handlePopUpClose("deleteCertificate");
|
||||
};
|
||||
|
||||
const handleCertificateExport = async (
|
||||
format: "pem" | "pkcs12",
|
||||
serialNumber: string,
|
||||
options?: ExportOptions
|
||||
) => {
|
||||
if (format === "pem") {
|
||||
handlePopUpOpen("certificateCert", { serialNumber });
|
||||
} else if (format === "pkcs12") {
|
||||
if (!currentProject?.slug) return;
|
||||
|
||||
if (!options?.pkcs12?.password || !options?.pkcs12?.alias) {
|
||||
createNotification({
|
||||
text: "Password and alias are required for PKCS12 export",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await downloadCertPkcs12({
|
||||
serialNumber,
|
||||
projectSlug: currentProject.slug,
|
||||
password: options.pkcs12.password,
|
||||
alias: options.pkcs12.alias
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "PKCS12 certificate downloaded successfully",
|
||||
type: "success"
|
||||
});
|
||||
} catch (error: any) {
|
||||
createNotification({
|
||||
text: error?.message || "Failed to download PKCS12 certificate",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-4 flex justify-between">
|
||||
@@ -76,9 +113,7 @@ export const CertificatesSection = () => {
|
||||
colorSchema="primary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() =>
|
||||
handlePopUpOpen(isLegacyTemplatesEnabled ? "certificate" : "certificateIssuance")
|
||||
}
|
||||
onClick={() => handlePopUpOpen("issueCertificate")}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Issue
|
||||
@@ -88,13 +123,14 @@ export const CertificatesSection = () => {
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<CertificatesTable handlePopUpOpen={handlePopUpOpen} />
|
||||
{isLegacyTemplatesEnabled ? (
|
||||
<CertificateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
) : (
|
||||
<CertificateIssuanceModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
)}
|
||||
<CertificateIssuanceModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<CertificateImportModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<CertificateCertModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<CertificateExportModal
|
||||
popUp={popUp}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
onFormatSelected={handleCertificateExport}
|
||||
/>
|
||||
<CertificateManageRenewalModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<CertificateRenewalModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<CertificateRevocationModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
|
||||
@@ -64,10 +64,11 @@ type Props = {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<
|
||||
[
|
||||
"certificate",
|
||||
"issueCertificate",
|
||||
"deleteCertificate",
|
||||
"revokeCertificate",
|
||||
"certificateCert",
|
||||
"certificateExport",
|
||||
"manageRenewal",
|
||||
"renewCertificate",
|
||||
"managePkiSyncs"
|
||||
@@ -275,7 +276,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={async () =>
|
||||
handlePopUpOpen("certificateCert", {
|
||||
handlePopUpOpen("certificateExport", {
|
||||
serialNumber: certificate.serialNumber
|
||||
})
|
||||
}
|
||||
@@ -297,7 +298,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
|
||||
!isAllowed && "pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={async () =>
|
||||
handlePopUpOpen("certificate", {
|
||||
handlePopUpOpen("issueCertificate", {
|
||||
serialNumber: certificate.serialNumber
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
faCertificate,
|
||||
faCog,
|
||||
faEllipsis,
|
||||
faFileContract,
|
||||
faPencil,
|
||||
faPlus,
|
||||
faTrash
|
||||
@@ -40,6 +41,7 @@ import {
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionCertificateActions,
|
||||
ProjectPermissionPkiTemplateActions,
|
||||
ProjectPermissionSub,
|
||||
useProject,
|
||||
@@ -50,6 +52,7 @@ import { useDeleteCertTemplateV2 } from "@app/hooks/api";
|
||||
import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries";
|
||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
|
||||
import { CertificateModal } from "../CertificatesPage/components/CertificateModal";
|
||||
import { CertificateTemplateEnrollmentModal } from "../CertificatesPage/components/CertificateTemplateEnrollmentModal";
|
||||
import { PkiTemplateForm } from "./components/PkiTemplateForm";
|
||||
|
||||
@@ -64,7 +67,8 @@ export const PkiTemplateListPage = () => {
|
||||
"certificateTemplate",
|
||||
"deleteTemplate",
|
||||
"enrollmentOptions",
|
||||
"estUpgradePlan"
|
||||
"estUpgradePlan",
|
||||
"certificateFromTemplate"
|
||||
] as const);
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
@@ -160,6 +164,27 @@ export const PkiTemplateListPage = () => {
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionCertificateActions.Create}
|
||||
a={ProjectPermissionSub.Certificates}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<DropdownMenuItem
|
||||
className={twMerge(
|
||||
!isAllowed &&
|
||||
"pointer-events-none cursor-not-allowed opacity-50"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpOpen("certificateFromTemplate", template);
|
||||
}}
|
||||
disabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faFileContract} />}
|
||||
>
|
||||
Issue Certificate
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionPkiTemplateActions.Edit}
|
||||
a={ProjectPermissionSub.CertificateTemplates}
|
||||
@@ -284,6 +309,16 @@ export const PkiTemplateListPage = () => {
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<CertificateTemplateEnrollmentModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<CertificateModal
|
||||
popUp={{
|
||||
certificate: {
|
||||
isOpen: popUp.certificateFromTemplate.isOpen,
|
||||
data: popUp.certificateFromTemplate.data
|
||||
}
|
||||
}}
|
||||
handlePopUpToggle={(_, state) => handlePopUpToggle("certificateFromTemplate", state)}
|
||||
preselectedTemplate={popUp.certificateFromTemplate.data}
|
||||
/>
|
||||
</div>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.estUpgradePlan.isOpen}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { ProjectType } from "@app/hooks/api/projects/types";
|
||||
import { CertificateProfilesTab } from "./components/CertificateProfilesTab";
|
||||
import { CertificatesTab } from "./components/CertificatesTab";
|
||||
import { CertificateTemplatesV2Tab } from "./components/CertificateTemplatesV2Tab";
|
||||
import { PkiCollectionsTab } from "./components/PkiCollectionsTab";
|
||||
|
||||
enum TabSections {
|
||||
CertificateProfiles = "profiles",
|
||||
@@ -54,9 +53,6 @@ export const PoliciesPage = () => {
|
||||
<Tab variant="project" value={TabSections.Certificates}>
|
||||
Certificates
|
||||
</Tab>
|
||||
<Tab variant="project" value={TabSections.PkiCollections}>
|
||||
Certificate Collections
|
||||
</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanel value={TabSections.CertificateProfiles}>
|
||||
@@ -70,10 +66,6 @@ export const PoliciesPage = () => {
|
||||
<TabPanel value={TabSections.Certificates}>
|
||||
<CertificatesTab />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={TabSections.PkiCollections}>
|
||||
<PkiCollectionsTab />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useState } from "react";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
@@ -14,8 +14,11 @@ import {
|
||||
useDeleteCertificateProfile
|
||||
} from "@app/hooks/api/certificateProfiles";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { CreateProfileModal } from "./CreateProfileModal";
|
||||
import { ProfileList } from "./ProfileList";
|
||||
import { RevealAcmeEabSecretModal } from "./RevealAcmeEabSecretModal";
|
||||
|
||||
export const CertificateProfilesTab = () => {
|
||||
const { permission } = useProjectPermission();
|
||||
@@ -23,9 +26,12 @@ export const CertificateProfilesTab = () => {
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [isRevealProfileAcmeEabSecretModalOpen, setIsRevealProfileAcmeEabSecretModalOpen] =
|
||||
useState(false);
|
||||
const [selectedProfile, setSelectedProfile] = useState<TCertificateProfileWithDetails | null>(
|
||||
null
|
||||
);
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||
|
||||
const deleteProfile = useDeleteCertificateProfile();
|
||||
|
||||
@@ -43,6 +49,11 @@ export const CertificateProfilesTab = () => {
|
||||
setIsEditModalOpen(true);
|
||||
};
|
||||
|
||||
const handleRevealProfileAcmeEabSecret = (profile: TCertificateProfileWithDetails) => {
|
||||
setSelectedProfile(profile);
|
||||
setIsRevealProfileAcmeEabSecretModalOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteProfile = (profile: TCertificateProfileWithDetails) => {
|
||||
setSelectedProfile(profile);
|
||||
setIsDeleteModalOpen(true);
|
||||
@@ -85,9 +96,23 @@ export const CertificateProfilesTab = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ProfileList onEditProfile={handleEditProfile} onDeleteProfile={handleDeleteProfile} />
|
||||
<ProfileList
|
||||
onEditProfile={handleEditProfile}
|
||||
onRevealProfileAcmeEabSecret={handleRevealProfileAcmeEabSecret}
|
||||
onDeleteProfile={handleDeleteProfile}
|
||||
/>
|
||||
|
||||
<CreateProfileModal isOpen={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} />
|
||||
<CreateProfileModal
|
||||
isOpen={isCreateModalOpen}
|
||||
onClose={() => setIsCreateModalOpen(false)}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature}
|
||||
text="Your current plan does not include access to managing template enrollment options for ACME. To unlock this feature, please upgrade to Infisical Enterprise plan."
|
||||
/>
|
||||
|
||||
{selectedProfile && (
|
||||
<>
|
||||
@@ -97,10 +122,22 @@ export const CertificateProfilesTab = () => {
|
||||
setIsEditModalOpen(false);
|
||||
setSelectedProfile(null);
|
||||
}}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
profile={selectedProfile}
|
||||
mode="edit"
|
||||
/>
|
||||
|
||||
{selectedProfile.enrollmentType === "acme" && (
|
||||
<RevealAcmeEabSecretModal
|
||||
isOpen={isRevealProfileAcmeEabSecretModalOpen}
|
||||
onClose={() => {
|
||||
setIsRevealProfileAcmeEabSecretModalOpen(false);
|
||||
setSelectedProfile(null);
|
||||
}}
|
||||
profile={selectedProfile}
|
||||
/>
|
||||
)}
|
||||
|
||||
<DeleteActionModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
title={`Delete Certificate Profile ${selectedProfile.slug}?`}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
TextArea,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { useProject } from "@app/context";
|
||||
import { useProject, useSubscription } from "@app/context";
|
||||
import { useListCasByProjectId } from "@app/hooks/api/ca/queries";
|
||||
import {
|
||||
TCertificateProfileWithDetails,
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
useUpdateCertificateProfile
|
||||
} from "@app/hooks/api/certificateProfiles";
|
||||
import { useListCertificateTemplatesV2 } from "@app/hooks/api/certificateTemplates/queries";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const createSchema = z
|
||||
.object({
|
||||
@@ -45,7 +46,7 @@ const createSchema = z
|
||||
.trim()
|
||||
.max(1000, "Description must be less than 1000 characters")
|
||||
.optional(),
|
||||
enrollmentType: z.enum(["api", "est"]),
|
||||
enrollmentType: z.enum(["api", "est", "acme"]),
|
||||
certificateAuthorityId: z.string().min(1, "Certificate Authority is required"),
|
||||
certificateTemplateId: z.string().min(1, "Certificate Template is required"),
|
||||
estConfig: z
|
||||
@@ -72,7 +73,8 @@ const createSchema = z
|
||||
autoRenew: z.boolean().optional(),
|
||||
renewBeforeDays: z.number().min(1).max(365).optional()
|
||||
})
|
||||
.optional()
|
||||
.optional(),
|
||||
acmeConfig: z.object({}).optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
@@ -82,6 +84,9 @@ const createSchema = z
|
||||
if (data.enrollmentType === "api" && !data.apiConfig) {
|
||||
return false;
|
||||
}
|
||||
if (data.enrollmentType === "acme" && !data.acmeConfig) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
@@ -105,7 +110,7 @@ const editSchema = z
|
||||
.trim()
|
||||
.max(1000, "Description must be less than 1000 characters")
|
||||
.optional(),
|
||||
enrollmentType: z.enum(["api", "est"]),
|
||||
enrollmentType: z.enum(["api", "est", "acme"]),
|
||||
certificateAuthorityId: z.string().optional(),
|
||||
certificateTemplateId: z.string().optional(),
|
||||
estConfig: z
|
||||
@@ -120,7 +125,8 @@ const editSchema = z
|
||||
autoRenew: z.boolean().optional(),
|
||||
renewBeforeDays: z.number().min(1).max(365).optional()
|
||||
})
|
||||
.optional()
|
||||
.optional(),
|
||||
acmeConfig: z.object({}).optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
@@ -130,6 +136,9 @@ const editSchema = z
|
||||
if (data.enrollmentType === "api" && !data.apiConfig) {
|
||||
return false;
|
||||
}
|
||||
if (data.enrollmentType === "acme" && !data.acmeConfig) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
@@ -142,12 +151,25 @@ export type FormData = z.infer<typeof createSchema>;
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["upgradePlan"]>,
|
||||
data?: {
|
||||
isEnterpriseFeature?: boolean;
|
||||
}
|
||||
) => void;
|
||||
profile?: TCertificateProfileWithDetails;
|
||||
mode?: "create" | "edit";
|
||||
}
|
||||
|
||||
export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }: Props) => {
|
||||
export const CreateProfileModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
handlePopUpOpen,
|
||||
profile,
|
||||
mode = "create"
|
||||
}: Props) => {
|
||||
const { currentProject } = useProject();
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { data: caData } = useListCasByProjectId(currentProject?.id || "");
|
||||
const { data: templateData } = useListCertificateTemplatesV2({
|
||||
@@ -188,7 +210,8 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
autoRenew: profile.apiConfig?.autoRenew || false,
|
||||
renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30
|
||||
}
|
||||
: undefined
|
||||
: undefined,
|
||||
acmeConfig: profile.enrollmentType === "acme" ? {} : undefined
|
||||
}
|
||||
: {
|
||||
slug: "",
|
||||
@@ -199,7 +222,8 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
apiConfig: {
|
||||
autoRenew: false,
|
||||
renewBeforeDays: 30
|
||||
}
|
||||
},
|
||||
acmeConfig: {}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -230,12 +254,22 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
autoRenew: profile.apiConfig?.autoRenew || false,
|
||||
renewBeforeDays: profile.apiConfig?.renewBeforeDays || 30
|
||||
}
|
||||
: undefined
|
||||
: undefined,
|
||||
acmeConfig: profile.enrollmentType === "acme" ? {} : undefined
|
||||
});
|
||||
}
|
||||
}, [isEdit, profile, reset]);
|
||||
|
||||
const onFormSubmit = async (data: FormData) => {
|
||||
if (!isEdit && !subscription?.pkiAcme && data.enrollmentType === "acme") {
|
||||
reset();
|
||||
onClose();
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
isEnterpriseFeature: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentProject?.id && !isEdit) return;
|
||||
|
||||
if (isEdit) {
|
||||
@@ -249,6 +283,8 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
updateData.estConfig = data.estConfig;
|
||||
} else if (data.enrollmentType === "api" && data.apiConfig) {
|
||||
updateData.apiConfig = data.apiConfig;
|
||||
} else if (data.enrollmentType === "acme" && data.acmeConfig) {
|
||||
updateData.acmeConfig = data.acmeConfig;
|
||||
}
|
||||
|
||||
await updateProfile.mutateAsync(updateData);
|
||||
@@ -274,6 +310,8 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
};
|
||||
} else if (data.enrollmentType === "api" && data.apiConfig) {
|
||||
createData.apiConfig = data.apiConfig;
|
||||
} else if (data.enrollmentType === "acme" && data.acmeConfig) {
|
||||
createData.acmeConfig = data.acmeConfig;
|
||||
}
|
||||
|
||||
await createProfile.mutateAsync(createData);
|
||||
@@ -376,17 +414,23 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
{...field}
|
||||
onValueChange={(value) => {
|
||||
if (watchedEnrollmentType === "est") {
|
||||
setValue("apiConfig", undefined);
|
||||
setValue("estConfig", {
|
||||
disableBootstrapCaValidation: false,
|
||||
passphrase: ""
|
||||
});
|
||||
setValue("apiConfig", undefined);
|
||||
} else {
|
||||
setValue("acmeConfig", undefined);
|
||||
} else if (watchedEnrollmentType === "api") {
|
||||
setValue("apiConfig", {
|
||||
autoRenew: false,
|
||||
renewBeforeDays: 30
|
||||
});
|
||||
setValue("estConfig", undefined);
|
||||
setValue("acmeConfig", undefined);
|
||||
} else if (watchedEnrollmentType === "acme") {
|
||||
setValue("estConfig", undefined);
|
||||
setValue("apiConfig", undefined);
|
||||
setValue("acmeConfig", {});
|
||||
}
|
||||
onChange(value);
|
||||
}}
|
||||
@@ -424,12 +468,18 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
disableBootstrapCaValidation: false,
|
||||
passphrase: ""
|
||||
});
|
||||
} else {
|
||||
setValue("estConfig", undefined);
|
||||
setValue("acmeConfig", undefined);
|
||||
} else if (value === "api") {
|
||||
setValue("apiConfig", {
|
||||
autoRenew: false,
|
||||
renewBeforeDays: 30
|
||||
});
|
||||
setValue("estConfig", undefined);
|
||||
setValue("acmeConfig", undefined);
|
||||
} else if (value === "acme") {
|
||||
setValue("apiConfig", undefined);
|
||||
setValue("estConfig", undefined);
|
||||
setValue("acmeConfig", {});
|
||||
}
|
||||
onChange(value);
|
||||
}}
|
||||
@@ -439,6 +489,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
>
|
||||
<SelectItem value="api">API</SelectItem>
|
||||
<SelectItem value="est">EST</SelectItem>
|
||||
<SelectItem value="acme">ACME</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
@@ -548,6 +599,20 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ACME Configuration */}
|
||||
{watchedEnrollmentType === "acme" && (
|
||||
<div className="mb-4 space-y-4">
|
||||
<Controller
|
||||
control={control}
|
||||
name="acmeConfig"
|
||||
render={({ fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||
<div className="flex items-center gap-2">{/* FIXME: ACME configuration */}</div>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{watchedAutoRenew && (
|
||||
<div className="mb-4 space-y-4">
|
||||
<Controller
|
||||
|
||||
@@ -20,9 +20,14 @@ import { ProfileRow } from "./ProfileRow";
|
||||
interface Props {
|
||||
onEditProfile: (profile: TCertificateProfileWithDetails) => void;
|
||||
onDeleteProfile: (profile: TCertificateProfileWithDetails) => void;
|
||||
onRevealProfileAcmeEabSecret: (profile: TCertificateProfileWithDetails) => void;
|
||||
}
|
||||
|
||||
export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => {
|
||||
export const ProfileList = ({
|
||||
onEditProfile,
|
||||
onRevealProfileAcmeEabSecret,
|
||||
onDeleteProfile
|
||||
}: Props) => {
|
||||
const { currentProject } = useProject();
|
||||
|
||||
const { data, isLoading } = useListCertificateProfiles({
|
||||
@@ -88,6 +93,7 @@ export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => {
|
||||
key={profile.id}
|
||||
profile={profile}
|
||||
onEditProfile={onEditProfile}
|
||||
onRevealProfileAcmeEabSecret={onRevealProfileAcmeEabSecret}
|
||||
onDeleteProfile={onDeleteProfile}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
faCopy,
|
||||
faEdit,
|
||||
faEllipsis,
|
||||
faEye,
|
||||
faPlus,
|
||||
faTrash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
@@ -36,15 +37,21 @@ import { CertificateIssuanceModal } from "@app/pages/cert-manager/CertificatesPa
|
||||
interface Props {
|
||||
profile: TCertificateProfile;
|
||||
onEditProfile: (profile: TCertificateProfile) => void;
|
||||
onRevealProfileAcmeEabSecret: (profile: TCertificateProfile) => void;
|
||||
onDeleteProfile: (profile: TCertificateProfile) => void;
|
||||
}
|
||||
|
||||
export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) => {
|
||||
export const ProfileRow = ({
|
||||
profile,
|
||||
onEditProfile,
|
||||
onRevealProfileAcmeEabSecret,
|
||||
onDeleteProfile
|
||||
}: Props) => {
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
const { data: caData } = useGetCaById(profile.caId);
|
||||
|
||||
const { popUp, handlePopUpToggle } = usePopUp(["certificateIssuance"] as const);
|
||||
const { popUp, handlePopUpToggle } = usePopUp(["issueCertificate"] as const);
|
||||
|
||||
const [isIdCopied, setIsIdCopied] = useToggle(false);
|
||||
|
||||
@@ -69,6 +76,11 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) =
|
||||
ProjectPermissionSub.CertificateAuthorities
|
||||
);
|
||||
|
||||
const canRevealProfileAcmeEabSecret = permission.can(
|
||||
ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret,
|
||||
ProjectPermissionSub.CertificateProfiles
|
||||
);
|
||||
|
||||
const canIssueCertificate = permission.can(
|
||||
ProjectPermissionCertificateProfileActions.IssueCert,
|
||||
ProjectPermissionSub.CertificateProfiles
|
||||
@@ -82,7 +94,8 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) =
|
||||
const getEnrollmentTypeBadge = (enrollmentType: string) => {
|
||||
const config = {
|
||||
api: { variant: "ghost" as const, label: "API" },
|
||||
est: { variant: "ghost" as const, label: "EST" }
|
||||
est: { variant: "ghost" as const, label: "EST" },
|
||||
acme: { variant: "ghost" as const, label: "ACME" }
|
||||
} as const;
|
||||
|
||||
const configKey = Object.keys(config).includes(enrollmentType)
|
||||
@@ -127,7 +140,7 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) =
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="p-1">
|
||||
<DropdownMenuItem
|
||||
icon={<FontAwesomeIcon icon={isIdCopied ? faCheck : faCopy} />}
|
||||
icon={<FontAwesomeIcon icon={isIdCopied ? faCheck : faCopy} className="w-3" />}
|
||||
onClick={() => handleCopyId()}
|
||||
>
|
||||
Copy Profile ID
|
||||
@@ -138,18 +151,29 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) =
|
||||
e.stopPropagation();
|
||||
onEditProfile(profile);
|
||||
}}
|
||||
icon={<FontAwesomeIcon icon={faEdit} />}
|
||||
icon={<FontAwesomeIcon icon={faEdit} className="w-3" />}
|
||||
>
|
||||
Edit Profile
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{canRevealProfileAcmeEabSecret && profile.enrollmentType === "acme" && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRevealProfileAcmeEabSecret(profile);
|
||||
}}
|
||||
icon={<FontAwesomeIcon icon={faEye} className="w-3" />}
|
||||
>
|
||||
Reveal ACME EAB
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{canIssueCertificate && profile.enrollmentType === "api" && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handlePopUpToggle("certificateIssuance");
|
||||
handlePopUpToggle("issueCertificate");
|
||||
}}
|
||||
icon={<FontAwesomeIcon icon={faPlus} />}
|
||||
icon={<FontAwesomeIcon icon={faPlus} className="w-3" />}
|
||||
>
|
||||
Issue Certificate
|
||||
</DropdownMenuItem>
|
||||
@@ -160,7 +184,7 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) =
|
||||
e.stopPropagation();
|
||||
onDeleteProfile(profile);
|
||||
}}
|
||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||
icon={<FontAwesomeIcon icon={faTrash} className="w-3" />}
|
||||
>
|
||||
Delete Profile
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Spinner
|
||||
} from "@app/components/v2";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import { TCertificateProfileWithDetails } from "@app/hooks/api/certificateProfiles";
|
||||
import { useRevealAcmeEabSecret } from "@app/hooks/api/certificateProfiles/queries";
|
||||
|
||||
const RESET_COPIED_DELAY = 1 * 1000;
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
profile: TCertificateProfileWithDetails;
|
||||
};
|
||||
|
||||
export const RevealAcmeEabSecretModal = ({ isOpen, onClose, profile }: Props) => {
|
||||
const [isAcmeDirectoryUrlCopied, setIsAcmeDirectoryUrlCopied] = useToggle(false);
|
||||
const [isEabKidCopied, setIsEabKidCopied] = useToggle(false);
|
||||
const [isEabSecretCopied, setIsEabSecretCopied] = useToggle(false);
|
||||
const revealAcmeEabSecret = useRevealAcmeEabSecret({ profileId: profile.id });
|
||||
const { data, isLoading, isError, error } = revealAcmeEabSecret;
|
||||
const { directoryUrl } = profile.acmeConfig!;
|
||||
const { eabKid, eabSecret } = data ?? { eabKid: "", eabSecret: "" };
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title="Reveal ACME EAB"
|
||||
subTitle="To issue certificates automatically, your ACME client needs the following details."
|
||||
>
|
||||
{isLoading && (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<Spinner size="sm" />
|
||||
</div>
|
||||
)}
|
||||
{isError && (
|
||||
<Alert variant="danger">
|
||||
<AlertDescription>Failed to reveal EAB secret: {error.message}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{data && (
|
||||
<>
|
||||
<FormLabel
|
||||
label="ACME Directory URL"
|
||||
tooltipText="The ACME directory URL for your ACME client to issue certificates."
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Input value={directoryUrl} disabled />
|
||||
<IconButton
|
||||
ariaLabel="copy"
|
||||
variant="outline_bg"
|
||||
colorSchema="secondary"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(directoryUrl);
|
||||
setIsAcmeDirectoryUrlCopied.on();
|
||||
setTimeout(() => {
|
||||
setIsAcmeDirectoryUrlCopied.off();
|
||||
}, RESET_COPIED_DELAY);
|
||||
}}
|
||||
className="w-10"
|
||||
>
|
||||
<FontAwesomeIcon icon={isAcmeDirectoryUrlCopied ? faCheck : faCopy} />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<FormLabel
|
||||
label="EAB KID"
|
||||
className="mt-4"
|
||||
tooltipText="The EAB Key Identifier (KID) for your ACME client to authenticate when registering a new account."
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Input value={eabKid} disabled />
|
||||
<IconButton
|
||||
ariaLabel="copy"
|
||||
variant="outline_bg"
|
||||
colorSchema="secondary"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(eabKid);
|
||||
setIsEabKidCopied.on();
|
||||
setTimeout(() => {
|
||||
setIsEabKidCopied.off();
|
||||
}, RESET_COPIED_DELAY);
|
||||
}}
|
||||
className="w-10"
|
||||
>
|
||||
<FontAwesomeIcon icon={isEabKidCopied ? faCheck : faCopy} />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<FormLabel
|
||||
label="EAB Secret"
|
||||
className="mt-4"
|
||||
tooltipText="The EAB Secret for your ACME client to authenticate when registering a new account."
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Input value={eabSecret} isDisabled />
|
||||
<IconButton
|
||||
ariaLabel="copy"
|
||||
variant="outline_bg"
|
||||
colorSchema="secondary"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(eabSecret);
|
||||
setIsEabSecretCopied.on();
|
||||
setTimeout(() => {
|
||||
setIsEabSecretCopied.off();
|
||||
}, RESET_COPIED_DELAY);
|
||||
}}
|
||||
className="w-10"
|
||||
>
|
||||
<FontAwesomeIcon icon={isEabSecretCopied ? faCheck : faCopy} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -251,7 +251,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
|
||||
|
||||
const { control, handleSubmit, reset, watch, setValue, formState } = useForm<FormData>({
|
||||
resolver: zodResolver(templateSchema),
|
||||
defaultValues: getDefaultValues()
|
||||
defaultValues: getDefaultValues(),
|
||||
mode: "onChange",
|
||||
reValidateMode: "onChange",
|
||||
criteriaMode: "all"
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -76,7 +77,9 @@ export const IdentityAliCloudAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityAliCloudAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAliCloudAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -144,7 +147,7 @@ export const IdentityAliCloudAuthForm = ({
|
||||
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
allowedArns,
|
||||
identityId,
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
@@ -154,7 +157,7 @@ export const IdentityAliCloudAuthForm = ({
|
||||
});
|
||||
} else {
|
||||
await addMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
allowedArns,
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -78,7 +79,9 @@ export const IdentityAwsAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityAwsAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAwsAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -154,7 +157,7 @@ export const IdentityAwsAuthForm = ({
|
||||
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
stsEndpoint,
|
||||
allowedPrincipalArns,
|
||||
allowedAccountIds,
|
||||
@@ -166,7 +169,7 @@ export const IdentityAwsAuthForm = ({
|
||||
});
|
||||
} else {
|
||||
await addMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
stsEndpoint: stsEndpoint || "",
|
||||
allowedPrincipalArns: allowedPrincipalArns || "",
|
||||
@@ -176,10 +179,8 @@ export const IdentityAwsAuthForm = ({
|
||||
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
|
||||
accessTokenTrustedIps
|
||||
});
|
||||
handlePopUpToggle("identityAuthMethod", false);
|
||||
}
|
||||
|
||||
handlePopUpToggle("identityAuthMethod", false);
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
|
||||
type: "success"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -73,7 +74,9 @@ export const IdentityAzureAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityAzureAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityAzureAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -150,7 +153,7 @@ export const IdentityAzureAuthForm = ({
|
||||
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
tenantId,
|
||||
resource,
|
||||
@@ -162,7 +165,7 @@ export const IdentityAzureAuthForm = ({
|
||||
});
|
||||
} else {
|
||||
await addMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
tenantId: tenantId || "",
|
||||
resource: resource || "",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -76,7 +77,9 @@ export const IdentityGcpAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityGcpAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityGcpAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -160,7 +163,7 @@ export const IdentityGcpAuthForm = ({
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
identityId,
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
type,
|
||||
allowedServiceAccounts,
|
||||
allowedProjects,
|
||||
@@ -173,7 +176,7 @@ export const IdentityGcpAuthForm = ({
|
||||
} else {
|
||||
await addMutateAsync({
|
||||
identityId,
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
type,
|
||||
allowedServiceAccounts: allowedServiceAccounts || "",
|
||||
allowedProjects: allowedProjects || "",
|
||||
@@ -191,7 +194,6 @@ export const IdentityGcpAuthForm = ({
|
||||
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -109,7 +110,9 @@ export const IdentityJwtAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityJwtAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityJwtAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -227,7 +230,7 @@ export const IdentityJwtAuthForm = ({
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
identityId,
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
configurationType,
|
||||
jwksUrl,
|
||||
jwksCaCert,
|
||||
@@ -252,7 +255,7 @@ export const IdentityJwtAuthForm = ({
|
||||
boundAudiences,
|
||||
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
|
||||
boundSubject,
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
accessTokenMaxTTL: Number(accessTokenMaxTTL),
|
||||
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
|
||||
@@ -266,7 +269,6 @@ export const IdentityJwtAuthForm = ({
|
||||
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { faInfoCircle, faPlus, faXmark } from "@fortawesome/free-solid-svg-icons
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -117,7 +118,9 @@ export const IdentityKubernetesAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityKubernetesAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityKubernetesAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -318,7 +321,7 @@ export const IdentityKubernetesAuthForm = ({
|
||||
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api
|
||||
? {
|
||||
kubernetesHost: kubernetesHost || ""
|
||||
@@ -341,7 +344,7 @@ export const IdentityKubernetesAuthForm = ({
|
||||
});
|
||||
} else {
|
||||
await addMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api
|
||||
? {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faQuestionCircle, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import ms from "ms";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -168,7 +169,9 @@ export const IdentityLdapAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityLdapAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityLdapAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -345,7 +348,7 @@ export const IdentityLdapAuthForm = ({
|
||||
ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000;
|
||||
|
||||
const basePayload = {
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
searchFilter,
|
||||
ldapCaCertificate,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -84,7 +85,9 @@ export const IdentityOciAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityOciAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityOciAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -156,7 +159,7 @@ export const IdentityOciAuthForm = ({
|
||||
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
tenancyOcid,
|
||||
allowedUsernames,
|
||||
identityId,
|
||||
@@ -167,7 +170,7 @@ export const IdentityOciAuthForm = ({
|
||||
});
|
||||
} else {
|
||||
await addMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
tenancyOcid,
|
||||
allowedUsernames: allowedUsernames || undefined,
|
||||
@@ -184,7 +187,6 @@ export const IdentityOciAuthForm = ({
|
||||
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { faQuestionCircle } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -96,7 +97,9 @@ export const IdentityOidcAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityOidcAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityOidcAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -211,7 +214,7 @@ export const IdentityOidcAuthForm = ({
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
identityId,
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
oidcDiscoveryUrl,
|
||||
caCert,
|
||||
boundIssuer,
|
||||
@@ -238,7 +241,7 @@ export const IdentityOidcAuthForm = ({
|
||||
? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value]))
|
||||
: undefined,
|
||||
boundSubject,
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
accessTokenMaxTTL: Number(accessTokenMaxTTL),
|
||||
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
|
||||
@@ -252,7 +255,6 @@ export const IdentityOidcAuthForm = ({
|
||||
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { faLink, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useState } from "react";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { LinkIcon, PlusIcon } from "lucide-react";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -14,17 +17,23 @@ import {
|
||||
} from "@app/context";
|
||||
import { OrgPermissionMachineIdentityAuthTemplateActions } from "@app/context/OrgPermissionContext/types";
|
||||
import { withPermission } from "@app/hoc";
|
||||
import { useDeleteIdentity } from "@app/hooks/api";
|
||||
import { useDeleteOrgIdentity } from "@app/hooks/api";
|
||||
import { useDeleteIdentityAuthTemplate } from "@app/hooks/api/identityAuthTemplates";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { IdentityAuthTemplateModal } from "./IdentityAuthTemplateModal";
|
||||
import { IdentityAuthTemplatesTable } from "./IdentityAuthTemplatesTable";
|
||||
import { IdentityLinkForm } from "./IdentityLinkForm";
|
||||
import { IdentityModal } from "./IdentityModal";
|
||||
import { IdentityTable } from "./IdentityTable";
|
||||
import { IdentityTokenAuthTokenModal } from "./IdentityTokenAuthTokenModal";
|
||||
import { MachineAuthTemplateUsagesModal } from "./MachineAuthTemplateUsagesModal";
|
||||
import { OrgIdentityLinkForm } from "./OrgIdentityLinkForm";
|
||||
import { OrgIdentityModal } from "./OrgIdentityModal";
|
||||
|
||||
enum IdentityWizardSteps {
|
||||
SelectAction = "select-action",
|
||||
LinkIdentity = "link-identity",
|
||||
OrganizationIdentity = "project-identity"
|
||||
}
|
||||
|
||||
export const IdentitySection = withPermission(
|
||||
() => {
|
||||
@@ -32,7 +41,9 @@ export const IdentitySection = withPermission(
|
||||
const { currentOrg, isSubOrganization } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteIdentity();
|
||||
const [wizardStep, setWizardStep] = useState(IdentityWizardSteps.SelectAction);
|
||||
|
||||
const { mutateAsync: deleteMutateAsync } = useDeleteOrgIdentity();
|
||||
const { mutateAsync: deleteTemplateMutateAsync } = useDeleteIdentityAuthTemplate();
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"identity",
|
||||
@@ -46,7 +57,7 @@ export const IdentitySection = withPermission(
|
||||
"editTemplate",
|
||||
"deleteTemplate",
|
||||
"viewUsages",
|
||||
"linkIdentity"
|
||||
"addOptions"
|
||||
] as const);
|
||||
|
||||
const isMoreIdentitiesAllowed = subscription?.identityLimit
|
||||
@@ -58,7 +69,7 @@ export const IdentitySection = withPermission(
|
||||
const onDeleteIdentitySubmit = async (identityId: string) => {
|
||||
await deleteMutateAsync({
|
||||
identityId,
|
||||
organizationId: orgId
|
||||
orgId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
@@ -91,50 +102,38 @@ export const IdentitySection = withPermission(
|
||||
<p className="text-xl font-medium text-mineshaft-100">Identities</p>
|
||||
<DocumentationLinkBadge href="https://infisical.com/docs/documentation/platform/identities/machine-identities" />
|
||||
</div>
|
||||
{isSubOrganization && (
|
||||
<div className="flex items-center">
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionIdentityActions.Create}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
variant="plain"
|
||||
colorSchema="secondary"
|
||||
leftIcon={<FontAwesomeIcon icon={faLink} />}
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
handlePopUpOpen("linkIdentity");
|
||||
if (!isMoreIdentitiesAllowed && !isEnterprise) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
description:
|
||||
"You can add more identities if you upgrade your Infisical Pro plan."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSubOrganization) {
|
||||
setWizardStep(IdentityWizardSteps.OrganizationIdentity);
|
||||
}
|
||||
|
||||
handlePopUpOpen("identity");
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Link Identity
|
||||
Create Identity
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionIdentityActions.Create}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="submit"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
if (!isMoreIdentitiesAllowed && !isEnterprise) {
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
text: "You have reached the maximum number of identities allowed on your current plan. Upgrade to Infisical Pro plan to add more identities."
|
||||
});
|
||||
return;
|
||||
}
|
||||
handlePopUpOpen("identity");
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Create Identity
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
</div>
|
||||
<IdentityTable handlePopUpOpen={handlePopUpOpen} />
|
||||
</div>
|
||||
@@ -173,7 +172,6 @@ export const IdentitySection = withPermission(
|
||||
</div>
|
||||
<IdentityAuthTemplatesTable handlePopUpOpen={handlePopUpOpen} />
|
||||
</div>
|
||||
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<IdentityAuthTemplateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<MachineAuthTemplateUsagesModal
|
||||
isOpen={popUp.viewUsages.isOpen}
|
||||
@@ -187,19 +185,99 @@ export const IdentitySection = withPermission(
|
||||
?.name || ""
|
||||
}
|
||||
/>
|
||||
<IdentityTokenAuthTokenModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<Modal
|
||||
isOpen={popUp.linkIdentity.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("linkIdentity", isOpen)}
|
||||
isOpen={popUp.identity.isOpen}
|
||||
onOpenChange={(open) => {
|
||||
handlePopUpToggle("identity", open);
|
||||
if (!open) {
|
||||
setWizardStep(IdentityWizardSteps.SelectAction);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title="Assign Existing Identity"
|
||||
subTitle="Assign an existing identity from your root organization to the sub organization. The identity will continue to be managed at its original scope."
|
||||
bodyClassName="overflow-visible"
|
||||
title="Add Identity"
|
||||
subTitle={
|
||||
isSubOrganization ? "Create a new identity or assign an existing identity" : undefined
|
||||
}
|
||||
>
|
||||
<IdentityLinkForm onClose={() => handlePopUpClose("linkIdentity")} />
|
||||
<AnimatePresence mode="wait">
|
||||
{wizardStep === IdentityWizardSteps.SelectAction && (
|
||||
<motion.div
|
||||
key="select-type-step"
|
||||
transition={{ duration: 0.1 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: -30 }}
|
||||
>
|
||||
<div
|
||||
className="cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setWizardStep(IdentityWizardSteps.OrganizationIdentity)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
setWizardStep(IdentityWizardSteps.OrganizationIdentity);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<PlusIcon size="1rem" />
|
||||
<div>Create New Identity</div>
|
||||
</div>
|
||||
<div className="mt-2 text-xs text-mineshaft-300">
|
||||
Create a new machine identity specifically for this sub-organization. This
|
||||
identity will be managed at the sub-organization level.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mt-4 cursor-pointer rounded-md border border-mineshaft-600 p-4 transition-all hover:bg-mineshaft-700"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => setWizardStep(IdentityWizardSteps.LinkIdentity)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
setWizardStep(IdentityWizardSteps.LinkIdentity);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<LinkIcon size="1rem" />
|
||||
<div>Assign Existing Identity</div>
|
||||
</div>
|
||||
<div className="mt-2 text-xs text-mineshaft-300">
|
||||
Assign an existing identity from your parent organization. The identity will
|
||||
continue to be managed at its original scope.
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
{wizardStep === IdentityWizardSteps.OrganizationIdentity && (
|
||||
<motion.div
|
||||
key="identity-step"
|
||||
transition={{ duration: 0.1 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: -30 }}
|
||||
>
|
||||
<OrgIdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
</motion.div>
|
||||
)}
|
||||
{wizardStep === IdentityWizardSteps.LinkIdentity && (
|
||||
<motion.div
|
||||
key="link-step"
|
||||
transition={{ duration: 0.1 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: -30 }}
|
||||
>
|
||||
<OrgIdentityLinkForm onClose={() => handlePopUpClose("identity")} />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<IdentityTokenAuthTokenModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteIdentity.isOpen}
|
||||
title={`Are you sure you want to delete ${
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useCallback, useState } from "react";
|
||||
import {
|
||||
faArrowDown,
|
||||
faArrowUp,
|
||||
faBuilding,
|
||||
faCheckCircle,
|
||||
faChevronRight,
|
||||
faEdit,
|
||||
@@ -46,6 +45,7 @@ import {
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { Badge, OrgIcon, SubOrgIcon } from "@app/components/v3";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import {
|
||||
getUserTablePreference,
|
||||
@@ -56,8 +56,8 @@ import { usePagination, useResetPageHelper } from "@app/hooks";
|
||||
import {
|
||||
identityAuthToNameMap,
|
||||
useGetOrgRoles,
|
||||
useSearchIdentities,
|
||||
useUpdateIdentity
|
||||
useSearchOrgIdentityMemberships,
|
||||
useUpdateOrgIdentity
|
||||
} from "@app/hooks/api";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import { OrgIdentityOrderBy } from "@app/hooks/api/organization/types";
|
||||
@@ -110,9 +110,9 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||
|
||||
const organizationId = currentOrg?.id || "";
|
||||
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentity();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateOrgIdentity();
|
||||
|
||||
const { data, isPending, isFetching } = useSearchIdentities({
|
||||
const { data, isPending, isFetching } = useSearchOrgIdentityMemberships({
|
||||
offset,
|
||||
limit,
|
||||
orderDirection,
|
||||
@@ -293,6 +293,8 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||
lastLoginAuthMethod,
|
||||
lastLoginTime
|
||||
}) => {
|
||||
const isSubOrgIdentity = currentOrg.id === orgId;
|
||||
|
||||
return (
|
||||
<Tr
|
||||
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
@@ -357,10 +359,19 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||
</Td>
|
||||
{isSubOrganization && (
|
||||
<Td>
|
||||
<p className="truncate">
|
||||
<FontAwesomeIcon size="sm" className="mr-1.5" icon={faBuilding} />
|
||||
{currentOrg.id === orgId ? "Sub Organization" : "Root Organization"}
|
||||
</p>
|
||||
<Badge variant={isSubOrgIdentity ? "sub-org" : "org"}>
|
||||
{isSubOrgIdentity ? (
|
||||
<>
|
||||
<SubOrgIcon />
|
||||
Sub-Organization
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<OrgIcon />
|
||||
Root Organization
|
||||
</>
|
||||
)}
|
||||
</Badge>
|
||||
</Td>
|
||||
)}
|
||||
<Td>
|
||||
@@ -394,7 +405,7 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Edit Identity
|
||||
Edit Identity {isSubOrgIdentity ? "" : "Membership"}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
@@ -414,7 +425,9 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => {
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||
>
|
||||
Delete Identity
|
||||
{isSubOrgIdentity
|
||||
? "Delete Identity"
|
||||
: "Remove From Sub-Organization"}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -71,7 +72,9 @@ export const IdentityTlsCertAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityTlsCertAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityTlsCertAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -141,7 +144,7 @@ export const IdentityTlsCertAuthForm = ({
|
||||
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
caCertificate,
|
||||
allowedCommonNames: allowedCommonNames || null,
|
||||
identityId,
|
||||
@@ -152,7 +155,7 @@ export const IdentityTlsCertAuthForm = ({
|
||||
});
|
||||
} else {
|
||||
await addMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
caCertificate,
|
||||
allowedCommonNames: allowedCommonNames || undefined,
|
||||
@@ -169,7 +172,6 @@ export const IdentityTlsCertAuthForm = ({
|
||||
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -70,7 +71,9 @@ export const IdentityTokenAuthForm = ({
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityTokenAuth();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentityTokenAuth();
|
||||
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
|
||||
@@ -134,7 +137,7 @@ export const IdentityTokenAuthForm = ({
|
||||
|
||||
if (data) {
|
||||
await updateMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
accessTokenMaxTTL: Number(accessTokenMaxTTL),
|
||||
@@ -143,7 +146,7 @@ export const IdentityTokenAuthForm = ({
|
||||
});
|
||||
} else {
|
||||
await addMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
accessTokenMaxTTL: Number(accessTokenMaxTTL),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, useFieldArray, useForm } from "react-hook-form";
|
||||
import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useParams } from "@tanstack/react-router";
|
||||
import ms from "ms";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -105,6 +106,9 @@ export const IdentityUniversalAuthForm = ({
|
||||
identityId,
|
||||
isUpdate
|
||||
}: Props) => {
|
||||
const { projectId } = useParams({
|
||||
strict: false
|
||||
});
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { subscription } = useSubscription();
|
||||
@@ -232,7 +236,7 @@ export const IdentityUniversalAuthForm = ({
|
||||
if (data) {
|
||||
// update universal auth configuration
|
||||
await updateMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
clientSecretTrustedIps,
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
@@ -249,7 +253,7 @@ export const IdentityUniversalAuthForm = ({
|
||||
// create new universal auth configuration
|
||||
|
||||
await addMutateAsync({
|
||||
organizationId: orgId,
|
||||
...(projectId ? { projectId } : { organizationId: orgId }),
|
||||
identityId,
|
||||
clientSecretTrustedIps,
|
||||
accessTokenTTL: Number(accessTokenTTL),
|
||||
@@ -270,7 +274,6 @@ export const IdentityUniversalAuthForm = ({
|
||||
text: `Successfully ${isUpdate ? "updated" : "created"} auth method`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FilterableSelect, FormControl } from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { useGetAvailableOrgIdentities, useGetOrgRoles } from "@app/hooks/api";
|
||||
import { useGetOrgRoles } from "@app/hooks/api";
|
||||
import { useCreateOrgIdentityMembership } from "@app/hooks/api/orgIdentityMembership";
|
||||
import { orgIdentityMembershipQuery } from "@app/hooks/api/orgIdentityMembership/queries";
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
@@ -22,15 +24,26 @@ type Props = {
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const IdentityLinkForm = ({ onClose }: Props) => {
|
||||
export const OrgIdentityLinkForm = ({ onClose }: Props) => {
|
||||
const navigate = useNavigate();
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
|
||||
const { data: roles } = useGetOrgRoles(orgId);
|
||||
|
||||
// const [searchValue, setSearchValue] = useState("");
|
||||
//
|
||||
// const [debouncedSearchValue] = useDebounce(searchValue);
|
||||
|
||||
const { mutateAsync: createMutateAsync } = useCreateOrgIdentityMembership();
|
||||
const { data: rootOrgIdentities, isPending: isRootOrgLoading } = useGetAvailableOrgIdentities();
|
||||
|
||||
// TODO: name filter needs to be implemented on backend
|
||||
const { data: rootOrgIdentities, isPending: isRootOrgLoading } = useQuery({
|
||||
...orgIdentityMembershipQuery.listAvailable({
|
||||
// identityName: debouncedSearchValue
|
||||
}),
|
||||
placeholderData: (prev) => prev
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
@@ -69,6 +82,7 @@ export const IdentityLinkForm = ({ onClose }: Props) => {
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder="Select identity..."
|
||||
// onInputChange={setSearchValue}
|
||||
options={rootOrgIdentities}
|
||||
getOptionValue={(option) => option.id}
|
||||
getOptionLabel={(option) => option.name}
|
||||
@@ -94,7 +108,6 @@ export const IdentityLinkForm = ({ onClose }: Props) => {
|
||||
placeholder="Select role..."
|
||||
getOptionValue={(option) => option.slug}
|
||||
getOptionLabel={(option) => option.name}
|
||||
// menuPortalTarget={document.body}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
@@ -14,13 +14,11 @@ import {
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Switch
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { findOrgMembershipRole } from "@app/helpers/roles";
|
||||
import { useCreateIdentity, useGetOrgRoles, useUpdateIdentity } from "@app/hooks/api";
|
||||
import { useCreateOrgIdentity, useGetOrgRoles, useUpdateOrgIdentity } from "@app/hooks/api";
|
||||
import { useAddIdentityUniversalAuth } from "@app/hooks/api/identities";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
@@ -47,7 +45,7 @@ type Props = {
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["identity"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
export const OrgIdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const navigate = useNavigate();
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
@@ -55,8 +53,8 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const { data: roles } = useGetOrgRoles(orgId);
|
||||
const isOrgIdentity = popUp?.identity?.data ? orgId === popUp?.identity?.data?.orgId : true;
|
||||
|
||||
const { mutateAsync: createMutateAsync } = useCreateIdentity();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateIdentity();
|
||||
const { mutateAsync: createMutateAsync } = useCreateOrgIdentity();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateOrgIdentity();
|
||||
const { mutateAsync: addMutateAsync } = useAddIdentityUniversalAuth();
|
||||
|
||||
const {
|
||||
@@ -173,75 +171,66 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.identity?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("identity", isOpen);
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
bodyClassName="overflow-visible"
|
||||
title={`${popUp?.identity?.data ? "Update" : "Create"} Identity`}
|
||||
>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
{isOrgIdentity && (
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="name"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="mb-4"
|
||||
label="Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="Machine 1" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
{isOrgIdentity && (
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="name"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="mb-4"
|
||||
label="Name"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="Machine 1" />
|
||||
</FormControl>
|
||||
)}
|
||||
<Controller
|
||||
control={control}
|
||||
name="role"
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label={`${popUp?.identity?.data ? "Update" : ""} Role`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
/>
|
||||
)}
|
||||
<Controller
|
||||
control={control}
|
||||
name="role"
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label={`${popUp?.identity?.data ? "Update" : ""} Role`}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<FilterableSelect
|
||||
placeholder="Select role..."
|
||||
options={roles}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
getOptionValue={(option) => option.slug}
|
||||
getOptionLabel={(option) => option.name}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{isOrgIdentity && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="hasDeleteProtection"
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
||||
<Switch
|
||||
className="mr-2 ml-0 bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
|
||||
containerClassName="flex-row-reverse w-fit"
|
||||
id="delete-protection-enabled"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
onCheckedChange={onChange}
|
||||
isChecked={value}
|
||||
>
|
||||
<FilterableSelect
|
||||
placeholder="Select role..."
|
||||
options={roles}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
getOptionValue={(option) => option.slug}
|
||||
getOptionLabel={(option) => option.name}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{isOrgIdentity && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="hasDeleteProtection"
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
||||
<FormControl errorText={error?.message} isError={Boolean(error)}>
|
||||
<Switch
|
||||
className="mr-2 ml-0 bg-mineshaft-400/80 shadow-inner data-[state=checked]:bg-green/80"
|
||||
containerClassName="flex-row-reverse w-fit"
|
||||
id="delete-protection-enabled"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
onCheckedChange={onChange}
|
||||
isChecked={value}
|
||||
>
|
||||
<p>Delete Protection {value ? "Enabled" : "Disabled"}</p>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<p>Delete Protection {value ? "Enabled" : "Disabled"}</p>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{isOrgIdentity && (
|
||||
<>
|
||||
<div>
|
||||
<FormLabel label="Metadata" />
|
||||
</div>
|
||||
@@ -303,26 +292,26 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
{popUp?.identity?.data ? "Update" : "Create"}
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("identity", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
{popUp?.identity?.data ? "Update" : "Create"}
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("identity", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,15 @@ import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
ModalClose,
|
||||
SecretInput,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
APP_CONNECTION_MAP,
|
||||
getAppConnectionMethodDetails,
|
||||
@@ -27,10 +35,13 @@ import {
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type ClientSecretForm = z.infer<typeof clientSecretSchema>;
|
||||
type CertificateForm = z.infer<typeof certificateSchema>;
|
||||
|
||||
type TInputFormData = ClientSecretForm | CertificateForm;
|
||||
|
||||
type Props = {
|
||||
appConnection?: TAzureClientSecretsConnection;
|
||||
onSubmit: (formData: ClientSecretForm) => Promise<void>;
|
||||
onSubmit: (formData: TInputFormData) => Promise<void>;
|
||||
projectId: string | undefined | null;
|
||||
};
|
||||
|
||||
@@ -53,7 +64,21 @@ const clientSecretSchema = baseSchema.extend({
|
||||
})
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [oauthSchema, clientSecretSchema]);
|
||||
const certificateSchema = baseSchema.extend({
|
||||
method: z.literal(AzureClientSecretsConnectionMethod.Certificate),
|
||||
credentials: z.object({
|
||||
clientId: z.string().trim().min(1, "Client ID is required"),
|
||||
certificateBody: z.string().trim().min(1, "Certificate is required"),
|
||||
privateKey: z.string().trim().min(1, "Private Key is required"),
|
||||
tenantId: z.string().trim().min(1, "Tenant ID is required")
|
||||
})
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
oauthSchema,
|
||||
clientSecretSchema,
|
||||
certificateSchema
|
||||
]);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
@@ -96,6 +121,20 @@ const getDefaultValues = (appConnection?: TAzureClientSecretsConnection): Partia
|
||||
};
|
||||
}
|
||||
break;
|
||||
case AzureClientSecretsConnectionMethod.Certificate:
|
||||
if ("clientId" in credentials && "tenantId" in credentials) {
|
||||
return {
|
||||
...base,
|
||||
method: AzureClientSecretsConnectionMethod.Certificate,
|
||||
credentials: {
|
||||
clientId: credentials.clientId,
|
||||
tenantId: credentials.tenantId,
|
||||
certificateBody: "",
|
||||
privateKey: ""
|
||||
}
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
@@ -152,6 +191,9 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit, proj
|
||||
case AzureClientSecretsConnectionMethod.ClientSecret:
|
||||
await onSubmit(formData);
|
||||
break;
|
||||
case AzureClientSecretsConnectionMethod.Certificate:
|
||||
await onSubmit(formData);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
|
||||
}
|
||||
@@ -207,7 +249,11 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit, proj
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="tenantId"
|
||||
name={
|
||||
selectedMethod === AzureClientSecretsConnectionMethod.OAuth
|
||||
? "tenantId"
|
||||
: "credentials.tenantId"
|
||||
}
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
@@ -264,6 +310,59 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit, proj
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedMethod === AzureClientSecretsConnectionMethod.Certificate && (
|
||||
<>
|
||||
<Controller
|
||||
name="credentials.clientId"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
label="Client ID"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="00000000-0000-0000-0000-000000000000" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.certificateBody"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
label="Certificate"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<SecretInput
|
||||
containerClassName="text-gray-400 group-focus-within:border-primary-400/50! border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder="-----BEGIN CERTIFICATE-----..."
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.privateKey"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
label="Private Key"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<SecretInput
|
||||
placeholder="-----BEGIN PRIVATE KEY-----..."
|
||||
containerClassName="text-gray-400 group-focus-within:border-primary-400/50! border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
|
||||
@@ -7,16 +7,21 @@ import { Link, useNavigate, useParams } from "@tanstack/react-router";
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { DeleteActionModal, PageHeader } from "@app/components/v2";
|
||||
import { Button, DeleteActionModal, Modal, ModalContent, PageHeader } from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { useDeleteIdentity, useGetIdentityById } from "@app/hooks/api";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionIdentityActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization
|
||||
} from "@app/context";
|
||||
import { useDeleteOrgIdentity, useGetOrgIdentityMembershipById } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
import { ViewIdentityAuthModal } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal";
|
||||
import { OrgAccessControlTabSections } from "@app/types/org";
|
||||
|
||||
import { IdentityAuthMethodModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthMethodModal";
|
||||
import { IdentityModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal";
|
||||
import { OrgIdentityModal } from "../AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/OrgIdentityModal";
|
||||
import {
|
||||
IdentityAuthenticationSection,
|
||||
IdentityDetailsSection,
|
||||
@@ -31,8 +36,8 @@ const Page = () => {
|
||||
const identityId = params.identityId as string;
|
||||
const { currentOrg, isSubOrganization } = useOrganization();
|
||||
const orgId = currentOrg?.id || "";
|
||||
const { data } = useGetIdentityById(identityId);
|
||||
const { mutateAsync: deleteIdentity } = useDeleteIdentity();
|
||||
const { data } = useGetOrgIdentityMembershipById(identityId);
|
||||
const { mutateAsync: deleteIdentity, isPending: isDeletingIdentity } = useDeleteOrgIdentity();
|
||||
const isAuthHidden = orgId !== data?.identity?.orgId;
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
@@ -46,7 +51,7 @@ const Page = () => {
|
||||
const onDeleteIdentitySubmit = async (id: string) => {
|
||||
await deleteIdentity({
|
||||
identityId: id,
|
||||
organizationId: orgId
|
||||
orgId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
@@ -81,7 +86,36 @@ const Page = () => {
|
||||
scope={isSubOrganization ? "namespace" : "org"}
|
||||
description={`${isSubOrganization ? "Sub-" : ""}Organization Identity`}
|
||||
title={data.identity.name}
|
||||
/>
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{isSubOrganization && data.identity.orgId !== currentOrg.id && (
|
||||
<OrgPermissionCan
|
||||
I={OrgPermissionActions.Delete}
|
||||
a={OrgPermissionSubjects.Identity}
|
||||
renderTooltip
|
||||
allowedLabel="Remove from sub-organization"
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
colorSchema="danger"
|
||||
variant="outline_bg"
|
||||
size="xs"
|
||||
isDisabled={!isAllowed}
|
||||
isLoading={isDeletingIdentity}
|
||||
onClick={() =>
|
||||
handlePopUpOpen("deleteIdentity", {
|
||||
identityId: data.identity.id,
|
||||
name: data.identity.name
|
||||
})
|
||||
}
|
||||
>
|
||||
Unlink Identity
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</div>
|
||||
</PageHeader>
|
||||
<div className="flex">
|
||||
<div className="mr-4 w-96">
|
||||
<IdentityDetailsSection
|
||||
@@ -100,7 +134,17 @@ const Page = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
<Modal
|
||||
isOpen={popUp?.identity?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("identity", isOpen)}
|
||||
>
|
||||
<ModalContent
|
||||
bodyClassName="overflow-visible"
|
||||
title={`${popUp?.identity?.data ? "Update" : "Create"} Identity`}
|
||||
>
|
||||
<OrgIdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<IdentityAuthMethodModal
|
||||
popUp={popUp}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
|
||||
@@ -4,7 +4,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, Tooltip } from "@app/components/v2";
|
||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
|
||||
import { IdentityAuthMethod, identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api";
|
||||
import {
|
||||
IdentityAuthMethod,
|
||||
identityAuthToNameMap,
|
||||
useGetOrgIdentityMembershipById
|
||||
} from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
@@ -16,7 +20,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export const IdentityAuthenticationSection = ({ identityId, handlePopUpOpen }: Props) => {
|
||||
const { data, refetch } = useGetIdentityById(identityId);
|
||||
const { data, refetch } = useGetOrgIdentityMembershipById(identityId);
|
||||
|
||||
return data ? (
|
||||
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
|
||||