Compare commits

..

48 Commits

Author SHA1 Message Date
ac2ee6884c misc: updated to use regex literal 2025-04-22 02:10:56 +08:00
c15a1c6ed3 misc: moved regex use to re2 2025-04-22 01:38:21 +08:00
eba12912f8 Merge pull request #3396 from akhilmhdh/feat/msg-crct
Updated error message on update org for saml/oidc enforcement
2025-04-20 12:07:07 -04:00
80edccc953 Update org-service.ts 2025-04-20 12:06:34 -04:00
f1b1d6f480 Merge pull request #3449 from Infisical/rbac-developer-role-correction
Documentation: Correct Developer Role Description
2025-04-18 14:19:09 -04:00
07d6616f3c Merge pull request #3448 from akhilmhdh/feat/better-saml-error-message
Improved saml error messages
2025-04-18 22:36:59 +05:30
28d056cf7a documentation: correct developer description 2025-04-18 09:54:58 -07:00
=
f5d7809515 feat: improved saml error messages 2025-04-18 22:24:01 +05:30
233740e029 Merge pull request #3429 from Infisical/windmill-connection-and-sync
Feature: Windmill Connection and Sync
2025-04-17 17:53:19 -07:00
767fdc645f Merge branch 'heads/main' into windmill-connection-and-sync 2025-04-18 04:44:32 +04:00
c477703dda Merge pull request #3406 from juhnny5/patch-1
Typo correction in the go-sdk example
2025-04-18 03:46:45 +04:00
923d639c40 Merge pull request #3445 from Infisical/vercel-connection-validation-fix
Fix: Vercel Connection Validation API Call
2025-04-17 14:20:23 -07:00
7655dc7f3c chore: remove unused type 2025-04-17 12:22:02 -07:00
6c6c4db92c fix: use a non-team based api call for validation 2025-04-17 12:20:02 -07:00
8cf125ed32 Merge pull request #3444 from Infisical/default-project-delete-protection-false
Improvement: Set Project Delete Protection to False by Default
2025-04-17 12:21:46 -04:00
886cc9a113 improvement: set project delete protection to false by default 2025-04-17 09:16:36 -07:00
e1016f0a8b Merge pull request #3441 from Infisical/revert-3439-daniel/remove-docs
Revert "fix: removed legacy sdk's"
2025-04-17 07:05:30 +04:00
9c0a5f0bd4 fix: deprecation notices 2025-04-17 07:03:20 +04:00
7facd0e89e Revert "fix: removed legacy sdk's" 2025-04-17 05:52:07 +04:00
3afe2552d5 documentation: fix grammar 2025-04-16 15:59:18 -07:00
1fdb695240 deconflict merge 2025-04-16 15:40:18 -07:00
d9bd1ac878 improvements: address feedback 2025-04-16 15:28:29 -07:00
ee185cbe47 Merge pull request #3425 from akhilmhdh/feat/aws-cf-invalidate
Added aws cf invalidation on cli deployment pipeline
2025-04-16 17:22:10 -04:00
abc2f3808e Merge pull request #3438 from akhilmhdh/doc/sql-change
Updated doc on db permission change
2025-04-16 17:15:57 -04:00
733440a7b5 update docs for pg permissions 2025-04-16 17:15:11 -04:00
1ef3525917 Merge pull request #3439 from Infisical/daniel/remove-docs
fix: removed legacy sdk's
2025-04-16 16:42:58 -04:00
242e8fd2c6 Merge pull request #3424 from Infisical/misc/allow-org-admins-to-bypass-sso-enforcement
misc: allow org admins to bypass sso enforcement
2025-04-17 00:22:18 +04:00
1137247e69 misc: addressed feedback 2025-04-17 04:00:02 +08:00
=
32b951f6e9 doc: updated doc on db permission change 2025-04-17 01:09:23 +05:30
6f5fe053cd Merge pull request #3422 from Infisical/feat/addProjectDeletionProtection
Add project delete protection
2025-04-16 16:30:46 -03:00
875ec6a24e fix: lowercase workspace for url 2025-04-16 12:08:22 -07:00
fc2e5d18b7 misc: displayed full admin login url 2025-04-17 00:52:24 +08:00
5d0bbce12d misc: added admin login url to tooltip 2025-04-17 00:41:42 +08:00
8c87c40467 misc: only bypass when from admin login 2025-04-17 00:33:07 +08:00
a9dab557d9 misc: correct labels 2025-04-17 00:06:27 +08:00
76c3f1c152 misc: made bypass opt-in 2025-04-16 23:58:20 +08:00
73dea6a0be Merge branch 'main' into feat/addProjectDeletionProtection 2025-04-16 10:00:23 -03:00
357381b0d6 feature: windmill connection and sync 2025-04-15 19:10:13 -07:00
82af77c480 Add hasDeleteProtection to update endpoint 2025-04-15 22:37:53 -03:00
dd0880825b doc: added reference to admin login portal 2025-04-15 19:50:49 +00:00
785173747f misc: introduce admin login portal 2025-04-16 03:28:04 +08:00
=
9a6e27d4be feat: added aws cf invalidation on cli deployment pipeline 2025-04-15 23:41:06 +05:30
d0db5c00e8 misc: allow org admins to bypass sso enforcement 2025-04-16 01:35:39 +08:00
1d8c513da1 Improve invalidateQueries for useToggleDeleteProjectProtection 2025-04-15 08:07:21 -03:00
101c056f43 Add project delete protection 2025-04-14 21:41:46 -03:00
fa030417ef Typo correction in the go-sdk example 2025-04-12 10:15:04 +02:00
=
7dcd3d24aa feat: corrected oidc message 2025-04-11 23:11:23 +05:30
=
3c5c6aeca8 feat: updated error message on update org for saml/oidc enforcement 2025-04-11 23:09:27 +05:30
162 changed files with 3059 additions and 225 deletions

View File

@ -145,3 +145,9 @@ jobs:
INFISICAL_CLI_REPO_SIGNING_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_SIGNING_KEY_ID }}
AWS_ACCESS_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY }}
- name: Invalidate Cloudfront cache
run: aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths '/deb/dists/stable/*'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY }}
CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID }}

View File

@ -104,6 +104,7 @@
"pkijs": "^3.2.4",
"posthog-node": "^3.6.2",
"probot": "^13.3.8",
"re2": "^1.21.4",
"safe-regex": "^2.1.1",
"scim-patch": "^0.8.3",
"scim2-parse-filter": "^0.2.10",
@ -6860,6 +6861,79 @@
"node": ">= 8"
}
},
"node_modules/@npmcli/agent": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz",
"integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==",
"license": "ISC",
"dependencies": {
"agent-base": "^7.1.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.1",
"lru-cache": "^10.0.1",
"socks-proxy-agent": "^8.0.3"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/@npmcli/agent/node_modules/agent-base": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/@npmcli/agent/node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@npmcli/agent/node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@npmcli/agent/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/@npmcli/fs": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz",
"integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==",
"license": "ISC",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/@octokit/auth-app": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.1.1.tgz",
@ -11863,6 +11937,115 @@
"node": ">=8"
}
},
"node_modules/cacache": {
"version": "18.0.4",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz",
"integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==",
"license": "ISC",
"dependencies": {
"@npmcli/fs": "^3.1.0",
"fs-minipass": "^3.0.0",
"glob": "^10.2.2",
"lru-cache": "^10.0.1",
"minipass": "^7.0.3",
"minipass-collect": "^2.0.1",
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.4",
"p-map": "^4.0.0",
"ssri": "^10.0.0",
"tar": "^6.1.11",
"unique-filename": "^3.0.0"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/cacache/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/cacache/node_modules/fs-minipass": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz",
"integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==",
"license": "ISC",
"dependencies": {
"minipass": "^7.0.3"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/cacache/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/cacache/node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/cacache/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/cacache/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/cacache/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/call-bind": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
@ -12842,6 +13025,16 @@
"node": ">= 0.8"
}
},
"node_modules/encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"license": "MIT",
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -12874,6 +13067,21 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"license": "MIT"
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@ -13644,6 +13852,12 @@
"node": ">=0.10.0"
}
},
"node_modules/exponential-backoff": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz",
"integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==",
"license": "Apache-2.0"
},
"node_modules/express": {
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
@ -15221,6 +15435,12 @@
],
"license": "MIT"
},
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
"license": "BSD-2-Clause"
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@ -15403,7 +15623,6 @@
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
"engines": {
"node": ">=0.8.19"
}
@ -15436,6 +15655,16 @@
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true
},
"node_modules/install-artifact-from-github": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/install-artifact-from-github/-/install-artifact-from-github-1.3.5.tgz",
"integrity": "sha512-gZHC7f/cJgXz7MXlHFBxPVMsvIbev1OQN1uKQYKVJDydGNm9oYf9JstbU4Atnh/eSvk41WtEovoRm+8IF686xg==",
"license": "BSD-3-Clause",
"bin": {
"install-from-cache": "bin/install-from-cache.js",
"save-to-github-cache": "bin/save-to-github-cache.js"
}
},
"node_modules/internal-slot": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz",
@ -15518,6 +15747,19 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"license": "MIT",
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/ip-num": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/ip-num/-/ip-num-1.5.1.tgz",
@ -15717,6 +15959,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-lambda": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
"integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
"license": "MIT"
},
"node_modules/is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@ -16860,6 +17108,38 @@
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"node_modules/make-fetch-happen": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz",
"integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==",
"license": "ISC",
"dependencies": {
"@npmcli/agent": "^2.0.0",
"cacache": "^18.0.0",
"http-cache-semantics": "^4.1.1",
"is-lambda": "^1.0.1",
"minipass": "^7.0.2",
"minipass-fetch": "^3.0.0",
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.4",
"negotiator": "^0.6.3",
"proc-log": "^4.2.0",
"promise-retry": "^2.0.1",
"ssri": "^10.0.0"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/make-fetch-happen/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -17033,6 +17313,125 @@
"node": ">=8"
}
},
"node_modules/minipass-collect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz",
"integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==",
"license": "ISC",
"dependencies": {
"minipass": "^7.0.3"
},
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minipass-collect/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minipass-fetch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz",
"integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==",
"license": "MIT",
"dependencies": {
"minipass": "^7.0.3",
"minipass-sized": "^1.0.3",
"minizlib": "^2.1.2"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
},
"optionalDependencies": {
"encoding": "^0.1.13"
}
},
"node_modules/minipass-fetch/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minipass-flush": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
"integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minipass-flush/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minipass-pipeline": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
"integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minipass-pipeline/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minipass-sized": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
"integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minipass-sized/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
@ -17354,6 +17753,12 @@
"node": ">=12"
}
},
"node_modules/nan": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz",
"integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==",
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
@ -17444,6 +17849,30 @@
}
}
},
"node_modules/node-gyp": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz",
"integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==",
"license": "MIT",
"dependencies": {
"env-paths": "^2.2.0",
"exponential-backoff": "^3.1.1",
"glob": "^10.3.10",
"graceful-fs": "^4.2.6",
"make-fetch-happen": "^13.0.0",
"nopt": "^7.0.0",
"proc-log": "^4.1.0",
"semver": "^7.3.5",
"tar": "^6.2.1",
"which": "^4.0.0"
},
"bin": {
"node-gyp": "bin/node-gyp.js"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/node-gyp-build": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.9.0.tgz",
@ -17465,6 +17894,122 @@
"node-gyp-build-optional-packages-test": "build-test.js"
}
},
"node_modules/node-gyp/node_modules/abbrev": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
"integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
"license": "ISC",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/node-gyp/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/node-gyp/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/node-gyp/node_modules/isexe": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
"license": "ISC",
"engines": {
"node": ">=16"
}
},
"node_modules/node-gyp/node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/node-gyp/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/node-gyp/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/node-gyp/node_modules/nopt": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
"integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
"license": "ISC",
"dependencies": {
"abbrev": "^2.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/node-gyp/node_modules/which": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
"license": "ISC",
"dependencies": {
"isexe": "^3.1.1"
},
"bin": {
"node-which": "bin/which.js"
},
"engines": {
"node": "^16.13.0 || >=18.0.0"
}
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
@ -18125,6 +18670,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
"license": "MIT",
"dependencies": {
"aggregate-error": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-queue": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
@ -19202,6 +19762,15 @@
"real-require": "^0.2.0"
}
},
"node_modules/proc-log": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz",
"integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==",
"license": "ISC",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -19230,6 +19799,28 @@
"node": ">=0.4.0"
}
},
"node_modules/promise-retry": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
"license": "MIT",
"dependencies": {
"err-code": "^2.0.2",
"retry": "^0.12.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/promise-retry/node_modules/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/prompt-sync": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz",
@ -19501,6 +20092,18 @@
"node": ">=0.10.0"
}
},
"node_modules/re2": {
"version": "1.21.4",
"resolved": "https://registry.npmjs.org/re2/-/re2-1.21.4.tgz",
"integrity": "sha512-MVIfXWJmsP28mRsSt8HeL750ifb8H5+oF2UDIxGaiJCr8fkMqhLZ7kcX9ADRk2dC8qeGKedB7UVYRfBVpEiLfA==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"install-artifact-from-github": "^1.3.5",
"nan": "^2.20.0",
"node-gyp": "^10.2.0"
}
},
"node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@ -20422,6 +21025,16 @@
"node": ">=8"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/smee-client": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/smee-client/-/smee-client-2.0.0.tgz",
@ -20625,6 +21238,60 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/socks": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
"integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==",
"license": "MIT",
"dependencies": {
"ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks-proxy-agent": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"socks": "^2.8.3"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/socks-proxy-agent/node_modules/agent-base": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/socks-proxy-agent/node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/sonic-boom": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz",
@ -20680,6 +21347,27 @@
"node": ">= 0.6"
}
},
"node_modules/ssri": {
"version": "10.0.6",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz",
"integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==",
"license": "ISC",
"dependencies": {
"minipass": "^7.0.3"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/ssri/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
@ -22482,6 +23170,30 @@
"node": ">=4"
}
},
"node_modules/unique-filename": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz",
"integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==",
"license": "ISC",
"dependencies": {
"unique-slug": "^4.0.0"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/unique-slug": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz",
"integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==",
"license": "ISC",
"dependencies": {
"imurmurhash": "^0.1.4"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/universal-github-app-jwt": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz",

View File

@ -221,6 +221,7 @@
"pkijs": "^3.2.4",
"posthog-node": "^3.6.2",
"probot": "^13.3.8",
"re2": "^1.21.4",
"safe-regex": "^2.1.1",
"scim-patch": "^0.8.3",
"scim2-parse-filter": "^0.2.10",

View File

@ -136,7 +136,7 @@ declare module "fastify" {
rateLimits: RateLimitConfiguration;
// passport data
passportUser: {
isUserCompleted: string;
isUserCompleted: boolean;
providerAuthToken: string;
};
kmipUser: {

View File

@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasCol = await knex.schema.hasColumn(TableName.Project, "hasDeleteProtection");
if (!hasCol) {
await knex.schema.alterTable(TableName.Project, (t) => {
t.boolean("hasDeleteProtection").defaultTo(false);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasCol = await knex.schema.hasColumn(TableName.Project, "hasDeleteProtection");
if (hasCol) {
await knex.schema.alterTable(TableName.Project, (t) => {
t.dropColumn("hasDeleteProtection");
});
}
}

View File

@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.Organization, "bypassOrgAuthEnabled"))) {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.boolean("bypassOrgAuthEnabled").defaultTo(false).notNullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.Organization, "bypassOrgAuthEnabled")) {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.dropColumn("bypassOrgAuthEnabled");
});
}
}

