mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 23:28:25 +00:00
Merge remote-tracking branch 'origin/main' into misc/export-org-data-feature
This commit is contained in:
@@ -36,16 +36,22 @@ CLIENT_ID_HEROKU=
|
|||||||
CLIENT_ID_VERCEL=
|
CLIENT_ID_VERCEL=
|
||||||
CLIENT_ID_NETLIFY=
|
CLIENT_ID_NETLIFY=
|
||||||
CLIENT_ID_GITHUB=
|
CLIENT_ID_GITHUB=
|
||||||
|
CLIENT_ID_GITHUB_APP=
|
||||||
|
CLIENT_SLUG_GITHUB_APP=
|
||||||
CLIENT_ID_GITLAB=
|
CLIENT_ID_GITLAB=
|
||||||
CLIENT_ID_BITBUCKET=
|
CLIENT_ID_BITBUCKET=
|
||||||
CLIENT_SECRET_HEROKU=
|
CLIENT_SECRET_HEROKU=
|
||||||
CLIENT_SECRET_VERCEL=
|
CLIENT_SECRET_VERCEL=
|
||||||
CLIENT_SECRET_NETLIFY=
|
CLIENT_SECRET_NETLIFY=
|
||||||
CLIENT_SECRET_GITHUB=
|
CLIENT_SECRET_GITHUB=
|
||||||
|
CLIENT_SECRET_GITHUB_APP=
|
||||||
CLIENT_SECRET_GITLAB=
|
CLIENT_SECRET_GITLAB=
|
||||||
CLIENT_SECRET_BITBUCKET=
|
CLIENT_SECRET_BITBUCKET=
|
||||||
CLIENT_SLUG_VERCEL=
|
CLIENT_SLUG_VERCEL=
|
||||||
|
|
||||||
|
CLIENT_PRIVATE_KEY_GITHUB_APP=
|
||||||
|
CLIENT_APP_ID_GITHUB_APP=
|
||||||
|
|
||||||
# Sentry (optional) for monitoring errors
|
# Sentry (optional) for monitoring errors
|
||||||
SENTRY_DSN=
|
SENTRY_DSN=
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
DB_CONNECTION_URI=
|
DB_CONNECTION_URI=
|
||||||
|
AUDIT_LOGS_DB_CONNECTION_URI=
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
- [ ] Bug fix
|
- [ ] Bug fix
|
||||||
- [ ] New feature
|
- [ ] New feature
|
||||||
|
- [ ] Improvement
|
||||||
- [ ] Breaking change
|
- [ ] Breaking change
|
||||||
- [ ] Documentation
|
- [ ] Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ on:
|
|||||||
description: "Version number"
|
description: "Version number"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./backend
|
working-directory: ./backend
|
||||||
@@ -49,9 +48,9 @@ jobs:
|
|||||||
- name: Package into node binary
|
- name: Package into node binary
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ matrix.os }}" != "linux" ]; then
|
if [ "${{ matrix.os }}" != "linux" ]; then
|
||||||
pkg --no-bytecode --public-packages "*" --public --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core-${{ matrix.os }}-${{ matrix.arch }} .
|
pkg --no-bytecode --public-packages "*" --public --compress GZip --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core-${{ matrix.os }}-${{ matrix.arch }} .
|
||||||
else
|
else
|
||||||
pkg --no-bytecode --public-packages "*" --public --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core .
|
pkg --no-bytecode --public-packages "*" --public --compress GZip --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core .
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Set up .deb package structure (Debian/Ubuntu only)
|
# Set up .deb package structure (Debian/Ubuntu only)
|
||||||
@@ -83,6 +82,86 @@ jobs:
|
|||||||
dpkg-deb --build infisical-core
|
dpkg-deb --build infisical-core
|
||||||
mv infisical-core.deb ./binary/infisical-core-${{matrix.arch}}.deb
|
mv infisical-core.deb ./binary/infisical-core-${{matrix.arch}}.deb
|
||||||
|
|
||||||
|
### RPM
|
||||||
|
|
||||||
|
# Set up .rpm package structure
|
||||||
|
- name: Set up .rpm package structure
|
||||||
|
if: matrix.os == 'linux'
|
||||||
|
run: |
|
||||||
|
mkdir -p infisical-core-rpm/usr/local/bin
|
||||||
|
cp ./binary/infisical-core infisical-core-rpm/usr/local/bin/
|
||||||
|
chmod +x infisical-core-rpm/usr/local/bin/infisical-core
|
||||||
|
|
||||||
|
# Install RPM build tools
|
||||||
|
- name: Install RPM build tools
|
||||||
|
if: matrix.os == 'linux'
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y rpm
|
||||||
|
|
||||||
|
# Create .spec file for RPM
|
||||||
|
- name: Create .spec file for RPM
|
||||||
|
if: matrix.os == 'linux'
|
||||||
|
run: |
|
||||||
|
cat <<EOF > infisical-core.spec
|
||||||
|
|
||||||
|
%global _enable_debug_package 0
|
||||||
|
%global debug_package %{nil}
|
||||||
|
%global __os_install_post /usr/lib/rpm/brp-compress %{nil}
|
||||||
|
|
||||||
|
Name: infisical-core
|
||||||
|
Version: ${{ github.event.inputs.version }}
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Summary: Infisical Core standalone executable
|
||||||
|
License: Proprietary
|
||||||
|
URL: https://app.infisical.com
|
||||||
|
|
||||||
|
%description
|
||||||
|
Infisical Core standalone executable (app.infisical.com)
|
||||||
|
|
||||||
|
%install
|
||||||
|
mkdir -p %{buildroot}/usr/local/bin
|
||||||
|
cp %{_sourcedir}/infisical-core %{buildroot}/usr/local/bin/
|
||||||
|
|
||||||
|
%files
|
||||||
|
/usr/local/bin/infisical-core
|
||||||
|
|
||||||
|
%pre
|
||||||
|
|
||||||
|
%post
|
||||||
|
|
||||||
|
%preun
|
||||||
|
|
||||||
|
%postun
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Build .rpm file
|
||||||
|
- name: Build .rpm package
|
||||||
|
if: matrix.os == 'linux'
|
||||||
|
run: |
|
||||||
|
# Create necessary directories
|
||||||
|
mkdir -p rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
||||||
|
|
||||||
|
# Copy the binary directly to SOURCES
|
||||||
|
cp ./binary/infisical-core rpmbuild/SOURCES/
|
||||||
|
|
||||||
|
# Run rpmbuild with verbose output
|
||||||
|
rpmbuild -vv -bb \
|
||||||
|
--define "_topdir $(pwd)/rpmbuild" \
|
||||||
|
--define "_sourcedir $(pwd)/rpmbuild/SOURCES" \
|
||||||
|
--define "_rpmdir $(pwd)/rpmbuild/RPMS" \
|
||||||
|
--target ${{ matrix.arch == 'x64' && 'x86_64' || 'aarch64' }} \
|
||||||
|
infisical-core.spec
|
||||||
|
|
||||||
|
# Try to find the RPM file
|
||||||
|
find rpmbuild -name "*.rpm"
|
||||||
|
|
||||||
|
# Move the RPM file if found
|
||||||
|
if [ -n "$(find rpmbuild -name '*.rpm')" ]; then
|
||||||
|
mv $(find rpmbuild -name '*.rpm') ./binary/infisical-core-${{matrix.arch}}.rpm
|
||||||
|
else
|
||||||
|
echo "RPM file not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.x" # Specify the Python version you need
|
python-version: "3.x" # Specify the Python version you need
|
||||||
@@ -97,6 +176,12 @@ jobs:
|
|||||||
working-directory: ./backend
|
working-directory: ./backend
|
||||||
run: cloudsmith push deb --republish --no-wait-for-sync --api-key=${{ secrets.CLOUDSMITH_API_KEY }} infisical/infisical-core/any-distro/any-version ./binary/infisical-core-${{ matrix.arch }}.deb
|
run: cloudsmith push deb --republish --no-wait-for-sync --api-key=${{ secrets.CLOUDSMITH_API_KEY }} infisical/infisical-core/any-distro/any-version ./binary/infisical-core-${{ matrix.arch }}.deb
|
||||||
|
|
||||||
|
# Publish .rpm file to Cloudsmith (Red Hat-based systems only)
|
||||||
|
- name: Publish .rpm to Cloudsmith
|
||||||
|
if: matrix.os == 'linux'
|
||||||
|
working-directory: ./backend
|
||||||
|
run: cloudsmith push rpm --republish --no-wait-for-sync --api-key=${{ secrets.CLOUDSMITH_API_KEY }} infisical/infisical-core/any-distro/any-version ./binary/infisical-core-${{ matrix.arch }}.rpm
|
||||||
|
|
||||||
# Publish .exe file to Cloudsmith (Windows only)
|
# Publish .exe file to Cloudsmith (Windows only)
|
||||||
- name: Publish to Cloudsmith (Windows)
|
- name: Publish to Cloudsmith (Windows)
|
||||||
if: matrix.os == 'win'
|
if: matrix.os == 'win'
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ jobs:
|
|||||||
- name: Change directory to backend and install dependencies
|
- name: Change directory to backend and install dependencies
|
||||||
env:
|
env:
|
||||||
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
||||||
|
AUDIT_LOGS_DB_CONNECTION_URI: ${{ secrets.AUDIT_LOGS_DB_CONNECTION_URI }}
|
||||||
run: |
|
run: |
|
||||||
cd backend
|
cd backend
|
||||||
npm install
|
npm install
|
||||||
|
|||||||
@@ -95,6 +95,10 @@ RUN mkdir frontend-build
|
|||||||
# Production stage
|
# Production stage
|
||||||
FROM base AS production
|
FROM base AS production
|
||||||
RUN apk add --upgrade --no-cache ca-certificates
|
RUN apk add --upgrade --no-cache ca-certificates
|
||||||
|
RUN apk add --no-cache bash curl && curl -1sLf \
|
||||||
|
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
|
||||||
|
&& apk add infisical=0.31.1 && apk add --no-cache git
|
||||||
|
|
||||||
RUN addgroup --system --gid 1001 nodejs \
|
RUN addgroup --system --gid 1001 nodejs \
|
||||||
&& adduser --system --uid 1001 non-root-user
|
&& adduser --system --uid 1001 non-root-user
|
||||||
|
|
||||||
|
|||||||
@@ -135,9 +135,7 @@ Lean about Infisical's code scanning feature [here](https://infisical.com/docs/c
|
|||||||
|
|
||||||
This repo available under the [MIT expat license](https://github.com/Infisical/infisical/blob/main/LICENSE), with the exception of the `ee` directory which will contain premium enterprise features requiring a Infisical license.
|
This repo available under the [MIT expat license](https://github.com/Infisical/infisical/blob/main/LICENSE), with the exception of the `ee` directory which will contain premium enterprise features requiring a Infisical license.
|
||||||
|
|
||||||
If you are interested in managed Infisical Cloud of self-hosted Enterprise Offering, take a look at [our website](https://infisical.com/) or [book a meeting with us](https://infisical.cal.com/vlad/infisical-demo):
|
If you are interested in managed Infisical Cloud of self-hosted Enterprise Offering, take a look at [our website](https://infisical.com/) or [book a meeting with us](https://infisical.cal.com/vlad/infisical-demo).
|
||||||
|
|
||||||
<a href="[https://infisical.cal.com/vlad/infisical-demo](https://infisical.cal.com/vlad/infisical-demo)"><img alt="Schedule a meeting" src="https://cal.com/book-with-cal-dark.svg" /></a>
|
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
@@ -163,4 +161,3 @@ Not sure where to get started? You can:
|
|||||||
- [Twitter](https://twitter.com/infisical) for fast news
|
- [Twitter](https://twitter.com/infisical) for fast news
|
||||||
- [YouTube](https://www.youtube.com/@infisical_os) for videos on secret management
|
- [YouTube](https://www.youtube.com/@infisical_os) for videos on secret management
|
||||||
- [Blog](https://infisical.com/blog) for secret management insights, articles, tutorials, and updates
|
- [Blog](https://infisical.com/blog) for secret management insights, articles, tutorials, and updates
|
||||||
- [Roadmap](https://www.notion.so/infisical/be2d2585a6694e40889b03aef96ea36b?v=5b19a8127d1a4060b54769567a8785fa) for planned features
|
|
||||||
@@ -123,7 +123,7 @@ describe("Project Environment Router", async () => {
|
|||||||
id: deletedProjectEnvironment.id,
|
id: deletedProjectEnvironment.id,
|
||||||
name: mockProjectEnv.name,
|
name: mockProjectEnv.name,
|
||||||
slug: mockProjectEnv.slug,
|
slug: mockProjectEnv.slug,
|
||||||
position: 4,
|
position: 5,
|
||||||
createdAt: expect.any(String),
|
createdAt: expect.any(String),
|
||||||
updatedAt: expect.any(String)
|
updatedAt: expect.any(String)
|
||||||
})
|
})
|
||||||
|
|||||||
Generated
+541
-67
@@ -21,12 +21,14 @@
|
|||||||
"@fastify/etag": "^5.1.0",
|
"@fastify/etag": "^5.1.0",
|
||||||
"@fastify/formbody": "^7.4.0",
|
"@fastify/formbody": "^7.4.0",
|
||||||
"@fastify/helmet": "^11.1.1",
|
"@fastify/helmet": "^11.1.1",
|
||||||
|
"@fastify/multipart": "8.3.0",
|
||||||
"@fastify/passport": "^2.4.0",
|
"@fastify/passport": "^2.4.0",
|
||||||
"@fastify/rate-limit": "^9.0.0",
|
"@fastify/rate-limit": "^9.0.0",
|
||||||
"@fastify/session": "^10.7.0",
|
"@fastify/session": "^10.7.0",
|
||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
|
"@octokit/auth-app": "^7.1.1",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
@@ -61,7 +63,7 @@
|
|||||||
"jwks-rsa": "^3.1.0",
|
"jwks-rsa": "^3.1.0",
|
||||||
"knex": "^3.0.1",
|
"knex": "^3.0.1",
|
||||||
"ldapjs": "^3.0.7",
|
"ldapjs": "^3.0.7",
|
||||||
"ldif": "^0.5.1",
|
"ldif": "0.5.1",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"mongodb": "^6.8.1",
|
"mongodb": "^6.8.1",
|
||||||
@@ -4311,6 +4313,15 @@
|
|||||||
"fast-uri": "^2.0.0"
|
"fast-uri": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fastify/busboy": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@fastify/cookie": {
|
"node_modules/@fastify/cookie": {
|
||||||
"version": "9.3.1",
|
"version": "9.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-9.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-9.3.1.tgz",
|
||||||
@@ -4381,6 +4392,20 @@
|
|||||||
"helmet": "^7.0.0"
|
"helmet": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fastify/multipart": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-A8h80TTyqUzaMVH0Cr9Qcm6RxSkVqmhK/MVBYHYeRRSUbUYv08WecjWKSlG2aSnD4aGI841pVxAjC+G1GafUeQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/busboy": "^2.1.0",
|
||||||
|
"@fastify/deepmerge": "^1.0.0",
|
||||||
|
"@fastify/error": "^3.0.0",
|
||||||
|
"fastify-plugin": "^4.0.0",
|
||||||
|
"secure-json-parse": "^2.4.0",
|
||||||
|
"stream-wormhole": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@fastify/passport": {
|
"node_modules/@fastify/passport": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/passport/-/passport-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fastify/passport/-/passport-2.4.0.tgz",
|
||||||
@@ -4976,24 +5001,73 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-app": {
|
"node_modules/@octokit/auth-app": {
|
||||||
"version": "6.0.3",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.1.1.tgz",
|
||||||
"integrity": "sha512-9N7IlBAKEJR3tJgPSubCxIDYGXSdc+2xbkjYpk9nCyqREnH8qEMoMhiEB1WgoA9yTFp91El92XNXAi+AjuKnfw==",
|
"integrity": "sha512-kRAd6yelV9OgvlEJE88H0VLlQdZcag9UlLr7dV0YYP37X8PPDvhgiTy66QVhDXdyoT0AleFN2w/qXkPdrSzINg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-oauth-app": "^7.0.0",
|
"@octokit/auth-oauth-app": "^8.1.0",
|
||||||
"@octokit/auth-oauth-user": "^4.0.0",
|
"@octokit/auth-oauth-user": "^5.1.0",
|
||||||
"@octokit/request": "^8.0.2",
|
"@octokit/request": "^9.1.1",
|
||||||
"@octokit/request-error": "^5.0.0",
|
"@octokit/request-error": "^6.1.1",
|
||||||
"@octokit/types": "^12.0.0",
|
"@octokit/types": "^13.4.1",
|
||||||
"deprecation": "^2.3.1",
|
|
||||||
"lru-cache": "^10.0.0",
|
"lru-cache": "^10.0.0",
|
||||||
"universal-github-app-jwt": "^1.1.2",
|
"universal-github-app-jwt": "^2.2.0",
|
||||||
"universal-user-agent": "^6.0.0"
|
"universal-user-agent": "^7.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/auth-app/node_modules/@octokit/endpoint": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "22.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
|
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-app/node_modules/@octokit/request": {
|
||||||
|
"version": "9.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
|
||||||
|
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/endpoint": "^10.0.0",
|
||||||
|
"@octokit/request-error": "^6.0.1",
|
||||||
|
"@octokit/types": "^13.1.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-app/node_modules/@octokit/request-error": {
|
||||||
|
"version": "6.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
|
||||||
|
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^13.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-app/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@octokit/auth-app/node_modules/lru-cache": {
|
"node_modules/@octokit/auth-app/node_modules/lru-cache": {
|
||||||
"version": "10.2.0",
|
"version": "10.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
|
||||||
@@ -5002,53 +5076,220 @@
|
|||||||
"node": "14 || >=16.14"
|
"node": "14 || >=16.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/auth-app/node_modules/universal-user-agent": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||||
|
},
|
||||||
"node_modules/@octokit/auth-oauth-app": {
|
"node_modules/@octokit/auth-oauth-app": {
|
||||||
"version": "7.0.1",
|
"version": "8.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz",
|
||||||
"integrity": "sha512-RE0KK0DCjCHXHlQBoubwlLijXEKfhMhKm9gO56xYvFmP1QTMb+vvwRPmQLLx0V+5AvV9N9I3lr1WyTzwL3rMDg==",
|
"integrity": "sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-oauth-device": "^6.0.0",
|
"@octokit/auth-oauth-device": "^7.0.0",
|
||||||
"@octokit/auth-oauth-user": "^4.0.0",
|
"@octokit/auth-oauth-user": "^5.0.1",
|
||||||
"@octokit/request": "^8.0.2",
|
"@octokit/request": "^9.0.0",
|
||||||
"@octokit/types": "^12.0.0",
|
"@octokit/types": "^13.0.0",
|
||||||
"@types/btoa-lite": "^1.0.0",
|
"universal-user-agent": "^7.0.0"
|
||||||
"btoa-lite": "^1.0.0",
|
|
||||||
"universal-user-agent": "^6.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/endpoint": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "22.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
|
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/request": {
|
||||||
|
"version": "9.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
|
||||||
|
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/endpoint": "^10.0.0",
|
||||||
|
"@octokit/request-error": "^6.0.1",
|
||||||
|
"@octokit/types": "^13.1.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/request-error": {
|
||||||
|
"version": "6.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
|
||||||
|
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^13.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-app/node_modules/universal-user-agent": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||||
|
},
|
||||||
"node_modules/@octokit/auth-oauth-device": {
|
"node_modules/@octokit/auth-oauth-device": {
|
||||||
"version": "6.0.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz",
|
||||||
"integrity": "sha512-yxU0rkL65QkjbqQedgVx3gmW7YM5fF+r5uaSj9tM/cQGVqloXcqP2xK90eTyYvl29arFVCW8Vz4H/t47mL0ELw==",
|
"integrity": "sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/oauth-methods": "^4.0.0",
|
"@octokit/oauth-methods": "^5.0.0",
|
||||||
"@octokit/request": "^8.0.0",
|
"@octokit/request": "^9.0.0",
|
||||||
"@octokit/types": "^12.0.0",
|
"@octokit/types": "^13.0.0",
|
||||||
"universal-user-agent": "^6.0.0"
|
"universal-user-agent": "^7.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-oauth-user": {
|
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/endpoint": {
|
||||||
"version": "4.0.1",
|
"version": "10.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||||
"integrity": "sha512-N94wWW09d0hleCnrO5wt5MxekatqEJ4zf+1vSe8MKMrhZ7gAXKFOKrDEZW2INltvBWJCyDUELgGRv8gfErH1Iw==",
|
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-oauth-device": "^6.0.0",
|
"@octokit/types": "^13.0.0",
|
||||||
"@octokit/oauth-methods": "^4.0.0",
|
"universal-user-agent": "^7.0.2"
|
||||||
"@octokit/request": "^8.0.2",
|
|
||||||
"@octokit/types": "^12.0.0",
|
|
||||||
"btoa-lite": "^1.0.0",
|
|
||||||
"universal-user-agent": "^6.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "22.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
|
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/request": {
|
||||||
|
"version": "9.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
|
||||||
|
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/endpoint": "^10.0.0",
|
||||||
|
"@octokit/request-error": "^6.0.1",
|
||||||
|
"@octokit/types": "^13.1.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/request-error": {
|
||||||
|
"version": "6.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
|
||||||
|
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^13.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-device/node_modules/universal-user-agent": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-user": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/auth-oauth-device": "^7.0.1",
|
||||||
|
"@octokit/oauth-methods": "^5.0.0",
|
||||||
|
"@octokit/request": "^9.0.1",
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"universal-user-agent": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/endpoint": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "22.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
|
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/request": {
|
||||||
|
"version": "9.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
|
||||||
|
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/endpoint": "^10.0.0",
|
||||||
|
"@octokit/request-error": "^6.0.1",
|
||||||
|
"@octokit/types": "^13.1.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/request-error": {
|
||||||
|
"version": "6.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
|
||||||
|
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^13.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/auth-oauth-user/node_modules/universal-user-agent": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||||
|
},
|
||||||
"node_modules/@octokit/auth-token": {
|
"node_modules/@octokit/auth-token": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||||
@@ -5112,28 +5353,82 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/oauth-authorization-url": {
|
"node_modules/@octokit/oauth-authorization-url": {
|
||||||
"version": "6.0.2",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz",
|
||||||
"integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==",
|
"integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/oauth-methods": {
|
"node_modules/@octokit/oauth-methods": {
|
||||||
"version": "4.0.1",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz",
|
||||||
"integrity": "sha512-1NdTGCoBHyD6J0n2WGXg9+yDLZrRNZ0moTEex/LSPr49m530WNKcCfXDghofYptr3st3eTii+EHoG5k/o+vbtw==",
|
"integrity": "sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/oauth-authorization-url": "^6.0.2",
|
"@octokit/oauth-authorization-url": "^7.0.0",
|
||||||
"@octokit/request": "^8.0.2",
|
"@octokit/request": "^9.1.0",
|
||||||
"@octokit/request-error": "^5.0.0",
|
"@octokit/request-error": "^6.1.0",
|
||||||
"@octokit/types": "^12.0.0",
|
"@octokit/types": "^13.0.0"
|
||||||
"btoa-lite": "^1.0.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/oauth-methods/node_modules/@octokit/endpoint": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "22.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
|
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/oauth-methods/node_modules/@octokit/request": {
|
||||||
|
"version": "9.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
|
||||||
|
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/endpoint": "^10.0.0",
|
||||||
|
"@octokit/request-error": "^6.0.1",
|
||||||
|
"@octokit/types": "^13.1.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/oauth-methods/node_modules/@octokit/request-error": {
|
||||||
|
"version": "6.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
|
||||||
|
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^13.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/oauth-methods/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/oauth-methods/node_modules/universal-user-agent": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||||
|
},
|
||||||
"node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/openapi-types": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz",
|
||||||
@@ -5248,13 +5543,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request": {
|
"node_modules/@octokit/request": {
|
||||||
"version": "8.1.6",
|
"version": "8.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz",
|
||||||
"integrity": "sha512-YhPaGml3ncZC1NfXpP3WZ7iliL1ap6tLkAp6MvbK2fTTPytzVUyUesBBogcdMm86uRYO5rHaM1xIWxigWZ17MQ==",
|
"integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/endpoint": "^9.0.0",
|
"@octokit/endpoint": "^9.0.1",
|
||||||
"@octokit/request-error": "^5.0.0",
|
"@octokit/request-error": "^5.1.0",
|
||||||
"@octokit/types": "^12.0.0",
|
"@octokit/types": "^13.1.0",
|
||||||
"universal-user-agent": "^6.0.0"
|
"universal-user-agent": "^6.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -5262,11 +5557,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request-error": {
|
"node_modules/@octokit/request-error": {
|
||||||
"version": "5.0.1",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz",
|
||||||
"integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==",
|
"integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^12.0.0",
|
"@octokit/types": "^13.1.0",
|
||||||
"deprecation": "^2.0.0",
|
"deprecation": "^2.0.0",
|
||||||
"once": "^1.4.0"
|
"once": "^1.4.0"
|
||||||
},
|
},
|
||||||
@@ -5274,6 +5569,32 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "22.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
|
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/request-error/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/request/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "22.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
|
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/request/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@octokit/rest": {
|
"node_modules/@octokit/rest": {
|
||||||
"version": "20.0.2",
|
"version": "20.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz",
|
||||||
@@ -14160,6 +14481,154 @@
|
|||||||
"@octokit/core": ">=5"
|
"@octokit/core": ">=5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-app": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-fWjIOpxnL8/YFY3kqquciFQ4o99aCqHw5kMFoGPYbz/h5HNZ11dJlV9zag5wS2nt0X1wJ5cs9BUo+CsAPfW4jQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/auth-oauth-app": "^7.1.0",
|
||||||
|
"@octokit/auth-oauth-user": "^4.1.0",
|
||||||
|
"@octokit/request": "^8.3.1",
|
||||||
|
"@octokit/request-error": "^5.1.0",
|
||||||
|
"@octokit/types": "^13.1.0",
|
||||||
|
"deprecation": "^2.3.1",
|
||||||
|
"lru-cache": "^10.0.0",
|
||||||
|
"universal-github-app-jwt": "^1.1.2",
|
||||||
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-app/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-app": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/auth-oauth-device": "^6.1.0",
|
||||||
|
"@octokit/auth-oauth-user": "^4.1.0",
|
||||||
|
"@octokit/request": "^8.3.1",
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"@types/btoa-lite": "^1.0.0",
|
||||||
|
"btoa-lite": "^1.0.0",
|
||||||
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-device": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/oauth-methods": "^4.1.0",
|
||||||
|
"@octokit/request": "^8.3.1",
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-user": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/auth-oauth-device": "^6.1.0",
|
||||||
|
"@octokit/oauth-methods": "^4.1.0",
|
||||||
|
"@octokit/request": "^8.3.1",
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"btoa-lite": "^1.0.0",
|
||||||
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/oauth-authorization-url": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/oauth-methods": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/oauth-authorization-url": "^6.0.2",
|
||||||
|
"@octokit/request": "^8.3.1",
|
||||||
|
"@octokit/request-error": "^5.1.0",
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"btoa-lite": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/oauth-methods/node_modules/@octokit/types": {
|
||||||
|
"version": "13.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
||||||
|
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "22.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
|
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/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=="
|
||||||
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/universal-github-app-jwt": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/jsonwebtoken": "^9.0.0",
|
||||||
|
"jsonwebtoken": "^9.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/oidc-token-hash": {
|
"node_modules/oidc-token-hash": {
|
||||||
"version": "5.0.3",
|
"version": "5.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
|
||||||
@@ -16604,6 +17073,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
|
||||||
"integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="
|
"integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/stream-wormhole": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stream-wormhole/-/stream-wormhole-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string_decoder": {
|
"node_modules/string_decoder": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
@@ -18143,13 +18621,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/universal-github-app-jwt": {
|
"node_modules/universal-github-app-jwt": {
|
||||||
"version": "1.1.2",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz",
|
||||||
"integrity": "sha512-t1iB2FmLFE+yyJY9+3wMx0ejB+MQpEVkH0gQv7dR6FZyltyq+ZZO0uDpbopxhrZ3SLEO4dCEkIujOMldEQ2iOA==",
|
"integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ=="
|
||||||
"dependencies": {
|
|
||||||
"@types/jsonwebtoken": "^9.0.0",
|
|
||||||
"jsonwebtoken": "^9.0.2"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/universal-user-agent": {
|
"node_modules/universal-user-agent": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
|
|||||||
+14
-6
@@ -45,13 +45,19 @@
|
|||||||
"test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts",
|
"test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts",
|
||||||
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
||||||
"generate:schema": "tsx ./scripts/generate-schema-types.ts",
|
"generate:schema": "tsx ./scripts/generate-schema-types.ts",
|
||||||
|
"auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:latest",
|
||||||
|
"auditlog-migration:up": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:up",
|
||||||
|
"auditlog-migration:down": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:down",
|
||||||
|
"auditlog-migration:list": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:list",
|
||||||
|
"auditlog-migration:status": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:status",
|
||||||
|
"auditlog-migration:rollback": "knex --knexfile ./src/db/auditlog-knexfile.ts migrate:rollback",
|
||||||
"migration:new": "tsx ./scripts/create-migration.ts",
|
"migration:new": "tsx ./scripts/create-migration.ts",
|
||||||
"migration:up": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:up",
|
"migration:up": "npm run auditlog-migration:up && knex --knexfile ./src/db/knexfile.ts --client pg migrate:up",
|
||||||
"migration:down": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:down",
|
"migration:down": "npm run auditlog-migration:down && knex --knexfile ./src/db/knexfile.ts --client pg migrate:down",
|
||||||
"migration:list": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:list",
|
"migration:list": "npm run auditlog-migration:list && knex --knexfile ./src/db/knexfile.ts --client pg migrate:list",
|
||||||
"migration:latest": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest",
|
"migration:latest": "npm run auditlog-migration:latest && knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest",
|
||||||
"migration:status": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:status",
|
"migration:status": "npm run auditlog-migration:status && knex --knexfile ./src/db/knexfile.ts --client pg migrate:status",
|
||||||
"migration:rollback": "knex --knexfile ./src/db/knexfile.ts migrate:rollback",
|
"migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./src/db/knexfile.ts migrate:rollback",
|
||||||
"migrate:org": "tsx ./scripts/migrate-organization.ts",
|
"migrate:org": "tsx ./scripts/migrate-organization.ts",
|
||||||
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
||||||
"seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
|
"seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
|
||||||
@@ -120,12 +126,14 @@
|
|||||||
"@fastify/etag": "^5.1.0",
|
"@fastify/etag": "^5.1.0",
|
||||||
"@fastify/formbody": "^7.4.0",
|
"@fastify/formbody": "^7.4.0",
|
||||||
"@fastify/helmet": "^11.1.1",
|
"@fastify/helmet": "^11.1.1",
|
||||||
|
"@fastify/multipart": "8.3.0",
|
||||||
"@fastify/passport": "^2.4.0",
|
"@fastify/passport": "^2.4.0",
|
||||||
"@fastify/rate-limit": "^9.0.0",
|
"@fastify/rate-limit": "^9.0.0",
|
||||||
"@fastify/session": "^10.7.0",
|
"@fastify/session": "^10.7.0",
|
||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
|
"@octokit/auth-app": "^7.1.1",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
|
|||||||
@@ -90,7 +90,12 @@ const main = async () => {
|
|||||||
.whereRaw("table_schema = current_schema()")
|
.whereRaw("table_schema = current_schema()")
|
||||||
.select<{ tableName: string }[]>("table_name as tableName")
|
.select<{ tableName: string }[]>("table_name as tableName")
|
||||||
.orderBy("table_name")
|
.orderBy("table_name")
|
||||||
).filter((el) => !el.tableName.includes("_migrations"));
|
).filter(
|
||||||
|
(el) =>
|
||||||
|
!el.tableName.includes("_migrations") &&
|
||||||
|
!el.tableName.includes("audit_logs_") &&
|
||||||
|
el.tableName !== "intermediate_audit_logs"
|
||||||
|
);
|
||||||
|
|
||||||
for (let i = 0; i < tables.length; i += 1) {
|
for (let i = 0; i < tables.length; i += 1) {
|
||||||
const { tableName } = tables[i];
|
const { tableName } = tables[i];
|
||||||
|
|||||||
Vendored
+2
@@ -39,6 +39,7 @@ import { TCertificateServiceFactory } from "@app/services/certificate/certificat
|
|||||||
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
||||||
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
||||||
import { TCmekServiceFactory } from "@app/services/cmek/cmek-service";
|
import { TCmekServiceFactory } from "@app/services/cmek/cmek-service";
|
||||||
|
import { TExternalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
|
||||||
import { TExternalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
import { TExternalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
||||||
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
@@ -185,6 +186,7 @@ declare module "fastify" {
|
|||||||
workflowIntegration: TWorkflowIntegrationServiceFactory;
|
workflowIntegration: TWorkflowIntegrationServiceFactory;
|
||||||
cmek: TCmekServiceFactory;
|
cmek: TCmekServiceFactory;
|
||||||
migration: TExternalMigrationServiceFactory;
|
migration: TExternalMigrationServiceFactory;
|
||||||
|
externalGroupOrgRoleMapping: TExternalGroupOrgRoleMappingServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
|||||||
Vendored
+10
@@ -336,6 +336,11 @@ import {
|
|||||||
TWorkflowIntegrationsInsert,
|
TWorkflowIntegrationsInsert,
|
||||||
TWorkflowIntegrationsUpdate
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
TExternalGroupOrgRoleMappings,
|
||||||
|
TExternalGroupOrgRoleMappingsInsert,
|
||||||
|
TExternalGroupOrgRoleMappingsUpdate
|
||||||
|
} from "@app/db/schemas/external-group-org-role-mappings";
|
||||||
import {
|
import {
|
||||||
TSecretV2TagJunction,
|
TSecretV2TagJunction,
|
||||||
TSecretV2TagJunctionInsert,
|
TSecretV2TagJunctionInsert,
|
||||||
@@ -808,5 +813,10 @@ declare module "knex/types/tables" {
|
|||||||
TWorkflowIntegrationsInsert,
|
TWorkflowIntegrationsInsert,
|
||||||
TWorkflowIntegrationsUpdate
|
TWorkflowIntegrationsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.ExternalGroupOrgRoleMapping]: KnexOriginal.CompositeTableType<
|
||||||
|
TExternalGroupOrgRoleMappings,
|
||||||
|
TExternalGroupOrgRoleMappingsInsert,
|
||||||
|
TExternalGroupOrgRoleMappingsUpdate
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// eslint-disable-next-line
|
||||||
|
import "ts-node/register";
|
||||||
|
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import type { Knex } from "knex";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
// Update with your config settings. .
|
||||||
|
dotenv.config({
|
||||||
|
path: path.join(__dirname, "../../../.env.migration")
|
||||||
|
});
|
||||||
|
dotenv.config({
|
||||||
|
path: path.join(__dirname, "../../../.env")
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!process.env.AUDIT_LOGS_DB_CONNECTION_URI && !process.env.AUDIT_LOGS_DB_HOST) {
|
||||||
|
console.info("Dedicated audit log database not found. No further migrations necessary");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info("Executing migration on audit log database...");
|
||||||
|
|
||||||
|
export default {
|
||||||
|
development: {
|
||||||
|
client: "postgres",
|
||||||
|
connection: {
|
||||||
|
connectionString: process.env.AUDIT_LOGS_DB_CONNECTION_URI,
|
||||||
|
host: process.env.AUDIT_LOGS_DB_HOST,
|
||||||
|
port: process.env.AUDIT_LOGS_DB_PORT,
|
||||||
|
user: process.env.AUDIT_LOGS_DB_USER,
|
||||||
|
database: process.env.AUDIT_LOGS_DB_NAME,
|
||||||
|
password: process.env.AUDIT_LOGS_DB_PASSWORD,
|
||||||
|
ssl: process.env.AUDIT_LOGS_DB_ROOT_CERT
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: Buffer.from(process.env.AUDIT_LOGS_DB_ROOT_CERT, "base64").toString("ascii")
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
},
|
||||||
|
pool: {
|
||||||
|
min: 2,
|
||||||
|
max: 10
|
||||||
|
},
|
||||||
|
seeds: {
|
||||||
|
directory: "./seeds"
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
tableName: "infisical_migrations"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
production: {
|
||||||
|
client: "postgres",
|
||||||
|
connection: {
|
||||||
|
connectionString: process.env.AUDIT_LOGS_DB_CONNECTION_URI,
|
||||||
|
host: process.env.AUDIT_LOGS_DB_HOST,
|
||||||
|
port: process.env.AUDIT_LOGS_DB_PORT,
|
||||||
|
user: process.env.AUDIT_LOGS_DB_USER,
|
||||||
|
database: process.env.AUDIT_LOGS_DB_NAME,
|
||||||
|
password: process.env.AUDIT_LOGS_DB_PASSWORD,
|
||||||
|
ssl: process.env.AUDIT_LOGS_DB_ROOT_CERT
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: Buffer.from(process.env.AUDIT_LOGS_DB_ROOT_CERT, "base64").toString("ascii")
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
},
|
||||||
|
pool: {
|
||||||
|
min: 2,
|
||||||
|
max: 10
|
||||||
|
},
|
||||||
|
migrations: {
|
||||||
|
tableName: "infisical_migrations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as Knex.Config;
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export type { TDbClient } from "./instance";
|
export type { TDbClient } from "./instance";
|
||||||
export { initDbConnection } from "./instance";
|
export { initAuditLogDbConnection, initDbConnection } from "./instance";
|
||||||
|
|||||||
@@ -70,3 +70,45 @@ export const initDbConnection = ({
|
|||||||
|
|
||||||
return db;
|
return db;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initAuditLogDbConnection = ({
|
||||||
|
dbConnectionUri,
|
||||||
|
dbRootCert
|
||||||
|
}: {
|
||||||
|
dbConnectionUri: string;
|
||||||
|
dbRootCert?: string;
|
||||||
|
}) => {
|
||||||
|
// akhilmhdh: the default Knex is knex.Knex<any, any[]>. but when assigned with knex({<config>}) the value is knex.Knex<any, unknown[]>
|
||||||
|
// this was causing issue with files like `snapshot-dal` `findRecursivelySnapshots` this i am explicitly putting the any and unknown[]
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const db: Knex<any, unknown[]> = knex({
|
||||||
|
client: "pg",
|
||||||
|
connection: {
|
||||||
|
connectionString: dbConnectionUri,
|
||||||
|
host: process.env.AUDIT_LOGS_DB_HOST,
|
||||||
|
// @ts-expect-error I have no clue why only for the port there is a type error
|
||||||
|
// eslint-disable-next-line
|
||||||
|
port: process.env.AUDIT_LOGS_DB_PORT,
|
||||||
|
user: process.env.AUDIT_LOGS_DB_USER,
|
||||||
|
database: process.env.AUDIT_LOGS_DB_NAME,
|
||||||
|
password: process.env.AUDIT_LOGS_DB_PASSWORD,
|
||||||
|
ssl: dbRootCert
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: Buffer.from(dbRootCert, "base64").toString("ascii")
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// we add these overrides so that auditLogDb and the primary DB are interchangeable
|
||||||
|
db.primaryNode = () => {
|
||||||
|
return db;
|
||||||
|
};
|
||||||
|
|
||||||
|
db.replicaNode = () => {
|
||||||
|
return db;
|
||||||
|
};
|
||||||
|
|
||||||
|
return db;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import kx, { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
const INTERMEDIATE_AUDIT_LOG_TABLE = "intermediate_audit_logs";
|
||||||
|
|
||||||
|
const formatPartitionDate = (date: Date) => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(date.getDate()).padStart(2, "0");
|
||||||
|
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAuditLogPartition = async (knex: Knex, startDate: Date, endDate: Date) => {
|
||||||
|
const startDateStr = formatPartitionDate(startDate);
|
||||||
|
const endDateStr = formatPartitionDate(endDate);
|
||||||
|
|
||||||
|
const partitionName = `${TableName.AuditLog}_${startDateStr.replace(/-/g, "")}_${endDateStr.replace(/-/g, "")}`;
|
||||||
|
|
||||||
|
await knex.schema.raw(
|
||||||
|
`CREATE TABLE ${partitionName} PARTITION OF ${TableName.AuditLog} FOR VALUES FROM ('${startDateStr}') TO ('${endDateStr}')`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const up = async (knex: Knex): Promise<void> => {
|
||||||
|
console.info("Dropping primary key of audit log table...");
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
// remove existing keys
|
||||||
|
t.dropPrimary();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get all indices of the audit log table and drop them
|
||||||
|
const indexNames: { rows: { indexname: string }[] } = await knex.raw(
|
||||||
|
`
|
||||||
|
SELECT indexname
|
||||||
|
FROM pg_indexes
|
||||||
|
WHERE tablename = '${TableName.AuditLog}'
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Deleting existing audit log indices:",
|
||||||
|
indexNames.rows.map((e) => e.indexname)
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const row of indexNames.rows) {
|
||||||
|
await knex.raw(`DROP INDEX IF EXISTS ${row.indexname}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// renaming audit log to intermediate table
|
||||||
|
console.log("Renaming audit log table to the intermediate name");
|
||||||
|
await knex.schema.renameTable(TableName.AuditLog, INTERMEDIATE_AUDIT_LOG_TABLE);
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.AuditLog))) {
|
||||||
|
const createTableSql = knex.schema
|
||||||
|
.createTable(TableName.AuditLog, (t) => {
|
||||||
|
t.uuid("id").defaultTo(knex.fn.uuid());
|
||||||
|
t.string("actor").notNullable();
|
||||||
|
t.jsonb("actorMetadata").notNullable();
|
||||||
|
t.string("ipAddress");
|
||||||
|
t.string("eventType").notNullable();
|
||||||
|
t.jsonb("eventMetadata");
|
||||||
|
t.string("userAgent");
|
||||||
|
t.string("userAgentType");
|
||||||
|
t.datetime("expiresAt");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.uuid("orgId");
|
||||||
|
t.string("projectId");
|
||||||
|
t.string("projectName");
|
||||||
|
t.primary(["id", "createdAt"]);
|
||||||
|
})
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
console.info("Creating partition table...");
|
||||||
|
await knex.schema.raw(`
|
||||||
|
${createTableSql} PARTITION BY RANGE ("createdAt");
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log("Adding indices...");
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
t.index(["projectId", "createdAt"]);
|
||||||
|
t.index(["orgId", "createdAt"]);
|
||||||
|
t.index("expiresAt");
|
||||||
|
t.index("orgId");
|
||||||
|
t.index("projectId");
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Adding GIN indices...");
|
||||||
|
|
||||||
|
await knex.raw(
|
||||||
|
`CREATE INDEX IF NOT EXISTS "audit_logs_actorMetadata_idx" ON ${TableName.AuditLog} USING gin("actorMetadata" jsonb_path_ops)`
|
||||||
|
);
|
||||||
|
console.log("GIN index for actorMetadata done");
|
||||||
|
|
||||||
|
await knex.raw(
|
||||||
|
`CREATE INDEX IF NOT EXISTS "audit_logs_eventMetadata_idx" ON ${TableName.AuditLog} USING gin("eventMetadata" jsonb_path_ops)`
|
||||||
|
);
|
||||||
|
console.log("GIN index for eventMetadata done");
|
||||||
|
|
||||||
|
// create default partition
|
||||||
|
console.log("Creating default partition...");
|
||||||
|
await knex.schema.raw(`CREATE TABLE ${TableName.AuditLog}_default PARTITION OF ${TableName.AuditLog} DEFAULT`);
|
||||||
|
|
||||||
|
const nextDate = new Date();
|
||||||
|
nextDate.setDate(nextDate.getDate() + 1);
|
||||||
|
const nextDateStr = formatPartitionDate(nextDate);
|
||||||
|
|
||||||
|
console.log("Attaching existing audit log table as a partition...");
|
||||||
|
await knex.schema.raw(`
|
||||||
|
ALTER TABLE ${INTERMEDIATE_AUDIT_LOG_TABLE} ADD CONSTRAINT audit_log_old
|
||||||
|
CHECK ( "createdAt" < DATE '${nextDateStr}' );
|
||||||
|
|
||||||
|
ALTER TABLE ${TableName.AuditLog} ATTACH PARTITION ${INTERMEDIATE_AUDIT_LOG_TABLE}
|
||||||
|
FOR VALUES FROM (MINVALUE) TO ('${nextDateStr}' );
|
||||||
|
`);
|
||||||
|
|
||||||
|
// create partition from now until end of month
|
||||||
|
console.log("Creating audit log partitions ahead of time... next date:", nextDateStr);
|
||||||
|
await createAuditLogPartition(knex, nextDate, new Date(nextDate.getFullYear(), nextDate.getMonth() + 1));
|
||||||
|
|
||||||
|
// create partitions 4 years ahead
|
||||||
|
const partitionMonths = 4 * 12;
|
||||||
|
const partitionPromises: Promise<void>[] = [];
|
||||||
|
for (let x = 1; x <= partitionMonths; x += 1) {
|
||||||
|
partitionPromises.push(
|
||||||
|
createAuditLogPartition(
|
||||||
|
knex,
|
||||||
|
new Date(nextDate.getFullYear(), nextDate.getMonth() + x, 1),
|
||||||
|
new Date(nextDate.getFullYear(), nextDate.getMonth() + (x + 1), 1)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(partitionPromises);
|
||||||
|
console.log("Partition migration complete");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const executeMigration = async (url: string) => {
|
||||||
|
console.log("Executing migration...");
|
||||||
|
const knex = kx({
|
||||||
|
client: "pg",
|
||||||
|
connection: url
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.transaction(async (tx) => {
|
||||||
|
await up(tx);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const dbUrl = process.env.AUDIT_LOGS_DB_CONNECTION_URI;
|
||||||
|
if (!dbUrl) {
|
||||||
|
console.error("Please provide a DB connection URL to the AUDIT_LOGS_DB_CONNECTION_URI env");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeMigration(dbUrl).then(() => {
|
||||||
|
console.log("Migration: partition-audit-logs DONE");
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
if (!hasSlug) {
|
||||||
|
// add slug back temporarily and set value equal to name
|
||||||
|
await knex.schema
|
||||||
|
.alterTable(TableName.KmsKey, (table) => {
|
||||||
|
table.string("slug", 32);
|
||||||
|
})
|
||||||
|
.then(() => knex(TableName.KmsKey).update("slug", knex.ref("name")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
if (hasSlug) {
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
||||||
|
table.dropColumn("slug");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||||
|
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
|
||||||
|
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
|
||||||
|
const doesProjectNameExist = await knex.schema.hasColumn(TableName.AuditLog, "projectName");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
if (doesOrgIdExist) {
|
||||||
|
t.dropForeign("orgId");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doesProjectIdExist) {
|
||||||
|
t.dropForeign("projectId");
|
||||||
|
}
|
||||||
|
|
||||||
|
// add normalized field
|
||||||
|
if (!doesProjectNameExist) {
|
||||||
|
t.string("projectName");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
|
||||||
|
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
|
||||||
|
const doesProjectNameExist = await knex.schema.hasColumn(TableName.AuditLog, "projectName");
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.AuditLog)) {
|
||||||
|
await knex.schema.alterTable(TableName.AuditLog, (t) => {
|
||||||
|
if (doesOrgIdExist) {
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
}
|
||||||
|
if (doesProjectIdExist) {
|
||||||
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove normalized field
|
||||||
|
if (doesProjectNameExist) {
|
||||||
|
t.dropColumn("projectName");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
// org default role
|
||||||
|
if (await knex.schema.hasTable(TableName.Organization)) {
|
||||||
|
const hasDefaultRoleCol = await knex.schema.hasColumn(TableName.Organization, "defaultMembershipRole");
|
||||||
|
|
||||||
|
if (!hasDefaultRoleCol) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (tb) => {
|
||||||
|
tb.string("defaultMembershipRole").notNullable().defaultTo("member");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
// org default role
|
||||||
|
if (await knex.schema.hasTable(TableName.Organization)) {
|
||||||
|
const hasDefaultRoleCol = await knex.schema.hasColumn(TableName.Organization, "defaultMembershipRole");
|
||||||
|
|
||||||
|
if (hasDefaultRoleCol) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (tb) => {
|
||||||
|
tb.dropColumn("defaultMembershipRole");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.IdentityMetadata, "value")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityMetadata, (t) => {
|
||||||
|
t.string("value", 1020).alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityMetadata, "value")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityMetadata, (t) => {
|
||||||
|
t.string("value", 255).alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "@app/db/utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
// add external group to org role mapping table
|
||||||
|
if (!(await knex.schema.hasTable(TableName.ExternalGroupOrgRoleMapping))) {
|
||||||
|
await knex.schema.createTable(TableName.ExternalGroupOrgRoleMapping, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("groupName").notNullable();
|
||||||
|
t.index("groupName");
|
||||||
|
t.string("role").notNullable();
|
||||||
|
t.uuid("roleId");
|
||||||
|
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
|
||||||
|
t.uuid("orgId").notNullable();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
t.unique(["orgId", "groupName"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ExternalGroupOrgRoleMapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.ExternalGroupOrgRoleMapping)) {
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ExternalGroupOrgRoleMapping);
|
||||||
|
|
||||||
|
await knex.schema.dropTable(TableName.ExternalGroupOrgRoleMapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,8 @@ export const AuditLogsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
orgId: z.string().uuid().nullable().optional(),
|
orgId: z.string().uuid().nullable().optional(),
|
||||||
projectId: z.string().nullable().optional()
|
projectId: z.string().nullable().optional(),
|
||||||
|
projectName: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAuditLogs = z.infer<typeof AuditLogsSchema>;
|
export type TAuditLogs = z.infer<typeof AuditLogsSchema>;
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const ExternalGroupOrgRoleMappingsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
groupName: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
roleId: z.string().uuid().nullable().optional(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TExternalGroupOrgRoleMappings = z.infer<typeof ExternalGroupOrgRoleMappingsSchema>;
|
||||||
|
export type TExternalGroupOrgRoleMappingsInsert = Omit<
|
||||||
|
z.input<typeof ExternalGroupOrgRoleMappingsSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TExternalGroupOrgRoleMappingsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof ExternalGroupOrgRoleMappingsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -16,7 +16,8 @@ export const KmsKeysSchema = z.object({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
projectId: z.string().nullable().optional()
|
projectId: z.string().nullable().optional(),
|
||||||
|
slug: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export enum TableName {
|
|||||||
Groups = "groups",
|
Groups = "groups",
|
||||||
GroupProjectMembership = "group_project_memberships",
|
GroupProjectMembership = "group_project_memberships",
|
||||||
GroupProjectMembershipRole = "group_project_membership_roles",
|
GroupProjectMembershipRole = "group_project_membership_roles",
|
||||||
|
ExternalGroupOrgRoleMapping = "external_group_org_role_mappings",
|
||||||
UserGroupMembership = "user_group_membership",
|
UserGroupMembership = "user_group_membership",
|
||||||
UserAliases = "user_aliases",
|
UserAliases = "user_aliases",
|
||||||
UserEncryptionKey = "user_encryption_keys",
|
UserEncryptionKey = "user_encryption_keys",
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export const OrganizationsSchema = z.object({
|
|||||||
authEnforced: z.boolean().default(false).nullable().optional(),
|
authEnforced: z.boolean().default(false).nullable().optional(),
|
||||||
scimEnabled: z.boolean().default(false).nullable().optional(),
|
scimEnabled: z.boolean().default(false).nullable().optional(),
|
||||||
kmsDefaultKeyId: z.string().uuid().nullable().optional(),
|
kmsDefaultKeyId: z.string().uuid().nullable().optional(),
|
||||||
kmsEncryptedDataKey: zodBuffer.nullable().optional()
|
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
|
||||||
|
defaultMembershipRole: z.string().default("member")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
||||||
|
|||||||
@@ -128,7 +128,10 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
.map((key) => {
|
.map((key) => {
|
||||||
// for the ones like in format: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email
|
// for the ones like in format: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email
|
||||||
const formatedKey = key.startsWith("http") ? key.split("/").at(-1) || "" : key;
|
const formatedKey = key.startsWith("http") ? key.split("/").at(-1) || "" : key;
|
||||||
return { key: formatedKey, value: String((profile.attributes as Record<string, string>)[key]) };
|
return {
|
||||||
|
key: formatedKey,
|
||||||
|
value: String((profile.attributes as Record<string, string>)[key]).substring(0, 1020)
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.filter((el) => el.key && !["email", "firstName", "lastName"].includes(el.key));
|
.filter((el) => el.key && !["email", "firstName", "lastName"].includes(el.key));
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const ScimUserSchema = z.object({
|
|||||||
z.object({
|
z.object({
|
||||||
primary: z.boolean(),
|
primary: z.boolean(),
|
||||||
value: z.string().email(),
|
value: z.string().email(),
|
||||||
type: z.string().trim()
|
type: z.string().trim().default("work")
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -210,8 +210,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
primary: z.boolean(),
|
primary: z.boolean(),
|
||||||
value: z.string().email(),
|
value: z.string().email()
|
||||||
type: z.string().trim()
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -281,8 +280,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
primary: z.boolean(),
|
primary: z.boolean(),
|
||||||
value: z.string().email(),
|
value: z.string().email()
|
||||||
type: z.string().trim()
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -301,7 +299,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
z.object({
|
z.object({
|
||||||
primary: z.boolean(),
|
primary: z.boolean(),
|
||||||
value: z.string().email(),
|
value: z.string().email(),
|
||||||
type: z.string().trim()
|
type: z.string().trim().default("work")
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
displayName: z.string().trim(),
|
displayName: z.string().trim(),
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
||||||
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -23,6 +25,13 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (!appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(req.auth.orgId)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Secret scanning is temporarily unavailable."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const session = await server.services.secretScanning.createInstallationSession({
|
const session = await server.services.secretScanning.createInstallationSession({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -30,6 +39,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.body.organizationId
|
orgId: req.body.organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Knex } from "knex";
|
// weird commonjs-related error in the CI requires us to do the import like this
|
||||||
|
import knex from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { AuditLogsSchema, TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueName } from "@app/queue";
|
import { QueueName } from "@app/queue";
|
||||||
@@ -46,7 +47,7 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
eventType?: EventType[];
|
eventType?: EventType[];
|
||||||
eventMetadata?: Record<string, string>;
|
eventMetadata?: Record<string, string>;
|
||||||
},
|
},
|
||||||
tx?: Knex
|
tx?: knex.Knex
|
||||||
) => {
|
) => {
|
||||||
if (!orgId && !projectId) {
|
if (!orgId && !projectId) {
|
||||||
throw new Error("Either orgId or projectId must be provided");
|
throw new Error("Either orgId or projectId must be provided");
|
||||||
@@ -55,11 +56,10 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
try {
|
try {
|
||||||
// Find statements
|
// Find statements
|
||||||
const sqlQuery = (tx || db.replicaNode())(TableName.AuditLog)
|
const sqlQuery = (tx || db.replicaNode())(TableName.AuditLog)
|
||||||
.leftJoin(TableName.Project, `${TableName.AuditLog}.projectId`, `${TableName.Project}.id`)
|
|
||||||
// eslint-disable-next-line func-names
|
// eslint-disable-next-line func-names
|
||||||
.where(function () {
|
.where(function () {
|
||||||
if (orgId) {
|
if (orgId) {
|
||||||
void this.where(`${TableName.Project}.orgId`, orgId).orWhere(`${TableName.AuditLog}.orgId`, orgId);
|
void this.where(`${TableName.AuditLog}.orgId`, orgId);
|
||||||
} else if (projectId) {
|
} else if (projectId) {
|
||||||
void this.where(`${TableName.AuditLog}.projectId`, projectId);
|
void this.where(`${TableName.AuditLog}.projectId`, projectId);
|
||||||
}
|
}
|
||||||
@@ -72,23 +72,19 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
// Select statements
|
// Select statements
|
||||||
void sqlQuery
|
void sqlQuery
|
||||||
.select(selectAllTableCols(TableName.AuditLog))
|
.select(selectAllTableCols(TableName.AuditLog))
|
||||||
.select(
|
|
||||||
db.ref("name").withSchema(TableName.Project).as("projectName"),
|
|
||||||
db.ref("slug").withSchema(TableName.Project).as("projectSlug")
|
|
||||||
)
|
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.orderBy(`${TableName.AuditLog}.createdAt`, "desc");
|
.orderBy(`${TableName.AuditLog}.createdAt`, "desc");
|
||||||
|
|
||||||
// Special case: Filter by actor ID
|
// Special case: Filter by actor ID
|
||||||
if (actorId) {
|
if (actorId) {
|
||||||
void sqlQuery.whereRaw(`"actorMetadata"->>'userId' = ?`, [actorId]);
|
void sqlQuery.whereRaw(`"actorMetadata" @> jsonb_build_object('userId', ?::text)`, [actorId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special case: Filter by key/value pairs in eventMetadata field
|
// Special case: Filter by key/value pairs in eventMetadata field
|
||||||
if (eventMetadata && Object.keys(eventMetadata).length) {
|
if (eventMetadata && Object.keys(eventMetadata).length) {
|
||||||
Object.entries(eventMetadata).forEach(([key, value]) => {
|
Object.entries(eventMetadata).forEach(([key, value]) => {
|
||||||
void sqlQuery.whereRaw(`"eventMetadata"->>'${key}' = ?`, [value]);
|
void sqlQuery.whereRaw(`"eventMetadata" @> jsonb_build_object(?::text, ?::text)`, [key, value]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,30 +105,25 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
if (endDate) {
|
if (endDate) {
|
||||||
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, "<=", endDate);
|
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, "<=", endDate);
|
||||||
}
|
}
|
||||||
const docs = await sqlQuery;
|
|
||||||
|
|
||||||
return docs.map((doc) => {
|
// we timeout long running queries to prevent DB resource issues (2 minutes)
|
||||||
// Our type system refuses to acknowledge that the project name and slug are present in the doc, due to the disjointed query structure above.
|
const docs = await sqlQuery.timeout(1000 * 120);
|
||||||
// This is a quick and dirty way to get around the types.
|
|
||||||
const projectDoc = doc as unknown as { projectName: string; projectSlug: string };
|
|
||||||
|
|
||||||
return {
|
return docs;
|
||||||
...AuditLogsSchema.parse(doc),
|
|
||||||
...(projectDoc?.projectSlug && {
|
|
||||||
project: {
|
|
||||||
name: projectDoc.projectName,
|
|
||||||
slug: projectDoc.projectSlug
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof knex.KnexTimeoutError) {
|
||||||
|
throw new GatewayTimeoutError({
|
||||||
|
error,
|
||||||
|
message: "Failed to fetch audit logs due to timeout. Add more search filters."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
throw new DatabaseError({ error });
|
throw new DatabaseError({ error });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// delete all audit log that have expired
|
// delete all audit log that have expired
|
||||||
const pruneAuditLog = async (tx?: Knex) => {
|
const pruneAuditLog = async (tx?: knex.Knex) => {
|
||||||
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
|
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
|
||||||
const MAX_RETRY_ON_FAILURE = 3;
|
const MAX_RETRY_ON_FAILURE = 3;
|
||||||
|
|
||||||
@@ -148,6 +139,7 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
.where("expiresAt", "<", today)
|
.where("expiresAt", "<", today)
|
||||||
.select("id")
|
.select("id")
|
||||||
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
|
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
deletedAuditLogIds = await (tx || db)(TableName.AuditLog)
|
deletedAuditLogIds = await (tx || db)(TableName.AuditLog)
|
||||||
.whereIn("id", findExpiredLogSubQuery)
|
.whereIn("id", findExpiredLogSubQuery)
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export const auditLogQueueServiceFactory = ({
|
|||||||
actorMetadata: actor.metadata,
|
actorMetadata: actor.metadata,
|
||||||
userAgent,
|
userAgent,
|
||||||
projectId,
|
projectId,
|
||||||
|
projectName: project?.name,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
orgId,
|
orgId,
|
||||||
eventType: event.type,
|
eventType: event.type,
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ export enum EventType {
|
|||||||
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
|
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
|
||||||
DELETE_WEBHOOK = "delete-webhook",
|
DELETE_WEBHOOK = "delete-webhook",
|
||||||
GET_SECRET_IMPORTS = "get-secret-imports",
|
GET_SECRET_IMPORTS = "get-secret-imports",
|
||||||
|
GET_SECRET_IMPORT = "get-secret-import",
|
||||||
CREATE_SECRET_IMPORT = "create-secret-import",
|
CREATE_SECRET_IMPORT = "create-secret-import",
|
||||||
UPDATE_SECRET_IMPORT = "update-secret-import",
|
UPDATE_SECRET_IMPORT = "update-secret-import",
|
||||||
DELETE_SECRET_IMPORT = "delete-secret-import",
|
DELETE_SECRET_IMPORT = "delete-secret-import",
|
||||||
@@ -189,7 +190,9 @@ export enum EventType {
|
|||||||
DELETE_CMEK = "delete-cmek",
|
DELETE_CMEK = "delete-cmek",
|
||||||
GET_CMEKS = "get-cmeks",
|
GET_CMEKS = "get-cmeks",
|
||||||
CMEK_ENCRYPT = "cmek-encrypt",
|
CMEK_ENCRYPT = "cmek-encrypt",
|
||||||
CMEK_DECRYPT = "cmek-decrypt"
|
CMEK_DECRYPT = "cmek-decrypt",
|
||||||
|
UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping",
|
||||||
|
GET_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "get-external-group-org-role-mapping"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@@ -1011,6 +1014,14 @@ interface GetSecretImportsEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetSecretImportEvent {
|
||||||
|
type: EventType.GET_SECRET_IMPORT;
|
||||||
|
metadata: {
|
||||||
|
secretImportId: string;
|
||||||
|
folderId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateSecretImportEvent {
|
interface CreateSecretImportEvent {
|
||||||
type: EventType.CREATE_SECRET_IMPORT;
|
type: EventType.CREATE_SECRET_IMPORT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1595,6 +1606,18 @@ interface CmekDecryptEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetExternalGroupOrgRoleMappingsEvent {
|
||||||
|
type: EventType.GET_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS;
|
||||||
|
metadata?: Record<string, never>; // not needed, based off orgId
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateExternalGroupOrgRoleMappingsEvent {
|
||||||
|
type: EventType.UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS;
|
||||||
|
metadata: {
|
||||||
|
mappings: { groupName: string; roleSlug: string }[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@@ -1674,6 +1697,7 @@ export type Event =
|
|||||||
| UpdateWebhookStatusEvent
|
| UpdateWebhookStatusEvent
|
||||||
| DeleteWebhookEvent
|
| DeleteWebhookEvent
|
||||||
| GetSecretImportsEvent
|
| GetSecretImportsEvent
|
||||||
|
| GetSecretImportEvent
|
||||||
| CreateSecretImportEvent
|
| CreateSecretImportEvent
|
||||||
| UpdateSecretImportEvent
|
| UpdateSecretImportEvent
|
||||||
| DeleteSecretImportEvent
|
| DeleteSecretImportEvent
|
||||||
@@ -1740,4 +1764,6 @@ export type Event =
|
|||||||
| DeleteCmekEvent
|
| DeleteCmekEvent
|
||||||
| GetCmeksEvent
|
| GetCmeksEvent
|
||||||
| CmekEncryptEvent
|
| CmekEncryptEvent
|
||||||
| CmekDecryptEvent;
|
| CmekDecryptEvent
|
||||||
|
| GetExternalGroupOrgRoleMappingsEvent
|
||||||
|
| UpdateExternalGroupOrgRoleMappingsEvent;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { compile } from "handlebars";
|
import handlebars from "handlebars";
|
||||||
import ldapjs from "ldapjs";
|
import ldapjs from "ldapjs";
|
||||||
import ldif from "ldif";
|
import ldif from "ldif";
|
||||||
import { customAlphabet } from "nanoid";
|
import { customAlphabet } from "nanoid";
|
||||||
@@ -40,7 +40,7 @@ const generateLDIF = ({
|
|||||||
EncodedPassword: encodePassword(password)
|
EncodedPassword: encodePassword(password)
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderTemplate = compile(ldifTemplate);
|
const renderTemplate = handlebars.compile(ldifTemplate);
|
||||||
const renderedLdif = renderTemplate(data);
|
const renderedLdif = renderTemplate(data);
|
||||||
|
|
||||||
return renderedLdif;
|
return renderedLdif;
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import {
|
import { OrgMembershipStatus, SecretKeyEncoding, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas";
|
||||||
OrgMembershipRole,
|
|
||||||
OrgMembershipStatus,
|
|
||||||
SecretKeyEncoding,
|
|
||||||
TableName,
|
|
||||||
TLdapConfigsUpdate,
|
|
||||||
TUsers
|
|
||||||
} from "@app/db/schemas";
|
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
@@ -28,6 +21,7 @@ import { TokenType } from "@app/services/auth-token/auth-token-types";
|
|||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
@@ -444,11 +438,14 @@ export const ldapConfigServiceFactory = ({
|
|||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgDAL.createMembership(
|
await orgDAL.createMembership(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
userId: userAlias.userId,
|
||||||
orgId,
|
orgId,
|
||||||
role: OrgMembershipRole.Member,
|
role,
|
||||||
|
roleId,
|
||||||
status: OrgMembershipStatus.Accepted,
|
status: OrgMembershipStatus.Accepted,
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
@@ -529,12 +526,15 @@ export const ldapConfigServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgMembershipDAL.create(
|
await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
orgId,
|
orgId,
|
||||||
role: OrgMembershipRole.Member,
|
role,
|
||||||
|
roleId,
|
||||||
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
|
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
import { OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
||||||
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
|
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
@@ -23,6 +23,7 @@ import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-se
|
|||||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
@@ -187,12 +188,15 @@ export const oidcConfigServiceFactory = ({
|
|||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgMembershipDAL.create(
|
await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
userId: userAlias.userId,
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
orgId,
|
orgId,
|
||||||
role: OrgMembershipRole.Member,
|
role,
|
||||||
|
roleId,
|
||||||
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
@@ -261,12 +265,15 @@ export const oidcConfigServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgMembershipDAL.create(
|
await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
orgId,
|
orgId,
|
||||||
role: OrgMembershipRole.Member,
|
role,
|
||||||
|
roleId,
|
||||||
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OrgMembershipRole,
|
|
||||||
OrgMembershipStatus,
|
OrgMembershipStatus,
|
||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
TableName,
|
TableName,
|
||||||
@@ -26,6 +25,7 @@ import { TokenType } from "@app/services/auth-token/auth-token-types";
|
|||||||
import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
|
import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
|
||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
@@ -369,12 +369,15 @@ export const samlConfigServiceFactory = ({
|
|||||||
{ tx }
|
{ tx }
|
||||||
);
|
);
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgMembershipDAL.create(
|
await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
userId: userAlias.userId,
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
orgId,
|
orgId,
|
||||||
role: OrgMembershipRole.Member,
|
role,
|
||||||
|
roleId,
|
||||||
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
@@ -472,12 +475,15 @@ export const samlConfigServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
|
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
|
||||||
|
|
||||||
await orgMembershipDAL.create(
|
await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
orgId,
|
orgId,
|
||||||
role: OrgMembershipRole.Member,
|
role,
|
||||||
|
roleId,
|
||||||
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import slugify from "@sindresorhus/slugify";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { scimPatch } from "scim-patch";
|
import { scimPatch } from "scim-patch";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus, TableName, TOrgMemberships, TUsers } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas";
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
@@ -13,9 +13,11 @@ import { BadRequestError, NotFoundError, ScimRequestError, UnauthorizedError } f
|
|||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
import { AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
|
import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { deleteOrgMembershipFn } from "@app/services/org/org-fns";
|
import { deleteOrgMembershipFn } from "@app/services/org/org-fns";
|
||||||
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
@@ -70,7 +72,10 @@ type TScimServiceFactoryDep = {
|
|||||||
| "transaction"
|
| "transaction"
|
||||||
| "updateMembershipById"
|
| "updateMembershipById"
|
||||||
>;
|
>;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById" | "findById">;
|
orgMembershipDAL: Pick<
|
||||||
|
TOrgMembershipDALFactory,
|
||||||
|
"find" | "findOne" | "create" | "updateById" | "findById" | "update"
|
||||||
|
>;
|
||||||
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">;
|
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
|
||||||
groupDAL: Pick<
|
groupDAL: Pick<
|
||||||
@@ -101,6 +106,7 @@ type TScimServiceFactoryDep = {
|
|||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
|
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>;
|
export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>;
|
||||||
@@ -121,7 +127,8 @@ export const scimServiceFactory = ({
|
|||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectUserAdditionalPrivilegeDAL,
|
projectUserAdditionalPrivilegeDAL,
|
||||||
smtpService
|
smtpService,
|
||||||
|
externalGroupOrgRoleMappingDAL
|
||||||
}: TScimServiceFactoryDep) => {
|
}: TScimServiceFactoryDep) => {
|
||||||
const createScimToken = async ({
|
const createScimToken = async ({
|
||||||
actor,
|
actor,
|
||||||
@@ -318,12 +325,15 @@ export const scimServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
|
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
|
||||||
|
|
||||||
orgMembership = await orgMembershipDAL.create(
|
orgMembership = await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
userId: userAlias.userId,
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
orgId,
|
orgId,
|
||||||
role: OrgMembershipRole.NoAccess,
|
role,
|
||||||
|
roleId,
|
||||||
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
@@ -391,12 +401,15 @@ export const scimServiceFactory = ({
|
|||||||
orgMembership = foundOrgMembership;
|
orgMembership = foundOrgMembership;
|
||||||
|
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
|
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
|
||||||
|
|
||||||
orgMembership = await orgMembershipDAL.create(
|
orgMembership = await orgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
orgId,
|
orgId,
|
||||||
role: OrgMembershipRole.Member,
|
role,
|
||||||
|
roleId,
|
||||||
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
@@ -685,6 +698,43 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const $syncNewMembersRoles = async (group: TGroups, members: TScimGroup["members"]) => {
|
||||||
|
// this function handles configuring newly provisioned users org membership if an external group mapping exists
|
||||||
|
|
||||||
|
if (!members.length) return;
|
||||||
|
|
||||||
|
const externalGroupMapping = await externalGroupOrgRoleMappingDAL.findOne({
|
||||||
|
orgId: group.orgId,
|
||||||
|
groupName: group.name
|
||||||
|
});
|
||||||
|
|
||||||
|
// no mapping, user will have default org membership
|
||||||
|
if (!externalGroupMapping) return;
|
||||||
|
|
||||||
|
// only get org memberships that are new (invites)
|
||||||
|
const newOrgMemberships = await orgMembershipDAL.find({
|
||||||
|
status: "invited",
|
||||||
|
$in: {
|
||||||
|
id: members.map((member) => member.value)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!newOrgMemberships.length) return;
|
||||||
|
|
||||||
|
// set new membership roles to group mapping value
|
||||||
|
await orgMembershipDAL.update(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
id: newOrgMemberships.map((membership) => membership.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: externalGroupMapping.role,
|
||||||
|
roleId: externalGroupMapping.roleId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const createScimGroup = async ({ displayName, orgId, members }: TCreateScimGroupDTO) => {
|
const createScimGroup = async ({ displayName, orgId, members }: TCreateScimGroupDTO) => {
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
if (!plan.groups)
|
if (!plan.groups)
|
||||||
@@ -738,6 +788,8 @@ export const scimServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await $syncNewMembersRoles(group, members);
|
||||||
|
|
||||||
return { group, newMembers };
|
return { group, newMembers };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -813,22 +865,41 @@ export const scimServiceFactory = ({
|
|||||||
orgId: string,
|
orgId: string,
|
||||||
{ displayName, members = [] }: { displayName: string; members: { value: string }[] }
|
{ displayName, members = [] }: { displayName: string; members: { value: string }[] }
|
||||||
) => {
|
) => {
|
||||||
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
let group = await groupDAL.findOne({
|
||||||
const [group] = await groupDAL.update(
|
id: groupId,
|
||||||
{
|
orgId
|
||||||
id: groupId,
|
});
|
||||||
orgId
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: displayName
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!group) {
|
if (!group) {
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
detail: "Group Not Found",
|
detail: "Group Not Found",
|
||||||
status: 404
|
status: 404
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
||||||
|
if (group.name !== displayName) {
|
||||||
|
await externalGroupOrgRoleMappingDAL.update(
|
||||||
|
{
|
||||||
|
groupName: group.name,
|
||||||
|
orgId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
groupName: displayName
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [modifiedGroup] = await groupDAL.update(
|
||||||
|
{
|
||||||
|
id: groupId,
|
||||||
|
orgId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: displayName
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
group = modifiedGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
const orgMemberships = members.length
|
const orgMemberships = members.length
|
||||||
@@ -885,6 +956,8 @@ export const scimServiceFactory = ({
|
|||||||
return group;
|
return group;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await $syncNewMembersRoles(group, members);
|
||||||
|
|
||||||
return updatedGroup;
|
return updatedGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { ProbotOctokit } from "probot";
|
import { ProbotOctokit } from "probot";
|
||||||
|
|
||||||
import { OrgMembershipRole } from "@app/db/schemas";
|
import { OrgMembershipRole, TableName } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
@@ -61,7 +61,7 @@ export const secretScanningQueueFactory = ({
|
|||||||
const getOrgAdminEmails = async (organizationId: string) => {
|
const getOrgAdminEmails = async (organizationId: string) => {
|
||||||
// get emails of admins
|
// get emails of admins
|
||||||
const adminsOfWork = await orgMemberDAL.findMembership({
|
const adminsOfWork = await orgMemberDAL.findMembership({
|
||||||
orgId: organizationId,
|
[`${TableName.Organization}.id` as string]: organizationId,
|
||||||
role: OrgMembershipRole.Admin
|
role: OrgMembershipRole.Admin
|
||||||
});
|
});
|
||||||
return adminsOfWork.filter((userObject) => userObject.email).map((userObject) => userObject.email as string);
|
return adminsOfWork.filter((userObject) => userObject.email).map((userObject) => userObject.email as string);
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export const secretScanningServiceFactory = ({
|
|||||||
const {
|
const {
|
||||||
data: { repositories }
|
data: { repositories }
|
||||||
} = await octokit.apps.listReposAccessibleToInstallation();
|
} = await octokit.apps.listReposAccessibleToInstallation();
|
||||||
if (!appCfg.DISABLE_SECRET_SCANNING) {
|
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(actorOrgId)) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
repositories.map(({ id, full_name }) =>
|
repositories.map(({ id, full_name }) =>
|
||||||
secretScanningQueue.startFullRepoScan({
|
secretScanningQueue.startFullRepoScan({
|
||||||
@@ -164,7 +164,7 @@ export const secretScanningServiceFactory = ({
|
|||||||
});
|
});
|
||||||
if (!installationLink) return;
|
if (!installationLink) return;
|
||||||
|
|
||||||
if (!appCfg.DISABLE_SECRET_SCANNING) {
|
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(installationLink.orgId)) {
|
||||||
await secretScanningQueue.startPushEventScan({
|
await secretScanningQueue.startPushEventScan({
|
||||||
commits,
|
commits,
|
||||||
pusher: { name: pusher.name, email: pusher.email },
|
pusher: { name: pusher.name, email: pusher.email },
|
||||||
|
|||||||
@@ -240,7 +240,8 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
const snapshotSecrets = await snapshotSecretV2BridgeDAL.insertMany(
|
|
||||||
|
const snapshotSecrets = await snapshotSecretV2BridgeDAL.batchInsert(
|
||||||
secretVersions.map(({ id }) => ({
|
secretVersions.map(({ id }) => ({
|
||||||
secretVersionId: id,
|
secretVersionId: id,
|
||||||
envId: folder.environment.envId,
|
envId: folder.environment.envId,
|
||||||
@@ -248,7 +249,8 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
const snapshotFolders = await snapshotFolderDAL.insertMany(
|
|
||||||
|
const snapshotFolders = await snapshotFolderDAL.batchInsert(
|
||||||
folderVersions.map(({ id }) => ({
|
folderVersions.map(({ id }) => ({
|
||||||
folderVersionId: id,
|
folderVersionId: id,
|
||||||
envId: folder.environment.envId,
|
envId: folder.environment.envId,
|
||||||
|
|||||||
@@ -533,7 +533,8 @@ export const ENVIRONMENTS = {
|
|||||||
CREATE: {
|
CREATE: {
|
||||||
workspaceId: "The ID of the project to create the environment in.",
|
workspaceId: "The ID of the project to create the environment in.",
|
||||||
name: "The name of the environment to create.",
|
name: "The name of the environment to create.",
|
||||||
slug: "The slug of the environment to create."
|
slug: "The slug of the environment to create.",
|
||||||
|
position: "The position of the environment. The lowest number will be displayed as the first environment."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
workspaceId: "The ID of the project to update the environment in.",
|
workspaceId: "The ID of the project to update the environment in.",
|
||||||
@@ -675,6 +676,9 @@ export const SECRET_IMPORTS = {
|
|||||||
environment: "The slug of the environment to list secret imports from.",
|
environment: "The slug of the environment to list secret imports from.",
|
||||||
path: "The path to list secret imports from."
|
path: "The path to list secret imports from."
|
||||||
},
|
},
|
||||||
|
GET: {
|
||||||
|
secretImportId: "The ID of the secret import to fetch."
|
||||||
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
environment: "The slug of the environment to import into.",
|
environment: "The slug of the environment to import into.",
|
||||||
path: "The path to import into.",
|
path: "The path to import into.",
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ const envSchema = z
|
|||||||
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default(
|
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default(
|
||||||
`postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`
|
`postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`
|
||||||
),
|
),
|
||||||
|
AUDIT_LOGS_DB_CONNECTION_URI: zpStr(
|
||||||
|
z.string().describe("Postgres database connection string for Audit logs").optional()
|
||||||
|
),
|
||||||
|
AUDIT_LOGS_DB_ROOT_CERT: zpStr(
|
||||||
|
z.string().describe("Postgres database base64-encoded CA cert for Audit logs").optional()
|
||||||
|
),
|
||||||
MAX_LEASE_LIMIT: z.coerce.number().default(10000),
|
MAX_LEASE_LIMIT: z.coerce.number().default(10000),
|
||||||
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
|
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
|
||||||
DB_HOST: zpStr(z.string().describe("Postgres database host").optional()),
|
DB_HOST: zpStr(z.string().describe("Postgres database host").optional()),
|
||||||
@@ -111,9 +117,16 @@ const envSchema = z
|
|||||||
// gcp secret manager
|
// gcp secret manager
|
||||||
CLIENT_ID_GCP_SECRET_MANAGER: zpStr(z.string().optional()),
|
CLIENT_ID_GCP_SECRET_MANAGER: zpStr(z.string().optional()),
|
||||||
CLIENT_SECRET_GCP_SECRET_MANAGER: zpStr(z.string().optional()),
|
CLIENT_SECRET_GCP_SECRET_MANAGER: zpStr(z.string().optional()),
|
||||||
// github
|
// github oauth
|
||||||
CLIENT_ID_GITHUB: zpStr(z.string().optional()),
|
CLIENT_ID_GITHUB: zpStr(z.string().optional()),
|
||||||
CLIENT_SECRET_GITHUB: zpStr(z.string().optional()),
|
CLIENT_SECRET_GITHUB: zpStr(z.string().optional()),
|
||||||
|
// github app
|
||||||
|
CLIENT_ID_GITHUB_APP: zpStr(z.string().optional()),
|
||||||
|
CLIENT_SECRET_GITHUB_APP: zpStr(z.string().optional()),
|
||||||
|
CLIENT_PRIVATE_KEY_GITHUB_APP: zpStr(z.string().optional()),
|
||||||
|
CLIENT_APP_ID_GITHUB_APP: z.coerce.number().optional(),
|
||||||
|
CLIENT_SLUG_GITHUB_APP: zpStr(z.string().optional()),
|
||||||
|
|
||||||
// azure
|
// azure
|
||||||
CLIENT_ID_AZURE: zpStr(z.string().optional()),
|
CLIENT_ID_AZURE: zpStr(z.string().optional()),
|
||||||
CLIENT_SECRET_AZURE: zpStr(z.string().optional()),
|
CLIENT_SECRET_AZURE: zpStr(z.string().optional()),
|
||||||
@@ -129,6 +142,7 @@ const envSchema = z
|
|||||||
SECRET_SCANNING_WEBHOOK_SECRET: zpStr(z.string().optional()),
|
SECRET_SCANNING_WEBHOOK_SECRET: zpStr(z.string().optional()),
|
||||||
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
|
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
|
||||||
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
|
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
|
||||||
|
SECRET_SCANNING_ORG_WHITELIST: zpStr(z.string().optional()),
|
||||||
// LICENSE
|
// LICENSE
|
||||||
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
|
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
|
||||||
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
|
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
|
||||||
@@ -164,7 +178,8 @@ const envSchema = z
|
|||||||
Boolean(data.SECRET_SCANNING_GIT_APP_ID) &&
|
Boolean(data.SECRET_SCANNING_GIT_APP_ID) &&
|
||||||
Boolean(data.SECRET_SCANNING_PRIVATE_KEY) &&
|
Boolean(data.SECRET_SCANNING_PRIVATE_KEY) &&
|
||||||
Boolean(data.SECRET_SCANNING_WEBHOOK_SECRET),
|
Boolean(data.SECRET_SCANNING_WEBHOOK_SECRET),
|
||||||
samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG
|
samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG,
|
||||||
|
SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(",")
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let envCfg: Readonly<z.infer<typeof envSchema>>;
|
let envCfg: Readonly<z.infer<typeof envSchema>>;
|
||||||
|
|||||||
@@ -23,6 +23,18 @@ export class InternalServerError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class GatewayTimeoutError extends Error {
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
error: unknown;
|
||||||
|
|
||||||
|
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) {
|
||||||
|
super(message || "Timeout error");
|
||||||
|
this.name = name || "GatewayTimeoutError";
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class UnauthorizedError extends Error {
|
export class UnauthorizedError extends Error {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@@ -59,6 +71,13 @@ export class BadRequestError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class RateLimitError extends Error {
|
||||||
|
constructor({ message }: { message?: string }) {
|
||||||
|
super(message || "Rate limit exceeded");
|
||||||
|
this.name = "RateLimitExceeded";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class NotFoundError extends Error {
|
export class NotFoundError extends Error {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
|||||||
@@ -70,3 +70,14 @@ export const objectify = <T, Key extends string | number | symbol, Value = T>(
|
|||||||
{} as Record<Key, Value>
|
{} as Record<Key, Value>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chunks an array into smaller arrays of the given size.
|
||||||
|
*/
|
||||||
|
export const chunkArray = <T>(array: T[], chunkSize: number): T[][] => {
|
||||||
|
const chunks: T[][] = [];
|
||||||
|
for (let i = 0; i < array.length; i += chunkSize) {
|
||||||
|
chunks.push(array.slice(i, i + chunkSize));
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
};
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ const appendParentToGroupingOperator = (parentPath: string, filter: Filter) => {
|
|||||||
return filter;
|
return filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateKnexQueryFromScim = (
|
const processDynamicQuery = (
|
||||||
rootQuery: Knex.QueryBuilder,
|
rootQuery: Knex.QueryBuilder,
|
||||||
rootScimFilter: string,
|
scimRootFilterAst: Filter,
|
||||||
getAttributeField: (attr: string) => string | null
|
getAttributeField: (attr: string) => string | null,
|
||||||
|
depth = 0
|
||||||
) => {
|
) => {
|
||||||
const scimRootFilterAst = parse(rootScimFilter);
|
if (depth > 20) return;
|
||||||
|
|
||||||
const stack = [
|
const stack = [
|
||||||
{
|
{
|
||||||
scimFilterAst: scimRootFilterAst,
|
scimFilterAst: scimRootFilterAst,
|
||||||
@@ -75,42 +77,35 @@ export const generateKnexQueryFromScim = (
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "and": {
|
case "and": {
|
||||||
void query.andWhere((subQueryBuilder) => {
|
scimFilterAst.filters.forEach((el) => {
|
||||||
scimFilterAst.filters.forEach((el) => {
|
void query.andWhere((subQueryBuilder) => {
|
||||||
stack.push({
|
processDynamicQuery(subQueryBuilder, el, getAttributeField, depth + 1);
|
||||||
query: subQueryBuilder,
|
|
||||||
scimFilterAst: el
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "or": {
|
case "or": {
|
||||||
void query.orWhere((subQueryBuilder) => {
|
scimFilterAst.filters.forEach((el) => {
|
||||||
scimFilterAst.filters.forEach((el) => {
|
void query.orWhere((subQueryBuilder) => {
|
||||||
stack.push({
|
processDynamicQuery(subQueryBuilder, el, getAttributeField, depth + 1);
|
||||||
query: subQueryBuilder,
|
|
||||||
scimFilterAst: el
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "not": {
|
case "not": {
|
||||||
void query.whereNot((subQueryBuilder) => {
|
void query.whereNot((subQueryBuilder) => {
|
||||||
stack.push({
|
processDynamicQuery(subQueryBuilder, scimFilterAst.filter, getAttributeField, depth + 1);
|
||||||
query: subQueryBuilder,
|
|
||||||
scimFilterAst: scimFilterAst.filter
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "[]": {
|
case "[]": {
|
||||||
void query.whereNot((subQueryBuilder) => {
|
void query.where((subQueryBuilder) => {
|
||||||
stack.push({
|
processDynamicQuery(
|
||||||
query: subQueryBuilder,
|
subQueryBuilder,
|
||||||
scimFilterAst: appendParentToGroupingOperator(scimFilterAst.attrPath, scimFilterAst.valFilter)
|
appendParentToGroupingOperator(scimFilterAst.attrPath, scimFilterAst.valFilter),
|
||||||
});
|
getAttributeField,
|
||||||
|
depth + 1
|
||||||
|
);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -119,3 +114,12 @@ export const generateKnexQueryFromScim = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateKnexQueryFromScim = (
|
||||||
|
rootQuery: Knex.QueryBuilder,
|
||||||
|
rootScimFilter: string,
|
||||||
|
getAttributeField: (attr: string) => string | null
|
||||||
|
) => {
|
||||||
|
const scimRootFilterAst = parse(rootScimFilter);
|
||||||
|
return processDynamicQuery(rootQuery, scimRootFilterAst, getAttributeField);
|
||||||
|
};
|
||||||
|
|||||||
+9
-2
@@ -1,7 +1,7 @@
|
|||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
import { initDbConnection } from "./db";
|
import { initAuditLogDbConnection, initDbConnection } from "./db";
|
||||||
import { keyStoreFactory } from "./keystore/keystore";
|
import { keyStoreFactory } from "./keystore/keystore";
|
||||||
import { formatSmtpConfig, initEnvConfig, IS_PACKAGED } from "./lib/config/env";
|
import { formatSmtpConfig, initEnvConfig, IS_PACKAGED } from "./lib/config/env";
|
||||||
import { isMigrationMode } from "./lib/fn";
|
import { isMigrationMode } from "./lib/fn";
|
||||||
@@ -25,6 +25,13 @@ const run = async () => {
|
|||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const auditLogDb = appCfg.AUDIT_LOGS_DB_CONNECTION_URI
|
||||||
|
? initAuditLogDbConnection({
|
||||||
|
dbConnectionUri: appCfg.AUDIT_LOGS_DB_CONNECTION_URI,
|
||||||
|
dbRootCert: appCfg.AUDIT_LOGS_DB_ROOT_CERT
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
|
||||||
// Case: App is running in packaged mode (binary), and migration mode is enabled.
|
// Case: App is running in packaged mode (binary), and migration mode is enabled.
|
||||||
// Run the migrations and exit the process after completion.
|
// Run the migrations and exit the process after completion.
|
||||||
if (IS_PACKAGED && isMigrationMode()) {
|
if (IS_PACKAGED && isMigrationMode()) {
|
||||||
@@ -46,7 +53,7 @@ const run = async () => {
|
|||||||
const queue = queueServiceFactory(appCfg.REDIS_URL);
|
const queue = queueServiceFactory(appCfg.REDIS_URL);
|
||||||
const keyStore = keyStoreFactory(appCfg.REDIS_URL);
|
const keyStore = keyStoreFactory(appCfg.REDIS_URL);
|
||||||
|
|
||||||
const server = await main({ db, smtp, logger, queue, keyStore });
|
const server = await main({ db, auditLogDb, smtp, logger, queue, keyStore });
|
||||||
const bootstrap = await bootstrapCheck({ db });
|
const bootstrap = await bootstrapCheck({ db });
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq";
|
import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq";
|
||||||
import Redis from "ioredis";
|
import Redis from "ioredis";
|
||||||
|
|
||||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import {
|
import {
|
||||||
TScanFullRepoEventPayload,
|
TScanFullRepoEventPayload,
|
||||||
@@ -32,7 +32,8 @@ export enum QueueName {
|
|||||||
SecretReplication = "secret-replication",
|
SecretReplication = "secret-replication",
|
||||||
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
||||||
ProjectV3Migration = "project-v3-migration",
|
ProjectV3Migration = "project-v3-migration",
|
||||||
AccessTokenStatusUpdate = "access-token-status-update"
|
AccessTokenStatusUpdate = "access-token-status-update",
|
||||||
|
ImportSecretsFromExternalSource = "import-secrets-from-external-source"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum QueueJobs {
|
export enum QueueJobs {
|
||||||
@@ -56,7 +57,8 @@ export enum QueueJobs {
|
|||||||
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
||||||
ProjectV3Migration = "project-v3-migration",
|
ProjectV3Migration = "project-v3-migration",
|
||||||
IdentityAccessTokenStatusUpdate = "identity-access-token-status-update",
|
IdentityAccessTokenStatusUpdate = "identity-access-token-status-update",
|
||||||
ServiceTokenStatusUpdate = "service-token-status-update"
|
ServiceTokenStatusUpdate = "service-token-status-update",
|
||||||
|
ImportSecretsFromExternalSource = "import-secrets-from-external-source"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TQueueJobTypes = {
|
export type TQueueJobTypes = {
|
||||||
@@ -166,6 +168,19 @@ export type TQueueJobTypes = {
|
|||||||
name: QueueJobs.ProjectV3Migration;
|
name: QueueJobs.ProjectV3Migration;
|
||||||
payload: { projectId: string };
|
payload: { projectId: string };
|
||||||
};
|
};
|
||||||
|
[QueueName.ImportSecretsFromExternalSource]: {
|
||||||
|
name: QueueJobs.ImportSecretsFromExternalSource;
|
||||||
|
payload: {
|
||||||
|
actorEmail: string;
|
||||||
|
data: {
|
||||||
|
iv: string;
|
||||||
|
tag: string;
|
||||||
|
ciphertext: string;
|
||||||
|
algorithm: SecretEncryptionAlgo;
|
||||||
|
encoding: SecretKeyEncoding;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import { fastifySwagger } from "./plugins/swagger";
|
|||||||
import { registerRoutes } from "./routes";
|
import { registerRoutes } from "./routes";
|
||||||
|
|
||||||
type TMain = {
|
type TMain = {
|
||||||
|
auditLogDb?: Knex;
|
||||||
db: Knex;
|
db: Knex;
|
||||||
smtp: TSmtpService;
|
smtp: TSmtpService;
|
||||||
logger?: Logger;
|
logger?: Logger;
|
||||||
@@ -38,7 +39,7 @@ type TMain = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Run the server!
|
// Run the server!
|
||||||
export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => {
|
export const main = async ({ db, auditLogDb, smtp, logger, queue, keyStore }: TMain) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const server = fastify({
|
const server = fastify({
|
||||||
logger: appCfg.NODE_ENV === "test" ? false : logger,
|
logger: appCfg.NODE_ENV === "test" ? false : logger,
|
||||||
@@ -94,7 +95,7 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => {
|
|||||||
|
|
||||||
await server.register(maintenanceMode);
|
await server.register(maintenanceMode);
|
||||||
|
|
||||||
await server.register(registerRoutes, { smtp, queue, db, keyStore });
|
await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore });
|
||||||
|
|
||||||
if (appCfg.isProductionMode) {
|
if (appCfg.isProductionMode) {
|
||||||
await server.register(registerExternalNextjs, {
|
await server.register(registerExternalNextjs, {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-lim
|
|||||||
import { Redis } from "ioredis";
|
import { Redis } from "ioredis";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { RateLimitError } from "@app/lib/errors";
|
||||||
|
|
||||||
export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
|
export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@@ -10,6 +11,11 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
errorResponseBuilder: (_, context) => {
|
||||||
|
throw new RateLimitError({
|
||||||
|
message: `Rate limit exceeded. Please try again in ${context.after}`
|
||||||
|
});
|
||||||
|
},
|
||||||
timeWindow: 60 * 1000,
|
timeWindow: 60 * 1000,
|
||||||
max: 600,
|
max: 600,
|
||||||
redis,
|
redis,
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ import fp from "fastify-plugin";
|
|||||||
|
|
||||||
import { DefaultResponseErrorsSchema } from "../routes/sanitizedSchemas";
|
import { DefaultResponseErrorsSchema } from "../routes/sanitizedSchemas";
|
||||||
|
|
||||||
|
const isScimRoutes = (pathname: string) =>
|
||||||
|
pathname.startsWith("/api/v1/scim/Users") || pathname.startsWith("/api/v1/scim/Groups");
|
||||||
|
|
||||||
export const addErrorsToResponseSchemas = fp(async (server) => {
|
export const addErrorsToResponseSchemas = fp(async (server) => {
|
||||||
server.addHook("onRoute", (routeOptions) => {
|
server.addHook("onRoute", (routeOptions) => {
|
||||||
if (routeOptions.schema && routeOptions.schema.response) {
|
if (routeOptions.schema && routeOptions.schema.response && !isScimRoutes(routeOptions.path)) {
|
||||||
routeOptions.schema.response = {
|
routeOptions.schema.response = {
|
||||||
...DefaultResponseErrorsSchema,
|
...DefaultResponseErrorsSchema,
|
||||||
...routeOptions.schema.response
|
...routeOptions.schema.response
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import {
|
|||||||
BadRequestError,
|
BadRequestError,
|
||||||
DatabaseError,
|
DatabaseError,
|
||||||
ForbiddenRequestError,
|
ForbiddenRequestError,
|
||||||
|
GatewayTimeoutError,
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
|
RateLimitError,
|
||||||
ScimRequestError,
|
ScimRequestError,
|
||||||
UnauthorizedError
|
UnauthorizedError
|
||||||
} from "@app/lib/errors";
|
} from "@app/lib/errors";
|
||||||
@@ -25,7 +27,9 @@ enum HttpStatusCodes {
|
|||||||
Unauthorized = 401,
|
Unauthorized = 401,
|
||||||
Forbidden = 403,
|
Forbidden = 403,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
InternalServerError = 500
|
InternalServerError = 500,
|
||||||
|
GatewayTimeout = 504,
|
||||||
|
TooManyRequests = 429
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
|
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
|
||||||
@@ -47,6 +51,10 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
void res
|
void res
|
||||||
.status(HttpStatusCodes.InternalServerError)
|
.status(HttpStatusCodes.InternalServerError)
|
||||||
.send({ statusCode: HttpStatusCodes.InternalServerError, message: "Something went wrong", error: error.name });
|
.send({ statusCode: HttpStatusCodes.InternalServerError, message: "Something went wrong", error: error.name });
|
||||||
|
} else if (error instanceof GatewayTimeoutError) {
|
||||||
|
void res
|
||||||
|
.status(HttpStatusCodes.GatewayTimeout)
|
||||||
|
.send({ statusCode: HttpStatusCodes.GatewayTimeout, message: error.message, error: error.name });
|
||||||
} else if (error instanceof ZodError) {
|
} else if (error instanceof ZodError) {
|
||||||
void res
|
void res
|
||||||
.status(HttpStatusCodes.Unauthorized)
|
.status(HttpStatusCodes.Unauthorized)
|
||||||
@@ -63,6 +71,12 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
message: error.message,
|
message: error.message,
|
||||||
error: error.name
|
error: error.name
|
||||||
});
|
});
|
||||||
|
} else if (error instanceof RateLimitError) {
|
||||||
|
void res.status(HttpStatusCodes.TooManyRequests).send({
|
||||||
|
statusCode: HttpStatusCodes.TooManyRequests,
|
||||||
|
message: error.message,
|
||||||
|
error: error.name
|
||||||
|
});
|
||||||
} else if (error instanceof ScimRequestError) {
|
} else if (error instanceof ScimRequestError) {
|
||||||
void res.status(error.status).send({
|
void res.status(error.status).send({
|
||||||
schemas: error.schemas,
|
schemas: error.schemas,
|
||||||
@@ -91,7 +105,11 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
message
|
message
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
void res.send(error);
|
void res.status(HttpStatusCodes.InternalServerError).send({
|
||||||
|
statusCode: HttpStatusCodes.InternalServerError,
|
||||||
|
error: "InternalServerError",
|
||||||
|
message: "Something went wrong"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ import { certificateTemplateDALFactory } from "@app/services/certificate-templat
|
|||||||
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
|
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
|
||||||
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
||||||
import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
|
import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
|
||||||
|
import { externalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
||||||
|
import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
|
||||||
|
import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue";
|
||||||
import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
||||||
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
|
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
|
||||||
@@ -214,16 +217,15 @@ import { registerV3Routes } from "./v3";
|
|||||||
export const registerRoutes = async (
|
export const registerRoutes = async (
|
||||||
server: FastifyZodProvider,
|
server: FastifyZodProvider,
|
||||||
{
|
{
|
||||||
|
auditLogDb,
|
||||||
db,
|
db,
|
||||||
smtp: smtpService,
|
smtp: smtpService,
|
||||||
queue: queueService,
|
queue: queueService,
|
||||||
keyStore
|
keyStore
|
||||||
}: { db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory }
|
}: { auditLogDb?: Knex; db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory }
|
||||||
) => {
|
) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
if (!appCfg.DISABLE_SECRET_SCANNING) {
|
await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" });
|
||||||
await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// db layers
|
// db layers
|
||||||
const userDAL = userDALFactory(db);
|
const userDAL = userDALFactory(db);
|
||||||
@@ -283,7 +285,7 @@ export const registerRoutes = async (
|
|||||||
const identityOidcAuthDAL = identityOidcAuthDALFactory(db);
|
const identityOidcAuthDAL = identityOidcAuthDALFactory(db);
|
||||||
const identityAzureAuthDAL = identityAzureAuthDALFactory(db);
|
const identityAzureAuthDAL = identityAzureAuthDALFactory(db);
|
||||||
|
|
||||||
const auditLogDAL = auditLogDALFactory(db);
|
const auditLogDAL = auditLogDALFactory(auditLogDb ?? db);
|
||||||
const auditLogStreamDAL = auditLogStreamDALFactory(db);
|
const auditLogStreamDAL = auditLogStreamDALFactory(db);
|
||||||
const trustedIpDAL = trustedIpDALFactory(db);
|
const trustedIpDAL = trustedIpDALFactory(db);
|
||||||
const telemetryDAL = telemetryDALFactory(db);
|
const telemetryDAL = telemetryDALFactory(db);
|
||||||
@@ -334,6 +336,8 @@ export const registerRoutes = async (
|
|||||||
const projectSlackConfigDAL = projectSlackConfigDALFactory(db);
|
const projectSlackConfigDAL = projectSlackConfigDALFactory(db);
|
||||||
const workflowIntegrationDAL = workflowIntegrationDALFactory(db);
|
const workflowIntegrationDAL = workflowIntegrationDALFactory(db);
|
||||||
|
|
||||||
|
const externalGroupOrgRoleMappingDAL = externalGroupOrgRoleMappingDALFactory(db);
|
||||||
|
|
||||||
const permissionService = permissionServiceFactory({
|
const permissionService = permissionServiceFactory({
|
||||||
permissionDAL,
|
permissionDAL,
|
||||||
orgRoleDAL,
|
orgRoleDAL,
|
||||||
@@ -440,7 +444,8 @@ export const registerRoutes = async (
|
|||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
smtpService
|
smtpService,
|
||||||
|
externalGroupOrgRoleMappingDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const ldapService = ldapConfigServiceFactory({
|
const ldapService = ldapConfigServiceFactory({
|
||||||
@@ -491,6 +496,9 @@ export const registerRoutes = async (
|
|||||||
authDAL,
|
authDAL,
|
||||||
userDAL
|
userDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
|
||||||
|
|
||||||
const orgService = orgServiceFactory({
|
const orgService = orgServiceFactory({
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
identityMetadataDAL,
|
identityMetadataDAL,
|
||||||
@@ -513,7 +521,8 @@ export const registerRoutes = async (
|
|||||||
userDAL,
|
userDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
oidcConfigDAL
|
oidcConfigDAL,
|
||||||
|
projectBotService
|
||||||
});
|
});
|
||||||
const signupService = authSignupServiceFactory({
|
const signupService = authSignupServiceFactory({
|
||||||
tokenService,
|
tokenService,
|
||||||
@@ -531,7 +540,12 @@ export const registerRoutes = async (
|
|||||||
orgService,
|
orgService,
|
||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
const orgRoleService = orgRoleServiceFactory({ permissionService, orgRoleDAL });
|
const orgRoleService = orgRoleServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
orgRoleDAL,
|
||||||
|
orgDAL,
|
||||||
|
externalGroupOrgRoleMappingDAL
|
||||||
|
});
|
||||||
const superAdminService = superAdminServiceFactory({
|
const superAdminService = superAdminServiceFactory({
|
||||||
userDAL,
|
userDAL,
|
||||||
authService: loginService,
|
authService: loginService,
|
||||||
@@ -572,7 +586,6 @@ export const registerRoutes = async (
|
|||||||
secretScanningDAL,
|
secretScanningDAL,
|
||||||
secretScanningQueue
|
secretScanningQueue
|
||||||
});
|
});
|
||||||
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
|
|
||||||
|
|
||||||
const projectMembershipService = projectMembershipServiceFactory({
|
const projectMembershipService = projectMembershipServiceFactory({
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
@@ -836,7 +849,10 @@ export const registerRoutes = async (
|
|||||||
integrationAuthDAL,
|
integrationAuthDAL,
|
||||||
snapshotDAL,
|
snapshotDAL,
|
||||||
snapshotSecretV2BridgeDAL,
|
snapshotSecretV2BridgeDAL,
|
||||||
secretApprovalRequestDAL
|
secretApprovalRequestDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
projectUserMembershipRoleDAL,
|
||||||
|
orgService
|
||||||
});
|
});
|
||||||
const secretImportService = secretImportServiceFactory({
|
const secretImportService = secretImportServiceFactory({
|
||||||
licenseService,
|
licenseService,
|
||||||
@@ -1201,12 +1217,33 @@ export const registerRoutes = async (
|
|||||||
permissionService
|
permissionService
|
||||||
});
|
});
|
||||||
|
|
||||||
const migrationService = externalMigrationServiceFactory({
|
const externalMigrationQueue = externalMigrationQueueFactory({
|
||||||
projectService,
|
|
||||||
orgService,
|
|
||||||
projectEnvService,
|
projectEnvService,
|
||||||
|
projectDAL,
|
||||||
|
projectService,
|
||||||
|
smtpService,
|
||||||
|
kmsService,
|
||||||
|
projectEnvDAL,
|
||||||
|
secretVersionDAL: secretVersionV2BridgeDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL: secretVersionTagV2BridgeDAL,
|
||||||
|
folderDAL,
|
||||||
|
secretDAL: secretV2BridgeDAL,
|
||||||
|
queueService,
|
||||||
|
secretV2BridgeService
|
||||||
|
});
|
||||||
|
|
||||||
|
const migrationService = externalMigrationServiceFactory({
|
||||||
|
externalMigrationQueue,
|
||||||
|
userDAL,
|
||||||
|
permissionService
|
||||||
|
});
|
||||||
|
|
||||||
|
const externalGroupOrgRoleMappingService = externalGroupOrgRoleMappingServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
secretService
|
licenseService,
|
||||||
|
orgRoleDAL,
|
||||||
|
externalGroupOrgRoleMappingDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
@@ -1294,7 +1331,8 @@ export const registerRoutes = async (
|
|||||||
orgAdmin: orgAdminService,
|
orgAdmin: orgAdminService,
|
||||||
slack: slackService,
|
slack: slackService,
|
||||||
workflowIntegration: workflowIntegrationService,
|
workflowIntegration: workflowIntegrationService,
|
||||||
migration: migrationService
|
migration: migrationService,
|
||||||
|
externalGroupOrgRoleMapping: externalGroupOrgRoleMappingService
|
||||||
});
|
});
|
||||||
|
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
firstName: true,
|
firstName: true,
|
||||||
lastName: true,
|
lastName: true,
|
||||||
email: true,
|
email: true,
|
||||||
id: true
|
id: true,
|
||||||
|
superAdmin: true
|
||||||
}).array()
|
}).array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ExternalGroupOrgRoleMappingsSchema } from "@app/db/schemas/external-group-org-role-mappings";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerExternalGroupOrgRoleMappingRouter = async (server: FastifyZodProvider) => {
|
||||||
|
// get mappings for current org
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: ExternalGroupOrgRoleMappingsSchema.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const mappings = server.services.externalGroupOrgRoleMapping.listExternalGroupOrgRoleMappings(req.permission);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
...req.auditLogInfo,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappings;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// update mappings for current org
|
||||||
|
server.route({
|
||||||
|
method: "PUT", // using put since this endpoint creates, updates and deletes mappings
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
mappings: z
|
||||||
|
.object({
|
||||||
|
groupName: z.string().trim().min(1),
|
||||||
|
roleSlug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.toLowerCase()
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Role must be a valid slug"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: ExternalGroupOrgRoleMappingsSchema.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { body, permission } = req;
|
||||||
|
|
||||||
|
const mappings = server.services.externalGroupOrgRoleMapping.updateExternalGroupOrgRoleMappings(body, permission);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
orgId: permission.orgId,
|
||||||
|
...req.auditLogInfo,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS,
|
||||||
|
metadata: body
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappings;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -22,7 +22,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Login with AWS Auth",
|
description: "Login with AWS Auth",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
identityId: z.string().describe(AWS_AUTH.LOGIN.identityId),
|
identityId: z.string().trim().describe(AWS_AUTH.LOGIN.identityId),
|
||||||
iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
|
iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
|
||||||
iamRequestBody: z.string().describe(AWS_AUTH.LOGIN.iamRequestBody),
|
iamRequestBody: z.string().describe(AWS_AUTH.LOGIN.iamRequestBody),
|
||||||
iamRequestHeaders: z.string().describe(AWS_AUTH.LOGIN.iamRequestHeaders)
|
iamRequestHeaders: z.string().describe(AWS_AUTH.LOGIN.iamRequestHeaders)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Login with Azure Auth",
|
description: "Login with Azure Auth",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
identityId: z.string().describe(AZURE_AUTH.LOGIN.identityId),
|
identityId: z.string().trim().describe(AZURE_AUTH.LOGIN.identityId),
|
||||||
jwt: z.string()
|
jwt: z.string()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Login with GCP Auth",
|
description: "Login with GCP Auth",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
identityId: z.string().describe(GCP_AUTH.LOGIN.identityId),
|
identityId: z.string().trim().describe(GCP_AUTH.LOGIN.identityId).trim(),
|
||||||
jwt: z.string()
|
jwt: z.string()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { registerProjectBotRouter } from "./bot-router";
|
|||||||
import { registerCaRouter } from "./certificate-authority-router";
|
import { registerCaRouter } from "./certificate-authority-router";
|
||||||
import { registerCertRouter } from "./certificate-router";
|
import { registerCertRouter } from "./certificate-router";
|
||||||
import { registerCertificateTemplateRouter } from "./certificate-template-router";
|
import { registerCertificateTemplateRouter } from "./certificate-template-router";
|
||||||
|
import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router";
|
||||||
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
|
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
|
||||||
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
|
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
|
||||||
import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
|
import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
|
||||||
@@ -106,4 +107,5 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerUserEngagementRouter, { prefix: "/user-engagement" });
|
await server.register(registerUserEngagementRouter, { prefix: "/user-engagement" });
|
||||||
await server.register(registerDashboardRouter, { prefix: "/dashboard" });
|
await server.register(registerDashboardRouter, { prefix: "/dashboard" });
|
||||||
await server.register(registerCmekRouter, { prefix: "/kms" });
|
await server.register(registerCmekRouter, { prefix: "/kms" });
|
||||||
|
await server.register(registerExternalGroupOrgRoleMappingRouter, { prefix: "/external-group-mappings" });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
code: z.string().trim(),
|
code: z.string().trim(),
|
||||||
integration: z.string().trim(),
|
integration: z.string().trim(),
|
||||||
|
installationId: z.string().trim().optional(),
|
||||||
url: z.string().trim().url().optional()
|
url: z.string().trim().url().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -452,6 +453,40 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:integrationAuthId/duplicate",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
integrationAuthId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
integrationAuth: integrationAuthPubSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const integrationAuth = await server.services.integrationAuth.duplicateIntegrationAuth({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
id: req.params.integrationAuthId,
|
||||||
|
projectId: req.body.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { integrationAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:integrationAuthId/github/envs",
|
url: "/:integrationAuthId/github/envs",
|
||||||
|
|||||||
@@ -52,7 +52,13 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
integration: IntegrationsSchema
|
integration: IntegrationsSchema.extend({
|
||||||
|
environment: z.object({
|
||||||
|
slug: z.string().trim(),
|
||||||
|
name: z.string().trim(),
|
||||||
|
id: z.string().trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -138,7 +144,13 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
integration: IntegrationsSchema
|
integration: IntegrationsSchema.extend({
|
||||||
|
environment: z.object({
|
||||||
|
slug: z.string().trim(),
|
||||||
|
name: z.string().trim(),
|
||||||
|
id: z.string().trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -11,13 +12,13 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
|
||||||
import { getLastMidnightDateISO } from "@app/lib/fn";
|
import { getLastMidnightDateISO } from "@app/lib/fn";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { integrationAuthPubSchema } from "../sanitizedSchemas";
|
||||||
|
|
||||||
export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -69,6 +70,35 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:organizationId/integration-authorizations",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
authorizations: integrationAuthPubSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const authorizations = await server.services.integrationAuth.listOrgIntegrationAuth({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { authorizations };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/audit-logs",
|
url: "/audit-logs",
|
||||||
@@ -125,12 +155,6 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.merge(
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
project: z
|
|
||||||
.object({
|
|
||||||
name: z.string(),
|
|
||||||
slug: z.string()
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
event: z.object({
|
event: z.object({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
metadata: z.any()
|
metadata: z.any()
|
||||||
@@ -145,13 +169,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
if (appCfg.isCloud) {
|
|
||||||
throw new BadRequestError({ message: "Infisical cloud audit log is in maintenance mode." });
|
|
||||||
}
|
|
||||||
|
|
||||||
const auditLogs = await server.services.auditLog.listAuditLogs({
|
const auditLogs = await server.services.auditLog.listAuditLogs({
|
||||||
filter: {
|
filter: {
|
||||||
...req.query,
|
...req.query,
|
||||||
@@ -168,6 +187,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actor: req.permission.type
|
actor: req.permission.type
|
||||||
});
|
});
|
||||||
|
|
||||||
return { auditLogs };
|
return { auditLogs };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -191,7 +211,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
email: true,
|
email: true,
|
||||||
firstName: true,
|
firstName: true,
|
||||||
lastName: true,
|
lastName: true,
|
||||||
id: true
|
id: true,
|
||||||
|
superAdmin: true
|
||||||
}).merge(z.object({ publicKey: z.string().nullable() }))
|
}).merge(z.object({ publicKey: z.string().nullable() }))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -229,7 +250,15 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
.regex(/^[a-zA-Z0-9-]+$/, "Slug must only contain alphanumeric characters or hyphens")
|
.regex(/^[a-zA-Z0-9-]+$/, "Slug must only contain alphanumeric characters or hyphens")
|
||||||
.optional(),
|
.optional(),
|
||||||
authEnforced: z.boolean().optional(),
|
authEnforced: z.boolean().optional(),
|
||||||
scimEnabled: z.boolean().optional()
|
scimEnabled: z.boolean().optional(),
|
||||||
|
defaultMembershipRoleSlug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.trim()
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Membership role must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
await server.services.password.changePassword({ ...req.body, userId: req.permission.id });
|
await server.services.password.changePassword({ ...req.body, userId: req.permission.id });
|
||||||
|
|
||||||
void res.cookie("jid", appCfg.COOKIE_SECRET_SIGN_KEY, {
|
void res.cookie("jid", "", {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: "/",
|
path: "/",
|
||||||
sameSite: "strict",
|
sameSite: "strict",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|||||||
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
|
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ENVIRONMENTS } from "@app/lib/api-docs";
|
import { ENVIRONMENTS } from "@app/lib/api-docs";
|
||||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
|
// NOTE(daniel): workspaceId isn't used, but we need to keep it for backwards compatibility. The endpoint defined below, uses no project ID, and is takes a pure environment ID.
|
||||||
workspaceId: z.string().trim().describe(ENVIRONMENTS.GET.workspaceId),
|
workspaceId: z.string().trim().describe(ENVIRONMENTS.GET.workspaceId),
|
||||||
envId: z.string().trim().describe(ENVIRONMENTS.GET.id)
|
envId: z.string().trim().describe(ENVIRONMENTS.GET.id)
|
||||||
}),
|
}),
|
||||||
@@ -39,7 +40,53 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId: req.params.workspaceId,
|
id: req.params.envId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: environment.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_ENVIRONMENT,
|
||||||
|
metadata: {
|
||||||
|
id: environment.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { environment };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/environments/:envId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get Environment by ID",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
envId: z.string().trim().describe(ENVIRONMENTS.GET.id)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
environment: ProjectEnvironmentsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const environment = await server.services.projectEnv.getEnvironmentById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
id: req.params.envId
|
id: req.params.envId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,6 +123,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().describe(ENVIRONMENTS.CREATE.name),
|
name: z.string().trim().describe(ENVIRONMENTS.CREATE.name),
|
||||||
|
position: z.number().min(1).optional().describe(ENVIRONMENTS.CREATE.position),
|
||||||
slug: z
|
slug: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
|
|||||||
@@ -365,7 +365,15 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
folder: SecretFoldersSchema
|
folder: SecretFoldersSchema.extend({
|
||||||
|
environment: z.object({
|
||||||
|
envId: z.string(),
|
||||||
|
envName: z.string(),
|
||||||
|
envSlug: z.string()
|
||||||
|
}),
|
||||||
|
path: z.string(),
|
||||||
|
projectId: z.string()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -312,6 +312,64 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:secretImportId",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get single secret import",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
secretImportId: z.string().trim().describe(SECRET_IMPORTS.GET.secretImportId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
secretImport: SecretImportsSchema.omit({ importEnv: true }).extend({
|
||||||
|
environment: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string()
|
||||||
|
}),
|
||||||
|
projectId: z.string(),
|
||||||
|
importEnv: z.object({ name: z.string(), slug: z.string(), id: z.string() }),
|
||||||
|
secretPath: z.string()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const secretImport = await server.services.secretImport.getImportById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.secretImportId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: secretImport.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_SECRET_IMPORT,
|
||||||
|
metadata: {
|
||||||
|
secretImportId: secretImport.id,
|
||||||
|
folderId: secretImport.folderId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { secretImport };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/secrets",
|
url: "/secrets",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
|||||||
@@ -1,30 +1,50 @@
|
|||||||
import { z } from "zod";
|
import fastifyMultipart from "@fastify/multipart";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const MB25_IN_BYTES = 26214400;
|
||||||
|
|
||||||
export const registerExternalMigrationRouter = async (server: FastifyZodProvider) => {
|
export const registerExternalMigrationRouter = async (server: FastifyZodProvider) => {
|
||||||
|
await server.register(fastifyMultipart);
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
bodyLimit: MB25_IN_BYTES,
|
||||||
url: "/env-key",
|
url: "/env-key",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
|
||||||
body: z.object({
|
|
||||||
decryptionKey: z.string().trim().min(1),
|
|
||||||
encryptedJson: z.object({
|
|
||||||
nonce: z.string().trim().min(1),
|
|
||||||
data: z.string().trim().min(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const data = await req.file({
|
||||||
|
limits: {
|
||||||
|
fileSize: MB25_IN_BYTES
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
throw new BadRequestError({ message: "No file provided" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullFile = Buffer.from(await data.toBuffer()).toString("utf8");
|
||||||
|
const parsedJsonFile = JSON.parse(fullFile) as { nonce: string; data: string };
|
||||||
|
|
||||||
|
const decryptionKey = (data.fields.decryptionKey as { value: string }).value;
|
||||||
|
|
||||||
|
if (!parsedJsonFile.nonce || !parsedJsonFile.data) {
|
||||||
|
throw new BadRequestError({ message: "Invalid file format. Nonce or data missing." });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decryptionKey) {
|
||||||
|
throw new BadRequestError({ message: "Decryption key is required" });
|
||||||
|
}
|
||||||
|
|
||||||
await server.services.migration.importEnvKeyData({
|
await server.services.migration.importEnvKeyData({
|
||||||
decryptionKey: req.body.decryptionKey,
|
decryptionKey,
|
||||||
encryptedJson: req.body.encryptedJson,
|
encryptedJson: parsedJsonFile,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
|||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
import { Tables } from "knex/types/tables";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { TExternalGroupOrgRoleMappings } from "@app/db/schemas/external-group-org-role-mappings";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TExternalGroupOrgRoleMappingDALFactory = ReturnType<typeof externalGroupOrgRoleMappingDALFactory>;
|
||||||
|
|
||||||
|
export const externalGroupOrgRoleMappingDALFactory = (db: TDbClient) => {
|
||||||
|
const externalGroupOrgRoleMappingOrm = ormify(db, TableName.ExternalGroupOrgRoleMapping);
|
||||||
|
|
||||||
|
const updateExternalGroupOrgRoleMappingForOrg = async (
|
||||||
|
orgId: string,
|
||||||
|
newMappings: readonly Tables[TableName.ExternalGroupOrgRoleMapping]["insert"][]
|
||||||
|
) => {
|
||||||
|
const currentMappings = await externalGroupOrgRoleMappingOrm.find({ orgId });
|
||||||
|
|
||||||
|
const newMap = new Map(newMappings.map((mapping) => [mapping.groupName, mapping]));
|
||||||
|
const currentMap = new Map(currentMappings.map((mapping) => [mapping.groupName, mapping]));
|
||||||
|
|
||||||
|
const mappingsToDelete = currentMappings.filter((mapping) => !newMap.has(mapping.groupName));
|
||||||
|
const mappingsToUpdate = currentMappings
|
||||||
|
.filter((mapping) => newMap.has(mapping.groupName))
|
||||||
|
.map((mapping) => ({ id: mapping.id, ...newMap.get(mapping.groupName) }));
|
||||||
|
const mappingsToInsert = newMappings.filter((mapping) => !currentMap.has(mapping.groupName));
|
||||||
|
|
||||||
|
const mappings = await externalGroupOrgRoleMappingOrm.transaction(async (tx) => {
|
||||||
|
await externalGroupOrgRoleMappingOrm.delete({ $in: { id: mappingsToDelete.map((mapping) => mapping.id) } }, tx);
|
||||||
|
|
||||||
|
const updatedMappings: TExternalGroupOrgRoleMappings[] = [];
|
||||||
|
for await (const { id, ...mappingData } of mappingsToUpdate) {
|
||||||
|
const updatedMapping = await externalGroupOrgRoleMappingOrm.update({ id }, mappingData, tx);
|
||||||
|
updatedMappings.push(updatedMapping[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertedMappings = await externalGroupOrgRoleMappingOrm.insertMany(mappingsToInsert, tx);
|
||||||
|
|
||||||
|
return [...updatedMappings, ...insertedMappings];
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappings;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...externalGroupOrgRoleMappingOrm, updateExternalGroupOrgRoleMappingForOrg };
|
||||||
|
};
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
||||||
|
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
|
||||||
|
|
||||||
|
import { TExternalGroupOrgMembershipRoleMappingDTO } from "./external-group-org-role-mapping-types";
|
||||||
|
|
||||||
|
export const constructGroupOrgMembershipRoleMappings = async ({
|
||||||
|
mappingsDTO,
|
||||||
|
orgId,
|
||||||
|
orgRoleDAL,
|
||||||
|
licenseService
|
||||||
|
}: {
|
||||||
|
mappingsDTO: TExternalGroupOrgMembershipRoleMappingDTO[];
|
||||||
|
orgRoleDAL: TOrgRoleDALFactory;
|
||||||
|
licenseService: TLicenseServiceFactory;
|
||||||
|
orgId: string;
|
||||||
|
}) => {
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
|
||||||
|
// prevent setting custom values if not in plan
|
||||||
|
if (mappingsDTO.some((map) => isCustomOrgRole(map.roleSlug)) && !plan?.rbac)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to set group organization role mapping due to plan RBAC restriction. Upgrade plan to set custom role mapping."
|
||||||
|
});
|
||||||
|
|
||||||
|
const customRoleSlugs = mappingsDTO
|
||||||
|
.filter((mapping) => isCustomOrgRole(mapping.roleSlug))
|
||||||
|
.map((mapping) => mapping.roleSlug);
|
||||||
|
|
||||||
|
let customRolesMap: Map<string, TOrgRoles> = new Map();
|
||||||
|
if (customRoleSlugs.length > 0) {
|
||||||
|
const customRoles = await orgRoleDAL.find({
|
||||||
|
$in: {
|
||||||
|
slug: customRoleSlugs
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
customRolesMap = new Map(customRoles.map((role) => [role.slug, role]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappings = mappingsDTO.map(({ roleSlug, groupName }) => {
|
||||||
|
if (isCustomOrgRole(roleSlug)) {
|
||||||
|
const customRole = customRolesMap.get(roleSlug);
|
||||||
|
|
||||||
|
if (!customRole) throw new NotFoundError({ message: `Custom role ${roleSlug} not found.` });
|
||||||
|
|
||||||
|
return {
|
||||||
|
groupName,
|
||||||
|
role: OrgMembershipRole.Custom,
|
||||||
|
roleId: customRole.id,
|
||||||
|
orgId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
groupName,
|
||||||
|
role: roleSlug,
|
||||||
|
roleId: null, // need to set explicitly null for updates
|
||||||
|
orgId
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappings;
|
||||||
|
};
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { FastifyRequest } from "fastify";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { constructGroupOrgMembershipRoleMappings } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-fns";
|
||||||
|
import { TSyncExternalGroupOrgMembershipRoleMappingsDTO } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-types";
|
||||||
|
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
||||||
|
|
||||||
|
import { TExternalGroupOrgRoleMappingDALFactory } from "./external-group-org-role-mapping-dal";
|
||||||
|
|
||||||
|
type TExternalGroupOrgRoleMappingServiceFactoryDep = {
|
||||||
|
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
|
||||||
|
permissionService: TPermissionServiceFactory;
|
||||||
|
licenseService: TLicenseServiceFactory;
|
||||||
|
orgRoleDAL: TOrgRoleDALFactory;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TExternalGroupOrgRoleMappingServiceFactory = ReturnType<typeof externalGroupOrgRoleMappingServiceFactory>;
|
||||||
|
|
||||||
|
export const externalGroupOrgRoleMappingServiceFactory = ({
|
||||||
|
externalGroupOrgRoleMappingDAL,
|
||||||
|
licenseService,
|
||||||
|
permissionService,
|
||||||
|
orgRoleDAL
|
||||||
|
}: TExternalGroupOrgRoleMappingServiceFactoryDep) => {
|
||||||
|
const listExternalGroupOrgRoleMappings = async (actor: FastifyRequest["permission"]) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor.type,
|
||||||
|
actor.id,
|
||||||
|
actor.orgId,
|
||||||
|
actor.authMethod,
|
||||||
|
actor.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: will need to change if we add support for ldap, oidc, etc.
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
|
||||||
|
|
||||||
|
const mappings = await externalGroupOrgRoleMappingDAL.find({
|
||||||
|
orgId: actor.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return mappings;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateExternalGroupOrgRoleMappings = async (
|
||||||
|
dto: TSyncExternalGroupOrgMembershipRoleMappingsDTO,
|
||||||
|
actor: FastifyRequest["permission"]
|
||||||
|
) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor.type,
|
||||||
|
actor.id,
|
||||||
|
actor.orgId,
|
||||||
|
actor.authMethod,
|
||||||
|
actor.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: will need to change if we add support for ldap, oidc, etc.
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
|
||||||
|
|
||||||
|
const mappings = await constructGroupOrgMembershipRoleMappings({
|
||||||
|
mappingsDTO: dto.mappings,
|
||||||
|
orgRoleDAL,
|
||||||
|
licenseService,
|
||||||
|
orgId: actor.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await externalGroupOrgRoleMappingDAL.updateExternalGroupOrgRoleMappingForOrg(actor.orgId, mappings);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateExternalGroupOrgRoleMappings,
|
||||||
|
listExternalGroupOrgRoleMappings
|
||||||
|
};
|
||||||
|
};
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
export type TExternalGroupOrgMembershipRoleMappingDTO = {
|
||||||
|
groupName: string;
|
||||||
|
roleSlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSyncExternalGroupOrgMembershipRoleMappingsDTO = {
|
||||||
|
mappings: TExternalGroupOrgMembershipRoleMappingDTO[];
|
||||||
|
};
|
||||||
@@ -4,22 +4,41 @@ import sjcl from "sjcl";
|
|||||||
import tweetnacl from "tweetnacl";
|
import tweetnacl from "tweetnacl";
|
||||||
import tweetnaclUtil from "tweetnacl-util";
|
import tweetnaclUtil from "tweetnacl-util";
|
||||||
|
|
||||||
import { OrgMembershipRole, ProjectMembershipRole, SecretType } from "@app/db/schemas";
|
import { SecretType } from "@app/db/schemas";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { chunkArray } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
import { TOrgServiceFactory } from "../org/org-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import { KmsDataKey } from "../kms/kms-types";
|
||||||
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { TProjectServiceFactory } from "../project/project-service";
|
import { TProjectServiceFactory } from "../project/project-service";
|
||||||
|
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||||
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
|
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
|
||||||
import { TSecretServiceFactory } from "../secret/secret-service";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
|
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
||||||
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
|
import { fnSecretBulkInsert, getAllNestedSecretReferences } from "../secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
|
import type { TSecretV2BridgeServiceFactory } from "../secret-v2-bridge/secret-v2-bridge-service";
|
||||||
|
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||||
|
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||||
import { InfisicalImportData, TEnvKeyExportJSON, TImportInfisicalDataCreate } from "./external-migration-types";
|
import { InfisicalImportData, TEnvKeyExportJSON, TImportInfisicalDataCreate } from "./external-migration-types";
|
||||||
|
|
||||||
export type TImportDataIntoInfisicalDTO = {
|
export type TImportDataIntoInfisicalDTO = {
|
||||||
projectService: TProjectServiceFactory;
|
projectDAL: Pick<TProjectDALFactory, "transaction">;
|
||||||
orgService: TOrgServiceFactory;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
||||||
projectEnvService: TProjectEnvServiceFactory;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
secretService: TSecretServiceFactory;
|
|
||||||
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||||
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||||
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||||
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath">;
|
||||||
|
projectService: Pick<TProjectServiceFactory, "createProject">;
|
||||||
|
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
|
||||||
|
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
|
||||||
|
|
||||||
input: TImportInfisicalDataCreate;
|
input: TImportInfisicalDataCreate;
|
||||||
};
|
};
|
||||||
@@ -46,13 +65,13 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
|
|||||||
const parsedJson: TEnvKeyExportJSON = JSON.parse(decryptedJson) as TEnvKeyExportJSON;
|
const parsedJson: TEnvKeyExportJSON = JSON.parse(decryptedJson) as TEnvKeyExportJSON;
|
||||||
|
|
||||||
const infisicalImportData: InfisicalImportData = {
|
const infisicalImportData: InfisicalImportData = {
|
||||||
projects: new Map<string, { name: string; id: string }>(),
|
projects: [],
|
||||||
environments: new Map<string, { name: string; id: string; projectId: string }>(),
|
environments: [],
|
||||||
secrets: new Map<string, { name: string; id: string; projectId: string; environmentId: string; value: string }>()
|
secrets: []
|
||||||
};
|
};
|
||||||
|
|
||||||
parsedJson.apps.forEach((app: { name: string; id: string }) => {
|
parsedJson.apps.forEach((app: { name: string; id: string }) => {
|
||||||
infisicalImportData.projects.set(app.id, { name: app.name, id: app.id });
|
infisicalImportData.projects.push({ name: app.name, id: app.id });
|
||||||
});
|
});
|
||||||
|
|
||||||
// string to string map for env templates
|
// string to string map for env templates
|
||||||
@@ -63,7 +82,7 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
|
|||||||
|
|
||||||
// environments
|
// environments
|
||||||
for (const env of parsedJson.baseEnvironments) {
|
for (const env of parsedJson.baseEnvironments) {
|
||||||
infisicalImportData.environments?.set(env.id, {
|
infisicalImportData.environments.push({
|
||||||
id: env.id,
|
id: env.id,
|
||||||
name: envTemplates.get(env.environmentRoleId)!,
|
name: envTemplates.get(env.environmentRoleId)!,
|
||||||
projectId: env.envParentId
|
projectId: env.envParentId
|
||||||
@@ -75,9 +94,8 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
|
|||||||
if (!env.includes("|")) {
|
if (!env.includes("|")) {
|
||||||
const envData = parsedJson.envs[env];
|
const envData = parsedJson.envs[env];
|
||||||
for (const secret of Object.keys(envData.variables)) {
|
for (const secret of Object.keys(envData.variables)) {
|
||||||
const id = randomUUID();
|
infisicalImportData.secrets.push({
|
||||||
infisicalImportData.secrets?.set(id, {
|
id: randomUUID(),
|
||||||
id,
|
|
||||||
name: secret,
|
name: secret,
|
||||||
environmentId: env,
|
environmentId: env,
|
||||||
value: envData.variables[secret].val
|
value: envData.variables[secret].val
|
||||||
@@ -91,9 +109,14 @@ export const parseEnvKeyDataFn = async (decryptedJson: string): Promise<Infisica
|
|||||||
|
|
||||||
export const importDataIntoInfisicalFn = async ({
|
export const importDataIntoInfisicalFn = async ({
|
||||||
projectService,
|
projectService,
|
||||||
orgService,
|
projectEnvDAL,
|
||||||
projectEnvService,
|
projectDAL,
|
||||||
secretService,
|
secretDAL,
|
||||||
|
kmsService,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
folderDAL,
|
||||||
input: { data, actor, actorId, actorOrgId, actorAuthMethod }
|
input: { data, actor, actorId, actorOrgId, actorAuthMethod }
|
||||||
}: TImportDataIntoInfisicalDTO) => {
|
}: TImportDataIntoInfisicalDTO) => {
|
||||||
// Import data to infisical
|
// Import data to infisical
|
||||||
@@ -103,95 +126,145 @@ export const importDataIntoInfisicalFn = async ({
|
|||||||
|
|
||||||
const originalToNewProjectId = new Map<string, string>();
|
const originalToNewProjectId = new Map<string, string>();
|
||||||
const originalToNewEnvironmentId = new Map<string, string>();
|
const originalToNewEnvironmentId = new Map<string, string>();
|
||||||
|
const projectsNotImported: string[] = [];
|
||||||
|
|
||||||
for await (const [id, project] of data.projects) {
|
await projectDAL.transaction(async (tx) => {
|
||||||
const newProject = await projectService
|
for await (const project of data.projects) {
|
||||||
.createProject({
|
const newProject = await projectService
|
||||||
actor,
|
.createProject({
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
workspaceName: project.name,
|
|
||||||
createDefaultEnvs: false
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
throw new BadRequestError({ message: `Failed to import to project [name:${project.name}] [id:${id}]` });
|
|
||||||
});
|
|
||||||
|
|
||||||
originalToNewProjectId.set(project.id, newProject.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invite user importing projects
|
|
||||||
const invites = await orgService.inviteUserToOrganization({
|
|
||||||
actorAuthMethod,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actor,
|
|
||||||
inviteeEmails: [],
|
|
||||||
orgId: actorOrgId,
|
|
||||||
organizationRoleSlug: OrgMembershipRole.NoAccess,
|
|
||||||
projects: Array.from(originalToNewProjectId.values()).map((project) => ({
|
|
||||||
id: project,
|
|
||||||
projectRoleSlug: [ProjectMembershipRole.Member]
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
if (!invites) {
|
|
||||||
throw new BadRequestError({ message: `Failed to invite user to projects: [userId:${actorId}]` });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import environments
|
|
||||||
if (data.environments) {
|
|
||||||
for await (const [id, environment] of data.environments) {
|
|
||||||
try {
|
|
||||||
const newEnvironment = await projectEnvService.createEnvironment({
|
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
name: environment.name,
|
workspaceName: project.name,
|
||||||
projectId: originalToNewProjectId.get(environment.projectId)!,
|
createDefaultEnvs: false,
|
||||||
slug: slugify(`${environment.name}-${alphaNumericNanoId(4)}`)
|
tx
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error(e, `Failed to import to project [name:${project.name}]`);
|
||||||
|
throw new BadRequestError({ message: `Failed to import to project [name:${project.name}]` });
|
||||||
});
|
});
|
||||||
|
originalToNewProjectId.set(project.id, newProject.id);
|
||||||
|
}
|
||||||
|
|
||||||
if (!newEnvironment) {
|
// Import environments
|
||||||
logger.error(`Failed to import environment: [name:${environment.name}] [id:${id}]`);
|
if (data.environments) {
|
||||||
|
for await (const environment of data.environments) {
|
||||||
|
const projectId = originalToNewProjectId.get(environment.projectId);
|
||||||
|
const slug = slugify(`${environment.name}-${alphaNumericNanoId(4)}`);
|
||||||
|
|
||||||
|
if (!projectId) {
|
||||||
|
projectsNotImported.push(environment.projectId);
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingEnv = await projectEnvDAL.findOne({ projectId, slug }, tx);
|
||||||
|
|
||||||
|
if (existingEnv) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: `Failed to import environment: [name:${environment.name}] [id:${id}]`
|
message: `Environment with slug '${slug}' already exist`,
|
||||||
|
name: "CreateEnvironment"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
originalToNewEnvironmentId.set(id, newEnvironment.slug);
|
|
||||||
} catch (error) {
|
const lastPos = await projectEnvDAL.findLastEnvPosition(projectId, tx);
|
||||||
throw new BadRequestError({
|
const doc = await projectEnvDAL.create({ slug, name: environment.name, projectId, position: lastPos + 1 }, tx);
|
||||||
message: `Failed to import environment: ${environment.name}]`,
|
await folderDAL.create({ name: "root", parentId: null, envId: doc.id, version: 1 }, tx);
|
||||||
name: "EnvKeyMigrationImportEnvironment"
|
|
||||||
|
originalToNewEnvironmentId.set(environment.id, doc.slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.secrets && data.secrets.length > 0) {
|
||||||
|
const mappedToEnvironmentId = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
secretKey: string;
|
||||||
|
secretValue: string;
|
||||||
|
}[]
|
||||||
|
>();
|
||||||
|
|
||||||
|
for (const secret of data.secrets) {
|
||||||
|
if (!originalToNewEnvironmentId.get(secret.environmentId)) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mappedToEnvironmentId.has(secret.environmentId)) {
|
||||||
|
mappedToEnvironmentId.set(secret.environmentId, []);
|
||||||
|
}
|
||||||
|
mappedToEnvironmentId.get(secret.environmentId)!.push({
|
||||||
|
secretKey: secret.name,
|
||||||
|
secretValue: secret.value || ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import secrets
|
// for each of the mappedEnvironmentId
|
||||||
if (data.secrets) {
|
for await (const [envId, secrets] of mappedToEnvironmentId) {
|
||||||
for await (const [id, secret] of data.secrets) {
|
const environment = data.environments.find((env) => env.id === envId);
|
||||||
const dataProjectId = data.environments?.get(secret.environmentId)?.projectId;
|
const projectId = originalToNewProjectId.get(environment?.projectId as string)!;
|
||||||
if (!dataProjectId) {
|
|
||||||
throw new BadRequestError({ message: `Failed to import secret "${secret.name}", project not found` });
|
if (!projectId) {
|
||||||
}
|
throw new BadRequestError({ message: `Failed to import secret, project not found` });
|
||||||
const projectId = originalToNewProjectId.get(dataProjectId);
|
}
|
||||||
const newSecret = await secretService.createSecretRaw({
|
|
||||||
actorId,
|
const { encryptor: secretManagerEncrypt } = await kmsService.createCipherPairWithDataKey(
|
||||||
actor,
|
{
|
||||||
actorOrgId,
|
type: KmsDataKey.SecretManager,
|
||||||
environment: originalToNewEnvironmentId.get(secret.environmentId)!,
|
projectId
|
||||||
actorAuthMethod,
|
},
|
||||||
projectId: projectId!,
|
tx
|
||||||
secretPath: "/",
|
);
|
||||||
secretName: secret.name,
|
|
||||||
type: SecretType.Shared,
|
const envSlug = originalToNewEnvironmentId.get(envId)!;
|
||||||
secretValue: secret.value
|
const folder = await folderDAL.findBySecretPath(projectId, envSlug, "/", tx);
|
||||||
});
|
if (!folder)
|
||||||
if (!newSecret) {
|
throw new NotFoundError({
|
||||||
throw new BadRequestError({ message: `Failed to import secret: [name:${secret.name}] [id:${id}]` });
|
message: `Folder not found for the given environment slug (${envSlug}) & secret path (/)`,
|
||||||
|
name: "Create secret"
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretBatches = chunkArray(secrets, 2500);
|
||||||
|
for await (const secretBatch of secretBatches) {
|
||||||
|
const secretsByKeys = await secretDAL.findBySecretKeys(
|
||||||
|
folder.id,
|
||||||
|
secretBatch.map((el) => ({
|
||||||
|
key: el.secretKey,
|
||||||
|
type: SecretType.Shared
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
if (secretsByKeys.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Secret already exist: ${secretsByKeys.map((el) => el.key).join(",")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await fnSecretBulkInsert({
|
||||||
|
inputSecrets: secretBatch.map((el) => {
|
||||||
|
const references = getAllNestedSecretReferences(el.secretValue);
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: 1,
|
||||||
|
encryptedValue: el.secretValue
|
||||||
|
? secretManagerEncrypt({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob
|
||||||
|
: undefined,
|
||||||
|
key: el.secretKey,
|
||||||
|
references,
|
||||||
|
type: SecretType.Shared
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
folderId: folder.id,
|
||||||
|
secretDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
tx
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
return { projectsNotImported };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
|
|
||||||
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
|
import { TProjectServiceFactory } from "../project/project-service";
|
||||||
|
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
||||||
|
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
|
||||||
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
|
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
|
||||||
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
|
import { TSecretV2BridgeServiceFactory } from "../secret-v2-bridge/secret-v2-bridge-service";
|
||||||
|
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||||
|
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||||
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
|
import { importDataIntoInfisicalFn } from "./external-migration-fns";
|
||||||
|
import { ExternalPlatforms, TImportInfisicalDataCreate } from "./external-migration-types";
|
||||||
|
|
||||||
|
export type TExternalMigrationQueueFactoryDep = {
|
||||||
|
smtpService: TSmtpService;
|
||||||
|
queueService: TQueueServiceFactory;
|
||||||
|
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "transaction">;
|
||||||
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
|
||||||
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||||
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||||
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||||
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath">;
|
||||||
|
projectService: Pick<TProjectServiceFactory, "createProject">;
|
||||||
|
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
|
||||||
|
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TExternalMigrationQueueFactory = ReturnType<typeof externalMigrationQueueFactory>;
|
||||||
|
|
||||||
|
export const externalMigrationQueueFactory = ({
|
||||||
|
queueService,
|
||||||
|
projectService,
|
||||||
|
smtpService,
|
||||||
|
projectDAL,
|
||||||
|
projectEnvService,
|
||||||
|
secretV2BridgeService,
|
||||||
|
kmsService,
|
||||||
|
projectEnvDAL,
|
||||||
|
secretDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
folderDAL
|
||||||
|
}: TExternalMigrationQueueFactoryDep) => {
|
||||||
|
const startImport = async (dto: {
|
||||||
|
actorEmail: string;
|
||||||
|
data: {
|
||||||
|
iv: string;
|
||||||
|
tag: string;
|
||||||
|
ciphertext: string;
|
||||||
|
algorithm: SecretEncryptionAlgo;
|
||||||
|
encoding: SecretKeyEncoding;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
await queueService.queue(
|
||||||
|
QueueName.ImportSecretsFromExternalSource,
|
||||||
|
QueueJobs.ImportSecretsFromExternalSource,
|
||||||
|
dto,
|
||||||
|
{
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
queueService.start(QueueName.ImportSecretsFromExternalSource, async (job) => {
|
||||||
|
try {
|
||||||
|
const { data, actorEmail } = job.data;
|
||||||
|
|
||||||
|
await smtpService.sendMail({
|
||||||
|
recipients: [actorEmail],
|
||||||
|
subjectLine: "Infisical import started",
|
||||||
|
substitutions: {
|
||||||
|
provider: ExternalPlatforms.EnvKey
|
||||||
|
},
|
||||||
|
template: SmtpTemplates.ExternalImportStarted
|
||||||
|
});
|
||||||
|
|
||||||
|
const decrypted = infisicalSymmetricDecrypt({
|
||||||
|
ciphertext: data.ciphertext,
|
||||||
|
iv: data.iv,
|
||||||
|
keyEncoding: data.encoding,
|
||||||
|
tag: data.tag
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedJson = JSON.parse(decrypted) as TImportInfisicalDataCreate;
|
||||||
|
|
||||||
|
const { projectsNotImported } = await importDataIntoInfisicalFn({
|
||||||
|
input: decryptedJson,
|
||||||
|
projectDAL,
|
||||||
|
projectEnvDAL,
|
||||||
|
secretDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
folderDAL,
|
||||||
|
kmsService,
|
||||||
|
projectService,
|
||||||
|
projectEnvService,
|
||||||
|
secretV2BridgeService
|
||||||
|
});
|
||||||
|
|
||||||
|
if (projectsNotImported.length) {
|
||||||
|
logger.info(
|
||||||
|
{
|
||||||
|
actorEmail,
|
||||||
|
actorOrgId: decryptedJson.actorOrgId,
|
||||||
|
projectsNotImported
|
||||||
|
},
|
||||||
|
"One or more projects were not imported during import from external source"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await smtpService.sendMail({
|
||||||
|
recipients: [actorEmail],
|
||||||
|
subjectLine: "Infisical import successful",
|
||||||
|
substitutions: {
|
||||||
|
provider: ExternalPlatforms.EnvKey
|
||||||
|
},
|
||||||
|
template: SmtpTemplates.ExternalImportSuccessful
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
await smtpService.sendMail({
|
||||||
|
recipients: [job.data.actorEmail],
|
||||||
|
subjectLine: "Infisical import failed",
|
||||||
|
substitutions: {
|
||||||
|
provider: ExternalPlatforms.EnvKey,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
|
||||||
|
error: (err as any)?.message || "Unknown error"
|
||||||
|
},
|
||||||
|
template: SmtpTemplates.ExternalImportFailed
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.error(err, "Failed to import data from external source");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
startImport
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,30 +1,25 @@
|
|||||||
import { OrgMembershipRole } from "@app/db/schemas";
|
import { OrgMembershipRole } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { ForbiddenRequestError } from "@app/lib/errors";
|
import { ForbiddenRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TOrgServiceFactory } from "../org/org-service";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TProjectServiceFactory } from "../project/project-service";
|
import { decryptEnvKeyDataFn, parseEnvKeyDataFn } from "./external-migration-fns";
|
||||||
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
|
import { TExternalMigrationQueueFactory } from "./external-migration-queue";
|
||||||
import { TSecretServiceFactory } from "../secret/secret-service";
|
|
||||||
import { decryptEnvKeyDataFn, importDataIntoInfisicalFn, parseEnvKeyDataFn } from "./external-migration-fns";
|
|
||||||
import { TImportEnvKeyDataCreate } from "./external-migration-types";
|
import { TImportEnvKeyDataCreate } from "./external-migration-types";
|
||||||
|
|
||||||
type TExternalMigrationServiceFactoryDep = {
|
type TExternalMigrationServiceFactoryDep = {
|
||||||
projectService: TProjectServiceFactory;
|
|
||||||
orgService: TOrgServiceFactory;
|
|
||||||
projectEnvService: TProjectEnvServiceFactory;
|
|
||||||
secretService: TSecretServiceFactory;
|
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
|
externalMigrationQueue: TExternalMigrationQueueFactory;
|
||||||
|
userDAL: Pick<TUserDALFactory, "findById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TExternalMigrationServiceFactory = ReturnType<typeof externalMigrationServiceFactory>;
|
export type TExternalMigrationServiceFactory = ReturnType<typeof externalMigrationServiceFactory>;
|
||||||
|
|
||||||
export const externalMigrationServiceFactory = ({
|
export const externalMigrationServiceFactory = ({
|
||||||
projectService,
|
|
||||||
orgService,
|
|
||||||
projectEnvService,
|
|
||||||
permissionService,
|
permissionService,
|
||||||
secretService
|
externalMigrationQueue,
|
||||||
|
userDAL
|
||||||
}: TExternalMigrationServiceFactoryDep) => {
|
}: TExternalMigrationServiceFactoryDep) => {
|
||||||
const importEnvKeyData = async ({
|
const importEnvKeyData = async ({
|
||||||
decryptionKey,
|
decryptionKey,
|
||||||
@@ -41,21 +36,28 @@ export const externalMigrationServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (membership.role !== OrgMembershipRole.Admin) {
|
if (membership.role !== OrgMembershipRole.Admin) {
|
||||||
throw new ForbiddenRequestError({ message: "Only admins can import data" });
|
throw new ForbiddenRequestError({ message: "Only admins can import data" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const user = await userDAL.findById(actorId);
|
||||||
const json = await decryptEnvKeyDataFn(decryptionKey, encryptedJson);
|
const json = await decryptEnvKeyDataFn(decryptionKey, encryptedJson);
|
||||||
const envKeyData = await parseEnvKeyDataFn(json);
|
const envKeyData = await parseEnvKeyDataFn(json);
|
||||||
const response = await importDataIntoInfisicalFn({
|
|
||||||
input: { data: envKeyData, actor, actorId, actorOrgId, actorAuthMethod },
|
const stringifiedJson = JSON.stringify({
|
||||||
projectService,
|
data: envKeyData,
|
||||||
orgService,
|
actor,
|
||||||
projectEnvService,
|
actorId,
|
||||||
secretService
|
actorOrgId,
|
||||||
|
actorAuthMethod
|
||||||
|
});
|
||||||
|
|
||||||
|
const encrypted = infisicalSymmetricEncypt(stringifiedJson);
|
||||||
|
|
||||||
|
await externalMigrationQueue.startImport({
|
||||||
|
actorEmail: user.email!,
|
||||||
|
data: encrypted
|
||||||
});
|
});
|
||||||
return response;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,26 +1,9 @@
|
|||||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||||
|
|
||||||
export type InfisicalImportData = {
|
export type InfisicalImportData = {
|
||||||
projects: Map<string, { name: string; id: string }>;
|
projects: Array<{ name: string; id: string }>;
|
||||||
|
environments: Array<{ name: string; id: string; projectId: string }>;
|
||||||
environments?: Map<
|
secrets: Array<{ name: string; id: string; environmentId: string; value: string }>;
|
||||||
string,
|
|
||||||
{
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
projectId: string;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
secrets?: Map<
|
|
||||||
string,
|
|
||||||
{
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
environmentId: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TImportEnvKeyDataCreate = {
|
export type TImportEnvKeyDataCreate = {
|
||||||
@@ -104,3 +87,7 @@ export type TEnvKeyExportJSON = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum ExternalPlatforms {
|
||||||
|
EnvKey = "EnvKey"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TIdentityOrgMemberships } from "@app/db/schemas";
|
import { TableName, TIdentityOrgMemberships, TOrgRoles } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
@@ -42,30 +42,50 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
tx?: Knex
|
tx?: Knex
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const paginatedFetchIdentity = (tx || db.replicaNode())(TableName.Identity)
|
const paginatedIdentity = (tx || db.replicaNode())(TableName.Identity)
|
||||||
.as(TableName.Identity)
|
.join(
|
||||||
.orderBy(`${TableName.Identity}.${orderBy}`, orderDirection);
|
TableName.IdentityOrgMembership,
|
||||||
|
`${TableName.IdentityOrgMembership}.identityId`,
|
||||||
|
`${TableName.Identity}.id`
|
||||||
|
)
|
||||||
|
.orderBy(`${TableName.Identity}.${orderBy}`, orderDirection)
|
||||||
|
.select(
|
||||||
|
selectAllTableCols(TableName.IdentityOrgMembership),
|
||||||
|
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
||||||
|
db.ref("authMethod").withSchema(TableName.Identity).as("identityAuthMethod")
|
||||||
|
)
|
||||||
|
.where(filter)
|
||||||
|
.as("paginatedIdentity");
|
||||||
|
|
||||||
if (search?.length) {
|
if (search?.length) {
|
||||||
void paginatedFetchIdentity.whereILike(`${TableName.Identity}.name`, `%${search}%`);
|
void paginatedIdentity.whereILike(`${TableName.Identity}.name`, `%${search}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (limit) {
|
if (limit) {
|
||||||
void paginatedFetchIdentity.offset(offset).limit(limit);
|
void paginatedIdentity.offset(offset).limit(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = (tx || db.replicaNode())(TableName.IdentityOrgMembership)
|
// akhilmhdh: refer this for pagination with multiple left queries
|
||||||
.where(filter)
|
type TSubquery = Awaited<typeof paginatedIdentity>;
|
||||||
.join<Awaited<typeof paginatedFetchIdentity>>(paginatedFetchIdentity, (queryBuilder) => {
|
const query = (tx || db.replicaNode())
|
||||||
queryBuilder.on(`${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`);
|
.from<TSubquery[number], TSubquery>(paginatedIdentity)
|
||||||
})
|
.leftJoin<TOrgRoles>(TableName.OrgRoles, `paginatedIdentity.roleId`, `${TableName.OrgRoles}.id`)
|
||||||
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
|
|
||||||
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
void queryBuilder
|
void queryBuilder
|
||||||
.on(`${TableName.IdentityOrgMembership}.identityId`, `${TableName.IdentityMetadata}.identityId`)
|
.on(`paginatedIdentity.identityId`, `${TableName.IdentityMetadata}.identityId`)
|
||||||
.andOn(`${TableName.IdentityOrgMembership}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
.andOn(`paginatedIdentity.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
})
|
})
|
||||||
.select(selectAllTableCols(TableName.IdentityOrgMembership))
|
.select(
|
||||||
|
db.ref("id").withSchema("paginatedIdentity"),
|
||||||
|
db.ref("role").withSchema("paginatedIdentity"),
|
||||||
|
db.ref("roleId").withSchema("paginatedIdentity"),
|
||||||
|
db.ref("orgId").withSchema("paginatedIdentity"),
|
||||||
|
db.ref("createdAt").withSchema("paginatedIdentity"),
|
||||||
|
db.ref("updatedAt").withSchema("paginatedIdentity"),
|
||||||
|
db.ref("identityId").withSchema("paginatedIdentity"),
|
||||||
|
db.ref("identityName").withSchema("paginatedIdentity"),
|
||||||
|
db.ref("identityAuthMethod").withSchema("paginatedIdentity")
|
||||||
|
)
|
||||||
// cr stands for custom role
|
// cr stands for custom role
|
||||||
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
|
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
|
||||||
.select(db.ref("name").as("crName").withSchema(TableName.OrgRoles))
|
.select(db.ref("name").as("crName").withSchema(TableName.OrgRoles))
|
||||||
@@ -73,17 +93,14 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("description").as("crDescription").withSchema(TableName.OrgRoles))
|
.select(db.ref("description").as("crDescription").withSchema(TableName.OrgRoles))
|
||||||
.select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles))
|
.select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles))
|
||||||
.select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles))
|
.select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles))
|
||||||
.select(db.ref("id").as("identityId").withSchema(TableName.Identity))
|
|
||||||
.select(
|
|
||||||
db.ref("name").as("identityName").withSchema(TableName.Identity),
|
|
||||||
db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity)
|
|
||||||
)
|
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||||
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||||
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
|
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
|
||||||
)
|
);
|
||||||
.orderBy(`${TableName.Identity}.${orderBy}`, orderDirection);
|
if (orderBy === OrgIdentityOrderBy.Name) {
|
||||||
|
void query.orderBy("identityName", orderDirection);
|
||||||
|
}
|
||||||
|
|
||||||
const docs = await query;
|
const docs = await query;
|
||||||
const formattedDocs = sqlNestRelationships({
|
const formattedDocs = sqlNestRelationships({
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { createAppAuth } from "@octokit/auth-app";
|
||||||
import { Octokit } from "@octokit/rest";
|
import { Octokit } from "@octokit/rest";
|
||||||
|
|
||||||
|
import { TIntegrationAuths } from "@app/db/schemas";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { IntegrationAuthMetadataSchema, TIntegrationAuthMetadata } from "./integration-auth-schema";
|
||||||
import { Integrations, IntegrationUrls } from "./integration-list";
|
import { Integrations, IntegrationUrls } from "./integration-list";
|
||||||
|
|
||||||
// akhilmhdh: check this part later. Copied from old base
|
// akhilmhdh: check this part later. Copied from old base
|
||||||
@@ -230,7 +234,13 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
/**
|
/**
|
||||||
* Return list of repositories for Github integration
|
* Return list of repositories for Github integration
|
||||||
*/
|
*/
|
||||||
const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
|
const getAppsGithub = async ({
|
||||||
|
accessToken,
|
||||||
|
authMetadata
|
||||||
|
}: {
|
||||||
|
accessToken: string;
|
||||||
|
authMetadata?: TIntegrationAuthMetadata;
|
||||||
|
}) => {
|
||||||
interface GitHubApp {
|
interface GitHubApp {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -242,6 +252,29 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authMetadata?.installationId) {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const octokit = new Octokit({
|
||||||
|
authStrategy: createAppAuth,
|
||||||
|
auth: {
|
||||||
|
appId: appCfg.CLIENT_APP_ID_GITHUB_APP,
|
||||||
|
privateKey: appCfg.CLIENT_PRIVATE_KEY_GITHUB_APP,
|
||||||
|
installationId: authMetadata.installationId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
|
const repos = await octokit.paginate("GET /installation/repositories", {
|
||||||
|
per_page: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
return repos.map((a) => ({
|
||||||
|
appId: String(a.id),
|
||||||
|
name: a.name,
|
||||||
|
owner: a.owner.login
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
const repos = (await new Octokit({
|
const repos = (await new Octokit({
|
||||||
auth: accessToken
|
auth: accessToken
|
||||||
@@ -455,6 +488,31 @@ const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
return apps;
|
return apps;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of projects for Databricks integration
|
||||||
|
*/
|
||||||
|
const getAppsDatabricks = async ({ url, accessToken }: { url?: string | null; accessToken: string }) => {
|
||||||
|
const databricksApiUrl = `${url}/api`;
|
||||||
|
|
||||||
|
const res = await request.get<{ scopes: { name: string; backend_type: string }[] }>(
|
||||||
|
`${databricksApiUrl}/2.0/secrets/scopes/list`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const scopes =
|
||||||
|
res.data?.scopes?.map((a) => ({
|
||||||
|
name: a.name, // name maps to unique scope name in Databricks
|
||||||
|
backend_type: a.backend_type
|
||||||
|
})) ?? [];
|
||||||
|
|
||||||
|
return scopes;
|
||||||
|
};
|
||||||
|
|
||||||
const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
|
const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
|
||||||
const res = (
|
const res = (
|
||||||
await request.get<{ id: string; slug: string }[]>(`${IntegrationUrls.TRAVISCI_API_URL}/repos`, {
|
await request.get<{ id: string; slug: string }[]>(`${IntegrationUrls.TRAVISCI_API_URL}/repos`, {
|
||||||
@@ -1031,6 +1089,7 @@ const getAppsAzureDevOps = async ({ accessToken, orgName }: { accessToken: strin
|
|||||||
|
|
||||||
export const getApps = async ({
|
export const getApps = async ({
|
||||||
integration,
|
integration,
|
||||||
|
integrationAuth,
|
||||||
accessToken,
|
accessToken,
|
||||||
accessId,
|
accessId,
|
||||||
teamId,
|
teamId,
|
||||||
@@ -1041,6 +1100,7 @@ export const getApps = async ({
|
|||||||
integration: string;
|
integration: string;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
accessId?: string;
|
accessId?: string;
|
||||||
|
integrationAuth: TIntegrationAuths;
|
||||||
teamId?: string | null;
|
teamId?: string | null;
|
||||||
azureDevOpsOrgName?: string | null;
|
azureDevOpsOrgName?: string | null;
|
||||||
workspaceSlug?: string;
|
workspaceSlug?: string;
|
||||||
@@ -1074,7 +1134,8 @@ export const getApps = async ({
|
|||||||
|
|
||||||
case Integrations.GITHUB:
|
case Integrations.GITHUB:
|
||||||
return getAppsGithub({
|
return getAppsGithub({
|
||||||
accessToken
|
accessToken,
|
||||||
|
authMetadata: IntegrationAuthMetadataSchema.parse(integrationAuth.metadata || {})
|
||||||
});
|
});
|
||||||
|
|
||||||
case Integrations.GITLAB:
|
case Integrations.GITLAB:
|
||||||
@@ -1104,6 +1165,12 @@ export const getApps = async ({
|
|||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
|
|
||||||
|
case Integrations.DATABRICKS:
|
||||||
|
return getAppsDatabricks({
|
||||||
|
url,
|
||||||
|
accessToken
|
||||||
|
});
|
||||||
|
|
||||||
case Integrations.LARAVELFORGE:
|
case Integrations.LARAVELFORGE:
|
||||||
return getAppsLaravelForge({
|
return getAppsLaravelForge({
|
||||||
accessToken,
|
accessToken,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TIntegrationAuths, TIntegrationAuthsUpdate } from "@app/db/schemas";
|
import { TableName, TIntegrationAuths, TIntegrationAuthsUpdate } from "@app/db/schemas";
|
||||||
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TIntegrationAuthDALFactory = ReturnType<typeof integrationAuthDALFactory>;
|
export type TIntegrationAuthDALFactory = ReturnType<typeof integrationAuthDALFactory>;
|
||||||
|
|
||||||
@@ -28,8 +28,23 @@ export const integrationAuthDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getByOrg = async (orgId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const integrationAuths = await (tx || db)(TableName.IntegrationAuth)
|
||||||
|
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.IntegrationAuth}.projectId`)
|
||||||
|
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.Project}.orgId`)
|
||||||
|
.where(`${TableName.Organization}.id`, "=", orgId)
|
||||||
|
.select(selectAllTableCols(TableName.IntegrationAuth));
|
||||||
|
|
||||||
|
return integrationAuths;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "get by org" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...integrationAuthOrm,
|
...integrationAuthOrm,
|
||||||
bulkUpdate
|
bulkUpdate,
|
||||||
|
getByOrg
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const IntegrationAuthMetadataSchema = z.object({
|
||||||
|
installationId: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIntegrationAuthMetadata = z.infer<typeof IntegrationAuthMetadataSchema>;
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { createAppAuth } from "@octokit/auth-app";
|
||||||
import { Octokit } from "@octokit/rest";
|
import { Octokit } from "@octokit/rest";
|
||||||
import AWS from "aws-sdk";
|
import AWS from "aws-sdk";
|
||||||
|
|
||||||
import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas";
|
import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TGenericPermission, TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
@@ -16,11 +18,13 @@ import { KmsDataKey } from "../kms/kms-types";
|
|||||||
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||||
import { getApps } from "./integration-app-list";
|
import { getApps } from "./integration-app-list";
|
||||||
import { TIntegrationAuthDALFactory } from "./integration-auth-dal";
|
import { TIntegrationAuthDALFactory } from "./integration-auth-dal";
|
||||||
|
import { IntegrationAuthMetadataSchema, TIntegrationAuthMetadata } from "./integration-auth-schema";
|
||||||
import {
|
import {
|
||||||
TBitbucketWorkspace,
|
TBitbucketWorkspace,
|
||||||
TChecklyGroups,
|
TChecklyGroups,
|
||||||
TDeleteIntegrationAuthByIdDTO,
|
TDeleteIntegrationAuthByIdDTO,
|
||||||
TDeleteIntegrationAuthsDTO,
|
TDeleteIntegrationAuthsDTO,
|
||||||
|
TDuplicateGithubIntegrationAuthDTO,
|
||||||
TGetIntegrationAuthDTO,
|
TGetIntegrationAuthDTO,
|
||||||
TGetIntegrationAuthTeamCityBuildConfigDTO,
|
TGetIntegrationAuthTeamCityBuildConfigDTO,
|
||||||
THerokuPipelineCoupling,
|
THerokuPipelineCoupling,
|
||||||
@@ -86,6 +90,24 @@ export const integrationAuthServiceFactory = ({
|
|||||||
return authorizations;
|
return authorizations;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listOrgIntegrationAuth = async ({ actorId, actor, actorOrgId, actorAuthMethod }: TGenericPermission) => {
|
||||||
|
const authorizations = await integrationAuthDAL.getByOrg(actorOrgId as string);
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
authorizations.filter(async (auth) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
auth.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
return permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const getIntegrationAuth = async ({ actor, id, actorId, actorAuthMethod, actorOrgId }: TGetIntegrationAuthDTO) => {
|
const getIntegrationAuth = async ({ actor, id, actorId, actorAuthMethod, actorOrgId }: TGetIntegrationAuthDTO) => {
|
||||||
const integrationAuth = await integrationAuthDAL.findById(id);
|
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||||
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
|
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
|
||||||
@@ -109,7 +131,8 @@ export const integrationAuthServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
integration,
|
integration,
|
||||||
url,
|
url,
|
||||||
code
|
code,
|
||||||
|
installationId
|
||||||
}: TOauthExchangeDTO) => {
|
}: TOauthExchangeDTO) => {
|
||||||
if (!Object.values(Integrations).includes(integration as Integrations))
|
if (!Object.values(Integrations).includes(integration as Integrations))
|
||||||
throw new BadRequestError({ message: "Invalid integration" });
|
throw new BadRequestError({ message: "Invalid integration" });
|
||||||
@@ -123,7 +146,7 @@ export const integrationAuthServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
const tokenExchange = await exchangeCode({ integration, code, url });
|
const tokenExchange = await exchangeCode({ integration, code, url, installationId });
|
||||||
const updateDoc: TIntegrationAuthsInsert = {
|
const updateDoc: TIntegrationAuthsInsert = {
|
||||||
projectId,
|
projectId,
|
||||||
integration,
|
integration,
|
||||||
@@ -141,6 +164,16 @@ export const integrationAuthServiceFactory = ({
|
|||||||
updateDoc.metadata = {
|
updateDoc.metadata = {
|
||||||
authMethod: "oauth2"
|
authMethod: "oauth2"
|
||||||
};
|
};
|
||||||
|
} else if (integration === Integrations.GITHUB && installationId) {
|
||||||
|
updateDoc.metadata = {
|
||||||
|
installationId,
|
||||||
|
installationName: tokenExchange.installationName,
|
||||||
|
authMethod: "app"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (installationId && integration === Integrations.GITHUB) {
|
||||||
|
return integrationAuthDAL.create(updateDoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId);
|
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId);
|
||||||
@@ -176,12 +209,23 @@ export const integrationAuthServiceFactory = ({
|
|||||||
updateDoc.accessCiphertext = accessEncToken.ciphertext;
|
updateDoc.accessCiphertext = accessEncToken.ciphertext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return integrationAuthDAL.transaction(async (tx) => {
|
return integrationAuthDAL.transaction(async (tx) => {
|
||||||
const doc = await integrationAuthDAL.findOne({ projectId, integration }, tx);
|
const integrationAuths = await integrationAuthDAL.find({ projectId, integration }, { tx });
|
||||||
if (!doc) {
|
let existingIntegrationAuth: TIntegrationAuths | undefined;
|
||||||
|
|
||||||
|
// we need to ensure that the integration auth that we use for Github is actually Oauth
|
||||||
|
if (integration === Integrations.GITHUB) {
|
||||||
|
existingIntegrationAuth = integrationAuths.find((integAuth) => !integAuth.metadata);
|
||||||
|
} else {
|
||||||
|
[existingIntegrationAuth] = integrationAuths;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existingIntegrationAuth) {
|
||||||
return integrationAuthDAL.create(updateDoc, tx);
|
return integrationAuthDAL.create(updateDoc, tx);
|
||||||
}
|
}
|
||||||
return integrationAuthDAL.updateById(doc.id, updateDoc, tx);
|
|
||||||
|
return integrationAuthDAL.updateById(existingIntegrationAuth.id, updateDoc, tx);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -334,6 +378,13 @@ export const integrationAuthServiceFactory = ({
|
|||||||
) {
|
) {
|
||||||
return { accessToken: "", accessId: "" };
|
return { accessToken: "", accessId: "" };
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
integrationAuth.integration === Integrations.GITHUB &&
|
||||||
|
IntegrationAuthMetadataSchema.parse(integrationAuth.metadata || {}).installationId
|
||||||
|
) {
|
||||||
|
return { accessToken: "", accessId: "" };
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
const { decryptor: secretManagerDecryptor, encryptor: secretManagerEncryptor } =
|
const { decryptor: secretManagerDecryptor, encryptor: secretManagerEncryptor } =
|
||||||
await kmsService.createCipherPairWithDataKey({
|
await kmsService.createCipherPairWithDataKey({
|
||||||
@@ -460,6 +511,7 @@ export const integrationAuthServiceFactory = ({
|
|||||||
const { accessToken, accessId } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
|
const { accessToken, accessId } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
|
||||||
const apps = await getApps({
|
const apps = await getApps({
|
||||||
integration: integrationAuth.integration,
|
integration: integrationAuth.integration,
|
||||||
|
integrationAuth,
|
||||||
accessToken,
|
accessToken,
|
||||||
accessId,
|
accessId,
|
||||||
teamId,
|
teamId,
|
||||||
@@ -575,6 +627,7 @@ export const integrationAuthServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getGithubOrgs = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TIntegrationAuthGithubOrgsDTO) => {
|
const getGithubOrgs = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TIntegrationAuthGithubOrgsDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
const integrationAuth = await integrationAuthDAL.findById(id);
|
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||||
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
|
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
|
||||||
|
|
||||||
@@ -587,9 +640,44 @@ export const integrationAuthServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
|
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
|
||||||
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
|
|
||||||
|
|
||||||
const octokit = new Octokit({
|
let octokit: Octokit;
|
||||||
|
const { installationId } = (integrationAuth.metadata as TIntegrationAuthMetadata) || {};
|
||||||
|
if (installationId) {
|
||||||
|
octokit = new Octokit({
|
||||||
|
authStrategy: createAppAuth,
|
||||||
|
auth: {
|
||||||
|
appId: appCfg.CLIENT_APP_ID_GITHUB_APP,
|
||||||
|
privateKey: appCfg.CLIENT_PRIVATE_KEY_GITHUB_APP,
|
||||||
|
installationId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
|
const repos = await octokit.paginate("GET /installation/repositories", {
|
||||||
|
per_page: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgSet: Set<string> = new Set();
|
||||||
|
|
||||||
|
return repos
|
||||||
|
.filter((repo) => repo.owner.type === "Organization")
|
||||||
|
.map((repo) => ({
|
||||||
|
name: repo.owner.login,
|
||||||
|
orgId: String(repo.owner.id)
|
||||||
|
}))
|
||||||
|
.filter((org) => {
|
||||||
|
const isOrgProcessed = orgSet.has(org.orgId);
|
||||||
|
if (!isOrgProcessed) {
|
||||||
|
orgSet.add(org.orgId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !isOrgProcessed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
|
||||||
|
octokit = new Octokit({
|
||||||
auth: accessToken
|
auth: accessToken
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -598,7 +686,9 @@ export const integrationAuthServiceFactory = ({
|
|||||||
"X-GitHub-Api-Version": "2022-11-28"
|
"X-GitHub-Api-Version": "2022-11-28"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!data) return [];
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return data.map(({ login: name, id: orgId }) => ({ name, orgId: String(orgId) }));
|
return data.map(({ login: name, id: orgId }) => ({ name, orgId: String(orgId) }));
|
||||||
};
|
};
|
||||||
@@ -626,9 +716,24 @@ export const integrationAuthServiceFactory = ({
|
|||||||
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
|
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
|
||||||
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
|
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
|
||||||
|
|
||||||
const octokit = new Octokit({
|
let octokit: Octokit;
|
||||||
auth: accessToken
|
const appCfg = getConfig();
|
||||||
});
|
|
||||||
|
const authMetadata = IntegrationAuthMetadataSchema.parse(integrationAuth.metadata || {});
|
||||||
|
if (authMetadata.installationId) {
|
||||||
|
octokit = new Octokit({
|
||||||
|
authStrategy: createAppAuth,
|
||||||
|
auth: {
|
||||||
|
appId: appCfg.CLIENT_APP_ID_GITHUB_APP,
|
||||||
|
privateKey: appCfg.CLIENT_PRIVATE_KEY_GITHUB_APP,
|
||||||
|
installationId: authMetadata.installationId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
octokit = new Octokit({
|
||||||
|
auth: accessToken
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { environments }
|
data: { environments }
|
||||||
@@ -1315,8 +1420,58 @@ export const integrationAuthServiceFactory = ({
|
|||||||
return delIntegrationAuth;
|
return delIntegrationAuth;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// At the moment, we only use this for Github App integration as it's a special case
|
||||||
|
const duplicateIntegrationAuth = async ({
|
||||||
|
id,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
projectId
|
||||||
|
}: TDuplicateGithubIntegrationAuthDTO) => {
|
||||||
|
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||||
|
if (!integrationAuth) {
|
||||||
|
throw new NotFoundError({ message: "Failed to find integration" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission: sourcePermission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
integrationAuth.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(sourcePermission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionSub.Integrations
|
||||||
|
);
|
||||||
|
|
||||||
|
const { permission: targetPermission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(targetPermission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionSub.Integrations
|
||||||
|
);
|
||||||
|
|
||||||
|
const newIntegrationAuth: Omit<typeof integrationAuth, "id"> & { id?: string } = {
|
||||||
|
...integrationAuth,
|
||||||
|
id: undefined,
|
||||||
|
projectId
|
||||||
|
};
|
||||||
|
|
||||||
|
return integrationAuthDAL.create(newIntegrationAuth);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listIntegrationAuthByProjectId,
|
listIntegrationAuthByProjectId,
|
||||||
|
listOrgIntegrationAuth,
|
||||||
getIntegrationOptions,
|
getIntegrationOptions,
|
||||||
getIntegrationAuth,
|
getIntegrationAuth,
|
||||||
oauthExchange,
|
oauthExchange,
|
||||||
@@ -1343,6 +1498,7 @@ export const integrationAuthServiceFactory = ({
|
|||||||
getNorthFlankSecretGroups,
|
getNorthFlankSecretGroups,
|
||||||
getTeamcityBuildConfigs,
|
getTeamcityBuildConfigs,
|
||||||
getBitbucketWorkspaces,
|
getBitbucketWorkspaces,
|
||||||
getIntegrationAccessToken
|
getIntegrationAccessToken,
|
||||||
|
duplicateIntegrationAuth
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export type TOauthExchangeDTO = {
|
|||||||
integration: string;
|
integration: string;
|
||||||
code: string;
|
code: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
installationId?: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TSaveIntegrationAccessTokenDTO = {
|
export type TSaveIntegrationAccessTokenDTO = {
|
||||||
@@ -107,6 +108,10 @@ export type TDeleteIntegrationAuthByIdDTO = {
|
|||||||
id: string;
|
id: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDuplicateGithubIntegrationAuthDTO = {
|
||||||
|
id: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetIntegrationAuthTeamCityBuildConfigDTO = {
|
export type TGetIntegrationAuthTeamCityBuildConfigDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
import { createAppAuth } from "@octokit/auth-app";
|
||||||
import { retry } from "@octokit/plugin-retry";
|
import { retry } from "@octokit/plugin-retry";
|
||||||
import { Octokit } from "@octokit/rest";
|
import { Octokit } from "@octokit/rest";
|
||||||
|
|
||||||
import { TIntegrationAuths, TIntegrations } from "@app/db/schemas";
|
import { TIntegrationAuths, TIntegrations } from "@app/db/schemas";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@@ -15,6 +18,7 @@ import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
|||||||
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||||
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
|
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
|
||||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
|
import { IntegrationAuthMetadataSchema, TIntegrationAuthMetadata } from "./integration-auth-schema";
|
||||||
import { TIntegrationAuthServiceFactory } from "./integration-auth-service";
|
import { TIntegrationAuthServiceFactory } from "./integration-auth-service";
|
||||||
import { Integrations } from "./integration-list";
|
import { Integrations } from "./integration-list";
|
||||||
|
|
||||||
@@ -154,10 +158,12 @@ const getIntegrationSecretsV1 = async (
|
|||||||
|
|
||||||
export const deleteGithubSecrets = async ({
|
export const deleteGithubSecrets = async ({
|
||||||
integration,
|
integration,
|
||||||
|
authMetadata,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken
|
accessToken
|
||||||
}: {
|
}: {
|
||||||
integration: Omit<TIntegrations, "envId">;
|
integration: Omit<TIntegrations, "envId">;
|
||||||
|
authMetadata: TIntegrationAuthMetadata;
|
||||||
secrets: Record<string, boolean>;
|
secrets: Record<string, boolean>;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
@@ -170,9 +176,23 @@ export const deleteGithubSecrets = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const OctokitWithRetry = Octokit.plugin(retry);
|
const OctokitWithRetry = Octokit.plugin(retry);
|
||||||
const octokit = new OctokitWithRetry({
|
let octokit: Octokit;
|
||||||
auth: accessToken
|
const appCfg = getConfig();
|
||||||
});
|
|
||||||
|
if (authMetadata.installationId) {
|
||||||
|
octokit = new OctokitWithRetry({
|
||||||
|
authStrategy: createAppAuth,
|
||||||
|
auth: {
|
||||||
|
appId: appCfg.CLIENT_APP_ID_GITHUB_APP,
|
||||||
|
privateKey: appCfg.CLIENT_PRIVATE_KEY_GITHUB_APP,
|
||||||
|
installationId: authMetadata.installationId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
octokit = new OctokitWithRetry({
|
||||||
|
auth: accessToken
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
enum GithubScope {
|
enum GithubScope {
|
||||||
Repo = "github-repo",
|
Repo = "github-repo",
|
||||||
@@ -192,6 +212,7 @@ export const deleteGithubSecrets = async ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GithubScope.Env: {
|
case GithubScope.Env: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
encryptedGithubSecrets = (
|
encryptedGithubSecrets = (
|
||||||
await octokit.request("GET /repositories/{repository_id}/environments/{environment_name}/secrets", {
|
await octokit.request("GET /repositories/{repository_id}/environments/{environment_name}/secrets", {
|
||||||
repository_id: Number(integration.appId),
|
repository_id: Number(integration.appId),
|
||||||
@@ -346,6 +367,7 @@ export const deleteIntegrationSecrets = async ({
|
|||||||
case Integrations.GITHUB: {
|
case Integrations.GITHUB: {
|
||||||
await deleteGithubSecrets({
|
await deleteGithubSecrets({
|
||||||
integration,
|
integration,
|
||||||
|
authMetadata: IntegrationAuthMetadataSchema.parse(integrationAuth.metadata || {}),
|
||||||
accessToken,
|
accessToken,
|
||||||
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets
|
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export enum Integrations {
|
|||||||
FLYIO = "flyio",
|
FLYIO = "flyio",
|
||||||
LARAVELFORGE = "laravel-forge",
|
LARAVELFORGE = "laravel-forge",
|
||||||
CIRCLECI = "circleci",
|
CIRCLECI = "circleci",
|
||||||
|
DATABRICKS = "databricks",
|
||||||
TRAVISCI = "travisci",
|
TRAVISCI = "travisci",
|
||||||
TEAMCITY = "teamcity",
|
TEAMCITY = "teamcity",
|
||||||
SUPABASE = "supabase",
|
SUPABASE = "supabase",
|
||||||
@@ -73,6 +74,7 @@ export enum IntegrationUrls {
|
|||||||
RAILWAY_API_URL = "https://backboard.railway.app/graphql/v2",
|
RAILWAY_API_URL = "https://backboard.railway.app/graphql/v2",
|
||||||
FLYIO_API_URL = "https://api.fly.io/graphql",
|
FLYIO_API_URL = "https://api.fly.io/graphql",
|
||||||
CIRCLECI_API_URL = "https://circleci.com/api",
|
CIRCLECI_API_URL = "https://circleci.com/api",
|
||||||
|
DATABRICKS_API_URL = "https:/xxxx.com/api",
|
||||||
TRAVISCI_API_URL = "https://api.travis-ci.com",
|
TRAVISCI_API_URL = "https://api.travis-ci.com",
|
||||||
SUPABASE_API_URL = "https://api.supabase.com",
|
SUPABASE_API_URL = "https://api.supabase.com",
|
||||||
LARAVELFORGE_API_URL = "https://forge.laravel.com",
|
LARAVELFORGE_API_URL = "https://forge.laravel.com",
|
||||||
@@ -94,7 +96,9 @@ export enum IntegrationUrls {
|
|||||||
GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com",
|
GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com",
|
||||||
GCP_SECRET_MANAGER_URL = `https://${GCP_SECRET_MANAGER_SERVICE_NAME}`,
|
GCP_SECRET_MANAGER_URL = `https://${GCP_SECRET_MANAGER_SERVICE_NAME}`,
|
||||||
GCP_SERVICE_USAGE_URL = "https://serviceusage.googleapis.com",
|
GCP_SERVICE_USAGE_URL = "https://serviceusage.googleapis.com",
|
||||||
GCP_CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform"
|
GCP_CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform",
|
||||||
|
|
||||||
|
GITHUB_USER_INSTALLATIONS = "https://api.github.com/user/installations"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getIntegrationOptions = async () => {
|
export const getIntegrationOptions = async () => {
|
||||||
@@ -136,6 +140,7 @@ export const getIntegrationOptions = async () => {
|
|||||||
isAvailable: true,
|
isAvailable: true,
|
||||||
type: "oauth",
|
type: "oauth",
|
||||||
clientId: appCfg.CLIENT_ID_GITHUB,
|
clientId: appCfg.CLIENT_ID_GITHUB,
|
||||||
|
clientSlug: appCfg.CLIENT_SLUG_GITHUB_APP,
|
||||||
docsLink: ""
|
docsLink: ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -210,6 +215,15 @@ export const getIntegrationOptions = async () => {
|
|||||||
clientId: "",
|
clientId: "",
|
||||||
docsLink: ""
|
docsLink: ""
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Databricks",
|
||||||
|
slug: "databricks",
|
||||||
|
image: "Databricks.png",
|
||||||
|
isAvailable: true,
|
||||||
|
type: "pat",
|
||||||
|
clientId: "",
|
||||||
|
docsLink: ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "GitLab",
|
name: "GitLab",
|
||||||
slug: "gitlab",
|
slug: "gitlab",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
CreateSecretCommand,
|
CreateSecretCommand,
|
||||||
|
DeleteSecretCommand,
|
||||||
DescribeSecretCommand,
|
DescribeSecretCommand,
|
||||||
GetSecretValueCommand,
|
GetSecretValueCommand,
|
||||||
ResourceNotFoundException,
|
ResourceNotFoundException,
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
UpdateSecretCommand
|
UpdateSecretCommand
|
||||||
} from "@aws-sdk/client-secrets-manager";
|
} from "@aws-sdk/client-secrets-manager";
|
||||||
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
|
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
|
||||||
|
import { createAppAuth } from "@octokit/auth-app";
|
||||||
import { Octokit } from "@octokit/rest";
|
import { Octokit } from "@octokit/rest";
|
||||||
import AWS, { AWSError } from "aws-sdk";
|
import AWS, { AWSError } from "aws-sdk";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
@@ -35,6 +37,7 @@ import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/
|
|||||||
|
|
||||||
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||||
import { IntegrationMetadataSchema } from "../integration/integration-schema";
|
import { IntegrationMetadataSchema } from "../integration/integration-schema";
|
||||||
|
import { IntegrationAuthMetadataSchema } from "./integration-auth-schema";
|
||||||
import { TIntegrationsWithEnvironment } from "./integration-auth-types";
|
import { TIntegrationsWithEnvironment } from "./integration-auth-types";
|
||||||
import {
|
import {
|
||||||
IntegrationInitialSyncBehavior,
|
IntegrationInitialSyncBehavior,
|
||||||
@@ -727,7 +730,7 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
awsParameterStoreSecretsObj[key].KeyId !== metadata.kmsKeyId;
|
awsParameterStoreSecretsObj[key].KeyId !== metadata.kmsKeyId;
|
||||||
|
|
||||||
// we ensure that the KMS key configured in the integration is applied for ALL parameters on AWS
|
// we ensure that the KMS key configured in the integration is applied for ALL parameters on AWS
|
||||||
if (shouldUpdateKms || awsParameterStoreSecretsObj[key].Value !== secrets[key].value) {
|
if (secrets[key].value && (shouldUpdateKms || awsParameterStoreSecretsObj[key].Value !== secrets[key].value)) {
|
||||||
await ssm
|
await ssm
|
||||||
.putParameter({
|
.putParameter({
|
||||||
Name: `${integration.path}${key}`,
|
Name: `${integration.path}${key}`,
|
||||||
@@ -788,7 +791,7 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
logger.info(
|
logger.info(
|
||||||
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=2]`
|
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=2]`
|
||||||
);
|
);
|
||||||
if (!(key in secrets)) {
|
if (!(key in secrets) || !secrets[key].value) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=3]`
|
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=3]`
|
||||||
);
|
);
|
||||||
@@ -899,12 +902,21 @@ const syncSecretsAWSSecretManager = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isEqual(secretToCompare, secretValue)) {
|
if (!isEqual(secretToCompare, secretValue)) {
|
||||||
await secretsManager.send(
|
if (secretValue) {
|
||||||
new UpdateSecretCommand({
|
await secretsManager.send(
|
||||||
SecretId: secretId,
|
new UpdateSecretCommand({
|
||||||
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue)
|
SecretId: secretId,
|
||||||
})
|
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue)
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
// delete it
|
||||||
|
} else {
|
||||||
|
await secretsManager.send(
|
||||||
|
new DeleteSecretCommand({
|
||||||
|
SecretId: secretId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
|
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
|
||||||
@@ -989,16 +1001,21 @@ const syncSecretsAWSSecretManager = async ({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// case 1: when AWS manager can't find the specified secret
|
// case 1: when AWS manager can't find the specified secret
|
||||||
if (err instanceof ResourceNotFoundException && secretsManager) {
|
if (err instanceof ResourceNotFoundException && secretsManager) {
|
||||||
await secretsManager.send(
|
if (secretValue) {
|
||||||
new CreateSecretCommand({
|
await secretsManager.send(
|
||||||
Name: secretId,
|
new CreateSecretCommand({
|
||||||
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue),
|
Name: secretId,
|
||||||
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
|
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue),
|
||||||
Tags: metadata.secretAWSTag
|
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
|
||||||
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ Key: tag.key, Value: tag.value }))
|
Tags: metadata.secretAWSTag
|
||||||
: []
|
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({
|
||||||
})
|
Key: tag.key,
|
||||||
);
|
Value: tag.value
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
// case 2: something unexpected went wrong, so we'll throw the error to reflect the error in the integration sync status
|
// case 2: something unexpected went wrong, so we'll throw the error to reflect the error in the integration sync status
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -1527,11 +1544,13 @@ const syncSecretsNetlify = async ({
|
|||||||
*/
|
*/
|
||||||
const syncSecretsGitHub = async ({
|
const syncSecretsGitHub = async ({
|
||||||
integration,
|
integration,
|
||||||
|
integrationAuth,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken,
|
accessToken,
|
||||||
appendices
|
appendices
|
||||||
}: {
|
}: {
|
||||||
integration: TIntegrations;
|
integration: TIntegrations;
|
||||||
|
integrationAuth: TIntegrationAuths;
|
||||||
secrets: Record<string, { value: string; comment?: string }>;
|
secrets: Record<string, { value: string; comment?: string }>;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
appendices?: { prefix: string; suffix: string };
|
appendices?: { prefix: string; suffix: string };
|
||||||
@@ -1553,9 +1572,24 @@ const syncSecretsGitHub = async ({
|
|||||||
selected_repositories_url?: string | undefined;
|
selected_repositories_url?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const octokit = new Octokit({
|
const authMetadata = IntegrationAuthMetadataSchema.parse(integrationAuth.metadata || {});
|
||||||
auth: accessToken
|
let octokit: Octokit;
|
||||||
});
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
if (authMetadata.installationId) {
|
||||||
|
octokit = new Octokit({
|
||||||
|
authStrategy: createAppAuth,
|
||||||
|
auth: {
|
||||||
|
appId: appCfg.CLIENT_APP_ID_GITHUB_APP,
|
||||||
|
privateKey: appCfg.CLIENT_PRIVATE_KEY_GITHUB_APP,
|
||||||
|
installationId: authMetadata.installationId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
octokit = new Octokit({
|
||||||
|
auth: accessToken
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
enum GithubScope {
|
enum GithubScope {
|
||||||
Repo = "github-repo",
|
Repo = "github-repo",
|
||||||
@@ -2085,6 +2119,80 @@ const syncSecretsCircleCI = async ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync/push [secrets] to Databricks project
|
||||||
|
*/
|
||||||
|
const syncSecretsDatabricks = async ({
|
||||||
|
integration,
|
||||||
|
integrationAuth,
|
||||||
|
secrets,
|
||||||
|
accessToken
|
||||||
|
}: {
|
||||||
|
integration: TIntegrations;
|
||||||
|
integrationAuth: TIntegrationAuths;
|
||||||
|
secrets: Record<string, { value: string; comment?: string }>;
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
const databricksApiUrl = `${integrationAuth.url}/api`;
|
||||||
|
|
||||||
|
// sync secrets to Databricks
|
||||||
|
await Promise.all(
|
||||||
|
Object.keys(secrets).map(async (key) =>
|
||||||
|
request.post(
|
||||||
|
`${databricksApiUrl}/2.0/secrets/put`,
|
||||||
|
{
|
||||||
|
scope: integration.app,
|
||||||
|
key,
|
||||||
|
string_value: secrets[key].value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// get secrets from Databricks
|
||||||
|
const getSecretsRes = (
|
||||||
|
await request.get<{ secrets: { key: string; last_updated_timestamp: number }[] }>(
|
||||||
|
`${databricksApiUrl}/2.0/secrets/list`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
scope: integration.app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).data.secrets;
|
||||||
|
|
||||||
|
// delete secrets from Databricks
|
||||||
|
await Promise.all(
|
||||||
|
getSecretsRes.map(async (sec) => {
|
||||||
|
if (!(sec.key in secrets)) {
|
||||||
|
return request.post(
|
||||||
|
`${databricksApiUrl}/2.0/secrets/delete`,
|
||||||
|
{
|
||||||
|
scope: integration.app,
|
||||||
|
key: sec.key
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync/push [secrets] to TravisCI project
|
* Sync/push [secrets] to TravisCI project
|
||||||
*/
|
*/
|
||||||
@@ -3980,6 +4088,7 @@ export const syncIntegrationSecrets = async ({
|
|||||||
case Integrations.GITHUB:
|
case Integrations.GITHUB:
|
||||||
await syncSecretsGitHub({
|
await syncSecretsGitHub({
|
||||||
integration,
|
integration,
|
||||||
|
integrationAuth,
|
||||||
secrets,
|
secrets,
|
||||||
accessToken,
|
accessToken,
|
||||||
appendices
|
appendices
|
||||||
@@ -4021,6 +4130,14 @@ export const syncIntegrationSecrets = async ({
|
|||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case Integrations.DATABRICKS:
|
||||||
|
await syncSecretsDatabricks({
|
||||||
|
integration,
|
||||||
|
integrationAuth,
|
||||||
|
secrets,
|
||||||
|
accessToken
|
||||||
|
});
|
||||||
|
break;
|
||||||
case Integrations.LARAVELFORGE:
|
case Integrations.LARAVELFORGE:
|
||||||
await syncSecretsLaravelForge({
|
await syncSecretsLaravelForge({
|
||||||
integration,
|
integration,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import jwt from "jsonwebtoken";
|
|||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { Integrations, IntegrationUrls } from "./integration-list";
|
import { Integrations, IntegrationUrls } from "./integration-list";
|
||||||
|
|
||||||
@@ -234,12 +234,73 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const exchangeCodeGithub = async ({ code }: { code: string }) => {
|
const exchangeCodeGithub = async ({ code, installationId }: { code: string; installationId?: string }) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
if (!appCfg.CLIENT_ID_GITHUB || !appCfg.CLIENT_SECRET_GITHUB) {
|
|
||||||
throw new BadRequestError({ message: "Missing client id and client secret" });
|
if (!installationId && (!appCfg.CLIENT_ID_GITHUB || !appCfg.CLIENT_SECRET_GITHUB)) {
|
||||||
|
throw new InternalServerError({ message: "Missing client id and client secret" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (installationId && (!appCfg.CLIENT_ID_GITHUB_APP || !appCfg.CLIENT_SECRET_GITHUB_APP)) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: "Missing Github app client ID and client secret"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (installationId) {
|
||||||
|
// handle app installations
|
||||||
|
const oauthRes = (
|
||||||
|
await request.get<ExchangeCodeGithubResponse>(IntegrationUrls.GITHUB_TOKEN_URL, {
|
||||||
|
params: {
|
||||||
|
client_id: appCfg.CLIENT_ID_GITHUB_APP,
|
||||||
|
client_secret: appCfg.CLIENT_SECRET_GITHUB_APP,
|
||||||
|
code,
|
||||||
|
redirect_uri: `${appCfg.SITE_URL}/integrations/github/oauth2/callback`
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
// use access token to validate installation ID
|
||||||
|
const installationsRes = (
|
||||||
|
await request.get<{
|
||||||
|
installations: {
|
||||||
|
id: number;
|
||||||
|
account: {
|
||||||
|
login: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
}>(IntegrationUrls.GITHUB_USER_INSTALLATIONS, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
Authorization: `Bearer ${oauthRes.access_token}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
const matchingInstallation = installationsRes.installations.find(
|
||||||
|
(installation) => installation.id === +installationId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!matchingInstallation) {
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message: "User has no access to the provided installation"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
accessToken: "", // for github app integrations, we only need the installationID from the metadata
|
||||||
|
refreshToken: null,
|
||||||
|
accessExpiresAt: null,
|
||||||
|
installationName: matchingInstallation.account.login
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle oauth github integration
|
||||||
const res = (
|
const res = (
|
||||||
await request.get<ExchangeCodeGithubResponse>(IntegrationUrls.GITHUB_TOKEN_URL, {
|
await request.get<ExchangeCodeGithubResponse>(IntegrationUrls.GITHUB_TOKEN_URL, {
|
||||||
params: {
|
params: {
|
||||||
@@ -346,6 +407,7 @@ type TExchangeReturn = {
|
|||||||
url?: string;
|
url?: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
|
installationName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -355,11 +417,13 @@ type TExchangeReturn = {
|
|||||||
export const exchangeCode = async ({
|
export const exchangeCode = async ({
|
||||||
integration,
|
integration,
|
||||||
code,
|
code,
|
||||||
url
|
url,
|
||||||
|
installationId
|
||||||
}: {
|
}: {
|
||||||
integration: string;
|
integration: string;
|
||||||
code: string;
|
code: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
installationId?: string;
|
||||||
}): Promise<TExchangeReturn> => {
|
}): Promise<TExchangeReturn> => {
|
||||||
switch (integration) {
|
switch (integration) {
|
||||||
case Integrations.GCP_SECRET_MANAGER:
|
case Integrations.GCP_SECRET_MANAGER:
|
||||||
@@ -384,7 +448,8 @@ export const exchangeCode = async ({
|
|||||||
});
|
});
|
||||||
case Integrations.GITHUB:
|
case Integrations.GITHUB:
|
||||||
return exchangeCodeGithub({
|
return exchangeCodeGithub({
|
||||||
code
|
code,
|
||||||
|
installationId
|
||||||
});
|
});
|
||||||
case Integrations.GITLAB:
|
case Integrations.GITLAB:
|
||||||
return exchangeCodeGitlab({
|
return exchangeCodeGitlab({
|
||||||
|
|||||||
@@ -120,7 +120,13 @@ export const integrationServiceFactory = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
projectId: integrationAuth.projectId
|
projectId: integrationAuth.projectId
|
||||||
});
|
});
|
||||||
return { integration, integrationAuth };
|
return {
|
||||||
|
integration: {
|
||||||
|
...integration,
|
||||||
|
environment: folder.environment
|
||||||
|
},
|
||||||
|
integrationAuth
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateIntegration = async ({
|
const updateIntegration = async ({
|
||||||
@@ -183,7 +189,10 @@ export const integrationServiceFactory = ({
|
|||||||
projectId: folder.projectId
|
projectId: folder.projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
return updatedIntegration;
|
return {
|
||||||
|
...updatedIntegration,
|
||||||
|
environment: folder.environment
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIntegration = async ({ id, actor, actorAuthMethod, actorId, actorOrgId }: TGetIntegrationDTO) => {
|
const getIntegration = async ({ id, actor, actorAuthMethod, actorId, actorOrgId }: TGetIntegrationDTO) => {
|
||||||
@@ -249,27 +258,7 @@ export const integrationServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedIntegration = await integrationDAL.transaction(async (tx) => {
|
const deletedIntegration = await integrationDAL.deleteById(id);
|
||||||
// delete integration
|
|
||||||
const deletedIntegrationResult = await integrationDAL.deleteById(id, tx);
|
|
||||||
|
|
||||||
// check if there are other integrations that share the same integration auth
|
|
||||||
const integrations = await integrationDAL.find(
|
|
||||||
{
|
|
||||||
integrationAuthId: integration.integrationAuthId
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
if (integrations.length === 0) {
|
|
||||||
// no other integration shares the same integration auth
|
|
||||||
// -> delete the integration auth
|
|
||||||
await integrationAuthDAL.deleteById(integration.integrationAuthId, tx);
|
|
||||||
}
|
|
||||||
|
|
||||||
return deletedIntegrationResult;
|
|
||||||
});
|
|
||||||
|
|
||||||
return { ...integration, ...deletedIntegration };
|
return { ...integration, ...deletedIntegration };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -160,8 +160,8 @@ export const kmsServiceFactory = ({
|
|||||||
* In mean time the rest of the request will wait until creation is finished followed by getting the created on
|
* In mean time the rest of the request will wait until creation is finished followed by getting the created on
|
||||||
* In real time this would be milliseconds
|
* In real time this would be milliseconds
|
||||||
*/
|
*/
|
||||||
const getOrgKmsKeyId = async (orgId: string) => {
|
const getOrgKmsKeyId = async (orgId: string, trx?: Knex) => {
|
||||||
let org = await orgDAL.findById(orgId);
|
let org = await orgDAL.findById(orgId, trx);
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({ message: "Org not found" });
|
throw new NotFoundError({ message: "Org not found" });
|
||||||
@@ -180,9 +180,9 @@ export const kmsServiceFactory = ({
|
|||||||
waitingCb: () => logger.info("KMS. Waiting for org key to be created")
|
waitingCb: () => logger.info("KMS. Waiting for org key to be created")
|
||||||
});
|
});
|
||||||
|
|
||||||
org = await orgDAL.findById(orgId);
|
org = await orgDAL.findById(orgId, trx);
|
||||||
} else {
|
} else {
|
||||||
const keyId = await orgDAL.transaction(async (tx) => {
|
const keyId = await (trx || orgDAL).transaction(async (tx) => {
|
||||||
org = await orgDAL.findById(orgId, tx);
|
org = await orgDAL.findById(orgId, tx);
|
||||||
if (org.kmsDefaultKeyId) {
|
if (org.kmsDefaultKeyId) {
|
||||||
return org.kmsDefaultKeyId;
|
return org.kmsDefaultKeyId;
|
||||||
@@ -240,11 +240,12 @@ export const kmsServiceFactory = ({
|
|||||||
|
|
||||||
const decryptWithKmsKey = async ({
|
const decryptWithKmsKey = async ({
|
||||||
kmsId,
|
kmsId,
|
||||||
depth = 0
|
depth = 0,
|
||||||
}: Omit<TDecryptWithKmsDTO, "cipherTextBlob"> & { depth?: number }) => {
|
tx
|
||||||
|
}: Omit<TDecryptWithKmsDTO, "cipherTextBlob"> & { depth?: number; tx?: Knex }) => {
|
||||||
if (depth > 2) throw new BadRequestError({ message: "KMS depth max limit" });
|
if (depth > 2) throw new BadRequestError({ message: "KMS depth max limit" });
|
||||||
|
|
||||||
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
|
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId, tx);
|
||||||
if (!kmsDoc) {
|
if (!kmsDoc) {
|
||||||
throw new NotFoundError({ message: "KMS ID not found" });
|
throw new NotFoundError({ message: "KMS ID not found" });
|
||||||
}
|
}
|
||||||
@@ -261,7 +262,8 @@ export const kmsServiceFactory = ({
|
|||||||
// we put a limit of depth to avoid too many cycles
|
// we put a limit of depth to avoid too many cycles
|
||||||
const orgKmsDecryptor = await decryptWithKmsKey({
|
const orgKmsDecryptor = await decryptWithKmsKey({
|
||||||
kmsId: kmsDoc.orgKms.id,
|
kmsId: kmsDoc.orgKms.id,
|
||||||
depth: depth + 1
|
depth: depth + 1,
|
||||||
|
tx
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgKmsDataKey = await orgKmsDecryptor({
|
const orgKmsDataKey = await orgKmsDecryptor({
|
||||||
@@ -375,9 +377,9 @@ export const kmsServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const $getOrgKmsDataKey = async (orgId: string) => {
|
const $getOrgKmsDataKey = async (orgId: string, trx?: Knex) => {
|
||||||
const kmsKeyId = await getOrgKmsKeyId(orgId);
|
const kmsKeyId = await getOrgKmsKeyId(orgId, trx);
|
||||||
let org = await orgDAL.findById(orgId);
|
let org = await orgDAL.findById(orgId, trx);
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({ message: "Org not found" });
|
throw new NotFoundError({ message: "Org not found" });
|
||||||
@@ -396,9 +398,9 @@ export const kmsServiceFactory = ({
|
|||||||
waitingCb: () => logger.info("KMS. Waiting for org data key to be created")
|
waitingCb: () => logger.info("KMS. Waiting for org data key to be created")
|
||||||
});
|
});
|
||||||
|
|
||||||
org = await orgDAL.findById(orgId);
|
org = await orgDAL.findById(orgId, trx);
|
||||||
} else {
|
} else {
|
||||||
const orgDataKey = await orgDAL.transaction(async (tx) => {
|
const orgDataKey = await (trx || orgDAL).transaction(async (tx) => {
|
||||||
org = await orgDAL.findById(orgId, tx);
|
org = await orgDAL.findById(orgId, tx);
|
||||||
if (org.kmsEncryptedDataKey) {
|
if (org.kmsEncryptedDataKey) {
|
||||||
return;
|
return;
|
||||||
@@ -455,8 +457,8 @@ export const kmsServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
|
const getProjectSecretManagerKmsKeyId = async (projectId: string, trx?: Knex) => {
|
||||||
let project = await projectDAL.findById(projectId);
|
let project = await projectDAL.findById(projectId, trx);
|
||||||
if (!project) {
|
if (!project) {
|
||||||
throw new NotFoundError({ message: "Project not found" });
|
throw new NotFoundError({ message: "Project not found" });
|
||||||
}
|
}
|
||||||
@@ -477,7 +479,7 @@ export const kmsServiceFactory = ({
|
|||||||
|
|
||||||
project = await projectDAL.findById(projectId);
|
project = await projectDAL.findById(projectId);
|
||||||
} else {
|
} else {
|
||||||
const kmsKeyId = await projectDAL.transaction(async (tx) => {
|
const kmsKeyId = await (trx || projectDAL).transaction(async (tx) => {
|
||||||
project = await projectDAL.findById(projectId, tx);
|
project = await projectDAL.findById(projectId, tx);
|
||||||
if (project.kmsSecretManagerKeyId) {
|
if (project.kmsSecretManagerKeyId) {
|
||||||
return project.kmsSecretManagerKeyId;
|
return project.kmsSecretManagerKeyId;
|
||||||
@@ -520,9 +522,9 @@ export const kmsServiceFactory = ({
|
|||||||
return project.kmsSecretManagerKeyId;
|
return project.kmsSecretManagerKeyId;
|
||||||
};
|
};
|
||||||
|
|
||||||
const $getProjectSecretManagerKmsDataKey = async (projectId: string) => {
|
const $getProjectSecretManagerKmsDataKey = async (projectId: string, trx?: Knex) => {
|
||||||
const kmsKeyId = await getProjectSecretManagerKmsKeyId(projectId);
|
const kmsKeyId = await getProjectSecretManagerKmsKeyId(projectId, trx);
|
||||||
let project = await projectDAL.findById(projectId);
|
let project = await projectDAL.findById(projectId, trx);
|
||||||
|
|
||||||
if (!project.kmsSecretManagerEncryptedDataKey) {
|
if (!project.kmsSecretManagerEncryptedDataKey) {
|
||||||
const lock = await keyStore
|
const lock = await keyStore
|
||||||
@@ -538,18 +540,21 @@ export const kmsServiceFactory = ({
|
|||||||
delay: 500
|
delay: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
project = await projectDAL.findById(projectId);
|
project = await projectDAL.findById(projectId, trx);
|
||||||
} else {
|
} else {
|
||||||
const projectDataKey = await projectDAL.transaction(async (tx) => {
|
const projectDataKey = await (trx || projectDAL).transaction(async (tx) => {
|
||||||
project = await projectDAL.findById(projectId, tx);
|
project = await projectDAL.findById(projectId, tx);
|
||||||
if (project.kmsSecretManagerEncryptedDataKey) {
|
if (project.kmsSecretManagerEncryptedDataKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataKey = randomSecureBytes();
|
const dataKey = randomSecureBytes();
|
||||||
const kmsEncryptor = await encryptWithKmsKey({
|
const kmsEncryptor = await encryptWithKmsKey(
|
||||||
kmsId: kmsKeyId
|
{
|
||||||
});
|
kmsId: kmsKeyId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
const { cipherTextBlob } = await kmsEncryptor({
|
const { cipherTextBlob } = await kmsEncryptor({
|
||||||
plainText: dataKey
|
plainText: dataKey
|
||||||
@@ -585,7 +590,8 @@ export const kmsServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const kmsDecryptor = await decryptWithKmsKey({
|
const kmsDecryptor = await decryptWithKmsKey({
|
||||||
kmsId: kmsKeyId
|
kmsId: kmsKeyId,
|
||||||
|
tx: trx
|
||||||
});
|
});
|
||||||
|
|
||||||
return kmsDecryptor({
|
return kmsDecryptor({
|
||||||
@@ -593,13 +599,13 @@ export const kmsServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const $getDataKey = async (dto: TEncryptWithKmsDataKeyDTO) => {
|
const $getDataKey = async (dto: TEncryptWithKmsDataKeyDTO, trx?: Knex) => {
|
||||||
switch (dto.type) {
|
switch (dto.type) {
|
||||||
case KmsDataKey.SecretManager: {
|
case KmsDataKey.SecretManager: {
|
||||||
return $getProjectSecretManagerKmsDataKey(dto.projectId);
|
return $getProjectSecretManagerKmsDataKey(dto.projectId, trx);
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return $getOrgKmsDataKey(dto.orgId);
|
return $getOrgKmsDataKey(dto.orgId, trx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -607,8 +613,9 @@ export const kmsServiceFactory = ({
|
|||||||
// by keeping the decrypted data key in inner scope
|
// by keeping the decrypted data key in inner scope
|
||||||
// none of the entities outside can interact directly or expose the data key
|
// none of the entities outside can interact directly or expose the data key
|
||||||
// NOTICE: If changing here update migrations/utils/kms
|
// NOTICE: If changing here update migrations/utils/kms
|
||||||
const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO) => {
|
const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO, trx?: Knex) => {
|
||||||
const dataKey = await $getDataKey(encryptionContext);
|
const dataKey = await $getDataKey(encryptionContext, trx);
|
||||||
|
|
||||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -106,14 +106,19 @@ export const orgDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("firstName").withSchema(TableName.Users),
|
db.ref("firstName").withSchema(TableName.Users),
|
||||||
db.ref("lastName").withSchema(TableName.Users),
|
db.ref("lastName").withSchema(TableName.Users),
|
||||||
db.ref("id").withSchema(TableName.Users).as("userId"),
|
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||||
|
db.ref("superAdmin").withSchema(TableName.Users),
|
||||||
db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
|
db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
|
||||||
)
|
)
|
||||||
.where({ isGhost: false }); // MAKE SURE USER IS NOT A GHOST USER
|
.where({ isGhost: false }) // MAKE SURE USER IS NOT A GHOST USER
|
||||||
|
.orderBy("firstName")
|
||||||
|
.orderBy("lastName");
|
||||||
|
|
||||||
return members.map(({ email, isEmailVerified, username, firstName, lastName, userId, publicKey, ...data }) => ({
|
return members.map(
|
||||||
...data,
|
({ email, isEmailVerified, username, firstName, lastName, userId, publicKey, superAdmin, ...data }) => ({
|
||||||
user: { email, isEmailVerified, username, firstName, lastName, id: userId, publicKey }
|
...data,
|
||||||
}));
|
user: { email, isEmailVerified, username, firstName, lastName, id: userId, publicKey, superAdmin }
|
||||||
|
})
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Find all org members" });
|
throw new DatabaseError({ error, name: "Find all org members" });
|
||||||
}
|
}
|
||||||
@@ -370,6 +375,7 @@ export const orgDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("firstName").withSchema(TableName.Users),
|
db.ref("firstName").withSchema(TableName.Users),
|
||||||
db.ref("lastName").withSchema(TableName.Users),
|
db.ref("lastName").withSchema(TableName.Users),
|
||||||
db.ref("scimEnabled").withSchema(TableName.Organization),
|
db.ref("scimEnabled").withSchema(TableName.Organization),
|
||||||
|
db.ref("defaultMembershipRole").withSchema(TableName.Organization),
|
||||||
db.ref("externalId").withSchema(TableName.UserAliases)
|
db.ref("externalId").withSchema(TableName.UserAliases)
|
||||||
)
|
)
|
||||||
.where({ isGhost: false });
|
.where({ isGhost: false });
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { OrgMembershipRole } from "@app/db/schemas";
|
||||||
|
import { TFeatureSet } from "@app/ee/services/license/license-types";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
||||||
|
|
||||||
|
const RESERVED_ORG_ROLE_SLUGS = Object.values(OrgMembershipRole).filter((role) => role !== "custom");
|
||||||
|
|
||||||
|
export const isCustomOrgRole = (roleSlug: string) => !RESERVED_ORG_ROLE_SLUGS.includes(roleSlug as OrgMembershipRole);
|
||||||
|
|
||||||
|
// this is only for updating an org
|
||||||
|
export const getDefaultOrgMembershipRoleForUpdateOrg = async ({
|
||||||
|
membershipRoleSlug,
|
||||||
|
orgRoleDAL,
|
||||||
|
plan,
|
||||||
|
orgId
|
||||||
|
}: {
|
||||||
|
orgId: string;
|
||||||
|
membershipRoleSlug: string;
|
||||||
|
orgRoleDAL: TOrgRoleDALFactory;
|
||||||
|
plan: TFeatureSet;
|
||||||
|
}) => {
|
||||||
|
if (isCustomOrgRole(membershipRoleSlug)) {
|
||||||
|
if (!plan?.rbac)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to set custom default role due to plan RBAC restriction. Upgrade plan to set custom default org membership role."
|
||||||
|
});
|
||||||
|
|
||||||
|
const customRole = await orgRoleDAL.findOne({ slug: membershipRoleSlug, orgId });
|
||||||
|
if (!customRole) throw new NotFoundError({ name: "UpdateOrg", message: "Organization role not found" });
|
||||||
|
|
||||||
|
// use ID for default role
|
||||||
|
return customRole.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not custom, use reserved slug
|
||||||
|
return membershipRoleSlug;
|
||||||
|
};
|
||||||
|
|
||||||
|
// this is only for creating an org membership
|
||||||
|
export const getDefaultOrgMembershipRole = async (
|
||||||
|
defaultOrgMembershipRole: string // can either be ID or reserved slug
|
||||||
|
) => {
|
||||||
|
if (isCustomOrgRole(defaultOrgMembershipRole))
|
||||||
|
return {
|
||||||
|
roleId: defaultOrgMembershipRole,
|
||||||
|
role: OrgMembershipRole.Custom
|
||||||
|
};
|
||||||
|
|
||||||
|
// will be reserved slug
|
||||||
|
return { roleId: undefined, role: defaultOrgMembershipRole as OrgMembershipRole };
|
||||||
|
};
|
||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
} from "@app/ee/services/permission/org-permission";
|
} from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
|
||||||
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
|
||||||
import { ActorAuthMethod } from "../auth/auth-type";
|
import { ActorAuthMethod } from "../auth/auth-type";
|
||||||
import { TOrgRoleDALFactory } from "./org-role-dal";
|
import { TOrgRoleDALFactory } from "./org-role-dal";
|
||||||
@@ -18,11 +20,18 @@ import { TOrgRoleDALFactory } from "./org-role-dal";
|
|||||||
type TOrgRoleServiceFactoryDep = {
|
type TOrgRoleServiceFactoryDep = {
|
||||||
orgRoleDAL: TOrgRoleDALFactory;
|
orgRoleDAL: TOrgRoleDALFactory;
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
|
orgDAL: TOrgDALFactory;
|
||||||
|
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgRoleServiceFactory = ReturnType<typeof orgRoleServiceFactory>;
|
export type TOrgRoleServiceFactory = ReturnType<typeof orgRoleServiceFactory>;
|
||||||
|
|
||||||
export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRoleServiceFactoryDep) => {
|
export const orgRoleServiceFactory = ({
|
||||||
|
orgRoleDAL,
|
||||||
|
orgDAL,
|
||||||
|
permissionService,
|
||||||
|
externalGroupOrgRoleMappingDAL
|
||||||
|
}: TOrgRoleServiceFactoryDep) => {
|
||||||
const createRole = async (
|
const createRole = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
orgId: string,
|
orgId: string,
|
||||||
@@ -129,6 +138,30 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol
|
|||||||
) => {
|
) => {
|
||||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Role);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Role);
|
||||||
|
|
||||||
|
const org = await orgDAL.findOrgById(orgId);
|
||||||
|
|
||||||
|
if (!org)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Failed to find organization"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (org.defaultMembershipRole === roleId)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Cannot delete default org membership role. Please re-assign and try again."
|
||||||
|
});
|
||||||
|
|
||||||
|
const externalGroupMapping = await externalGroupOrgRoleMappingDAL.findOne({
|
||||||
|
orgId,
|
||||||
|
roleId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (externalGroupMapping)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Cannot delete role assigned to external group organization role mapping. Please re-assign external mapping and try again."
|
||||||
|
});
|
||||||
|
|
||||||
const [deletedRole] = await orgRoleDAL.delete({ id: roleId, orgId });
|
const [deletedRole] = await orgRoleDAL.delete({ id: roleId, orgId });
|
||||||
if (!deletedRole) throw new NotFoundError({ message: "Organization role not found", name: "Update role" });
|
if (!deletedRole) throw new NotFoundError({ message: "Organization role not found", name: "Update role" });
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedErro
|
|||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { isDisposableEmail } from "@app/lib/validator";
|
import { isDisposableEmail } from "@app/lib/validator";
|
||||||
|
import { getDefaultOrgMembershipRoleForUpdateOrg } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||||
|
|
||||||
@@ -40,8 +41,9 @@ import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
|||||||
import { TokenType } from "../auth-token/auth-token-types";
|
import { TokenType } from "../auth-token/auth-token-types";
|
||||||
import { TIdentityMetadataDALFactory } from "../identity/identity-metadata-dal";
|
import { TIdentityMetadataDALFactory } from "../identity/identity-metadata-dal";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { assignWorkspaceKeysToMembers } from "../project/project-fns";
|
import { assignWorkspaceKeysToMembers, createProjectKey } from "../project/project-fns";
|
||||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
||||||
|
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||||
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
||||||
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
||||||
@@ -79,7 +81,7 @@ type TOrgServiceFactoryDep = {
|
|||||||
TProjectMembershipDALFactory,
|
TProjectMembershipDALFactory,
|
||||||
"findProjectMembershipsByUserId" | "delete" | "create" | "find" | "insertMany" | "transaction"
|
"findProjectMembershipsByUserId" | "delete" | "create" | "find" | "insertMany" | "transaction"
|
||||||
>;
|
>;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "insertMany" | "findLatestProjectKey">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "insertMany" | "findLatestProjectKey" | "create">;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOne" | "findById">;
|
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOne" | "findById">;
|
||||||
incidentContactDAL: TIncidentContactsDALFactory;
|
incidentContactDAL: TIncidentContactsDALFactory;
|
||||||
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">;
|
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">;
|
||||||
@@ -93,8 +95,9 @@ type TOrgServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne" | "updateById">;
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "create">;
|
||||||
|
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
|
||||||
@@ -121,7 +124,8 @@ export const orgServiceFactory = ({
|
|||||||
oidcConfigDAL,
|
oidcConfigDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
identityMetadataDAL
|
identityMetadataDAL,
|
||||||
|
projectBotService
|
||||||
}: TOrgServiceFactoryDep) => {
|
}: TOrgServiceFactoryDep) => {
|
||||||
/*
|
/*
|
||||||
* Get organization details by the organization id
|
* Get organization details by the organization id
|
||||||
@@ -264,7 +268,7 @@ export const orgServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
orgId,
|
orgId,
|
||||||
data: { name, slug, authEnforced, scimEnabled }
|
data: { name, slug, authEnforced, scimEnabled, defaultMembershipRoleSlug }
|
||||||
}: TUpdateOrgDTO) => {
|
}: TUpdateOrgDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
||||||
@@ -298,11 +302,22 @@ export const orgServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let defaultMembershipRole: string | undefined;
|
||||||
|
if (defaultMembershipRoleSlug) {
|
||||||
|
defaultMembershipRole = await getDefaultOrgMembershipRoleForUpdateOrg({
|
||||||
|
membershipRoleSlug: defaultMembershipRoleSlug,
|
||||||
|
orgId,
|
||||||
|
orgRoleDAL,
|
||||||
|
plan
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const org = await orgDAL.updateById(orgId, {
|
const org = await orgDAL.updateById(orgId, {
|
||||||
name,
|
name,
|
||||||
slug: slug ? slugify(slug) : undefined,
|
slug: slug ? slugify(slug) : undefined,
|
||||||
authEnforced,
|
authEnforced,
|
||||||
scimEnabled
|
scimEnabled,
|
||||||
|
defaultMembershipRole
|
||||||
});
|
});
|
||||||
if (!org) throw new NotFoundError({ message: "Organization not found" });
|
if (!org) throw new NotFoundError({ message: "Organization not found" });
|
||||||
return org;
|
return org;
|
||||||
@@ -706,20 +721,67 @@ export const orgServiceFactory = ({
|
|||||||
|
|
||||||
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
||||||
|
|
||||||
const ghostUser = await projectDAL.findProjectGhostUser(projectId, tx);
|
// this will auto generate bot
|
||||||
if (!ghostUser) {
|
const { botKey, bot: autoGeneratedBot } = await projectBotService.getBotKey(projectId, true);
|
||||||
throw new NotFoundError({
|
|
||||||
name: "InviteUser",
|
|
||||||
message: "Failed to find project owner"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId, tx);
|
const ghostUser = await projectDAL.findProjectGhostUser(projectId, tx);
|
||||||
if (!ghostUserLatestKey) {
|
let ghostUserId = ghostUser?.id;
|
||||||
throw new NotFoundError({
|
|
||||||
name: "InviteUser",
|
// backfill missing ghost user
|
||||||
message: "Failed to find project owner's latest key"
|
if (!ghostUserId) {
|
||||||
|
const newGhostUser = await addGhostUser(project.orgId, tx);
|
||||||
|
const projectMembership = await projectMembershipDAL.create(
|
||||||
|
{
|
||||||
|
userId: newGhostUser.user.id,
|
||||||
|
projectId: project.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await projectUserMembershipRoleDAL.create(
|
||||||
|
{ projectMembershipId: projectMembership.id, role: ProjectMembershipRole.Admin },
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const { key: encryptedProjectKey, iv: encryptedProjectKeyIv } = createProjectKey({
|
||||||
|
publicKey: newGhostUser.keys.publicKey,
|
||||||
|
privateKey: newGhostUser.keys.plainPrivateKey,
|
||||||
|
plainProjectKey: botKey
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 4. Save the project key for the ghost user.
|
||||||
|
await projectKeyDAL.create(
|
||||||
|
{
|
||||||
|
projectId: project.id,
|
||||||
|
receiverId: newGhostUser.user.id,
|
||||||
|
encryptedKey: encryptedProjectKey,
|
||||||
|
nonce: encryptedProjectKeyIv,
|
||||||
|
senderId: newGhostUser.user.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(
|
||||||
|
newGhostUser.keys.plainPrivateKey
|
||||||
|
);
|
||||||
|
if (autoGeneratedBot) {
|
||||||
|
await projectBotDAL.updateById(
|
||||||
|
autoGeneratedBot.id,
|
||||||
|
{
|
||||||
|
tag,
|
||||||
|
iv,
|
||||||
|
encryptedProjectKey,
|
||||||
|
encryptedProjectKeyNonce: encryptedProjectKeyIv,
|
||||||
|
encryptedPrivateKey: ciphertext,
|
||||||
|
isActive: true,
|
||||||
|
publicKey: newGhostUser.keys.publicKey,
|
||||||
|
senderId: newGhostUser.user.id,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding: encoding
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ghostUserId = newGhostUser.user.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bot = await projectBotDAL.findOne({ projectId }, tx);
|
const bot = await projectBotDAL.findOne({ projectId }, tx);
|
||||||
@@ -730,6 +792,14 @@ export const orgServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUserId, projectId, tx);
|
||||||
|
if (!ghostUserLatestKey) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
name: "InviteUser",
|
||||||
|
message: "Failed to find project owner's latest key"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const botPrivateKey = infisicalSymmetricDecrypt({
|
const botPrivateKey = infisicalSymmetricDecrypt({
|
||||||
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
||||||
iv: bot.iv,
|
iv: bot.iv,
|
||||||
@@ -773,7 +843,7 @@ export const orgServiceFactory = ({
|
|||||||
newWsMembers.map((el) => ({
|
newWsMembers.map((el) => ({
|
||||||
encryptedKey: el.workspaceEncryptedKey,
|
encryptedKey: el.workspaceEncryptedKey,
|
||||||
nonce: el.workspaceEncryptedNonce,
|
nonce: el.workspaceEncryptedNonce,
|
||||||
senderId: ghostUser.id,
|
senderId: ghostUserId,
|
||||||
receiverId: el.orgMembershipId,
|
receiverId: el.orgMembershipId,
|
||||||
projectId
|
projectId
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -26,18 +26,13 @@ export type TDeleteOrgMembershipDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TInviteUserToOrgDTO = {
|
export type TInviteUserToOrgDTO = {
|
||||||
actorId: string;
|
|
||||||
actor: ActorType;
|
|
||||||
orgId: string;
|
|
||||||
actorOrgId: string | undefined;
|
|
||||||
actorAuthMethod: ActorAuthMethod;
|
|
||||||
inviteeEmails: string[];
|
inviteeEmails: string[];
|
||||||
organizationRoleSlug: string;
|
organizationRoleSlug: string;
|
||||||
projects?: {
|
projects?: {
|
||||||
id: string;
|
id: string;
|
||||||
projectRoleSlug?: string[];
|
projectRoleSlug?: string[];
|
||||||
}[];
|
}[];
|
||||||
};
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TVerifyUserToOrgDTO = {
|
export type TVerifyUserToOrgDTO = {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -63,7 +58,13 @@ export type TFindAllWorkspacesDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateOrgDTO = {
|
export type TUpdateOrgDTO = {
|
||||||
data: Partial<{ name: string; slug: string; authEnforced: boolean; scimEnabled: boolean }>;
|
data: Partial<{
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
authEnforced: boolean;
|
||||||
|
scimEnabled: boolean;
|
||||||
|
defaultMembershipRoleSlug: string;
|
||||||
|
}>;
|
||||||
} & TOrgPermission;
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TGetOrgGroupsDTO = TOrgPermission;
|
export type TGetOrgGroupsDTO = TOrgPermission;
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ export const getBotKeyFnFactory = (
|
|||||||
projectBotDAL: TProjectBotDALFactory,
|
projectBotDAL: TProjectBotDALFactory,
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">
|
projectDAL: Pick<TProjectDALFactory, "findById">
|
||||||
) => {
|
) => {
|
||||||
const getBotKeyFn = async (projectId: string) => {
|
const getBotKeyFn = async (projectId: string, shouldGetBotKey?: boolean) => {
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
if (!project)
|
if (!project)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Project not found during bot lookup. Are you sure you are using the correct project ID?"
|
message: "Project not found during bot lookup. Are you sure you are using the correct project ID?"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (project.version === 3) {
|
if (project.version === 3 && !shouldGetBotKey) {
|
||||||
return { project, shouldUseSecretV2Bridge: true };
|
return { project, shouldUseSecretV2Bridge: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,8 +65,9 @@ export const getBotKeyFnFactory = (
|
|||||||
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(botKey.privateKey);
|
const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(botKey.privateKey);
|
||||||
const encryptedWorkspaceKey = encryptAsymmetric(workspaceKey, botKey.publicKey, userPrivateKey);
|
const encryptedWorkspaceKey = encryptAsymmetric(workspaceKey, botKey.publicKey, userPrivateKey);
|
||||||
|
|
||||||
|
let botId;
|
||||||
if (!bot) {
|
if (!bot) {
|
||||||
await projectBotDAL.create({
|
const newBot = await projectBotDAL.create({
|
||||||
name: "Infisical Bot (Ghost)",
|
name: "Infisical Bot (Ghost)",
|
||||||
projectId,
|
projectId,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -80,8 +81,9 @@ export const getBotKeyFnFactory = (
|
|||||||
encryptedProjectKeyNonce: encryptedWorkspaceKey.nonce,
|
encryptedProjectKeyNonce: encryptedWorkspaceKey.nonce,
|
||||||
senderId: projectV1Keys.userId
|
senderId: projectV1Keys.userId
|
||||||
});
|
});
|
||||||
|
botId = newBot.id;
|
||||||
} else {
|
} else {
|
||||||
await projectBotDAL.updateById(bot.id, {
|
const updatedBot = await projectBotDAL.updateById(bot.id, {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
tag,
|
tag,
|
||||||
iv,
|
iv,
|
||||||
@@ -93,8 +95,10 @@ export const getBotKeyFnFactory = (
|
|||||||
encryptedProjectKeyNonce: encryptedWorkspaceKey.nonce,
|
encryptedProjectKeyNonce: encryptedWorkspaceKey.nonce,
|
||||||
senderId: projectV1Keys.userId
|
senderId: projectV1Keys.userId
|
||||||
});
|
});
|
||||||
|
botId = updatedBot.id;
|
||||||
}
|
}
|
||||||
return { botKey: workspaceKey, project, shouldUseSecretV2Bridge: false };
|
|
||||||
|
return { botKey: workspaceKey, project, shouldUseSecretV2Bridge: false, bot: { id: botId } };
|
||||||
}
|
}
|
||||||
|
|
||||||
const botPrivateKey = getBotPrivateKey({ bot });
|
const botPrivateKey = getBotPrivateKey({ bot });
|
||||||
@@ -104,7 +108,7 @@ export const getBotKeyFnFactory = (
|
|||||||
nonce: bot.encryptedProjectKeyNonce,
|
nonce: bot.encryptedProjectKeyNonce,
|
||||||
publicKey: bot.sender.publicKey
|
publicKey: bot.sender.publicKey
|
||||||
});
|
});
|
||||||
return { botKey, project, shouldUseSecretV2Bridge: false };
|
return { botKey, project, shouldUseSecretV2Bridge: false, bot: { id: bot.id } };
|
||||||
};
|
};
|
||||||
|
|
||||||
return getBotKeyFn;
|
return getBotKeyFn;
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ export const projectBotServiceFactory = ({
|
|||||||
}: TProjectBotServiceFactoryDep) => {
|
}: TProjectBotServiceFactoryDep) => {
|
||||||
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
|
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
|
||||||
|
|
||||||
const getBotKey = async (projectId: string) => {
|
const getBotKey = async (projectId: string, shouldGetBotKey?: boolean) => {
|
||||||
return getBotKeyFn(projectId);
|
return getBotKeyFn(projectId, shouldGetBotKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
const findBotByProjectId = async ({
|
const findBotByProjectId = async ({
|
||||||
|
|||||||
@@ -65,10 +65,16 @@ export const projectEnvDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const shiftPositions = async (projectId: string, pos: number, tx?: Knex) => {
|
||||||
|
// Shift all positions >= the new position up by 1
|
||||||
|
await (tx || db)(TableName.Environment).where({ projectId }).where("position", ">=", pos).increment("position", 1);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...projectEnvOrm,
|
...projectEnvOrm,
|
||||||
findBySlugs,
|
findBySlugs,
|
||||||
findLastEnvPosition,
|
findLastEnvPosition,
|
||||||
updateAllPosition
|
updateAllPosition,
|
||||||
|
shiftPositions
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const projectEnvServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
|
position,
|
||||||
name,
|
name,
|
||||||
slug
|
slug
|
||||||
}: TCreateEnvDTO) => {
|
}: TCreateEnvDTO) => {
|
||||||
@@ -83,9 +84,25 @@ export const projectEnvServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const env = await projectEnvDAL.transaction(async (tx) => {
|
const env = await projectEnvDAL.transaction(async (tx) => {
|
||||||
|
if (position !== undefined) {
|
||||||
|
// Check if there's an environment at the specified position
|
||||||
|
const existingEnvWithPosition = await projectEnvDAL.findOne({ projectId, position }, tx);
|
||||||
|
|
||||||
|
// If there is, then shift positions
|
||||||
|
if (existingEnvWithPosition) {
|
||||||
|
await projectEnvDAL.shiftPositions(projectId, position, tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = await projectEnvDAL.create({ slug, name, projectId, position }, tx);
|
||||||
|
await folderDAL.create({ name: "root", parentId: null, envId: doc.id, version: 1 }, tx);
|
||||||
|
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
// If no position is specified, add to the end
|
||||||
const lastPos = await projectEnvDAL.findLastEnvPosition(projectId, tx);
|
const lastPos = await projectEnvDAL.findLastEnvPosition(projectId, tx);
|
||||||
const doc = await projectEnvDAL.create({ slug, name, projectId, position: lastPos + 1 }, tx);
|
const doc = await projectEnvDAL.create({ slug, name, projectId, position: lastPos + 1 }, tx);
|
||||||
await folderDAL.create({ name: "root", parentId: null, envId: doc.id, version: 1 }, tx);
|
await folderDAL.create({ name: "root", parentId: null, envId: doc.id, version: 1 }, tx);
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -150,7 +167,11 @@ export const projectEnvServiceFactory = ({
|
|||||||
|
|
||||||
const env = await projectEnvDAL.transaction(async (tx) => {
|
const env = await projectEnvDAL.transaction(async (tx) => {
|
||||||
if (position) {
|
if (position) {
|
||||||
await projectEnvDAL.updateAllPosition(projectId, oldEnv.position, position, tx);
|
const existingEnvWithPosition = await projectEnvDAL.findOne({ projectId, position }, tx);
|
||||||
|
|
||||||
|
if (existingEnvWithPosition && existingEnvWithPosition.id !== oldEnv.id) {
|
||||||
|
await projectEnvDAL.updateAllPosition(projectId, oldEnv.position, position, tx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return projectEnvDAL.updateById(oldEnv.id, { name, slug, position }, tx);
|
return projectEnvDAL.updateById(oldEnv.id, { name, slug, position }, tx);
|
||||||
});
|
});
|
||||||
@@ -199,7 +220,6 @@ export const projectEnvServiceFactory = ({
|
|||||||
name: "DeleteEnvironment"
|
name: "DeleteEnvironment"
|
||||||
});
|
});
|
||||||
|
|
||||||
await projectEnvDAL.updateAllPosition(projectId, doc.position, -1, tx);
|
|
||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -215,29 +235,26 @@ export const projectEnvServiceFactory = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEnvironmentById = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod, id }: TGetEnvDTO) => {
|
const getEnvironmentById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TGetEnvDTO) => {
|
||||||
|
const environment = await projectEnvDAL.findById(id);
|
||||||
|
|
||||||
|
if (!environment) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Environment does not exist"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
environment.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
||||||
|
|
||||||
const [env] = await projectEnvDAL.find({
|
return environment;
|
||||||
id,
|
|
||||||
projectId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!env) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
message: "Environment does not exist"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return env;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { TProjectPermission } from "@app/lib/types";
|
|||||||
export type TCreateEnvDTO = {
|
export type TCreateEnvDTO = {
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
position?: number;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TUpdateEnvDTO = {
|
export type TUpdateEnvDTO = {
|
||||||
@@ -23,4 +24,4 @@ export type TReorderEnvDTO = {
|
|||||||
|
|
||||||
export type TGetEnvDTO = {
|
export type TGetEnvDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
} & TProjectPermission;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ export const projectServiceFactory = ({
|
|||||||
workspaceName,
|
workspaceName,
|
||||||
slug: projectSlug,
|
slug: projectSlug,
|
||||||
kmsKeyId,
|
kmsKeyId,
|
||||||
|
tx: trx,
|
||||||
createDefaultEnvs = true
|
createDefaultEnvs = true
|
||||||
}: TCreateProjectDTO) => {
|
}: TCreateProjectDTO) => {
|
||||||
const organization = await orgDAL.findOne({ id: actorOrgId });
|
const organization = await orgDAL.findOne({ id: actorOrgId });
|
||||||
@@ -169,7 +170,7 @@ export const projectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await projectDAL.transaction(async (tx) => {
|
const results = await (trx || projectDAL).transaction(async (tx) => {
|
||||||
const ghostUser = await orgService.addGhostUser(organization.id, tx);
|
const ghostUser = await orgService.addGhostUser(organization.id, tx);
|
||||||
|
|
||||||
if (kmsKeyId) {
|
if (kmsKeyId) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TProjectKeys } from "@app/db/schemas";
|
import { TProjectKeys } from "@app/db/schemas";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ export type TCreateProjectDTO = {
|
|||||||
slug?: string;
|
slug?: string;
|
||||||
kmsKeyId?: string;
|
kmsKeyId?: string;
|
||||||
createDefaultEnvs?: boolean;
|
createDefaultEnvs?: boolean;
|
||||||
|
tx?: Knex;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDeleteProjectBySlugDTO = {
|
export type TDeleteProjectBySlugDTO = {
|
||||||
|
|||||||
@@ -502,12 +502,21 @@ export const secretFolderServiceFactory = ({
|
|||||||
|
|
||||||
const getFolderById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TGetFolderByIdDTO) => {
|
const getFolderById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TGetFolderByIdDTO) => {
|
||||||
const folder = await folderDAL.findById(id);
|
const folder = await folderDAL.findById(id);
|
||||||
if (!folder) throw new NotFoundError({ message: "folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
||||||
// folder list is allowed to be read by anyone
|
// folder list is allowed to be read by anyone
|
||||||
// permission to check does user has access
|
// permission to check does user has access
|
||||||
await permissionService.getProjectPermission(actor, actorId, folder.projectId, actorAuthMethod, actorOrgId);
|
await permissionService.getProjectPermission(actor, actorId, folder.projectId, actorAuthMethod, actorOrgId);
|
||||||
|
|
||||||
return folder;
|
const [folderWithPath] = await folderDAL.findSecretPathByFolderIds(folder.projectId, [folder.id]);
|
||||||
|
|
||||||
|
if (!folderWithPath) {
|
||||||
|
throw new NotFoundError({ message: "Folder path not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...folder,
|
||||||
|
path: folderWithPath.path
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user