Merge branch 'main' of https://github.com/Infisical/infisical into feat/adds-GETtokenAuthTokenById-api-endpoint

This commit is contained in:
Piyush Gupta
2025-11-17 20:22:09 +05:30
422 changed files with 15873 additions and 4805 deletions
+77 -47
View File
@@ -150,7 +150,8 @@
"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"
"license": "MIT",
"peer": true
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
@@ -195,7 +196,6 @@
"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,7 +488,6 @@
"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"
},
@@ -547,7 +546,6 @@
"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",
@@ -1325,7 +1323,6 @@
"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"
},
@@ -2016,7 +2013,6 @@
"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",
@@ -4412,7 +4408,6 @@
"integrity": "sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/utils": "^8.13.0",
"eslint-visitor-keys": "^4.2.0",
@@ -5038,7 +5033,6 @@
"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",
@@ -5250,6 +5244,7 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
@@ -5270,6 +5265,7 @@
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"dequal": "^2.0.3"
}
@@ -5280,6 +5276,7 @@
"integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@adobe/css-tools": "^4.4.0",
"aria-query": "^5.0.0",
@@ -5299,7 +5296,8 @@
"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"
"license": "MIT",
"peer": true
},
"node_modules/@testing-library/user-event": {
"version": "14.6.1",
@@ -5307,6 +5305,7 @@
"integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12",
"npm": ">=6"
@@ -5327,7 +5326,8 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
@@ -5380,6 +5380,7 @@
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/deep-eql": "*",
"assertion-error": "^2.0.1"
@@ -5453,7 +5454,8 @@
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/doctrine": {
"version": "0.0.9",
@@ -5589,7 +5591,6 @@
"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"
@@ -5601,7 +5602,6 @@
"integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^18.0.0"
}
@@ -5649,7 +5649,6 @@
"integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.34.0",
@@ -5690,7 +5689,6 @@
"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",
@@ -5962,6 +5960,7 @@
"integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/spy": "3.2.4",
@@ -5979,6 +5978,7 @@
"integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/spy": "3.2.4",
"estree-walker": "^3.0.3",
@@ -6006,6 +6006,7 @@
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "^1.0.0"
}
@@ -6016,6 +6017,7 @@
"integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"tinyrainbow": "^2.0.0"
},
@@ -6029,6 +6031,7 @@
"integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"tinyspy": "^4.0.3"
},
@@ -6042,6 +6045,7 @@
"integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@vitest/pretty-format": "3.2.4",
"loupe": "^3.1.4",
@@ -6115,7 +6119,6 @@
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6232,6 +6235,7 @@
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">= 0.4"
}
@@ -6280,6 +6284,7 @@
"integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1",
@@ -6360,6 +6365,7 @@
"integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1",
@@ -6447,6 +6453,7 @@
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -6457,6 +6464,7 @@
"integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.0.1"
},
@@ -6469,7 +6477,8 @@
"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"
"license": "MIT",
"peer": true
},
"node_modules/asynckit": {
"version": "0.4.0",
@@ -6519,6 +6528,7 @@
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">= 0.4"
}
@@ -6620,6 +6630,7 @@
"integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"open": "^8.0.4"
},
@@ -6856,7 +6867,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746",
@@ -7038,6 +7048,7 @@
"integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"assertion-error": "^2.0.1",
"check-error": "^2.1.1",
@@ -7108,6 +7119,7 @@
"integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 16"
}
@@ -7493,7 +7505,8 @@
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/cssesc": {
"version": "3.0.0",
@@ -7512,8 +7525,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/cva": {
"name": "class-variance-authority",
@@ -7585,7 +7597,6 @@
"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"
}
@@ -7639,7 +7650,8 @@
"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"
"license": "BSD-2-Clause",
"peer": true
},
"node_modules/data-view-buffer": {
"version": "1.0.1",
@@ -7749,6 +7761,7 @@
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=6"
}
@@ -7783,6 +7796,7 @@
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8"
}
@@ -7904,7 +7918,8 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/dom-helpers": {
"version": "5.2.1",
@@ -8114,6 +8129,7 @@
"integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1",
@@ -8197,7 +8213,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -8239,6 +8254,7 @@
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"debug": "^4.3.4"
},
@@ -8275,7 +8291,6 @@
"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",
@@ -8332,7 +8347,6 @@
"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",
@@ -8355,7 +8369,6 @@
"integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"confusing-browser-globals": "^1.0.10",
"object.assign": "^4.1.2",
@@ -8386,7 +8399,6 @@
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -8486,7 +8498,6 @@
"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",
@@ -8616,6 +8627,7 @@
"integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"array-includes": "^3.1.8",
"array.prototype.findlast": "^1.2.5",
@@ -8649,7 +8661,6 @@
"integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@@ -8673,6 +8684,7 @@
"integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"esutils": "^2.0.2"
},
@@ -8686,6 +8698,7 @@
"integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
@@ -8704,6 +8717,7 @@
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true,
"license": "ISC",
"peer": true,
"bin": {
"semver": "bin/semver.js"
}
@@ -8865,6 +8879,7 @@
"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"
@@ -9895,7 +9910,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.23.2"
},
@@ -9989,6 +10003,7 @@
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8"
}
@@ -10265,6 +10280,7 @@
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"is-docker": "cli.js"
},
@@ -10586,6 +10602,7 @@
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"is-docker": "^2.0.0"
},
@@ -10621,6 +10638,7 @@
"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"
@@ -10632,6 +10650,7 @@
"integrity": "sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"define-data-property": "^1.1.4",
"es-object-atoms": "^1.0.0",
@@ -10781,6 +10800,7 @@
"integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"array-includes": "^3.1.6",
"array.prototype.flat": "^1.3.1",
@@ -10815,7 +10835,8 @@
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
"integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
"dev": true,
"license": "CC0-1.0"
"license": "CC0-1.0",
"peer": true
},
"node_modules/language-tags": {
"version": "1.0.9",
@@ -10823,6 +10844,7 @@
"integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"language-subtag-registry": "^0.3.20"
},
@@ -10855,6 +10877,7 @@
"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"
},
@@ -11179,7 +11202,8 @@
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
"integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/lru-cache": {
"version": "5.1.1",
@@ -11206,6 +11230,7 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -11897,6 +11922,7 @@
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=4"
}
@@ -12245,6 +12271,7 @@
"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",
@@ -12514,6 +12541,7 @@
"integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 14.16"
}
@@ -12665,7 +12693,6 @@
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -12782,6 +12809,7 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -12797,6 +12825,7 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@@ -12809,7 +12838,8 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/prismjs": {
"version": "1.30.0",
@@ -13010,7 +13040,6 @@
"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"
},
@@ -13038,7 +13067,6 @@
"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",
@@ -13130,7 +13158,6 @@
"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"
@@ -13181,7 +13208,6 @@
"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"
},
@@ -13432,6 +13458,7 @@
"integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ast-types": "^0.16.1",
"esprima": "~4.0.0",
@@ -13449,6 +13476,7 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -13465,6 +13493,7 @@
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"indent-string": "^4.0.0",
"strip-indent": "^3.0.0"
@@ -13479,6 +13508,7 @@
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"min-indent": "^1.0.0"
},
@@ -13693,7 +13723,6 @@
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -14204,6 +14233,7 @@
"integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1",
@@ -14219,6 +14249,7 @@
"integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1",
@@ -14246,6 +14277,7 @@
"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"
@@ -14466,8 +14498,7 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.2.1",
@@ -14571,6 +14602,7 @@
"integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=14.0.0"
}
@@ -14581,6 +14613,7 @@
"integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=14.0.0"
}
@@ -14865,7 +14898,6 @@
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -15251,7 +15283,6 @@
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -15669,6 +15700,7 @@
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
},
@@ -15713,7 +15745,6 @@
"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"
},
@@ -15864,7 +15895,6 @@
"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"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

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 = () => (
+36 -30
View File
@@ -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
+19 -1
View File
@@ -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,11 +1,11 @@
/* 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,
@@ -13,6 +13,10 @@ const syntaxHighlight = (
isImport?: boolean,
isLoadingValue?: 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;
@@ -29,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}`}>
&#36;&#123;
<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>
);
})}
&#125;
</span>
);
@@ -62,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";
@@ -81,12 +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
@@ -94,7 +177,7 @@ 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
className={twMerge(
@@ -108,6 +191,12 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
isImport,
isLoadingValue,
isErrorLoadingValue,
(part) => {
setHoveredPart(part);
},
hoveredPart,
isCmdOrCtrlPressed,
onClickSegment,
placeholder
)}
</span>
@@ -138,6 +227,9 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
onBlur?.(evt);
setIsSecretFocused.off();
}}
onMouseLeave={() => {
setHoveredPart(undefined);
}}
value={value || ""}
{...props}
readOnly={isReadOnly || isLoadingValue || isErrorLoadingValue}
-3
View File
@@ -26,9 +26,6 @@ export const envConfig = {
import.meta.env.VITE_TELEMETRY_CAPTURING_ENABLED === true
);
},
get ACME_FEATURE_ENABLED() {
return window?.__INFISICAL_RUNTIME_ENV__?.ACME_FEATURE_ENABLED ?? false;
},
get PLATFORM_VERSION() {
return import.meta.env.VITE_INFISICAL_PLATFORM_VERSION;
@@ -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 {
-1
View File
@@ -7,7 +7,6 @@ declare global {
POSTHOG_API_KEY?: string;
INTERCOM_ID?: string;
TELEMETRY_CAPTURING_ENABLED: string;
ACME_FEATURE_ENABLED?: boolean;
};
}
}
@@ -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",
@@ -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",
@@ -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,
@@ -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;
};
+505 -216
View File
@@ -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;
+156 -76
View File
@@ -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;
@@ -783,8 +858,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;
};
+4
View File
@@ -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(),
+13 -1
View File
@@ -3,7 +3,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 PamSessionStatus {
+13 -1
View File
@@ -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,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 -155
View File
@@ -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,
-18
View File
@@ -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;
@@ -59,6 +59,8 @@ export type TViewSharedSecretResponse = {
tag: string;
accessType: SecretSharingAccessType;
orgName?: string;
expiresAt?: Date | string;
expiresAfterViews?: number | null;
};
};
+1
View File
@@ -0,0 +1 @@
export * from "./types";
+37
View File
@@ -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;
@@ -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);
}
}
@@ -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>
);
};
@@ -9,10 +9,11 @@ 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";
@@ -24,11 +25,13 @@ import { CertificatesTable } from "./CertificatesTable";
export const CertificatesSection = () => {
const { currentProject } = useProject();
const { mutateAsync: deleteCert } = useDeleteCert();
const { mutateAsync: downloadCertPkcs12 } = useDownloadCertPkcs12();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"issueCertificate",
"certificateImport",
"certificateCert",
"certificateExport",
"deleteCertificate",
"revokeCertificate",
"manageRenewal",
@@ -49,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">
@@ -84,6 +126,11 @@ export const CertificatesSection = () => {
<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} />
@@ -68,6 +68,7 @@ type Props = {
"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
})
}
@@ -2,6 +2,7 @@ import { useState } from "react";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { Button, DeleteActionModal } from "@app/components/v2";
import { useProjectPermission } from "@app/context";
@@ -9,6 +10,7 @@ import {
ProjectPermissionActions,
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { usePopUp } from "@app/hooks";
import {
TCertificateProfileWithDetails,
useDeleteCertificateProfile
@@ -29,6 +31,7 @@ export const CertificateProfilesTab = () => {
const [selectedProfile, setSelectedProfile] = useState<TCertificateProfileWithDetails | null>(
null
);
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
const deleteProfile = useDeleteCertificateProfile();
@@ -99,7 +102,17 @@ export const CertificateProfilesTab = () => {
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 && (
<>
@@ -109,6 +122,7 @@ export const CertificateProfilesTab = () => {
setIsEditModalOpen(false);
setSelectedProfile(null);
}}
handlePopUpOpen={handlePopUpOpen}
profile={selectedProfile}
mode="edit"
/>
@@ -18,8 +18,7 @@ import {
TextArea,
Tooltip
} from "@app/components/v2";
import { envConfig } from "@app/config/env";
import { useProject } from "@app/context";
import { useProject, useSubscription } from "@app/context";
import { useListCasByProjectId } from "@app/hooks/api/ca/queries";
import {
TCertificateProfileWithDetails,
@@ -29,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({
@@ -151,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({
@@ -248,6 +261,15 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
}, [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) {
@@ -467,7 +489,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
>
<SelectItem value="api">API</SelectItem>
<SelectItem value="est">EST</SelectItem>
{envConfig.ACME_FEATURE_ENABLED && <SelectItem value="acme">ACME</SelectItem>}
<SelectItem value="acme">ACME</SelectItem>
</Select>
</FormControl>
)}
@@ -10,7 +10,7 @@ import {
ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types";
import { useDeleteCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/mutations";
import { TCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/types";
import { type TCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/types";
import { CreateTemplateModal } from "./CreateTemplateModal";
import { TemplateList } from "./TemplateList";
@@ -84,7 +84,12 @@ export const CertificateTemplatesV2Tab = () => {
<TemplateList onEditTemplate={handleEditTemplate} onDeleteTemplate={handleDeleteTemplate} />
<CreateTemplateModal isOpen={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} />
<CreateTemplateModal
isOpen={isCreateModalOpen}
onClose={() => {
setIsCreateModalOpen(false);
}}
/>
{selectedTemplate && (
<>
@@ -36,13 +36,18 @@ import {
CertDurationUnit,
CertExtendedKeyUsageType,
CertKeyUsageType,
CertSanInclude,
CertSubjectAlternativeNameType,
CertSubjectAttributeInclude,
CertSubjectAttributeType,
SAN_INCLUDE_OPTIONS,
SAN_TYPE_OPTIONS,
SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS,
SUBJECT_ATTRIBUTE_TYPE_OPTIONS
SUBJECT_ATTRIBUTE_TYPE_OPTIONS,
TEMPLATE_PRESET_IDS,
type TemplatePresetId
} from "./shared/certificate-constants";
import { CERTIFICATE_TEMPLATE_PRESETS } from "./shared/template-presets";
import { KeyUsagesSection, TemplateFormData, templateSchema } from "./shared";
export type FormData = TemplateFormData;
@@ -91,7 +96,7 @@ const SIGNATURE_ALGORITHMS = [
"SHA256-ECDSA",
"SHA384-ECDSA",
"SHA512-ECDSA"
];
] as const;
const KEY_ALGORITHMS = [
"RSA-2048",
@@ -100,7 +105,7 @@ const KEY_ALGORITHMS = [
"ECDSA-P256",
"ECDSA-P384",
"ECDSA-P521"
];
] as const;
export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }: Props) => {
const { currentProject } = useProject();
@@ -117,7 +122,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
subj.allowed.forEach((allowedValue) => {
attributes.push({
type: subj.type as CertSubjectAttributeType,
include: "optional",
include: CertSubjectAttributeInclude.OPTIONAL,
value: [allowedValue]
});
});
@@ -126,7 +131,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
subj.denied.forEach((deniedValue) => {
attributes.push({
type: subj.type as CertSubjectAttributeType,
include: "prohibit",
include: CertSubjectAttributeInclude.PROHIBIT,
value: [deniedValue]
});
});
@@ -141,7 +146,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
san.required.forEach((requiredValue) => {
subjectAlternativeNames.push({
type: san.type as CertSubjectAlternativeNameType,
include: "mandatory",
include: CertSanInclude.MANDATORY,
value: [requiredValue]
});
});
@@ -150,7 +155,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
san.allowed.forEach((allowedValue) => {
subjectAlternativeNames.push({
type: san.type as CertSubjectAlternativeNameType,
include: "optional",
include: CertSanInclude.OPTIONAL,
value: [allowedValue]
});
});
@@ -159,7 +164,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
san.denied.forEach((deniedValue) => {
subjectAlternativeNames.push({
type: san.type as CertSubjectAlternativeNameType,
include: "prohibit",
include: CertSanInclude.PROHIBIT,
value: [deniedValue]
});
});
@@ -219,6 +224,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
};
return {
preset: TEMPLATE_PRESET_IDS.CUSTOM,
name: templateData.name || "",
description: templateData.description || "",
attributes,
@@ -231,25 +237,30 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
};
};
const getDefaultValues = (): FormData => ({
name: "",
description: "",
attributes: [],
keyUsages: { requiredUsages: [], optionalUsages: [] },
extendedKeyUsages: { requiredUsages: [], optionalUsages: [] },
subjectAlternativeNames: [],
validity: {
maxDuration: { value: 365, unit: CertDurationUnit.DAYS }
},
signatureAlgorithm: {
allowedAlgorithms: []
},
keyAlgorithm: {
allowedKeyTypes: []
}
});
const getDefaultValues = (): FormData & { preset: TemplatePresetId } => {
return {
preset: TEMPLATE_PRESET_IDS.CUSTOM,
name: "",
description: "",
attributes: [],
keyUsages: { requiredUsages: [], optionalUsages: [] },
extendedKeyUsages: { requiredUsages: [], optionalUsages: [] },
subjectAlternativeNames: [],
validity: {
maxDuration: { value: 365, unit: CertDurationUnit.DAYS }
},
signatureAlgorithm: {
allowedAlgorithms: []
},
keyAlgorithm: {
allowedKeyTypes: []
}
};
};
const { control, handleSubmit, reset, watch, setValue, formState } = useForm<FormData>({
const { control, handleSubmit, reset, watch, setValue, formState } = useForm<
FormData & { preset: TemplatePresetId }
>({
resolver: zodResolver(templateSchema),
defaultValues: getDefaultValues(),
mode: "onChange",
@@ -260,7 +271,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
useEffect(() => {
if (isEdit && template) {
const convertedData = convertApiToUiFormat(template);
reset(convertedData);
reset({ ...convertedData, preset: TEMPLATE_PRESET_IDS.CUSTOM });
} else if (!isEdit) {
reset(getDefaultValues());
}
@@ -273,6 +284,37 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
requiredUsages: [],
optionalUsages: []
};
const watchedPreset = watch("preset") || TEMPLATE_PRESET_IDS.CUSTOM;
const handlePresetChange = (presetId: TemplatePresetId) => {
setValue("preset", presetId);
if (presetId === TEMPLATE_PRESET_IDS.CUSTOM) {
return;
}
const selectedPreset = CERTIFICATE_TEMPLATE_PRESETS.find((p) => p.id === presetId);
if (selectedPreset) {
if (selectedPreset.formData.keyUsages) {
setValue("keyUsages", selectedPreset.formData.keyUsages);
}
if (selectedPreset.formData.extendedKeyUsages) {
setValue("extendedKeyUsages", selectedPreset.formData.extendedKeyUsages);
}
if (selectedPreset.formData.attributes) {
setValue("attributes", selectedPreset.formData.attributes);
}
if (selectedPreset.formData.subjectAlternativeNames) {
setValue("subjectAlternativeNames", selectedPreset.formData.subjectAlternativeNames);
}
if (selectedPreset.formData.signatureAlgorithm) {
setValue("signatureAlgorithm", selectedPreset.formData.signatureAlgorithm);
}
if (selectedPreset.formData.keyAlgorithm) {
setValue("keyAlgorithm", selectedPreset.formData.keyAlgorithm);
}
}
};
const consolidateByType = <
T extends { type: string; allowed?: string[]; required?: string[]; denied?: string[] }
@@ -309,9 +351,17 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
data.attributes?.map((attr) => {
const result: AttributeTransform = { type: attr.type };
if (attr.include === "optional" && attr.value && attr.value.length > 0) {
if (
attr.include === CertSubjectAttributeInclude.OPTIONAL &&
attr.value &&
attr.value.length > 0
) {
result.allowed = attr.value;
} else if (attr.include === "prohibit" && attr.value && attr.value.length > 0) {
} else if (
attr.include === CertSubjectAttributeInclude.PROHIBIT &&
attr.value &&
attr.value.length > 0
) {
result.denied = attr.value;
}
@@ -322,11 +372,11 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
data.subjectAlternativeNames?.map((san) => {
const result: SanTransform = { type: san.type };
if (san.include === "mandatory" && san.value && san.value.length > 0) {
if (san.include === CertSanInclude.MANDATORY && san.value && san.value.length > 0) {
result.required = san.value;
} else if (san.include === "optional" && san.value && san.value.length > 0) {
} else if (san.include === CertSanInclude.OPTIONAL && san.value && san.value.length > 0) {
result.allowed = san.value;
} else if (san.include === "prohibit" && san.value && san.value.length > 0) {
} else if (san.include === CertSanInclude.PROHIBIT && san.value && san.value.length > 0) {
result.denied = san.value;
}
@@ -464,11 +514,19 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: ["*"]
};
setValue("attributes", [...watchedAttributes, newAttribute]);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
const removeAttribute = (index: number) => {
const newAttributes = watchedAttributes.filter((_, i) => i !== index);
setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
const addSan = () => {
@@ -478,11 +536,19 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: ["*"]
};
setValue("subjectAlternativeNames", [...watchedSans, newSan]);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
const removeSan = (index: number) => {
const newSans = watchedSans.filter((_, i) => i !== index);
setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
const handleKeyUsagesChange = (usages: {
@@ -493,6 +559,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
requiredUsages: usages.requiredUsages,
optionalUsages: usages.optionalUsages
});
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
const handleExtendedKeyUsagesChange = (usages: {
@@ -503,6 +573,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
requiredUsages: usages.requiredUsages,
optionalUsages: usages.optionalUsages
});
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
};
return (
@@ -555,6 +629,33 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
</FormControl>
)}
/>
<Controller
control={control}
name="preset"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Template Preset"
isError={Boolean(error)}
errorText={error?.message}
>
<Select
value={field.value}
onValueChange={handlePresetChange}
className="w-full"
position="popper"
dropdownContainerClassName="max-w-none"
>
<SelectItem value={TEMPLATE_PRESET_IDS.CUSTOM}>Custom</SelectItem>
{CERTIFICATE_TEMPLATE_PRESETS.map((preset) => (
<SelectItem key={preset.id} value={preset.id}>
{preset.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
</div>
<AccordionItem value="attributes">
<AccordionTrigger>Subject Attributes</AccordionTrigger>
@@ -595,6 +696,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
type: value as CertSubjectAttributeType
};
setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className="w-48"
>
@@ -615,6 +720,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value as (typeof SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS)[number]
};
setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className="w-32"
>
@@ -635,6 +744,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: e.target.value.trim() ? [e.target.value.trim()] : []
};
setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className={`flex-1 ${
attr.value && attr.value.length > 0 && attr.value[0] === ""
@@ -701,6 +814,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
type: value as CertSubjectAlternativeNameType
};
setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className="w-36"
>
@@ -720,6 +837,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
include: value as (typeof SAN_INCLUDE_OPTIONS)[number]
};
setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className="w-32"
>
@@ -740,6 +861,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: e.target.value.trim() ? [e.target.value.trim()] : []
};
setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}}
className={`flex-1 ${
san.value && san.value.length > 0 && san.value[0] === ""
@@ -150,8 +150,19 @@ export const SUBJECT_ATTRIBUTE_TYPE_OPTIONS = Object.values(CertSubjectAttribute
export const ATTRIBUTE_RULE_OPTIONS = Object.values(CertAttributeRule);
export const SAN_EFFECT_OPTIONS = Object.values(CertSanEffect);
export const SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS = ["optional", "prohibit"] as const;
export const SAN_INCLUDE_OPTIONS = ["mandatory", "optional", "prohibit"] as const;
export enum CertSubjectAttributeInclude {
OPTIONAL = "optional",
PROHIBIT = "prohibit"
}
export enum CertSanInclude {
MANDATORY = "mandatory",
OPTIONAL = "optional",
PROHIBIT = "prohibit"
}
export const SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS = Object.values(CertSubjectAttributeInclude);
export const SAN_INCLUDE_OPTIONS = Object.values(CertSanInclude);
export const USAGE_STATES = {
REQUIRED: "required",
@@ -239,3 +250,27 @@ export const mapTemplateKeyAlgorithmToApi = (templateFormat: string): string =>
};
return mapping[templateFormat] || templateFormat;
};
export const TEMPLATE_PRESET_IDS = {
CUSTOM: "custom",
TLS_SERVER: "tls-server",
TLS_CLIENT: "tls-client",
CODE_SIGNING: "code-signing",
DEVICE: "device",
USER: "user",
EMAIL_PROTECTION: "email-protection",
DUAL_PURPOSE_SERVER: "dual-purpose-server"
} as const;
export type TemplatePresetId = (typeof TEMPLATE_PRESET_IDS)[keyof typeof TEMPLATE_PRESET_IDS];
export const ALGORITHM_FAMILIES = {
ECDSA: {
signature: ["SHA256-ECDSA", "SHA384-ECDSA", "SHA512-ECDSA"] as const,
key: ["ECDSA-P256", "ECDSA-P384", "ECDSA-P521"] as const
},
RSA: {
signature: ["SHA256-RSA", "SHA384-RSA", "SHA512-RSA"] as const,
key: ["RSA-2048", "RSA-3072", "RSA-4096"] as const
}
} as const;
@@ -4,21 +4,22 @@ import {
CertDurationUnit,
CertExtendedKeyUsageType,
CertKeyUsageType,
CertSanInclude,
CertSubjectAlternativeNameType,
CertSubjectAttributeInclude,
CertSubjectAttributeType,
SAN_INCLUDE_OPTIONS,
SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS
TEMPLATE_PRESET_IDS
} from "./certificate-constants";
export const uiAttributeSchema = z.object({
type: z.nativeEnum(CertSubjectAttributeType),
include: z.enum(SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS),
include: z.nativeEnum(CertSubjectAttributeInclude),
value: z.array(z.string().min(1, "Value cannot be empty"))
});
export const uiSanSchema = z.object({
type: z.nativeEnum(CertSubjectAlternativeNameType),
include: z.enum(SAN_INCLUDE_OPTIONS),
include: z.nativeEnum(CertSanInclude),
value: z.array(z.string().min(1, "Value cannot be empty"))
});
@@ -51,7 +52,21 @@ export const uiKeyAlgorithmSchema = z.object({
.min(1, "At least one key type must be selected")
});
export const uiPresetSchema = z
.enum([
TEMPLATE_PRESET_IDS.CUSTOM,
TEMPLATE_PRESET_IDS.TLS_SERVER,
TEMPLATE_PRESET_IDS.TLS_CLIENT,
TEMPLATE_PRESET_IDS.CODE_SIGNING,
TEMPLATE_PRESET_IDS.DEVICE,
TEMPLATE_PRESET_IDS.USER,
TEMPLATE_PRESET_IDS.EMAIL_PROTECTION,
TEMPLATE_PRESET_IDS.DUAL_PURPOSE_SERVER
])
.default(TEMPLATE_PRESET_IDS.CUSTOM);
export const templateSchema = z.object({
preset: uiPresetSchema,
name: z
.string()
.trim()
@@ -0,0 +1,385 @@
import {
ALGORITHM_FAMILIES,
CertDurationUnit,
CertExtendedKeyUsageType,
CertKeyUsageType,
CertSanInclude,
CertSubjectAlternativeNameType,
CertSubjectAttributeInclude,
CertSubjectAttributeType,
TEMPLATE_PRESET_IDS,
type TemplatePresetId
} from "./certificate-constants";
import { TemplateFormData } from ".";
export interface CertificateTemplatePreset {
readonly id: TemplatePresetId;
readonly name: string;
readonly description: string;
readonly useCase: string;
readonly formData: Omit<TemplateFormData, "preset">;
}
export const CERTIFICATE_TEMPLATE_PRESETS: CertificateTemplatePreset[] = [
{
id: TEMPLATE_PRESET_IDS.TLS_SERVER,
name: "TLS Server Certificate",
description: "Standard TLS/SSL server certificate for HTTPS services and API endpoints.",
useCase: "Web servers, API endpoints, HTTPS services",
formData: {
name: "TLS Server Certificate",
description: "Standard TLS/SSL server certificate for HTTPS services and API endpoints.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_ENCIPHERMENT]
},
extendedKeyUsages: {
requiredUsages: [CertExtendedKeyUsageType.SERVER_AUTH],
optionalUsages: [CertExtendedKeyUsageType.SERVER_AUTH]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.DNS_NAME,
include: CertSanInclude.OPTIONAL,
value: ["*"]
},
{
type: CertSubjectAlternativeNameType.IP_ADDRESS,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.TLS_CLIENT,
name: "TLS Client Certificate",
description: "Client certificate for mutual TLS authentication and API access.",
useCase: "Client authentication, mTLS, API authentication",
formData: {
name: "TLS Client Certificate",
description: "Client certificate for mutual TLS authentication and API access.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_AGREEMENT]
},
extendedKeyUsages: {
requiredUsages: [CertExtendedKeyUsageType.CLIENT_AUTH],
optionalUsages: [CertExtendedKeyUsageType.CLIENT_AUTH]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.EMAIL,
include: CertSanInclude.OPTIONAL,
value: ["*"]
},
{
type: CertSubjectAlternativeNameType.DNS_NAME,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.CODE_SIGNING,
name: "Code Signing Certificate",
description:
"Certificate for signing software, executables, and packages. Requires hardware security modules.",
useCase: "Software signing, executable authentication, package validation",
formData: {
name: "Code Signing Certificate",
description:
"Certificate for signing software, executables, and packages. Requires hardware security modules.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION],
optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION]
},
extendedKeyUsages: {
requiredUsages: [CertExtendedKeyUsageType.CODE_SIGNING],
optionalUsages: [
CertExtendedKeyUsageType.CODE_SIGNING,
CertExtendedKeyUsageType.TIME_STAMPING
]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.EMAIL,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.RSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.RSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.DEVICE,
name: "Device Certificate",
description:
"Certificate for IoT devices and embedded systems authentication. IEEE 802.1AR compliant.",
useCase: "Device authentication, IoT security, embedded systems",
formData: {
name: "Device Certificate",
description:
"Certificate for IoT devices and embedded systems authentication. IEEE 802.1AR compliant.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_AGREEMENT]
},
extendedKeyUsages: {
requiredUsages: [CertExtendedKeyUsageType.CLIENT_AUTH],
optionalUsages: [CertExtendedKeyUsageType.CLIENT_AUTH, CertExtendedKeyUsageType.SERVER_AUTH]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.DNS_NAME,
include: CertSanInclude.OPTIONAL,
value: ["*"]
},
{
type: CertSubjectAlternativeNameType.IP_ADDRESS,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.USER,
name: "User Certificate",
description:
"Personal certificate for user authentication and email signing. FIPS 201 PIV compliant.",
useCase: "Personal authentication, smart cards, email protection",
formData: {
name: "User Certificate",
description:
"Personal certificate for user authentication and email signing. FIPS 201 PIV compliant.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [
CertKeyUsageType.DIGITAL_SIGNATURE,
CertKeyUsageType.KEY_ENCIPHERMENT,
CertKeyUsageType.KEY_AGREEMENT
]
},
extendedKeyUsages: {
requiredUsages: [
CertExtendedKeyUsageType.CLIENT_AUTH,
CertExtendedKeyUsageType.EMAIL_PROTECTION
],
optionalUsages: [
CertExtendedKeyUsageType.CLIENT_AUTH,
CertExtendedKeyUsageType.EMAIL_PROTECTION
]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.EMAIL,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.EMAIL_PROTECTION,
name: "Email Protection Certificate",
description: "S/MIME certificate for email encryption and digital signing. RFC 8550 compliant.",
useCase: "Email encryption, digital signing, secure messaging",
formData: {
name: "Email Protection Certificate",
description:
"S/MIME certificate for email encryption and digital signing. RFC 8550 compliant.",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [
CertKeyUsageType.DIGITAL_SIGNATURE,
CertKeyUsageType.KEY_ENCIPHERMENT,
CertKeyUsageType.KEY_AGREEMENT
]
},
extendedKeyUsages: {
requiredUsages: [CertExtendedKeyUsageType.EMAIL_PROTECTION],
optionalUsages: [CertExtendedKeyUsageType.EMAIL_PROTECTION]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.EMAIL,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.RSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.RSA.key]
}
}
},
{
id: TEMPLATE_PRESET_IDS.DUAL_PURPOSE_SERVER,
name: "Dual-Purpose Server Certificate",
description:
"Certificate for services requiring both server and client authentication capabilities",
useCase: "Microservices, service mesh, dual authentication",
formData: {
name: "Dual-Purpose Server Certificate",
description:
"Certificate for services requiring both server and client authentication capabilities",
keyUsages: {
requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE],
optionalUsages: [
CertKeyUsageType.DIGITAL_SIGNATURE,
CertKeyUsageType.KEY_ENCIPHERMENT,
CertKeyUsageType.KEY_AGREEMENT
]
},
extendedKeyUsages: {
requiredUsages: [
CertExtendedKeyUsageType.SERVER_AUTH,
CertExtendedKeyUsageType.CLIENT_AUTH
],
optionalUsages: [CertExtendedKeyUsageType.SERVER_AUTH, CertExtendedKeyUsageType.CLIENT_AUTH]
},
validity: {
maxDuration: {
value: 365,
unit: CertDurationUnit.DAYS
}
},
attributes: [
{
type: CertSubjectAttributeType.COMMON_NAME,
include: CertSubjectAttributeInclude.OPTIONAL,
value: ["*"]
}
],
subjectAlternativeNames: [
{
type: CertSubjectAlternativeNameType.DNS_NAME,
include: CertSanInclude.OPTIONAL,
value: ["*"]
},
{
type: CertSubjectAlternativeNameType.IP_ADDRESS,
include: CertSanInclude.OPTIONAL,
value: ["*"]
}
],
signatureAlgorithm: {
allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature]
},
keyAlgorithm: {
allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key]
}
}
}
];
@@ -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>
);
};
@@ -4,8 +4,10 @@ import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tab } from "@headlessui/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { z } from "zod";
import { OrgPermissionCan } from "@app/components/permissions";
import {
Button,
FormControl,
@@ -18,8 +20,11 @@ import {
TextArea,
Tooltip
} from "@app/components/v2";
import { OrgPermissionSubjects, useSubscription } from "@app/context";
import { OrgGatewayPermissionActions } from "@app/context/OrgPermissionContext/types";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
import { DistinguishedNameRegex, UserPrincipalNameRegex } from "@app/helpers/string";
import { gatewaysQueryKeys } from "@app/hooks/api";
import {
LdapConnectionMethod,
LdapConnectionProvider,
@@ -84,6 +89,7 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => {
defaultValues: appConnection ?? {
app: AppConnection.LDAP,
method: LdapConnectionMethod.SimpleBind,
gatewayId: null,
credentials: {
provider: LdapConnectionProvider.ActiveDirectory,
url: "",
@@ -104,6 +110,8 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => {
const selectedProvider = watch("credentials.provider");
const sslEnabled = watch("credentials.url")?.startsWith("ldaps://") ?? false;
const { subscription } = useSubscription();
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
return (
<FormProvider {...form}>
@@ -114,6 +122,57 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => {
}}
>
{!isUpdate && <GenericAppConnectionsFields />}
{subscription.gateway && (
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
>
{(isAllowed) => (
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
label="Gateway"
>
<Tooltip
isDisabled={isAllowed}
content="Restricted access. You don't have permission to attach gateways to resources."
>
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
isLoading={isGatewaysLoading}
placeholder="Default: Internet Gateway"
position="popper"
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
>
Internet Gateway
</SelectItem>
{gateways?.map((el) => (
<SelectItem value={el.id} key={el.id}>
{el.name}
</SelectItem>
))}
</Select>
</div>
</Tooltip>
</FormControl>
)}
/>
)}
</OrgPermissionCan>
)}
<div className="grid grid-cols-2 items-center gap-2">
<Controller
name="method"
@@ -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">
@@ -7,9 +7,9 @@ import { Button, IconButton, Tooltip } from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import { useTimedReset } from "@app/hooks";
import {
useGetIdentityById,
useGetIdentityUniversalAuth,
useGetIdentityUniversalAuthClientSecrets
useGetIdentityUniversalAuthClientSecrets,
useGetOrgIdentityMembershipById
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
@@ -30,7 +30,7 @@ export const IdentityClientSecrets = ({ identityId, handlePopUpOpen }: Props) =>
initialState: "Copy Client ID to clipboard"
});
const { data } = useGetIdentityById(identityId);
const { data } = useGetOrgIdentityMembershipById(identityId);
const { data: identityUniversalAuth } = useGetIdentityUniversalAuth(identityId);
const { data: clientSecrets } = useGetIdentityUniversalAuthClientSecrets(identityId);
return (
@@ -11,7 +11,7 @@ import {
IconButton,
Tooltip
} from "@app/components/v2";
import { useGetIdentityById, useGetIdentityTokensTokenAuth } from "@app/hooks/api";
import { useGetIdentityTokensTokenAuth, useGetOrgIdentityMembershipById } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -23,7 +23,7 @@ type Props = {
};
export const IdentityTokens = ({ identityId, handlePopUpOpen }: Props) => {
const { data } = useGetIdentityById(identityId);
const { data } = useGetOrgIdentityMembershipById(identityId);
const { data: tokens } = useGetIdentityTokensTokenAuth(identityId);
return (
<div>
@@ -23,7 +23,7 @@ import {
} from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useTimedReset } from "@app/hooks";
import { identityAuthToNameMap, useGetIdentityById } from "@app/hooks/api";
import { identityAuthToNameMap, useGetOrgIdentityMembershipById } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
@@ -41,7 +41,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
});
const { isSubOrganization } = useOrganization();
const { data } = useGetIdentityById(identityId);
const { data } = useGetOrgIdentityMembershipById(identityId);
return data ? (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
@@ -86,7 +86,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
}}
disabled={!isAllowed}
>
Edit Identity
{isOrgIdentity ? "Edit Identity" : "Edit Identity Role"}
</DropdownMenuItem>
)}
</OrgPermissionCan>
@@ -110,7 +110,7 @@ export const IdentityDetailsSection = ({ identityId, handlePopUpOpen, isOrgIdent
icon={<FontAwesomeIcon icon={faTrash} />}
disabled={!isAllowed}
>
Delete Identity
{!isOrgIdentity ? "Remove From Sub-Organization" : "Delete Identity"}
</DropdownMenuItem>
)}
</OrgPermissionCan>
@@ -14,7 +14,7 @@ import {
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
useAddIdentityToWorkspace,
useCreateProjectIdentityMembership,
useGetIdentityProjectMemberships,
useGetProjectRoles,
useGetUserProjects,
@@ -45,7 +45,7 @@ type Props = {
const Content = ({ identityId, handlePopUpToggle }: Omit<Props, "popUp">) => {
const { currentOrg } = useOrganization();
const { data: workspaces = [] } = useGetUserProjects();
const { mutateAsync: addIdentityToWorkspace } = useAddIdentityToWorkspace();
const { mutateAsync: addIdentityToWorkspace } = useCreateProjectIdentityMembership();
const {
control,
@@ -9,7 +9,7 @@ import { IconButton, Td, Tooltip, Tr } from "@app/components/v2";
import { getProjectBaseURL } from "@app/helpers/project";
import { formatProjectRoleName } from "@app/helpers/roles";
import { useGetUserProjects } from "@app/hooks/api";
import { IdentityMembership } from "@app/hooks/api/identities/types";
import { IdentityProjectMembershipV1 } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
export enum TabSections {
@@ -20,7 +20,7 @@ export enum TabSections {
}
type Props = {
membership: IdentityMembership;
membership: IdentityProjectMembershipV1;
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["removeIdentityFromProject"]>,
data?: object
@@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { DeleteActionModal, IconButton } from "@app/components/v2";
import { useDeleteIdentityFromWorkspace } from "@app/hooks/api";
import { useDeleteProjectIdentityMembership } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { IdentityAddToProjectModal } from "./IdentityAddToProjectModal";
@@ -14,7 +14,7 @@ type Props = {
};
export const IdentityProjectsSection = ({ identityId }: Props) => {
const { mutateAsync: deleteMutateAsync } = useDeleteIdentityFromWorkspace();
const { mutateAsync: deleteMutateAsync } = useDeleteProjectIdentityMembership();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"addIdentityToProject",
@@ -1,11 +1,18 @@
import { useState } from "react";
import { subject } from "@casl/ability";
import { UseMutationResult } from "@tanstack/react-query";
import { useParams } from "@tanstack/react-router";
import ms from "ms";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { VariablePermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import {
OrgPermissionIdentityActions,
OrgPermissionSubjects,
ProjectPermissionIdentityActions,
ProjectPermissionSub
} from "@app/context";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
@@ -31,7 +38,11 @@ export const LockoutFields = ({
const [lockedOutState, setLockedOutState] = useState(lockedOut);
const clearLockouts = async () => {
const { projectId } = useParams({
strict: false
});
async function clearLockouts() {
const deleted = await mutateAsync({ identityId });
createNotification({
text: `Successfully cleared ${deleted} lockout${deleted === 1 ? "" : "s"}`,
@@ -39,13 +50,23 @@ export const LockoutFields = ({
});
setLockedOutState(false);
onResetAllLockouts();
};
}
return (
<>
<div className="col-span-2 mt-3 flex justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Lockout Options</span>
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={projectId ? ProjectPermissionIdentityActions.Edit : OrgPermissionIdentityActions.Edit}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Button
isDisabled={!isAllowed || !lockedOutState || isPending}
@@ -57,7 +78,7 @@ export const LockoutFields = ({
Reset All Lockouts
</Button>
)}
</OrgPermissionCan>
</VariablePermissionCan>
</div>
<IdentityAuthFieldDisplay label="Lockout Threshold">
{data.lockoutThreshold}
@@ -1,10 +1,12 @@
import { useState } from "react";
import { subject } from "@casl/ability";
import { faBan, faEdit, faKey, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useParams } from "@tanstack/react-router";
import { format } from "date-fns";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { VariablePermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
@@ -20,7 +22,12 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import {
OrgPermissionIdentityActions,
OrgPermissionSubjects,
ProjectPermissionIdentityActions,
ProjectPermissionSub
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { useRevokeIdentityTokenAuthToken } from "@app/hooks/api";
import { IdentityAccessToken } from "@app/hooks/api/identities/types";
@@ -37,6 +44,10 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
"revokeToken"
] as const);
const { projectId } = useParams({
strict: false
});
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(5);
@@ -68,7 +79,17 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
<div className="col-span-2 mt-3">
<div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Access Tokens</span>
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={projectId ? ProjectPermissionIdentityActions.Edit : OrgPermissionIdentityActions.Edit}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Button
size="xs"
@@ -84,7 +105,7 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
Add Token
</Button>
)}
</OrgPermissionCan>
</VariablePermissionCan>
</div>
<TableContainer className="mt-4 rounded-none border-none">
<Table>
@@ -132,9 +153,20 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
</Td>
<Td>
<div className="flex items-center gap-2">
<OrgPermissionCan
I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity}
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={
projectId
? ProjectPermissionIdentityActions.Edit
: OrgPermissionIdentityActions.Edit
}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Tooltip content={isAllowed ? "Edit Token" : "Access Restricted"}>
@@ -155,11 +187,22 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
</VariablePermissionCan>
{!isAccessTokenRevoked && (
<OrgPermissionCan
I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity}
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={
projectId
? ProjectPermissionIdentityActions.Edit
: OrgPermissionIdentityActions.Edit
}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Tooltip content={isAllowed ? "Revoke Token" : "Access Restricted"}>
@@ -181,7 +224,7 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => {
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
</VariablePermissionCan>
)}
</div>
</Td>
@@ -1,10 +1,12 @@
import { useState } from "react";
import { subject } from "@casl/ability";
import { faKey, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useParams } from "@tanstack/react-router";
import { format } from "date-fns";
import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions";
import { VariablePermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
@@ -20,7 +22,12 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import {
OrgPermissionIdentityActions,
OrgPermissionSubjects,
ProjectPermissionIdentityActions,
ProjectPermissionSub
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { useRevokeIdentityUniversalAuthClientSecret } from "@app/hooks/api";
import { ClientSecretData } from "@app/hooks/api/identities/types";
@@ -37,6 +44,10 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
"clientSecret"
] as const);
const { projectId } = useParams({
strict: false
});
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(5);
@@ -60,7 +71,17 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
<div className="col-span-2">
<div className="flex items-end justify-between border-b border-mineshaft-500 pb-2">
<span className="text-bunker-300">Client Secrets</span>
<OrgPermissionCan I={OrgPermissionIdentityActions.Edit} a={OrgPermissionSubjects.Identity}>
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={projectId ? ProjectPermissionIdentityActions.Edit : OrgPermissionIdentityActions.Edit}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Button
isDisabled={!isAllowed}
@@ -76,7 +97,7 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
Add Client Secret
</Button>
)}
</OrgPermissionCan>
</VariablePermissionCan>
</div>
<TableContainer className="mt-4 rounded-none border-none">
<Table>
@@ -120,9 +141,20 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
{expiresAt ? format(expiresAt, "yyyy-MM-dd") : "-"}
</Td>
<Td>
<OrgPermissionCan
I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity}
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={
projectId
? ProjectPermissionIdentityActions.Edit
: OrgPermissionIdentityActions.Edit
}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<Tooltip content={isAllowed ? "Delete Secret" : "Access Restricted"}>
@@ -143,7 +175,7 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit
</IconButton>
</Tooltip>
)}
</OrgPermissionCan>
</VariablePermissionCan>
</Td>
</Tr>
);
@@ -49,6 +49,7 @@ export const ViewIdentityAliCloudAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -1,3 +1,5 @@
import { useParams } from "@tanstack/react-router";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { DeleteActionModal, Modal, ModalContent } from "@app/components/v2";
@@ -46,8 +48,7 @@ type Props = {
type TRevokeOptions = {
identityId: string;
organizationId: string;
};
} & ({ projectId: string } | { organizationId: string });
export const Content = ({
identityId,
@@ -61,7 +62,9 @@ export const Content = ({
>) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { projectId } = useParams({
strict: false
});
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"revokeAuthMethod",
"upgradePlan",
@@ -142,7 +145,11 @@ export const Content = ({
const handleDeleteAuthMethod = async () => {
await revokeMethod({
identityId,
organizationId: orgId
...(projectId
? { projectId }
: {
organizationId: orgId
})
});
createNotification({
@@ -46,6 +46,7 @@ export const ViewIdentityAwsAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -46,6 +46,7 @@ export const ViewIdentityAzureAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
@@ -1,8 +1,10 @@
import { ReactNode } from "react";
import { subject } from "@casl/ability";
import { faChevronDown, faEdit, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useParams } from "@tanstack/react-router";
import { OrgPermissionCan } from "@app/components/permissions";
import { VariablePermissionCan } from "@app/components/permissions";
import {
Button,
DropdownMenu,
@@ -10,15 +12,25 @@ import {
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/context";
import {
OrgPermissionIdentityActions,
OrgPermissionSubjects,
ProjectPermissionIdentityActions,
ProjectPermissionSub
} from "@app/context";
type Props = {
children: ReactNode;
onEdit: VoidFunction;
onDelete: VoidFunction;
identityId: string;
};
export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props) => {
export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit, identityId }: Props) => {
const { projectId } = useParams({
strict: false
});
return (
<div className="flex flex-col gap-4">
<div>
@@ -36,9 +48,20 @@ export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="mt-3 min-w-[120px]" align="end">
<OrgPermissionCan
I={OrgPermissionIdentityActions.Edit}
a={OrgPermissionSubjects.Identity}
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={
projectId
? ProjectPermissionIdentityActions.Edit
: OrgPermissionIdentityActions.Edit
}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<DropdownMenuItem
@@ -49,10 +72,21 @@ export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props
Edit
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionIdentityActions.Delete}
a={OrgPermissionSubjects.Identity}
</VariablePermissionCan>
<VariablePermissionCan
type={projectId ? "project" : "org"}
I={
projectId
? ProjectPermissionIdentityActions.Delete
: OrgPermissionIdentityActions.Delete
}
a={
projectId
? subject(ProjectPermissionSub.Identity, {
identityId
})
: OrgPermissionSubjects.Identity
}
>
{(isAllowed) => (
<DropdownMenuItem
@@ -63,7 +97,7 @@ export const ViewIdentityContentWrapper = ({ children, onDelete, onEdit }: Props
Delete
</DropdownMenuItem>
)}
</OrgPermissionCan>
</VariablePermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</div>
@@ -46,6 +46,7 @@ export const ViewIdentityGcpAuthContent = ({
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
identityId={identityId}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}

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