View File

@ -26,7 +26,8 @@ export const OrganizationsSchema = z.object({
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
shouldUseNewPrivilegeSystem: z.boolean().default(true),
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
privilegeUpgradeInitiatedAt: z.date().nullable().optional()
privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
bypassOrgAuthEnabled: z.boolean().default(false)
});
export type TOrganizations = z.infer<typeof OrganizationsSchema>;

View File

@ -26,7 +26,8 @@ export const ProjectsSchema = z.object({
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
description: z.string().nullable().optional(),
type: z.string(),
enforceCapitalization: z.boolean().default(false)
enforceCapitalization: z.boolean().default(false),
hasDeleteProtection: z.boolean().default(true).nullable().optional()
});
export type TProjects = z.infer<typeof ProjectsSchema>;

View File

@ -223,12 +223,18 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
samlConfigId: z.string().trim()
})
},
preValidation: passport.authenticate("saml", {
session: false,
failureFlash: true,
failureRedirect: "/login/provider/error"
// this is due to zod type difference
}) as any,
preValidation: passport.authenticate(
"saml",
{
session: false
},
async (req, res, err, user) => {
if (err) {
throw new BadRequestError({ message: `Saml authentication failed. ${err?.message}`, error: err });
}
req.passportUser = user as { isUserCompleted: boolean; providerAuthToken: string };
}
) as any, // this is due to zod type difference
handler: (req, res) => {
if (req.passportUser.isUserCompleted) {
return res.redirect(

View File

@ -2,6 +2,7 @@ import handlebars from "handlebars";
import ldapjs from "ldapjs";
import ldif from "ldif";
import { customAlphabet } from "nanoid";
import RE2 from "re2";
import { z } from "zod";
import { BadRequestError } from "@app/lib/errors";
@ -194,7 +195,8 @@ export const LdapProvider = (): TDynamicProviderFns => {
const client = await $getClient(providerInputs);
if (providerInputs.credentialType === LdapCredentialType.Static) {
const dnMatch = providerInputs.rotationLdif.match(/^dn:\s*(.+)/m);
const dnRegex = new RE2("^dn:\\s*(.+)", "m");
const dnMatch = dnRegex.exec(providerInputs.rotationLdif);
if (dnMatch) {
const username = dnMatch[1];
@ -238,7 +240,8 @@ export const LdapProvider = (): TDynamicProviderFns => {
const client = await $getClient(providerInputs);
if (providerInputs.credentialType === LdapCredentialType.Static) {
const dnMatch = providerInputs.rotationLdif.match(/^dn:\s*(.+)/m);
const dnRegex = new RE2("^dn:\\s*(.+)", "m");
const dnMatch = dnRegex.exec(providerInputs.rotationLdif);
if (dnMatch) {
const username = dnMatch[1];

View File

@ -1,6 +1,5 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
export type TOidcConfigDALFactory = ReturnType<typeof oidcConfigDALFactory>;
@ -8,22 +7,5 @@ export type TOidcConfigDALFactory = ReturnType<typeof oidcConfigDALFactory>;
export const oidcConfigDALFactory = (db: TDbClient) => {
const oidcCfgOrm = ormify(db, TableName.OidcConfig);
const findEnforceableOidcCfg = async (orgId: string) => {
try {
const oidcCfg = await db
.replicaNode()(TableName.OidcConfig)
.where({
orgId,
isActive: true
})
.whereNotNull("lastUsed")
.first();
return oidcCfg;
} catch (error) {
throw new DatabaseError({ error, name: "Find org by id" });
}
};
return { ...oidcCfgOrm, findEnforceableOidcCfg };
return oidcCfgOrm;
};

View File

@ -3,6 +3,7 @@ import { z } from "zod";
import { TDbClient } from "@app/db";
import {
IdentityProjectMembershipRoleSchema,
OrgMembershipRole,
OrgMembershipsSchema,
TableName,
TProjectRoles,
@ -53,6 +54,7 @@ export const permissionDALFactory = (db: TDbClient) => {
db.ref("slug").withSchema(TableName.OrgRoles).withSchema(TableName.OrgRoles).as("customRoleSlug"),
db.ref("permissions").withSchema(TableName.OrgRoles),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled"),
db.ref("groupId").withSchema("userGroups"),
db.ref("groupOrgId").withSchema("userGroups"),
db.ref("groupName").withSchema("userGroups"),
@ -71,6 +73,7 @@ export const permissionDALFactory = (db: TDbClient) => {
OrgMembershipsSchema.extend({
permissions: z.unknown(),
orgAuthEnforced: z.boolean().optional().nullable(),
bypassOrgAuthEnabled: z.boolean(),
customRoleSlug: z.string().optional().nullable(),
shouldUseNewPrivilegeSystem: z.boolean()
}).parse(el),
@ -571,6 +574,11 @@ export const permissionDALFactory = (db: TDbClient) => {
})
.join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId]))
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
.join(TableName.OrgMembership, (qb) => {
void qb
.on(`${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.andOn(`${TableName.OrgMembership}.orgId`, `${TableName.Organization}.id`);
})
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
void queryBuilder
.on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`)
@ -670,6 +678,8 @@ export const permissionDALFactory = (db: TDbClient) => {
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled"),
db.ref("role").withSchema(TableName.OrgMembership).as("orgRole"),
db.ref("orgId").withSchema(TableName.Project),
db.ref("type").withSchema(TableName.Project).as("projectType"),
db.ref("id").withSchema(TableName.Project).as("projectId"),
@ -683,6 +693,7 @@ export const permissionDALFactory = (db: TDbClient) => {
orgId,
username,
orgAuthEnforced,
orgRole,
membershipId,
groupMembershipId,
membershipCreatedAt,
@ -690,10 +701,12 @@ export const permissionDALFactory = (db: TDbClient) => {
groupMembershipUpdatedAt,
membershipUpdatedAt,
projectType,
shouldUseNewPrivilegeSystem
shouldUseNewPrivilegeSystem,
bypassOrgAuthEnabled
}) => ({
orgId,
orgAuthEnforced,
orgRole: orgRole as OrgMembershipRole,
userId,
projectId,
username,
@ -701,7 +714,8 @@ export const permissionDALFactory = (db: TDbClient) => {
id: membershipId || groupMembershipId,
createdAt: membershipCreatedAt || groupMembershipCreatedAt,
updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt,
shouldUseNewPrivilegeSystem
shouldUseNewPrivilegeSystem,
bypassOrgAuthEnabled
}),
childrenMapper: [
{

View File

@ -2,7 +2,7 @@
import { ForbiddenError, MongoAbility, PureAbility, subject } from "@casl/ability";
import { z } from "zod";
import { TOrganizations } from "@app/db/schemas";
import { OrgMembershipRole, TOrganizations } from "@app/db/schemas";
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
@ -118,11 +118,20 @@ function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
].includes(actorAuthMethod);
}
function validateOrgSSO(actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrganizations["authEnforced"]) {
function validateOrgSSO(
actorAuthMethod: ActorAuthMethod,
isOrgSsoEnforced: TOrganizations["authEnforced"],
isOrgSsoBypassEnabled: TOrganizations["bypassOrgAuthEnabled"],
orgRole: OrgMembershipRole
) {
if (actorAuthMethod === undefined) {
throw new UnauthorizedError({ name: "No auth method defined" });
}
if (isOrgSsoEnforced && isOrgSsoBypassEnabled && orgRole === OrgMembershipRole.Admin) {
return;
}
if (
isOrgSsoEnforced &&
actorAuthMethod !== null &&

View File

@ -139,7 +139,12 @@ export const permissionServiceFactory = ({
throw new ForbiddenRequestError({ name: "You are not logged into this organization" });
}
validateOrgSSO(authMethod, membership.orgAuthEnforced);
validateOrgSSO(
authMethod,
membership.orgAuthEnforced,
membership.bypassOrgAuthEnabled,
membership.role as OrgMembershipRole
);
const finalPolicyRoles = [{ role: membership.role, permissions: membership.permissions }].concat(
membership?.groups?.map(({ role, customRolePermission }) => ({
@ -226,7 +231,12 @@ export const permissionServiceFactory = ({
throw new ForbiddenRequestError({ name: "You are not logged into this organization" });
}
validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced);
validateOrgSSO(
authMethod,
userProjectPermission.orgAuthEnforced,
userProjectPermission.bypassOrgAuthEnabled,
userProjectPermission.orgRole
);
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
throw new BadRequestError({

View File

@ -1,6 +1,5 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
export type TSamlConfigDALFactory = ReturnType<typeof samlConfigDALFactory>;
@ -8,25 +7,5 @@ export type TSamlConfigDALFactory = ReturnType<typeof samlConfigDALFactory>;
export const samlConfigDALFactory = (db: TDbClient) => {
const samlCfgOrm = ormify(db, TableName.SamlConfig);
const findEnforceableSamlCfg = async (orgId: string) => {
try {
const samlCfg = await db
.replicaNode()(TableName.SamlConfig)
.where({
orgId,
isActive: true
})
.whereNotNull("lastUsed")
.first();
return samlCfg;
} catch (error) {
throw new DatabaseError({ error, name: "Find org by id" });
}
};
return {
...samlCfgOrm,
findEnforceableSamlCfg
};
return samlCfgOrm;
};

View File

@ -1,4 +1,5 @@
import { isIP } from "net";
import RE2 from "re2";
import { isFQDN } from "@app/lib/validator/validate-url";
@ -10,7 +11,7 @@ export const isValidUserPattern = (value: string): boolean => {
if (value === "*") return true; // Handle wildcard separately
// Simpler, more specific pattern for usernames
const userRegex = /^[a-z_][a-z0-9_-]*$/i;
const userRegex = new RE2(/^[a-z_][a-z0-9_-]*$/i);
return userRegex.test(value);
};

View File

@ -4,6 +4,7 @@ import { promises as fs } from "fs";
import { Knex } from "knex";
import os from "os";
import path from "path";
import RE2 from "re2";
import { promisify } from "util";
import { TSshCertificateTemplates } from "@app/db/schemas";
@ -156,14 +157,14 @@ export const validateSshCertificatePrincipals = (
});
}
if (/\r|\n|\t|\0/.test(sanitized)) {
if (new RE2(/\r|\n|\t|\0/).test(sanitized)) {
throw new BadRequestError({
message: `Principal '${sanitized}' contains invalid whitespace or control characters.`
});
}
// disallow whitespace anywhere
if (/\s/.test(sanitized)) {
if (new RE2(/\s/).test(sanitized)) {
throw new BadRequestError({
message: `Principal '${sanitized}' cannot contain whitespace.`
});

View File

@ -478,7 +478,8 @@ export const PROJECTS = {
name: "The new name of the project.",
projectDescription: "An optional description label for the project.",
autoCapitalization: "Disable or enable auto-capitalization for the project.",
slug: "An optional slug for the project. (must be unique within the organization)"
slug: "An optional slug for the project. (must be unique within the organization)",
hasDeleteProtection: "Enable or disable delete protection for the project."
},
GET_KEY: {
workspaceId: "The ID of the project to get the key from."
@ -1807,6 +1808,10 @@ export const AppConnections = {
CAMUNDA: {
clientId: "The client ID used to authenticate with Camunda.",
clientSecret: "The client secret used to authenticate with Camunda."
},
WINDMILL: {
instanceUrl: "The Windmill instance URL to connect with (defaults to https://app.windmill.dev).",
accessToken: "The access token to use to connect with Windmill."
}
}
};
@ -1942,6 +1947,10 @@ export const SecretSyncs = {
env: "The ID of the Vercel environment to sync secrets to.",
branch: "The branch to sync preview secrets to.",
teamId: "The ID of the Vercel team to sync secrets to."
},
WINDMILL: {
workspace: "The Windmill workspace to sync secrets to.",
path: "The Windmill workspace path to sync secrets to."
}
}
};

View File

@ -1,12 +1,16 @@
import RE2 from "re2";
type Base64Options = {
urlSafe?: boolean;
padding?: boolean;
};
const base64WithPadding = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/;
const base64WithoutPadding = /^[A-Za-z0-9+/]+$/;
const base64UrlWithPadding = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}==|[A-Za-z0-9_-]{3}=|[A-Za-z0-9_-]{4})$/;
const base64UrlWithoutPadding = /^[A-Za-z0-9_-]+$/;
const base64WithPadding = new RE2(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/);
const base64WithoutPadding = new RE2(/^[A-Za-z0-9+/]+$/);
const base64UrlWithPadding = new RE2(
/^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}==|[A-Za-z0-9_-]{3}=|[A-Za-z0-9_-]{4})$/
);
const base64UrlWithoutPadding = new RE2(/^[A-Za-z0-9_-]+$/);
export const isBase64 = (str: string, options: Base64Options = {}): boolean => {
if (typeof str !== "string") {

View File

@ -1,4 +1,5 @@
import path from "path";
import RE2 from "re2";
// given two paths irrespective of ending with / or not
// this will return true if its equal
@ -15,4 +16,6 @@ export const prefixWithSlash = (str: string) => {
return `/${str}`;
};
export const startsWithVowel = (str: string) => /^[aeiou]/i.test(str);
const vowelRegex = new RE2(/^[aeiou]/i);
export const startsWithVowel = (str: string) => vowelRegex.test(str);

View File

@ -1,3 +1,4 @@
import RE2 from "re2";
import { z } from "zod";
export enum CharacterType {
@ -92,7 +93,7 @@ export const characterValidator = (allowedCharacters: CharacterType[]) => {
const combinedPattern = allowedCharacters.map((char) => patternMap[char]).join("");
// Create a regex that matches only the allowed characters
const regex = new RegExp(`^[${combinedPattern}]+$`);
const regex = new RE2(`^[${combinedPattern}]+$`);
/**
* Validates if the input string contains only the allowed character types

View File

@ -1,6 +1,7 @@
import dns from "node:dns/promises";
import { isIPv4 } from "net";
import RE2 from "re2";
import { getConfig } from "@app/lib/config/env";
@ -80,42 +81,47 @@ export const isFQDN = (str: string, options: FQDNOptions = {}): boolean => {
if (
!opts.allow_numeric_tld &&
!/^([a-z\u00A1-\u00A8\u00AA-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)
!new RE2(/^([a-z\u00A1-\u00A8\u00AA-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}|xn[a-z0-9-]{2,})$/i).test(tld)
) {
return false;
}
// disallow spaces
if (/\s/.test(tld)) {
if (new RE2(/\s/).test(tld)) {
return false;
}
}
// reject numeric TLDs
if (!opts.allow_numeric_tld && /^\d+$/.test(tld)) {
if (!opts.allow_numeric_tld && new RE2(/^\d+$/).test(tld)) {
return false;
}
const partRegex = new RE2(/^[a-z_\u00a1-\uffff0-9-]+$/i);
const fullWidthRegex = new RE2(/[\uff01-\uff5e]/);
const hyphenRegex = new RE2(/^-|-$/);
const underscoreRegex = new RE2(/_/);
return parts.every((part) => {
if (part.length > 63 && !opts.ignore_max_length) {
return false;
}
if (!/^[a-z_\u00a1-\uffff0-9-]+$/i.test(part)) {
if (!partRegex.test(part)) {
return false;
}
// disallow full-width chars
if (/[\uff01-\uff5e]/.test(part)) {
if (fullWidthRegex.test(part)) {
return false;
}
// disallow parts starting or ending with hyphen
if (/^-|-$/.test(part)) {
if (hyphenRegex.test(part)) {
return false;
}
if (!opts.allow_underscores && /_/.test(part)) {
if (!opts.allow_underscores && underscoreRegex.test(part)) {
return false;
}

View File

@ -1,3 +1,4 @@
import RE2 from "re2";
import { z, ZodTypeAny } from "zod";
// this is a patched zod string to remove empty string to undefined
@ -11,3 +12,8 @@ export const zpStr = <T extends ZodTypeAny>(schema: T, opt: { stripNull: boolean
export const zodBuffer = z.custom<Buffer>((data) => Buffer.isBuffer(data) || data instanceof Uint8Array, {
message: "Expected binary data (Buffer Or Uint8Array)"
});
export const re2Validator = (pattern: string | RegExp) => {
const re2Pattern = new RE2(pattern);
return (value: string) => re2Pattern.test(value);
};

View File

@ -260,7 +260,8 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({
upgradeStatus: true,
pitVersionLimit: true,
kmsCertificateKeyId: true,
auditLogsRetentionDays: true
auditLogsRetentionDays: true,
hasDeleteProtection: true
});
export const SanitizedTagSchema = SecretTagsSchema.pick({

View File

@ -37,6 +37,10 @@ import {
TerraformCloudConnectionListItemSchema
} from "@app/services/app-connection/terraform-cloud";
import { SanitizedVercelConnectionSchema, VercelConnectionListItemSchema } from "@app/services/app-connection/vercel";
import {
SanitizedWindmillConnectionSchema,
WindmillConnectionListItemSchema
} from "@app/services/app-connection/windmill";
import { AuthMode } from "@app/services/auth/auth-type";
// can't use discriminated due to multiple schemas for certain apps
@ -53,6 +57,7 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedPostgresConnectionSchema.options,
...SanitizedMsSqlConnectionSchema.options,
...SanitizedCamundaConnectionSchema.options,
...SanitizedWindmillConnectionSchema.options,
...SanitizedAuth0ConnectionSchema.options
]);
@ -69,6 +74,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
PostgresConnectionListItemSchema,
MsSqlConnectionListItemSchema,
CamundaConnectionListItemSchema,
WindmillConnectionListItemSchema,
Auth0ConnectionListItemSchema
]);

View File

@ -13,6 +13,7 @@ import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
import { registerVercelConnectionRouter } from "./vercel-connection-router";
import { registerWindmillConnectionRouter } from "./windmill-connection-router";
export * from "./app-connection-router";
@ -30,5 +31,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Postgres]: registerPostgresConnectionRouter,
[AppConnection.MsSql]: registerMsSqlConnectionRouter,
[AppConnection.Camunda]: registerCamundaConnectionRouter,
[AppConnection.Windmill]: registerWindmillConnectionRouter,
[AppConnection.Auth0]: registerAuth0ConnectionRouter
};

View File

@ -0,0 +1,53 @@
import z from "zod";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateWindmillConnectionSchema,
SanitizedWindmillConnectionSchema,
UpdateWindmillConnectionSchema
} from "@app/services/app-connection/windmill";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerWindmillConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.Windmill,
server,
sanitizedResponseSchema: SanitizedWindmillConnectionSchema,
createSchema: CreateWindmillConnectionSchema,
updateSchema: UpdateWindmillConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/workspaces`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z
.object({
id: z.string(),
name: z.string()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const workspaces = await server.services.appConnection.windmill.listWorkspaces(connectionId, req.permission);
return workspaces;
}
});
};

View File

@ -31,7 +31,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
200: z.object({
organizations: sanitizedOrganizationSchema
.extend({
orgAuthMethod: z.string()
orgAuthMethod: z.string(),
userRole: z.string()
})
.array()
})
@ -259,7 +260,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(),
enforceMfa: z.boolean().optional(),
selectedMfaMethod: z.nativeEnum(MfaMethod).optional(),
allowSecretSharingOutsideOrganization: z.boolean().optional()
allowSecretSharingOutsideOrganization: z.boolean().optional(),
bypassOrgAuthEnabled: z.boolean().optional()
}),
response: {
200: z.object({

View File

@ -15,6 +15,7 @@ import {
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECTS } from "@app/lib/api-docs";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { re2Validator } from "@app/lib/zod";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
@ -312,14 +313,15 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
.optional()
.describe(PROJECTS.UPDATE.projectDescription),
autoCapitalization: z.boolean().optional().describe(PROJECTS.UPDATE.autoCapitalization),
hasDeleteProtection: z.boolean().optional().describe(PROJECTS.UPDATE.hasDeleteProtection),
slug: z
.string()
.trim()
.regex(
/^[a-z0-9]+(?:[_-][a-z0-9]+)*$/,
"Project slug can only contain lowercase letters and numbers, with optional single hyphens (-) or underscores (_) between words. Cannot start or end with a hyphen or underscore."
)
.max(64, { message: "Slug must be 64 characters or fewer" })
.refine(re2Validator(/^[a-z0-9]+(?:[_-][a-z0-9]+)*$/), {
message:
"Project slug can only contain lowercase letters and numbers, with optional single hyphens (-) or underscores (_) between words. Cannot start or end with a hyphen or underscore."
})
.optional()
.describe(PROJECTS.UPDATE.slug)
}),
@ -340,6 +342,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
name: req.body.name,
description: req.body.description,
autoCapitalization: req.body.autoCapitalization,
hasDeleteProtection: req.body.hasDeleteProtection,
slug: req.body.slug
},
actorAuthMethod: req.permission.authMethod,
@ -390,6 +393,43 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "POST",
url: "/:workspaceId/delete-protection",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
hasDeleteProtection: z.boolean()
}),
response: {
200: z.object({
message: z.string(),
workspace: SanitizedProjectSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const workspace = await server.services.project.toggleDeleteProtection({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
hasDeleteProtection: req.body.hasDeleteProtection
});
return {
message: "Successfully changed workspace settings",
workspace
};
}
});
server.route({
method: "PUT",
url: "/:workspaceSlug/version-limit",

View File

@ -11,6 +11,7 @@ import { registerGitHubSyncRouter } from "./github-sync-router";
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
import { registerVercelSyncRouter } from "./vercel-sync-router";
import { registerWindmillSyncRouter } from "./windmill-sync-router";
export * from "./secret-sync-router";
@ -25,5 +26,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
[SecretSync.Humanitec]: registerHumanitecSyncRouter,
[SecretSync.TerraformCloud]: registerTerraformCloudSyncRouter,
[SecretSync.Camunda]: registerCamundaSyncRouter,
[SecretSync.Vercel]: registerVercelSyncRouter
[SecretSync.Vercel]: registerVercelSyncRouter,
[SecretSync.Windmill]: registerWindmillSyncRouter
};

View File

@ -25,6 +25,7 @@ import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel";
import { WindmillSyncListItemSchema, WindmillSyncSchema } from "@app/services/secret-sync/windmill";
const SecretSyncSchema = z.discriminatedUnion("destination", [
AwsParameterStoreSyncSchema,
@ -37,7 +38,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
HumanitecSyncSchema,
TerraformCloudSyncSchema,
CamundaSyncSchema,
VercelSyncSchema
VercelSyncSchema,
WindmillSyncSchema
]);
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
@ -51,7 +53,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
HumanitecSyncListItemSchema,
TerraformCloudSyncListItemSchema,
CamundaSyncListItemSchema,
VercelSyncListItemSchema
VercelSyncListItemSchema,
WindmillSyncListItemSchema
]);
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {

View File

@ -0,0 +1,17 @@
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
CreateWindmillSyncSchema,
UpdateWindmillSyncSchema,
WindmillSyncSchema
} from "@app/services/secret-sync/windmill";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerWindmillSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.Windmill,
server,
responseSchema: WindmillSyncSchema,
createSchema: CreateWindmillSyncSchema,
updateSchema: UpdateWindmillSyncSchema
});

View File

@ -303,7 +303,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
body: z.object({
name: z.string().trim().optional().describe(PROJECTS.UPDATE.name),
description: z.string().trim().optional().describe(PROJECTS.UPDATE.projectDescription),
autoCapitalization: z.boolean().optional().describe(PROJECTS.UPDATE.autoCapitalization)
autoCapitalization: z.boolean().optional().describe(PROJECTS.UPDATE.autoCapitalization),
hasDeleteProtection: z.boolean().optional().describe(PROJECTS.UPDATE.hasDeleteProtection)
}),
response: {
200: SanitizedProjectSchema
@ -321,7 +322,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
update: {
name: req.body.name,
description: req.body.description,
autoCapitalization: req.body.autoCapitalization
autoCapitalization: req.body.autoCapitalization,
hasDeleteProtection: req.body.hasDeleteProtection
},
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,

View File

@ -11,6 +11,7 @@ export enum AppConnection {
Postgres = "postgres",
MsSql = "mssql",
Camunda = "camunda",
Windmill = "windmill",
Auth0 = "auth0"
}

View File

@ -50,6 +50,11 @@ import {
} from "./terraform-cloud";
import { VercelConnectionMethod } from "./vercel";
import { getVercelConnectionListItem, validateVercelConnectionCredentials } from "./vercel/vercel-connection-fns";
import {
getWindmillConnectionListItem,
validateWindmillConnectionCredentials,
WindmillConnectionMethod
} from "./windmill";
export const listAppConnectionOptions = () => {
return [
@ -65,6 +70,7 @@ export const listAppConnectionOptions = () => {
getPostgresConnectionListItem(),
getMsSqlConnectionListItem(),
getCamundaConnectionListItem(),
getWindmillConnectionListItem(),
getAuth0ConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name));
};
@ -128,7 +134,8 @@ export const validateAppConnectionCredentials = async (
[AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator
};
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
@ -159,6 +166,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case PostgresConnectionMethod.UsernameAndPassword:
case MsSqlConnectionMethod.UsernameAndPassword:
return "Username & Password";
case WindmillConnectionMethod.AccessToken:
return "Access Token";
case Auth0ConnectionMethod.ClientCredentials:
return "Client Credentials";
default:
@ -204,5 +213,6 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
[AppConnection.Auth0]: platformManagedCredentialsNotSupported
};

View File

@ -13,5 +13,6 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.Postgres]: "PostgreSQL",
[AppConnection.MsSql]: "Microsoft SQL Server",
[AppConnection.Camunda]: "Camunda",
[AppConnection.Windmill]: "Windmill",
[AppConnection.Auth0]: "Auth0"
};

View File

@ -49,6 +49,8 @@ import { ValidateTerraformCloudConnectionCredentialsSchema } from "./terraform-c
import { terraformCloudConnectionService } from "./terraform-cloud/terraform-cloud-connection-service";
import { ValidateVercelConnectionCredentialsSchema } from "./vercel";
import { vercelConnectionService } from "./vercel/vercel-connection-service";
import { ValidateWindmillConnectionCredentialsSchema } from "./windmill";
import { windmillConnectionService } from "./windmill/windmill-connection-service";
export type TAppConnectionServiceFactoryDep = {
appConnectionDAL: TAppConnectionDALFactory;
@ -71,6 +73,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema
};
@ -446,6 +449,7 @@ export const appConnectionServiceFactory = ({
terraformCloud: terraformCloudConnectionService(connectAppConnectionById),
camunda: camundaConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
vercel: vercelConnectionService(connectAppConnectionById),
windmill: windmillConnectionService(connectAppConnectionById),
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService)
};
};

View File

@ -75,6 +75,12 @@ import {
TVercelConnectionConfig,
TVercelConnectionInput
} from "./vercel";
import {
TValidateWindmillConnectionCredentialsSchema,
TWindmillConnection,
TWindmillConnectionConfig,
TWindmillConnectionInput
} from "./windmill";
export type TAppConnection = { id: string } & (
| TAwsConnection
@ -89,6 +95,7 @@ export type TAppConnection = { id: string } & (
| TPostgresConnection
| TMsSqlConnection
| TCamundaConnection
| TWindmillConnection
| TAuth0Connection
);
@ -109,6 +116,7 @@ export type TAppConnectionInput = { id: string } & (
| TPostgresConnectionInput
| TMsSqlConnectionInput
| TCamundaConnectionInput
| TWindmillConnectionInput
| TAuth0ConnectionInput
);
@ -132,9 +140,10 @@ export type TAppConnectionConfig =
| TDatabricksConnectionConfig
| THumanitecConnectionConfig
| TTerraformCloudConnectionConfig
| TVercelConnectionConfig
| TSqlConnectionConfig
| TCamundaConnectionConfig
| TVercelConnectionConfig
| TWindmillConnectionConfig
| TAuth0ConnectionConfig;
export type TValidateAppConnectionCredentialsSchema =
@ -148,8 +157,9 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidatePostgresConnectionCredentialsSchema
| TValidateMsSqlConnectionCredentialsSchema
| TValidateCamundaConnectionCredentialsSchema
| TValidateVercelConnectionCredentialsSchema
| TValidateTerraformCloudConnectionCredentialsSchema
| TValidateVercelConnectionCredentialsSchema
| TValidateWindmillConnectionCredentialsSchema
| TValidateAuth0ConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = {

View File

@ -44,7 +44,7 @@ export const validateTerraformCloudConnectionCredentials = async (config: TTerra
});
}
throw new BadRequestError({
message: "Unable to validate connection - verify credentials"
message: "Unable to validate connection: verify credentials"
});
}

View File

@ -1,5 +1,5 @@
/* eslint-disable no-await-in-loop */
import { AxiosError, AxiosResponse } from "axios";
import { AxiosError } from "axios";
import { request } from "@app/lib/config/request";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
@ -27,10 +27,8 @@ export const getVercelConnectionListItem = () => {
export const validateVercelConnectionCredentials = async (config: TVercelConnectionConfig) => {
const { credentials: inputCredentials } = config;
let response: AxiosResponse<VercelApp[]> | null = null;
try {
response = await request.get<VercelApp[]>(`${IntegrationUrls.VERCEL_API_URL}/v9/projects`, {
await request.get(`${IntegrationUrls.VERCEL_API_URL}/v2/user`, {
headers: {
Authorization: `Bearer ${inputCredentials.apiToken}`
}
@ -38,17 +36,14 @@ export const validateVercelConnectionCredentials = async (config: TVercelConnect
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
message: `Failed to validate credentials: ${
error.response?.data ? JSON.stringify(error.response?.data) : error.message || "Unknown error"
}`
});
}
throw new BadRequestError({
message: "Unable to validate connection - verify credentials"
});
}
if (!response?.data) {
throw new InternalServerError({
message: "Failed to get organizations: Response was empty"
message: `Unable to validate connection: ${(error as Error).message || "Verify credentials"}`
});
}

View File

@ -0,0 +1,4 @@
export * from "./windmill-connection-enums";
export * from "./windmill-connection-fns";
export * from "./windmill-connection-schemas";
export * from "./windmill-connection-types";

View File

@ -0,0 +1,3 @@
export enum WindmillConnectionMethod {
AccessToken = "access-token"
}

View File

@ -0,0 +1,65 @@
import { AxiosError } from "axios";
import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { WindmillConnectionMethod } from "./windmill-connection-enums";
import { TWindmillConnection, TWindmillConnectionConfig, TWindmillWorkspace } from "./windmill-connection-types";
export const getWindmillInstanceUrl = async (config: TWindmillConnectionConfig) => {
const instanceUrl = config.credentials.instanceUrl
? removeTrailingSlash(config.credentials.instanceUrl)
: "https://app.windmill.dev";
await blockLocalAndPrivateIpAddresses(instanceUrl);
return instanceUrl;
};
export const getWindmillConnectionListItem = () => {
return {
name: "Windmill" as const,
app: AppConnection.Windmill as const,
methods: Object.values(WindmillConnectionMethod) as [WindmillConnectionMethod.AccessToken]
};
};
export const validateWindmillConnectionCredentials = async (config: TWindmillConnectionConfig) => {
const instanceUrl = await getWindmillInstanceUrl(config);
const { accessToken } = config.credentials;
try {
await request.get(`${instanceUrl}/api/workspaces/list`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to validate connection: verify credentials"
});
}
return config.credentials;
};
export const listWindmillWorkspaces = async (appConnection: TWindmillConnection) => {
const instanceUrl = await getWindmillInstanceUrl(appConnection);
const { accessToken } = appConnection.credentials;
const resp = await request.get<TWindmillWorkspace[]>(`${instanceUrl}/api/workspaces/list`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
return resp.data.filter((workspace) => !workspace.deleted);
};

View File

@ -0,0 +1,70 @@
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { WindmillConnectionMethod } from "./windmill-connection-enums";
export const WindmillConnectionAccessTokenCredentialsSchema = z.object({
accessToken: z
.string()
.trim()
.min(1, "Access Token required")
.describe(AppConnections.CREDENTIALS.WINDMILL.accessToken),
instanceUrl: z
.string()
.trim()
.url("Invalid Instance URL")
.optional()
.describe(AppConnections.CREDENTIALS.WINDMILL.instanceUrl)
});
const BaseWindmillConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Windmill) });
export const WindmillConnectionSchema = BaseWindmillConnectionSchema.extend({
method: z.literal(WindmillConnectionMethod.AccessToken),
credentials: WindmillConnectionAccessTokenCredentialsSchema
});
export const SanitizedWindmillConnectionSchema = z.discriminatedUnion("method", [
BaseWindmillConnectionSchema.extend({
method: z.literal(WindmillConnectionMethod.AccessToken),
credentials: WindmillConnectionAccessTokenCredentialsSchema.pick({
instanceUrl: true
})
})
]);
export const ValidateWindmillConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(WindmillConnectionMethod.AccessToken)
.describe(AppConnections.CREATE(AppConnection.Windmill).method),
credentials: WindmillConnectionAccessTokenCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.Windmill).credentials
)
})
]);
export const CreateWindmillConnectionSchema = ValidateWindmillConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Windmill)
);
export const UpdateWindmillConnectionSchema = z
.object({
credentials: WindmillConnectionAccessTokenCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.Windmill).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Windmill));
export const WindmillConnectionListItemSchema = z.object({
name: z.literal("Windmill"),
app: z.literal(AppConnection.Windmill),
methods: z.nativeEnum(WindmillConnectionMethod).array()
});

View File

@ -0,0 +1,28 @@
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listWindmillWorkspaces } from "./windmill-connection-fns";
import { TWindmillConnection } from "./windmill-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TWindmillConnection>;
export const windmillConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listWorkspaces = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Windmill, connectionId, actor);
try {
const workspaces = await listWindmillWorkspaces(appConnection);
return workspaces;
} catch (error) {
return [];
}
};
return {
listWorkspaces
};
};

View File

@ -0,0 +1,27 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateWindmillConnectionSchema,
ValidateWindmillConnectionCredentialsSchema,
WindmillConnectionSchema
} from "./windmill-connection-schemas";
export type TWindmillConnection = z.infer<typeof WindmillConnectionSchema>;
export type TWindmillConnectionInput = z.infer<typeof CreateWindmillConnectionSchema> & {
app: AppConnection.Windmill;
};
export type TValidateWindmillConnectionCredentialsSchema = typeof ValidateWindmillConnectionCredentialsSchema;
export type TWindmillConnectionConfig = DiscriminativePick<
TWindmillConnectionInput,
"method" | "app" | "credentials"
> & {
orgId: string;
};
export type TWindmillWorkspace = { id: string; name: string; deleted: boolean };

View File

@ -2,7 +2,7 @@ import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { Knex } from "knex";
import { TUsers, UserDeviceSchema } from "@app/db/schemas";
import { OrgMembershipRole, TUsers, UserDeviceSchema } from "@app/db/schemas";
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
@ -174,20 +174,25 @@ export const authLoginServiceFactory = ({
const userEnc = await userDAL.findUserEncKeyByUsername({
username: email
});
const serverCfg = await getServerCfg();
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
throw new Error("Failed to find user");
}
if (
serverCfg.enabledLoginMethods &&
!serverCfg.enabledLoginMethods.includes(LoginMethod.EMAIL) &&
!providerAuthToken
) {
throw new BadRequestError({
message: "Login with email is disabled by administrator."
});
}
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
throw new Error("Failed to find user");
// bypass server configuration when user is an organization admin - this is to prevent lockout
const userOrgs = await orgDAL.findAllOrgsByUserId(userEnc.userId);
if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) {
throw new BadRequestError({
message: "Login with email is disabled by administrator."
});
}
}
if (!userEnc.authMethods?.includes(AuthMethod.EMAIL)) {
@ -573,28 +578,40 @@ export const authLoginServiceFactory = ({
switch (authMethod) {
case AuthMethod.GITHUB: {
if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GITHUB)) {
throw new BadRequestError({
message: "Login with Github is disabled by administrator.",
name: "Oauth 2 login"
});
// bypass server configuration when user is an organization admin - this is to prevent lockout
const userOrgs = await orgDAL.findAllOrgsByUserId(user.id);
if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) {
throw new BadRequestError({
message: "Login with Github is disabled by administrator.",
name: "Oauth 2 login"
});
}
}
break;
}
case AuthMethod.GOOGLE: {
if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GOOGLE)) {
throw new BadRequestError({
message: "Login with Google is disabled by administrator.",
name: "Oauth 2 login"
});
// bypass server configuration when user is an organization admin - this is to prevent lockout
const userOrgs = await orgDAL.findAllOrgsByUserId(user.id);
if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) {
throw new BadRequestError({
message: "Login with Google is disabled by administrator.",
name: "Oauth 2 login"
});
}
}
break;
}
case AuthMethod.GITLAB: {
if (!serverCfg.enabledLoginMethods.includes(LoginMethod.GITLAB)) {
throw new BadRequestError({
message: "Login with Gitlab is disabled by administrator.",
name: "Oauth 2 login"
});
// bypass server configuration when user is an organization admin - this is to prevent lockout
const userOrgs = await orgDAL.findAllOrgsByUserId(user.id);
if (!userOrgs.some((org) => org.userRole === OrgMembershipRole.Admin)) {
throw new BadRequestError({
message: "Login with Gitlab is disabled by administrator.",
name: "Oauth 2 login"
});
}
}
break;
}

View File

@ -1,3 +1,5 @@
import RE2 from "re2";
import { TCertificateTemplates } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors";
import { ms } from "@app/lib/ms";
@ -12,7 +14,7 @@ export const validateCertificateDetailsAgainstTemplate = (
template: TCertificateTemplates
) => {
// these are validated in router using validateTemplateRegexField
const commonNameRegex = new RegExp(template.commonName);
const commonNameRegex = new RE2(template.commonName);
if (!commonNameRegex.test(cert.commonName)) {
throw new BadRequestError({
message: "Invalid common name based on template policy"
@ -25,7 +27,7 @@ export const validateCertificateDetailsAgainstTemplate = (
});
}
const subjectAlternativeNameRegex = new RegExp(template.subjectAlternativeName);
const subjectAlternativeNameRegex = new RE2(template.subjectAlternativeName);
cert.altNames.forEach((altName) => {
if (!subjectAlternativeNameRegex.test(altName)) {
throw new BadRequestError({

View File

@ -2,6 +2,7 @@
import { ForbiddenError } from "@casl/ability";
import axios from "axios";
import jwt from "jsonwebtoken";
import RE2 from "re2";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@ -125,7 +126,7 @@ export const identityAwsAuthServiceFactory = ({
// convert wildcard ARN to a regular expression: "arn:aws:iam::123456789012:*" -> "^arn:aws:iam::123456789012:.*$"
// considers exact matches + wildcard matches
// heavily validated in router
const regex = new RegExp(`^${principalArn.replaceAll("*", ".*")}$`);
const regex = new RE2(`^${principalArn.replaceAll("*", ".*")}$`);
return regex.test(extractPrincipalArn(Arn));
});

View File

@ -1,9 +1,10 @@
import RE2 from "re2";
import safe from "safe-regex";
import { z } from "zod";
const twelveDigitRegex = /^\d{12}$/;
const twelveDigitRegex = new RE2(/^\d{12}$/);
// akhilmhdh: change this to a normal function later. Checked no redosable at the moment
const arnRegex = /^arn:aws:iam::\d{12}:(user\/[a-zA-Z0-9_.@+*/-]+|role\/[a-zA-Z0-9_.@+*/-]+|\*)$/;
const arnRegex = new RE2(/^arn:aws:iam::\d{12}:(user\/[a-zA-Z0-9_.@+*/-]+|role\/[a-zA-Z0-9_.@+*/-]+|\*)$/);
export const validateAccountIds = z
.string()

View File

@ -27,6 +27,7 @@ import { randomUUID } from "crypto";
import https from "https";
import sodium from "libsodium-wrappers";
import isEqual from "lodash.isequal";
import RE2 from "re2";
import { z } from "zod";
import { SecretType, TIntegrationAuths, TIntegrations } from "@app/db/schemas";
@ -4151,7 +4152,7 @@ const syncSecretsWindmill = async ({
);
// eslint-disable-next-line
const pattern = new RegExp("^(u/|f/)[a-zA-Z0-9_-]+/([a-zA-Z0-9_-]+/)*[a-zA-Z0-9_-]*[^/]$");
const pattern = new RE2("^(u/|f/)[a-zA-Z0-9_-]+/([a-zA-Z0-9_-]+/)*[a-zA-Z0-9_-]*[^/]$");
for await (const key of Object.keys(secrets)) {
if ((key.startsWith("u/") || key.startsWith("f/")) && pattern.test(key)) {
if (!(key in res)) {

View File

@ -96,7 +96,9 @@ export const orgDALFactory = (db: TDbClient) => {
};
// special query
const findAllOrgsByUserId = async (userId: string): Promise<(TOrganizations & { orgAuthMethod: string })[]> => {
const findAllOrgsByUserId = async (
userId: string
): Promise<(TOrganizations & { orgAuthMethod: string; userRole: string })[]> => {
try {
const org = (await db
.replicaNode()(TableName.OrgMembership)
@ -117,6 +119,7 @@ export const orgDALFactory = (db: TDbClient) => {
);
})
.select(selectAllTableCols(TableName.Organization))
.select(db.ref("role").withSchema(TableName.OrgMembership).as("userRole"))
.select(
db.raw(`
CASE
@ -125,7 +128,7 @@ export const orgDALFactory = (db: TDbClient) => {
ELSE ''
END as "orgAuthMethod"
`)
)) as (TOrganizations & { orgAuthMethod: string })[];
)) as (TOrganizations & { orgAuthMethod: string; userRole: string })[];
return org;
} catch (error) {

View File

@ -16,5 +16,6 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({
allowSecretSharingOutsideOrganization: true,
shouldUseNewPrivilegeSystem: true,
privilegeUpgradeInitiatedByUsername: true,
privilegeUpgradeInitiatedAt: true
privilegeUpgradeInitiatedAt: true,
bypassOrgAuthEnabled: true
});

View File

@ -110,8 +110,8 @@ type TOrgServiceFactoryDep = {
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "insertMany" | "findLatestProjectKey" | "create">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOne" | "findById">;
incidentContactDAL: TIncidentContactsDALFactory;
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">;
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne" | "findEnforceableOidcCfg">;
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne">;
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne">;
smtpService: TSmtpService;
tokenService: TAuthTokenServiceFactory;
permissionService: TPermissionServiceFactory;
@ -349,7 +349,8 @@ export const orgServiceFactory = ({
defaultMembershipRoleSlug,
enforceMfa,
selectedMfaMethod,
allowSecretSharingOutsideOrganization
allowSecretSharingOutsideOrganization,
bypassOrgAuthEnabled
}
}: TUpdateOrgDTO) => {
const appCfg = getConfig();
@ -402,13 +403,33 @@ export const orgServiceFactory = ({
}
if (authEnforced) {
const samlCfg = await samlConfigDAL.findEnforceableSamlCfg(orgId);
const oidcCfg = await oidcConfigDAL.findEnforceableOidcCfg(orgId);
const samlCfg = await samlConfigDAL.findOne({
orgId,
isActive: true
});
const oidcCfg = await oidcConfigDAL.findOne({
orgId,
isActive: true
});
if (!samlCfg && !oidcCfg)
throw new NotFoundError({
message: `SAML or OIDC configuration for organization with ID '${orgId}' not found`
});
if (samlCfg && !samlCfg.lastUsed) {
throw new BadRequestError({
message:
"To apply the new SAML auth enforcement, please log in via SAML at least once. This step is required to enforce SAML-based authentication."
});
}
if (oidcCfg && !oidcCfg.lastUsed) {
throw new BadRequestError({
message:
"To apply the new OIDC auth enforcement, please log in via OIDC at least once. This step is required to enforce OIDC-based authentication."
});
}
}
let defaultMembershipRole: string | undefined;
@ -429,7 +450,8 @@ export const orgServiceFactory = ({
defaultMembershipRole,
enforceMfa,
selectedMfaMethod,
allowSecretSharingOutsideOrganization
allowSecretSharingOutsideOrganization,
bypassOrgAuthEnabled
});
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
return org;

View File

@ -73,6 +73,7 @@ export type TUpdateOrgDTO = {
enforceMfa: boolean;
selectedMfaMethod: MfaMethod;
allowSecretSharingOutsideOrganization: boolean;
bypassOrgAuthEnabled: boolean;
}>;
} & TOrgPermission;

View File

@ -86,6 +86,7 @@ import {
TProjectAccessRequestDTO,
TSearchProjectsDTO,
TToggleProjectAutoCapitalizationDTO,
TToggleProjectDeleteProtectionDTO,
TUpdateAuditLogsRetentionDTO,
TUpdateProjectDTO,
TUpdateProjectKmsDTO,
@ -482,6 +483,12 @@ export const projectServiceFactory = ({
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
if (project.hasDeleteProtection) {
throw new ForbiddenRequestError({
message: "Project delete protection is enabled"
});
}
const deletedProject = await projectDAL.transaction(async (tx) => {
// delete these so that project custom roles can be deleted in cascade effect
// direct deletion of project without these will cause fk error
@ -616,6 +623,7 @@ export const projectServiceFactory = ({
description: update.description,
autoCapitalization: update.autoCapitalization,
enforceCapitalization: update.autoCapitalization,
hasDeleteProtection: update.hasDeleteProtection,
slug: update.slug
});
@ -648,6 +656,29 @@ export const projectServiceFactory = ({
return updatedProject;
};
const toggleDeleteProtection = async ({
projectId,
actor,
actorId,
actorOrgId,
actorAuthMethod,
hasDeleteProtection
}: TToggleProjectDeleteProtectionDTO) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
const updatedProject = await projectDAL.updateById(projectId, { hasDeleteProtection });
return updatedProject;
};
const updateVersionLimit = async ({
actor,
actorId,
@ -1499,6 +1530,7 @@ export const projectServiceFactory = ({
getProjectUpgradeStatus,
getAProject,
toggleAutoCapitalization,
toggleDeleteProtection,
updateName,
upgradeProject,
listProjectCas,

View File

@ -66,6 +66,10 @@ export type TToggleProjectAutoCapitalizationDTO = {
autoCapitalization: boolean;
} & TProjectPermission;
export type TToggleProjectDeleteProtectionDTO = {
hasDeleteProtection: boolean;
} & TProjectPermission;
export type TUpdateProjectVersionLimitDTO = {
pitVersionLimit: number;
workspaceSlug: string;
@ -86,6 +90,7 @@ export type TUpdateProjectDTO = {
name?: string;
description?: string;
autoCapitalization?: boolean;
hasDeleteProtection?: boolean;
slug?: string;
};
} & Omit<TProjectPermission, "projectId">;

View File

@ -9,7 +9,8 @@ export enum SecretSync {
Humanitec = "humanitec",
TerraformCloud = "terraform-cloud",
Camunda = "camunda",
Vercel = "vercel"
Vercel = "vercel",
Windmill = "windmill"
}
export enum SecretSyncInitialSyncBehavior {

View File

@ -29,6 +29,7 @@ import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud";
import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel";
import { WINDMILL_SYNC_LIST_OPTION, WindmillSyncFns } from "./windmill";
const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.AWSParameterStore]: AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
@ -41,7 +42,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.Humanitec]: HUMANITEC_SYNC_LIST_OPTION,
[SecretSync.TerraformCloud]: TERRAFORM_CLOUD_SYNC_LIST_OPTION,
[SecretSync.Camunda]: CAMUNDA_SYNC_LIST_OPTION,
[SecretSync.Vercel]: VERCEL_SYNC_LIST_OPTION
[SecretSync.Vercel]: VERCEL_SYNC_LIST_OPTION,
[SecretSync.Windmill]: WINDMILL_SYNC_LIST_OPTION
};
export const listSecretSyncOptions = () => {
@ -136,6 +138,8 @@ export const SecretSyncFns = {
}).syncSecrets(secretSync, secretMap);
case SecretSync.Vercel:
return VercelSyncFns.syncSecrets(secretSync, secretMap);
case SecretSync.Windmill:
return WindmillSyncFns.syncSecrets(secretSync, secretMap);
default:
throw new Error(
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@ -192,6 +196,9 @@ export const SecretSyncFns = {
case SecretSync.Vercel:
secretMap = await VercelSyncFns.getSecrets(secretSync);
break;
case SecretSync.Windmill:
secretMap = await WindmillSyncFns.getSecrets(secretSync);
break;
default:
throw new Error(
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@ -243,6 +250,8 @@ export const SecretSyncFns = {
}).removeSecrets(secretSync, secretMap);
case SecretSync.Vercel:
return VercelSyncFns.removeSecrets(secretSync, secretMap);
case SecretSync.Windmill:
return WindmillSyncFns.removeSecrets(secretSync, secretMap);
default:
throw new Error(
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`

View File

@ -12,7 +12,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.Humanitec]: "Humanitec",
[SecretSync.TerraformCloud]: "Terraform Cloud",
[SecretSync.Camunda]: "Camunda",
[SecretSync.Vercel]: "Vercel"
[SecretSync.Vercel]: "Vercel",
[SecretSync.Windmill]: "Windmill"
};
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
@ -26,5 +27,6 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.Humanitec]: AppConnection.Humanitec,
[SecretSync.TerraformCloud]: AppConnection.TerraformCloud,
[SecretSync.Camunda]: AppConnection.Camunda,
[SecretSync.Vercel]: AppConnection.Vercel
[SecretSync.Vercel]: AppConnection.Vercel,
[SecretSync.Windmill]: AppConnection.Windmill
};

View File

@ -76,6 +76,7 @@ type TSecretSyncQueueFactoryDep = {
| "findBySecretKeys"
| "bulkUpdate"
| "deleteMany"
| "invalidateSecretCacheByProjectId"
>;
secretImportDAL: Pick<TSecretImportDALFactory, "find" | "findByFolderIds">;
secretSyncDAL: Pick<TSecretSyncDALFactory, "findById" | "find" | "updateById" | "deleteById">;
@ -382,6 +383,9 @@ export const secretSyncQueueFactory = ({
});
}
if (secretsToUpdate.length || secretsToCreate.length)
await secretV2BridgeDAL.invalidateSecretCacheByProjectId(projectId);
return importedSecretMap;
};

View File

@ -29,6 +29,12 @@ import {
} from "@app/services/secret-sync/github";
import { TSecretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal";
import { SecretSync, SecretSyncImportBehavior } from "@app/services/secret-sync/secret-sync-enums";
import {
TWindmillSync,
TWindmillSyncInput,
TWindmillSyncListItem,
TWindmillSyncWithCredentials
} from "@app/services/secret-sync/windmill";
import {
TAwsParameterStoreSync,
@ -74,7 +80,8 @@ export type TSecretSync =
| THumanitecSync
| TTerraformCloudSync
| TCamundaSync
| TVercelSync;
| TVercelSync
| TWindmillSync;
export type TSecretSyncWithCredentials =
| TAwsParameterStoreSyncWithCredentials
@ -87,7 +94,8 @@ export type TSecretSyncWithCredentials =
| THumanitecSyncWithCredentials
| TTerraformCloudSyncWithCredentials
| TCamundaSyncWithCredentials
| TVercelSyncWithCredentials;
| TVercelSyncWithCredentials
| TWindmillSyncWithCredentials;
export type TSecretSyncInput =
| TAwsParameterStoreSyncInput
@ -100,7 +108,8 @@ export type TSecretSyncInput =
| THumanitecSyncInput
| TTerraformCloudSyncInput
| TCamundaSyncInput
| TVercelSyncInput;
| TVercelSyncInput
| TWindmillSyncInput;
export type TSecretSyncListItem =
| TAwsParameterStoreSyncListItem
@ -113,7 +122,8 @@ export type TSecretSyncListItem =
| THumanitecSyncListItem
| TTerraformCloudSyncListItem
| TCamundaSyncListItem
| TVercelSyncListItem;
| TVercelSyncListItem
| TWindmillSyncListItem;
export type TSyncOptionsConfig = {
canImportSecrets: boolean;

View File

@ -0,0 +1,4 @@
export * from "./windmill-sync-constants";
export * from "./windmill-sync-fns";
export * from "./windmill-sync-schemas";
export * from "./windmill-sync-types";

View File

@ -0,0 +1,10 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
export const WINDMILL_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Windmill",
destination: SecretSync.Windmill,
connection: AppConnection.Windmill,
canImportSecrets: true
};

View File

@ -0,0 +1,241 @@
import { request } from "@app/lib/config/request";
import { getWindmillInstanceUrl } from "@app/services/app-connection/windmill";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import {
TDeleteWindmillVariable,
TPostWindmillVariable,
TWindmillListVariables,
TWindmillListVariablesResponse,
TWindmillSyncWithCredentials,
TWindmillVariable
} from "@app/services/secret-sync/windmill/windmill-sync-types";
import { TSecretMap } from "../secret-sync-types";
const PAGE_LIMIT = 100;
const listWindmillVariables = async ({ instanceUrl, workspace, accessToken, path }: TWindmillListVariables) => {
const variables: Record<string, TWindmillVariable> = {};
// windmill paginates but doesn't return if there's more pages so we need to check if page size full
let page: number | null = 1;
while (page) {
// eslint-disable-next-line no-await-in-loop
const { data: variablesPage } = await request.get<TWindmillListVariablesResponse>(
`${instanceUrl}/api/w/${workspace}/variables/list`,
{
headers: {
Authorization: `Bearer ${accessToken}`
},
params: {
page,
limit: PAGE_LIMIT,
path_start: path
}
}
);
for (const variable of variablesPage) {
const variableName = variable.path.replace(path, "");
if (variable.is_secret) {
// eslint-disable-next-line no-await-in-loop
const { data: variableValue } = await request.get<string>(
`${instanceUrl}/api/w/${workspace}/variables/get_value/${variable.path}`,
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
variables[variableName] = {
...variable,
value: variableValue
};
} else {
variables[variableName] = variable;
}
}
if (variablesPage.length >= PAGE_LIMIT) {
page += 1;
} else {
page = null;
}
}
return variables;
};
const createWindmillVariable = async ({
path,
value,
instanceUrl,
accessToken,
workspace,
description
}: TPostWindmillVariable) =>
request.post(
`${instanceUrl}/api/w/${workspace}/variables/create`,
{
path,
value,
is_secret: true,
description
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
);
const updateWindmillVariable = async ({
path,
value,
instanceUrl,
accessToken,
workspace,
description
}: TPostWindmillVariable) =>
request.post(
`${instanceUrl}/api/w/${workspace}/variables/update/${path}`,
{
value,
is_secret: true,
description
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
}
);
const deleteWindmillVariable = async ({ path, instanceUrl, accessToken, workspace }: TDeleteWindmillVariable) =>
request.delete(`${instanceUrl}/api/w/${workspace}/variables/delete/${path}`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
export const WindmillSyncFns = {
syncSecrets: async (secretSync: TWindmillSyncWithCredentials, secretMap: TSecretMap) => {
const {
connection,
destinationConfig: { path },
syncOptions: { disableSecretDeletion }
} = secretSync;
// url needs to be lowercase
const workspace = secretSync.destinationConfig.workspace.toLowerCase();
const instanceUrl = await getWindmillInstanceUrl(connection);
const { accessToken } = connection.credentials;
const variables = await listWindmillVariables({ instanceUrl, accessToken, workspace, path });
for await (const entry of Object.entries(secretMap)) {
const [key, { value, comment = "" }] = entry;
try {
const payload = {
instanceUrl,
workspace,
path: path + key,
value,
accessToken,
description: comment
};
if (key in variables) {
if (variables[key].value !== value || variables[key].description !== comment)
await updateWindmillVariable(payload);
} else {
await createWindmillVariable(payload);
}
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
}
}
if (disableSecretDeletion) return;
for await (const [key, variable] of Object.entries(variables)) {
if (!(key in secretMap)) {
try {
await deleteWindmillVariable({
instanceUrl,
workspace,
path: variable.path,
accessToken
});
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
}
}
}
},
removeSecrets: async (secretSync: TWindmillSyncWithCredentials, secretMap: TSecretMap) => {
const {
connection,
destinationConfig: { path }
} = secretSync;
// url needs to be lowercase
const workspace = secretSync.destinationConfig.workspace.toLowerCase();
const instanceUrl = await getWindmillInstanceUrl(connection);
const { accessToken } = connection.credentials;
const variables = await listWindmillVariables({ instanceUrl, accessToken, workspace, path });
for await (const [key, variable] of Object.entries(variables)) {
if (key in secretMap) {
try {
await deleteWindmillVariable({
path: variable.path,
instanceUrl,
workspace,
accessToken
});
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
}
}
}
},
getSecrets: async (secretSync: TWindmillSyncWithCredentials) => {
const {
connection,
destinationConfig: { path }
} = secretSync;
// url needs to be lowercase
const workspace = secretSync.destinationConfig.workspace.toLowerCase();
const instanceUrl = await getWindmillInstanceUrl(connection);
const { accessToken } = connection.credentials;
const variables = await listWindmillVariables({ instanceUrl, accessToken, workspace, path });
return Object.fromEntries(
Object.entries(variables).map(([key, variable]) => [key, { value: variable.value ?? "" }])
);
}
};

View File

@ -0,0 +1,66 @@
import { z } from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
BaseSecretSyncSchema,
GenericCreateSecretSyncFieldsSchema,
GenericUpdateSecretSyncFieldsSchema
} from "@app/services/secret-sync/secret-sync-schemas";
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const pathCharacterValidator = characterValidator([
CharacterType.AlphaNumeric,
CharacterType.Underscore,
CharacterType.Hyphen
]);
const WindmillSyncDestinationConfigSchema = z.object({
workspace: z.string().trim().min(1, "Workspace required").describe(SecretSyncs.DESTINATION_CONFIG.WINDMILL.workspace),
path: z
.string()
.trim()
.min(1, "Path required")
.refine(
(val) =>
(val.startsWith("u/") || val.startsWith("f/")) &&
val.endsWith("/") &&
val.split("/").length >= 3 &&
val
.split("/")
.slice(0, -1) // Remove last empty segment from trailing slash
.every((segment) => segment && pathCharacterValidator(segment)),
'Invalid path - must follow Windmill path format. ex: "f/folder/path/"'
)
.describe(SecretSyncs.DESTINATION_CONFIG.WINDMILL.path)
});
const WindmillSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
export const WindmillSyncSchema = BaseSecretSyncSchema(SecretSync.Windmill, WindmillSyncOptionsConfig).extend({
destination: z.literal(SecretSync.Windmill),
destinationConfig: WindmillSyncDestinationConfigSchema
});
export const CreateWindmillSyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.Windmill,
WindmillSyncOptionsConfig
).extend({
destinationConfig: WindmillSyncDestinationConfigSchema
});
export const UpdateWindmillSyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.Windmill,
WindmillSyncOptionsConfig
).extend({
destinationConfig: WindmillSyncDestinationConfigSchema.optional()
});
export const WindmillSyncListItemSchema = z.object({
name: z.literal("Windmill"),
connection: z.literal(AppConnection.Windmill),
destination: z.literal(SecretSync.Windmill),
canImportSecrets: z.literal(true)
});

View File

@ -0,0 +1,39 @@
import { z } from "zod";
import { TWindmillConnection } from "@app/services/app-connection/windmill";
import { CreateWindmillSyncSchema, WindmillSyncListItemSchema, WindmillSyncSchema } from "./windmill-sync-schemas";
export type TWindmillSync = z.infer<typeof WindmillSyncSchema>;
export type TWindmillSyncInput = z.infer<typeof CreateWindmillSyncSchema>;
export type TWindmillSyncListItem = z.infer<typeof WindmillSyncListItemSchema>;
export type TWindmillSyncWithCredentials = TWindmillSync & {
connection: TWindmillConnection;
};
export type TWindmillVariable = {
path: string;
value: string;
is_secret: boolean;
is_oauth: boolean;
description: string;
};
export type TWindmillListVariablesResponse = TWindmillVariable[];
export type TWindmillListVariables = {
accessToken: string;
instanceUrl: string;
path: string;
workspace: string;
description?: string;
};
export type TPostWindmillVariable = TWindmillListVariables & {
value: string;
};
export type TDeleteWindmillVariable = TWindmillListVariables;

View File

@ -1,5 +1,7 @@
import path from "node:path";
import RE2 from "re2";
import { TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
@ -13,9 +15,8 @@ import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
const INTERPOLATION_SYNTAX_REG = /\${([a-zA-Z0-9-_.]+)}/g;
// akhilmhdh: JS regex with global save state in .test
const INTERPOLATION_SYNTAX_REG_NON_GLOBAL = /\${([a-zA-Z0-9-_.]+)}/;
const INTERPOLATION_PATTERN_STRING = String.raw`\${([a-zA-Z0-9-_.]+)}`;
const INTERPOLATION_TEST_REGEX = new RE2(INTERPOLATION_PATTERN_STRING);
export const shouldUseSecretV2Bridge = (version: number) => version === 3;
@ -36,7 +37,14 @@ export const shouldUseSecretV2Bridge = (version: number) => version === 3;
* // ]
*/
export const getAllSecretReferences = (maybeSecretReference: string) => {
const references = Array.from(maybeSecretReference.matchAll(INTERPOLATION_SYNTAX_REG), (m) => m[1]);
const references = [];
let match;
const regex = new RE2(INTERPOLATION_PATTERN_STRING, "g");
// eslint-disable-next-line no-cond-assign
while ((match = regex.exec(maybeSecretReference)) !== null) {
references.push(match[1]);
}
const nestedReferences = references
.filter((el) => el.includes("."))
@ -531,9 +539,17 @@ export const expandSecretReferencesFactory = ({
// eslint-disable-next-line no-continue
if (depth > MAX_SECRET_REFERENCE_DEPTH) continue;
const refs = value?.match(INTERPOLATION_SYNTAX_REG);
if (refs) {
const matchRegex = new RE2(INTERPOLATION_PATTERN_STRING, "g");
const refs = [];
let match;
// eslint-disable-next-line no-cond-assign
while ((match = matchRegex.exec(value || "")) !== null) {
refs.push(match[0]);
}
if (refs.length > 0) {
for (const interpolationSyntax of refs) {
const interpolationKey = interpolationSyntax.slice(2, interpolationSyntax.length - 1);
const entities = interpolationKey.trim().split(".");
@ -592,7 +608,7 @@ export const expandSecretReferencesFactory = ({
trace
};
const shouldExpandMore = INTERPOLATION_SYNTAX_REG_NON_GLOBAL.test(referencedSecretValue);
const shouldExpandMore = INTERPOLATION_TEST_REGEX.test(referencedSecretValue);
if (dto.shouldStackTrace) {
const stackTraceNode = { ...node, children: [], key: referencedSecretKey, trace: null };
trace?.children.push(stackTraceNode);
@ -626,7 +642,7 @@ export const expandSecretReferencesFactory = ({
}) => {
if (!inputSecret.value) return inputSecret.value;
const shouldExpand = Boolean(inputSecret.value?.match(INTERPOLATION_SYNTAX_REG));
const shouldExpand = INTERPOLATION_TEST_REGEX.test(inputSecret.value);
if (!shouldExpand) return inputSecret.value;
const { expandedValue } = await recursivelyExpandSecret(inputSecret);

View File

@ -1,5 +1,6 @@
/* eslint-disable no-await-in-loop */
import path from "path";
import RE2 from "re2";
import {
ActionProjectType,
@ -218,7 +219,9 @@ type TInterpolateSecretArg = {
};
const MAX_SECRET_REFERENCE_DEPTH = 5;
const INTERPOLATION_SYNTAX_REG = /\${([a-zA-Z0-9-_.]+)}/g;
const INTERPOLATION_PATTERN_STRING = String.raw`\${([a-zA-Z0-9-_.]+)}`;
const INTERPOLATION_TEST_REGEX = new RE2(INTERPOLATION_PATTERN_STRING);
export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderDAL }: TInterpolateSecretArg) => {
const secretCache: Record<string, Record<string, string>> = {};
const getCacheUniqueKey = (environment: string, secretPath: string) => `${environment}-${secretPath}`;
@ -273,9 +276,17 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD
if (!value) return "";
if (depth > MAX_SECRET_REFERENCE_DEPTH) return "";
const refs = value.match(INTERPOLATION_SYNTAX_REG);
const refs = [];
let match;
const execRegex = new RE2(INTERPOLATION_PATTERN_STRING, "g");
// eslint-disable-next-line no-cond-assign
while ((match = execRegex.exec(value)) !== null) {
refs.push(match[0]);
}
let expandedValue = value;
if (refs) {
if (refs.length > 0) {
for (const interpolationSyntax of refs) {
const interpolationKey = interpolationSyntax.slice(2, interpolationSyntax.length - 1);
const entities = interpolationKey.trim().split(".");
@ -284,7 +295,7 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD
const [secretKey] = entities;
// eslint-disable-next-line
let referenceValue = await fetchSecret(environment, secretPath, secretKey);
if (INTERPOLATION_SYNTAX_REG.test(referenceValue)) {
if (INTERPOLATION_TEST_REGEX.test(referenceValue)) {
// eslint-disable-next-line
referenceValue = await recursivelyExpandSecret({
environment,
@ -305,7 +316,7 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD
// eslint-disable-next-line
let referenceValue = await fetchSecret(secretReferenceEnvironment, secretReferencePath, secretReferenceKey);
if (INTERPOLATION_SYNTAX_REG.test(referenceValue)) {
if (INTERPOLATION_TEST_REGEX.test(referenceValue)) {
// eslint-disable-next-line
referenceValue = await recursivelyExpandSecret({
environment: secretReferenceEnvironment,
@ -332,7 +343,7 @@ export const interpolateSecrets = ({ projectId, secretEncKey, secretDAL, folderD
}) => {
if (!inputSecret.value) return inputSecret.value;
const shouldExpand = Boolean(inputSecret.value?.match(INTERPOLATION_SYNTAX_REG));
const shouldExpand = INTERPOLATION_TEST_REGEX.test(inputSecret.value);
if (!shouldExpand) return inputSecret.value;
const expandedSecretValue = await recursivelyExpandSecret(inputSecret);
@ -451,7 +462,18 @@ export const fnSecretBlindIndexCheckV2 = async ({
* // ]
*/
export const getAllNestedSecretReferences = (maybeSecretReference: string) => {
const references = Array.from(maybeSecretReference.matchAll(INTERPOLATION_SYNTAX_REG), (m) => m[1]);
const matches = [];
let match;
const execRegex = new RE2(INTERPOLATION_PATTERN_STRING, "g");
// eslint-disable-next-line no-cond-assign
while ((match = execRegex.exec(maybeSecretReference)) !== null) {
matches.push(match);
}
const references = matches.map((m) => m[1]);
return references
.filter((el) => el.includes("."))
.map((el) => {

View File

@ -0,0 +1,4 @@
---
title: "Available"
openapi: "GET /api/v1/app-connections/windmill/available"
---

View File

@ -0,0 +1,9 @@
---
title: "Create"
openapi: "POST /api/v1/app-connections/windmill"
---
<Note>
Check out the configuration docs for [Windmill Connections](/integrations/app-connections/windmill) to learn how to obtain
the required credentials.
</Note>

View File

@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v1/app-connections/windmill/{connectionId}"
---

View File

@ -0,0 +1,4 @@
---
title: "Get by ID"
openapi: "GET /api/v1/app-connections/windmill/{connectionId}"
---

View File

@ -0,0 +1,4 @@
---
title: "Get by Name"
openapi: "GET /api/v1/app-connections/windmill/connection-name/{connectionName}"
---

View File

@ -0,0 +1,4 @@
---
title: "List"
openapi: "GET /api/v1/app-connections/windmill"
---

View File

@ -0,0 +1,9 @@
---
title: "Update"
openapi: "PATCH /api/v1/app-connections/windmill/{connectionId}"
---
<Note>
Check out the configuration docs for [Windmill Connections](/integrations/app-connections/windmill) to learn how to obtain
the required credentials.
</Note>

View File

@ -0,0 +1,4 @@
---
title: "Create"
openapi: "POST /api/v1/secret-syncs/windmill"
---

View File

@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v1/secret-syncs/windmill/{syncId}"
---

View File

@ -0,0 +1,4 @@
---
title: "Get by ID"
openapi: "GET /api/v1/secret-syncs/windmill/{syncId}"
---

View File

@ -0,0 +1,4 @@
---
title: "Get by Name"
openapi: "GET /api/v1/secret-syncs/windmill/sync-name/{syncName}"
---

View File

@ -0,0 +1,4 @@
---
title: "Import Secrets"
openapi: "POST /api/v1/secret-syncs/windmill/{syncId}/import-secrets"
---

View File

@ -0,0 +1,4 @@
---
title: "List"
openapi: "GET /api/v1/secret-syncs/windmill"
---

View File

@ -0,0 +1,4 @@
---
title: "Remove Secrets"
openapi: "POST /api/v1/secret-syncs/windmill/{syncId}/remove-secrets"
---

View File

@ -0,0 +1,4 @@
---
title: "Sync Secrets"
openapi: "POST /api/v1/secret-syncs/windmill/{syncId}/sync-secrets"
---

View File

@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PATCH /api/v1/secret-syncs/windmill/{syncId}"
---

View File

@ -25,7 +25,7 @@ By default, every user in a project is either a **viewer**, **developer**, or an
As such:
- **Admin**: This role enables identities to have access to all environments, folders, secrets, and actions within the project.
- **Developers**: This role restricts identities from performing project control actions, updating Approval Workflow policies, managing roles/members, and more.
- **Developers**: This role restricts identities from performing project control actions, updating Approval Workflow policies, managing roles, editing and removing project members, and more.
- **Viewer**: The most limiting bulit-in role on the project level  it forbids user and machine identities to perform any action and rather shows them in the read-only mode.
![Project member role](/images/platform/access-controls/rbac.png)

View File

@ -65,7 +65,9 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
We recommend ensuring that your account is provisioned using the application in Auth0
prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>

View File

@ -72,6 +72,10 @@ description: "Learn how to configure Auth0 SAML for Infisical SSO."
To enforce SAML SSO, you're required to test out the SAML connection by successfully authenticating at least one Auth0 user with Infisical;
Once you've completed this requirement, you can toggle the **Enforce SAML SSO** button to enforce SAML SSO.
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>

View File

@ -106,6 +106,9 @@ description: "Learn how to configure Microsoft Entra ID for Infisical SSO."
We recommend ensuring that your account is provisioned the application in Azure
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>

View File

@ -66,6 +66,9 @@ Prerequisites:
<Warning>
We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>

View File

@ -81,6 +81,9 @@ description: "Learn how to configure Google SAML for Infisical SSO."
We recommend ensuring that your account is provisioned the application in Google
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>

View File

@ -86,6 +86,9 @@ description: "Learn how to configure JumpCloud SAML for Infisical SSO."
We recommend ensuring that your account is provisioned the application in JumpCloud
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>

View File

@ -92,7 +92,9 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."
We recommend ensuring that your account is provisioned using the application in Keycloak
prior to enforcing OIDC SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>

View File

@ -127,6 +127,9 @@ description: "Learn how to configure Keycloak SAML for Infisical SSO."
We recommend ensuring that your account is provisioned the application in Keycloak
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>

View File

@ -94,6 +94,9 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
We recommend ensuring that your account is provisioned the application in Okta
prior to enforcing SAML SSO to prevent any unintended issues.
</Warning>
<Info>
In case of a lockout, an organization admin can use the admin login portal in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
</Info>
</Step>
</Steps>

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

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