Compare commits

..

98 Commits

Author SHA1 Message Date
Sheen Capadngan
291d29ec41 Merge remote-tracking branch 'origin/main' into feat/resource-metadata 2025-01-07 19:15:33 +08:00
Maidul Islam
0fa20f7839 Merge pull request #2943 from Infisical/aws-sm-integration-force-delete
Fix: Set Force Flag on Delete Secret Command for AWS Secrets Manager
2025-01-06 20:51:48 -05:00
Scott Wilson
40ef75d3bd fix: use force flag when deleting secrets from aws secret manager integration 2025-01-06 16:11:32 -08:00
Maidul Islam
26af13453c Merge pull request #2942 from akhilmhdh/fix/text-typo
fix: resolved typo in layout
2025-01-06 17:31:06 -05:00
=
ad1f71883d fix: resolved typo in layout 2025-01-07 02:58:56 +05:30
Maidul Islam
2659ea7170 Merge pull request #2940 from Infisical/misc/updated-openssh-installation-for-fips
misc: updated openssh installation for fips
2025-01-06 14:08:07 -05:00
Sheen Capadngan
d2e3f504fd misc: updated openssh installation for fips 2025-01-07 03:04:57 +08:00
Maidul Islam
ca4151a34d Merge pull request #2935 from akhilmhdh/refactor/rbr
Adios nextjs
2025-01-06 14:01:22 -05:00
=
d4bc104fd1 feat: adios nextjs 2025-01-06 18:38:57 +00:00
=
7e3a3fcdb0 feat: updated the installation id to coerce string 2025-01-06 23:37:16 +05:30
=
7f67912d2f feat: resolved failing be lint check 2025-01-06 22:18:49 +05:30
=
a7f4020c08 feat: bumped nodejs from 16 to 20 2025-01-06 22:13:56 +05:30
=
d2d89034ba feat: moved more of isLoading to isPending 2025-01-06 22:13:16 +05:30
=
2fc6c564c0 feat: removed all existBeforeEnter error 2025-01-06 19:56:53 +05:30
=
240b86c1d5 fix: resolved project list issue and app connection github not listed 2025-01-06 19:48:50 +05:30
=
30d66cef89 feat: resolved slack failing and approval url correction 2025-01-06 19:18:10 +05:30
=
b7d11444a9 feat: resolved bug on org change select and project on change 2025-01-06 19:01:10 +05:30
=
0a6aef0afa feat: lint fixes 2025-01-06 14:56:51 +05:30
=
0ac7ec460b feat: resolving some bugs 2025-01-06 14:56:50 +05:30
=
808a901aee feat: updated env in docker files 2025-01-06 14:56:50 +05:30
=
d5412f916f feat: updated frontend use run time config inject value 2025-01-06 14:56:50 +05:30
=
d0648ca596 feat: added runtime env config endpoint 2025-01-06 14:56:50 +05:30
=
3291f1f908 feat: resolved typo in integration redirects 2025-01-06 14:56:50 +05:30
=
965d30bd03 feat: updated docker 2025-01-06 14:56:50 +05:30
=
68fe7c589a feat: updated backend to support bare react as standalone 2025-01-06 14:56:50 +05:30
=
54377a44d3 feat: renamed old frontend and fixed some minor bugs 2025-01-06 14:56:49 +05:30
=
8c902d7699 feat: added error page and not found page handler 2025-01-06 14:55:14 +05:30
=
c25c84aeb3 feat: added csp and minor changes 2025-01-06 14:55:14 +05:30
=
4359eb7313 feat: testing all todo comments and cli 2025-01-06 14:55:13 +05:30
=
322536d738 feat: completed migration of all integrations pages 2025-01-06 14:55:13 +05:30
=
6c5db3a187 feat: completed secret manager integration list and detail page 2025-01-06 14:55:13 +05:30
=
a337e6badd feat: bug fixes in overview page and dashboard page 2025-01-06 14:55:13 +05:30
=
524a97e9a6 fix: resolved infinite rendering issue caused by zustand 2025-01-06 14:55:13 +05:30
=
c56f598115 feat: switched to official lottie react and adjusted all the existings ones 2025-01-06 14:55:13 +05:30
=
19d32a1a3b feat: completed migration of ssh product 2025-01-06 14:55:12 +05:30
=
7e5417a0eb feat: completed migration of app connection 2025-01-06 14:55:12 +05:30
=
afd6de27fe feat: re-arranged route paths 2025-01-06 14:55:12 +05:30
=
7781a6b7e7 feat: added static images and fixing minor layout issues 2025-01-06 14:55:12 +05:30
=
b3b4e41d92 feat: resolved ico header, failing translation and lottie icon issues 2025-01-06 14:55:12 +05:30
=
5225f5136a feat: resolving issues on loader 2025-01-06 14:55:12 +05:30
=
398adfaf76 feat: resolved all ts issues 2025-01-06 14:55:12 +05:30
=
d77c26fa38 feat: resolved almost all ts issues 2025-01-06 14:55:11 +05:30
=
ef7b81734a feat: added kms routes 2025-01-06 14:55:11 +05:30
=
09b489a348 feat: completed cert routes 2025-01-06 14:55:11 +05:30
=
6b5c50def0 feat: added secret-manager routes 2025-01-06 14:55:11 +05:30
=
1f2d52176c feat: added org route 2025-01-06 14:55:11 +05:30
=
7002e297c8 feat: removed routes and added kms, cert to pages setup 2025-01-06 14:55:11 +05:30
=
71864a131f feat: changed to virtual route for secret-manager 2025-01-06 14:55:11 +05:30
=
9964d2ecaa feat: completed switch to virtual for org pages 2025-01-06 14:55:10 +05:30
=
3ebbaefc2a feat: changed to virtual route for auth and personal settings 2025-01-06 14:55:10 +05:30
=
dd5c494bdb feat: secret manager routes migration completed 2025-01-06 14:55:10 +05:30
=
bace8af5a1 feat: removed $organizationId from the routes 2025-01-06 14:55:10 +05:30
=
f56196b820 feat: completed project layout base 2025-01-06 14:55:10 +05:30
=
7042d73410 feat: upgrade react-query to v5 and changed hooks to staletime inf for prev context based ones 2025-01-06 14:55:09 +05:30
=
cb22ee315e feat: resolved a lot of ts issues, planning to migrate to v5 tanstack query 2025-01-06 14:55:09 +05:30
=
701eb7cfc6 feat: resolved breaking dev 2025-01-06 14:55:09 +05:30
=
bf8df14b01 feat: added hoc, resolved dropdown type issue 2025-01-06 14:55:09 +05:30
=
1ba8b6394b feat: added virtual route to mount the layout without wrapping again 2025-01-06 14:55:09 +05:30
=
c442c8483a feat: completed signup and personal settings ui 2025-01-06 14:55:09 +05:30
=
0435305a68 feat: resolved eslint issues and seperated org layout to make it simple 2025-01-06 14:55:09 +05:30
=
febf11f502 feat: added organization layout base with subscription and user loading 2025-01-06 14:55:08 +05:30
=
64fd15c32d feat: modified backend token endpoint to return organizationid 2025-01-06 14:55:08 +05:30
=
a2c9494d52 feat: added signup routes and moved server config to router context 2025-01-06 14:55:08 +05:30
=
18460e0678 feat: base for signup pages completed 2025-01-06 14:55:08 +05:30
=
3d03fece74 feat: first login page base completed 2025-01-06 14:55:08 +05:30
=
234e7eb9be feat: added ui components 2025-01-06 14:55:08 +05:30
=
04af313bf0 feat: added all old dependencies 2025-01-06 14:55:08 +05:30
=
9b038ccc45 feat: added tanstack router 2025-01-06 14:55:07 +05:30
=
9beb384546 feat: added tailwindcss with prettier tailwind support 2025-01-06 14:55:07 +05:30
=
12ec9b4b4e feat: added type check, lint and prettier 2025-01-06 14:55:07 +05:30
Maidul Islam
96b8e7fda8 Merge pull request #2930 from Infisical/vmatsiiako-wiki-patch-1 2025-01-01 18:12:48 -05:00
Vlad Matsiiako
93b9108aa3 Update onboarding.mdx 2025-01-01 15:04:50 -08:00
Sheen
99017ea1ae Merge pull request #2923 from Infisical/akhilmhdh-patch-azure
fix: resolved azure app config api not pulling all keys
2024-12-30 23:19:54 +08:00
Akhil Mohan
f32588112e fix: resolved azure app config api not pulling all keys 2024-12-29 19:51:59 +05:30
Maidul Islam
f9b0b6700a Merge pull request #2922 from Infisical/feat/authenticate-key-vault-against-specific-tenant
feat: auth key vault against specific tenant
2024-12-29 08:18:52 -05:00
Maidul Islam
b45d9398f0 Merge pull request #2920 from Infisical/vmatsiiako-intercom-patch-1 2024-12-27 21:47:36 -05:00
Maidul Islam
1d1140237f Update azure-app-configuration.mdx 2024-12-27 00:59:49 -05:00
Maidul Islam
937560fd8d Update azure-app-configuration.mdx 2024-12-27 00:58:48 -05:00
Maidul Islam
5f4b7b9ea7 Merge pull request #2921 from Infisical/patch-azure-label
Azure App Config Label patch
2024-12-27 00:40:42 -05:00
Maidul Islam
05139820a5 add docs for label and refereces 2024-12-26 16:36:27 -05:00
Sheen Capadngan
7f6bc3ecfe misc: added additional note for azure app config 2024-12-26 19:07:45 +08:00
Sheen Capadngan
d8cc000ad1 feat: authenticate key vault against specific tenant 2024-12-26 19:07:16 +08:00
Maidul Islam
8fc03c06d9 handle empty label 2024-12-26 01:17:47 -05:00
Vlad Matsiiako
50ceedf39f Remove intercom from docs 2024-12-25 16:40:45 -08:00
BlackMagiq
550096e72b Merge pull request #2787 from Mhammad-riyaz/riyaz/query
Fix Error When Clicking on CA Table Row After Viewing Certificate in Certificate Authorities Tab
2024-12-25 02:05:27 -08:00
Sheen Capadngan
e763a6f683 misc: added default for metadataSyncMode 2024-12-23 21:29:51 +08:00
Sheen Capadngan
cb1b006118 misc: add secret metadata to batch update 2024-12-23 14:46:23 +08:00
Sheen Capadngan
356e7c5958 feat: added approval handling for metadata in replicate 2024-12-23 14:36:41 +08:00
Maidul Islam
634b500244 Merge pull request #2907 from Infisical/temp-hide-app-connection-docs 2024-12-20 18:53:20 -05:00
Scott Wilson
54b4d4ae55 docs: temp hide app connections 2024-12-20 15:07:23 -08:00
Sheen Capadngan
1a68765f15 feat: secret approval and replication 2024-12-21 02:51:47 +08:00
BlackMagiq
2f6dab3f63 Merge pull request #2901 from Infisical/ssh-cli-docs
Documentation for SSH Command in CLI
2024-12-20 08:49:38 -08:00
Sheen Capadngan
ae07d38c19 Merge remote-tracking branch 'origin/main' into feat/resource-metadata 2024-12-20 14:23:14 +08:00
Sheen Capadngan
025b4b8761 feat: aws secret manager metadata sync 2024-12-19 23:54:47 +08:00
Sheen Capadngan
ef688efc8d feat: integrated secret metadata to ui 2024-12-19 22:02:20 +08:00
Sheen Capadngan
8c98565715 feat: completed basic CRUD 2024-12-19 17:47:39 +08:00
Sheen Capadngan
e9358cd1d8 misc: backend setup + create secret metadata 2024-12-19 15:05:16 +08:00
mohammad riyaz
242595fceb changed getCaCerts key from ca-cert -> ca-certs 2024-11-24 21:05:39 +05:30
1465 changed files with 34638 additions and 42432 deletions

View File

@@ -18,18 +18,18 @@ jobs:
steps: steps:
- name: ☁️ Checkout source - name: ☁️ Checkout source
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: 🔧 Setup Node 16 - name: 🔧 Setup Node 20
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: "16" node-version: "20"
cache: "npm" cache: "npm"
cache-dependency-path: frontend/package-lock.json cache-dependency-path: frontend/package-lock.json
- name: 📦 Install dependencies - name: 📦 Install dependencies
run: npm install run: npm install
working-directory: frontend working-directory: frontend
- name: 🏗️ Run Type check - name: 🏗️ Run Type check
run: npm run type:check run: npm run type:check
working-directory: frontend working-directory: frontend
- name: 🏗️ Run Link check - name: 🏗️ Run Link check
run: npm run lint:fix run: npm run lint:fix
working-directory: frontend working-directory: frontend

View File

@@ -8,7 +8,7 @@ FROM node:20-slim AS base
FROM base AS frontend-dependencies FROM base AS frontend-dependencies
WORKDIR /app WORKDIR /app
COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./ COPY frontend/package.json frontend/package-lock.json ./
# Install dependencies # Install dependencies
RUN npm ci --only-production --ignore-scripts RUN npm ci --only-production --ignore-scripts
@@ -23,17 +23,16 @@ COPY --from=frontend-dependencies /app/node_modules ./node_modules
COPY /frontend . COPY /frontend .
ENV NODE_ENV production ENV NODE_ENV production
ENV NEXT_PUBLIC_ENV production
ARG POSTHOG_HOST ARG POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST ENV VITE_POSTHOG_HOST $POSTHOG_HOST
ARG POSTHOG_API_KEY ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY ENV VITE_POSTHOG_API_KEY $POSTHOG_API_KEY
ARG INTERCOM_ID ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID ENV VITE_INTERCOM_ID $INTERCOM_ID
ARG INFISICAL_PLATFORM_VERSION ARG INFISICAL_PLATFORM_VERSION
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION ENV VITE_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
ARG CAPTCHA_SITE_KEY ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
# Build # Build
RUN npm run build RUN npm run build
@@ -44,20 +43,10 @@ WORKDIR /app
RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user
RUN mkdir -p /app/.next/cache/images && chown non-root-user:nodejs /app/.next/cache/images COPY --from=frontend-builder --chown=non-root-user:nodejs /app/dist ./
VOLUME /app/.next/cache/images
COPY --chown=non-root-user:nodejs --chmod=555 frontend/scripts ./scripts
COPY --from=frontend-builder /app/public ./public
RUN chown non-root-user:nodejs ./public/data
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/standalone ./
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/static ./.next/static
USER non-root-user USER non-root-user
ENV NEXT_TELEMETRY_DISABLED 1
## ##
## BACKEND ## BACKEND
## ##
@@ -137,7 +126,7 @@ RUN apt-get update && apt-get install -y \
freetds-dev \ freetds-dev \
freetds-bin \ freetds-bin \
tdsodbc \ tdsodbc \
openssh \ openssh-client \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Configure ODBC in production # Configure ODBC in production
@@ -160,14 +149,11 @@ RUN chmod u+rx /usr/sbin/update-ca-certificates
## set pre baked keys ## set pre baked keys
ARG POSTHOG_API_KEY ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \ ENV POSTHOG_API_KEY=$POSTHOG_API_KEY
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
ARG INTERCOM_ID=intercom-id ARG INTERCOM_ID=intercom-id
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \ ENV INTERCOM_ID=$INTERCOM_ID
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
ARG CAPTCHA_SITE_KEY ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY \ ENV CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
BAKED_NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
WORKDIR / WORKDIR /
@@ -192,4 +178,4 @@ EXPOSE 443
USER non-root-user USER non-root-user
CMD ["./standalone-entrypoint.sh"] CMD ["./standalone-entrypoint.sh"]

View File

@@ -12,7 +12,7 @@ RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./ COPY frontend/package.json frontend/package-lock.json ./
# Install dependencies # Install dependencies
RUN npm ci --only-production --ignore-scripts RUN npm ci --only-production --ignore-scripts
@@ -27,17 +27,16 @@ COPY --from=frontend-dependencies /app/node_modules ./node_modules
COPY /frontend . COPY /frontend .
ENV NODE_ENV production ENV NODE_ENV production
ENV NEXT_PUBLIC_ENV production
ARG POSTHOG_HOST ARG POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST ENV VITE_POSTHOG_HOST $POSTHOG_HOST
ARG POSTHOG_API_KEY ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY ENV VITE_POSTHOG_API_KEY $POSTHOG_API_KEY
ARG INTERCOM_ID ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID ENV VITE_INTERCOM_ID $INTERCOM_ID
ARG INFISICAL_PLATFORM_VERSION ARG INFISICAL_PLATFORM_VERSION
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION ENV VITE_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
ARG CAPTCHA_SITE_KEY ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
# Build # Build
RUN npm run build RUN npm run build
@@ -49,20 +48,10 @@ WORKDIR /app
RUN addgroup --system --gid 1001 nodejs RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 non-root-user RUN adduser --system --uid 1001 non-root-user
RUN mkdir -p /app/.next/cache/images && chown non-root-user:nodejs /app/.next/cache/images COPY --from=frontend-builder --chown=non-root-user:nodejs /app/dist ./
VOLUME /app/.next/cache/images
COPY --chown=non-root-user:nodejs --chmod=555 frontend/scripts ./scripts
COPY --from=frontend-builder /app/public ./public
RUN chown non-root-user:nodejs ./public/data
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/standalone ./
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/static ./.next/static
USER non-root-user USER non-root-user
ENV NEXT_TELEMETRY_DISABLED 1
## ##
## BACKEND ## BACKEND
## ##
@@ -159,14 +148,11 @@ RUN chmod u+rx /usr/sbin/update-ca-certificates
## set pre baked keys ## set pre baked keys
ARG POSTHOG_API_KEY ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \ ENV POSTHOG_API_KEY=$POSTHOG_API_KEY
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
ARG INTERCOM_ID=intercom-id ARG INTERCOM_ID=intercom-id
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \ ENV INTERCOM_ID=$INTERCOM_ID
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
ARG CAPTCHA_SITE_KEY ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY \ ENV CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
BAKED_NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
COPY --from=backend-runner /app /backend COPY --from=backend-runner /app /backend
@@ -189,4 +175,4 @@ EXPOSE 443
USER non-root-user USER non-root-user
CMD ["./standalone-entrypoint.sh"] CMD ["./standalone-entrypoint.sh"]

View File

@@ -26,6 +26,7 @@
"@fastify/rate-limit": "^9.0.0", "@fastify/rate-limit": "^9.0.0",
"@fastify/request-context": "^5.1.0", "@fastify/request-context": "^5.1.0",
"@fastify/session": "^10.7.0", "@fastify/session": "^10.7.0",
"@fastify/static": "^7.0.4",
"@fastify/swagger": "^8.14.0", "@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^2.1.0", "@fastify/swagger-ui": "^2.1.0",
"@google-cloud/kms": "^4.5.0", "@google-cloud/kms": "^4.5.0",
@@ -5406,6 +5407,7 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz",
"integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==", "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
} }
@@ -5545,6 +5547,7 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz", "resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz",
"integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==", "integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@lukeed/ms": "^2.0.1", "@lukeed/ms": "^2.0.1",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
@@ -5563,16 +5566,85 @@
} }
}, },
"node_modules/@fastify/static": { "node_modules/@fastify/static": {
"version": "6.12.0", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.12.0.tgz", "resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.4.tgz",
"integrity": "sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==", "integrity": "sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==",
"license": "MIT",
"dependencies": { "dependencies": {
"@fastify/accept-negotiator": "^1.0.0", "@fastify/accept-negotiator": "^1.0.0",
"@fastify/send": "^2.0.0", "@fastify/send": "^2.0.0",
"content-disposition": "^0.5.3", "content-disposition": "^0.5.3",
"fastify-plugin": "^4.0.0", "fastify-plugin": "^4.0.0",
"glob": "^8.0.1", "fastq": "^1.17.0",
"p-limit": "^3.1.0" "glob": "^10.3.4"
}
},
"node_modules/@fastify/static/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@fastify/static/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@fastify/static/node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/@fastify/static/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@fastify/static/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/@fastify/swagger": { "node_modules/@fastify/swagger": {
@@ -5599,6 +5671,20 @@
"yaml": "^2.2.2" "yaml": "^2.2.2"
} }
}, },
"node_modules/@fastify/swagger-ui/node_modules/@fastify/static": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.12.0.tgz",
"integrity": "sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==",
"license": "MIT",
"dependencies": {
"@fastify/accept-negotiator": "^1.0.0",
"@fastify/send": "^2.0.0",
"content-disposition": "^0.5.3",
"fastify-plugin": "^4.0.0",
"glob": "^8.0.1",
"p-limit": "^3.1.0"
}
},
"node_modules/@google-cloud/kms": { "node_modules/@google-cloud/kms": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/@google-cloud/kms/-/kms-4.5.0.tgz", "resolved": "https://registry.npmjs.org/@google-cloud/kms/-/kms-4.5.0.tgz",
@@ -6062,9 +6148,10 @@
"integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ==" "integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ=="
}, },
"node_modules/@lukeed/ms": { "node_modules/@lukeed/ms": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.1.tgz", "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz",
"integrity": "sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==", "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==",
"license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -13879,9 +13966,9 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.21.1", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
@@ -13903,7 +13990,7 @@
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.10", "path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.13.0", "qs": "6.13.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
@@ -13918,6 +14005,10 @@
}, },
"engines": { "engines": {
"node": ">= 0.10.0" "node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
} }
}, },
"node_modules/express-session": { "node_modules/express-session": {
@@ -17388,15 +17479,16 @@
} }
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.7", "version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"license": "MIT",
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@@ -18383,9 +18475,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.10", "version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-type": { "node_modules/path-type": {

View File

@@ -134,6 +134,7 @@
"@fastify/rate-limit": "^9.0.0", "@fastify/rate-limit": "^9.0.0",
"@fastify/request-context": "^5.1.0", "@fastify/request-context": "^5.1.0",
"@fastify/session": "^10.7.0", "@fastify/session": "^10.7.0",
"@fastify/static": "^7.0.4",
"@fastify/swagger": "^8.14.0", "@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^2.1.0", "@fastify/swagger-ui": "^2.1.0",
"@google-cloud/kms": "^4.5.0", "@google-cloud/kms": "^4.5.0",

View File

@@ -218,6 +218,9 @@ import {
TRateLimit, TRateLimit,
TRateLimitInsert, TRateLimitInsert,
TRateLimitUpdate, TRateLimitUpdate,
TResourceMetadata,
TResourceMetadataInsert,
TResourceMetadataUpdate,
TSamlConfigs, TSamlConfigs,
TSamlConfigsInsert, TSamlConfigsInsert,
TSamlConfigsUpdate, TSamlConfigsUpdate,
@@ -887,6 +890,11 @@ declare module "knex/types/tables" {
TProjectSplitBackfillIdsInsert, TProjectSplitBackfillIdsInsert,
TProjectSplitBackfillIdsUpdate TProjectSplitBackfillIdsUpdate
>; >;
[TableName.ResourceMetadata]: KnexOriginal.CompositeTableType<
TResourceMetadata,
TResourceMetadataInsert,
TResourceMetadataUpdate
>;
[TableName.AppConnection]: KnexOriginal.CompositeTableType< [TableName.AppConnection]: KnexOriginal.CompositeTableType<
TAppConnections, TAppConnections,
TAppConnectionsInsert, TAppConnectionsInsert,

View File

@@ -0,0 +1,40 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ResourceMetadata))) {
await knex.schema.createTable(TableName.ResourceMetadata, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.string("key").notNullable();
tb.string("value", 1020).notNullable();
tb.uuid("orgId").notNullable();
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
tb.uuid("userId");
tb.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
tb.uuid("identityId");
tb.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
tb.uuid("secretId");
tb.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
tb.timestamps(true, true, true);
});
}
const hasSecretMetadataField = await knex.schema.hasColumn(TableName.SecretApprovalRequestSecretV2, "secretMetadata");
if (!hasSecretMetadataField) {
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
t.jsonb("secretMetadata");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ResourceMetadata);
const hasSecretMetadataField = await knex.schema.hasColumn(TableName.SecretApprovalRequestSecretV2, "secretMetadata");
if (hasSecretMetadataField) {
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
t.dropColumn("secretMetadata");
});
}
}

View File

@@ -71,6 +71,7 @@ export * from "./project-user-additional-privilege";
export * from "./project-user-membership-roles"; export * from "./project-user-membership-roles";
export * from "./projects"; export * from "./projects";
export * from "./rate-limit"; export * from "./rate-limit";
export * from "./resource-metadata";
export * from "./saml-configs"; export * from "./saml-configs";
export * from "./scim-tokens"; export * from "./scim-tokens";
export * from "./secret-approval-policies"; export * from "./secret-approval-policies";

View File

@@ -80,6 +80,7 @@ export enum TableName {
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege", IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
// used by both identity and users // used by both identity and users
IdentityMetadata = "identity_metadata", IdentityMetadata = "identity_metadata",
ResourceMetadata = "resource_metadata",
ScimToken = "scim_tokens", ScimToken = "scim_tokens",
AccessApprovalPolicy = "access_approval_policies", AccessApprovalPolicy = "access_approval_policies",
AccessApprovalPolicyApprover = "access_approval_policies_approvers", AccessApprovalPolicyApprover = "access_approval_policies_approvers",

View File

@@ -0,0 +1,24 @@
// 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 ResourceMetadataSchema = z.object({
id: z.string().uuid(),
key: z.string(),
value: z.string(),
orgId: z.string().uuid(),
userId: z.string().uuid().nullable().optional(),
identityId: z.string().uuid().nullable().optional(),
secretId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TResourceMetadata = z.infer<typeof ResourceMetadataSchema>;
export type TResourceMetadataInsert = Omit<z.input<typeof ResourceMetadataSchema>, TImmutableDBKeys>;
export type TResourceMetadataUpdate = Partial<Omit<z.input<typeof ResourceMetadataSchema>, TImmutableDBKeys>>;

View File

@@ -24,7 +24,8 @@ export const SecretApprovalRequestsSecretsV2Schema = z.object({
requestId: z.string().uuid(), requestId: z.string().uuid(),
op: z.string(), op: z.string(),
secretId: z.string().uuid().nullable().optional(), secretId: z.string().uuid().nullable().optional(),
secretVersion: z.string().uuid().nullable().optional() secretVersion: z.string().uuid().nullable().optional(),
secretMetadata: z.unknown().nullable().optional()
}); });
export type TSecretApprovalRequestsSecretsV2 = z.infer<typeof SecretApprovalRequestsSecretsV2Schema>; export type TSecretApprovalRequestsSecretsV2 = z.infer<typeof SecretApprovalRequestsSecretsV2Schema>;

View File

@@ -12,6 +12,7 @@ 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 { secretRawSchema } from "@app/server/routes/sanitizedSchemas"; import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
const approvalRequestUser = z.object({ userId: z.string().nullable().optional() }).merge( const approvalRequestUser = z.object({ userId: z.string().nullable().optional() }).merge(
UsersSchema.pick({ UsersSchema.pick({
@@ -274,6 +275,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
.extend({ .extend({
op: z.string(), op: z.string(),
tags: tagSchema, tags: tagSchema,
secretMetadata: ResourceMetadataSchema.nullish(),
secret: z secret: z
.object({ .object({
id: z.string(), id: z.string(),
@@ -291,7 +293,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
secretKey: z.string(), secretKey: z.string(),
secretValue: z.string().optional(), secretValue: z.string().optional(),
secretComment: z.string().optional(), secretComment: z.string().optional(),
tags: tagSchema tags: tagSchema,
secretMetadata: ResourceMetadataSchema.nullish()
}) })
.optional() .optional()
}) })

View File

@@ -213,7 +213,7 @@ export const accessApprovalRequestServiceFactory = ({
); );
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`; const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
const approvalUrl = `${cfg.SITE_URL}/project/${project.id}/approval`; const approvalUrl = `${cfg.SITE_URL}/secret-manager/${project.id}/approval`;
await triggerSlackNotification({ await triggerSlackNotification({
projectId: project.id, projectId: project.id,

View File

@@ -36,7 +36,7 @@ export const sendApprovalEmailsFn = async ({
firstName: reviewerUser.firstName, firstName: reviewerUser.firstName,
projectName: project.name, projectName: project.name,
organizationName: project.organization.name, organizationName: project.organization.name,
approvalUrl: `${cfg.SITE_URL}/project/${project.id}/approval?requestId=${secretApprovalRequest.id}` approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval?requestId=${secretApprovalRequest.id}`
}, },
template: SmtpTemplates.SecretApprovalRequestNeedsReview template: SmtpTemplates.SecretApprovalRequestNeedsReview
}); });

View File

@@ -256,6 +256,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
`${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`, `${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`,
db.ref("id").withSchema("secVerTag") db.ref("id").withSchema("secVerTag")
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(selectAllTableCols(TableName.SecretApprovalRequestSecretV2)) .select(selectAllTableCols(TableName.SecretApprovalRequestSecretV2))
.select({ .select({
secVerTagId: "secVerTag.id", secVerTagId: "secVerTag.id",
@@ -279,6 +280,11 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"), db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"),
db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"), db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"),
db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment") db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment")
)
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
); );
const formatedDoc = sqlNestRelationships({ const formatedDoc = sqlNestRelationships({
data: doc, data: doc,
@@ -338,9 +344,19 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
}) })
} }
] ]
},
{
key: "metadataId",
label: "oldSecretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
} }
] ]
}); });
return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({ return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({
...el, ...el,
secret: secret?.[0], secret: secret?.[0],

View File

@@ -22,6 +22,8 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "@app/services/secret/secret-dal"; import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import { import {
decryptSecretWithBot, decryptSecretWithBot,
@@ -91,6 +93,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">; secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">; snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">; secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">; secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
smtpService: Pick<TSmtpService, "sendMail">; smtpService: Pick<TSmtpService, "sendMail">;
userDAL: Pick<TUserDALFactory, "find" | "findOne" | "findById">; userDAL: Pick<TUserDALFactory, "find" | "findOne" | "findById">;
@@ -138,7 +141,8 @@ export const secretApprovalRequestServiceFactory = ({
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
licenseService, licenseService,
projectSlackConfigDAL projectSlackConfigDAL,
resourceMetadataDAL
}: TSecretApprovalRequestServiceFactoryDep) => { }: TSecretApprovalRequestServiceFactoryDep) => {
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => { const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
@@ -241,6 +245,7 @@ export const secretApprovalRequestServiceFactory = ({
secretKey: el.key, secretKey: el.key,
id: el.id, id: el.id,
version: el.version, version: el.version,
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "", secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
secretComment: el.encryptedComment secretComment: el.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
@@ -269,7 +274,8 @@ export const secretApprovalRequestServiceFactory = ({
secretComment: el.secretVersion.encryptedComment secretComment: el.secretVersion.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString() ? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
: "", : "",
tags: el.secretVersion.tags tags: el.secretVersion.tags,
secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO
} }
: undefined : undefined
})); }));
@@ -543,6 +549,7 @@ export const secretApprovalRequestServiceFactory = ({
? await fnSecretV2BridgeBulkInsert({ ? await fnSecretV2BridgeBulkInsert({
tx, tx,
folderId, folderId,
orgId: actorOrgId,
inputSecrets: secretCreationCommits.map((el) => ({ inputSecrets: secretCreationCommits.map((el) => ({
tagIds: el?.tags.map(({ id }) => id), tagIds: el?.tags.map(({ id }) => id),
version: 1, version: 1,
@@ -550,6 +557,7 @@ export const secretApprovalRequestServiceFactory = ({
encryptedValue: el.encryptedValue, encryptedValue: el.encryptedValue,
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
key: el.key, key: el.key,
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
references: el.encryptedValue references: el.encryptedValue
? getAllSecretReferencesV2Bridge( ? getAllSecretReferencesV2Bridge(
secretManagerDecryptor({ secretManagerDecryptor({
@@ -559,6 +567,7 @@ export const secretApprovalRequestServiceFactory = ({
: [], : [],
type: SecretType.Shared type: SecretType.Shared
})), })),
resourceMetadataDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL, secretTagDAL,
@@ -568,6 +577,7 @@ export const secretApprovalRequestServiceFactory = ({
const updatedSecrets = secretUpdationCommits.length const updatedSecrets = secretUpdationCommits.length
? await fnSecretV2BridgeBulkUpdate({ ? await fnSecretV2BridgeBulkUpdate({
folderId, folderId,
orgId: actorOrgId,
tx, tx,
inputSecrets: secretUpdationCommits.map((el) => { inputSecrets: secretUpdationCommits.map((el) => {
const encryptedValue = const encryptedValue =
@@ -592,6 +602,7 @@ export const secretApprovalRequestServiceFactory = ({
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
key: el.key, key: el.key,
tags: el?.tags.map(({ id }) => id), tags: el?.tags.map(({ id }) => id),
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
...encryptedValue ...encryptedValue
} }
}; };
@@ -599,7 +610,8 @@ export const secretApprovalRequestServiceFactory = ({
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL: secretVersionTagV2BridgeDAL secretVersionTagDAL: secretVersionTagV2BridgeDAL,
resourceMetadataDAL
}) })
: []; : [];
const deletedSecret = secretDeletionCommits.length const deletedSecret = secretDeletionCommits.length
@@ -824,6 +836,7 @@ export const secretApprovalRequestServiceFactory = ({
} }
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId: actorOrgId,
secretPath: folder.path, secretPath: folder.path,
environmentSlug: folder.environmentSlug, environmentSlug: folder.environmentSlug,
actorId, actorId,
@@ -852,7 +865,7 @@ export const secretApprovalRequestServiceFactory = ({
bypassReason, bypassReason,
secretPath: policy.secretPath, secretPath: policy.secretPath,
environment: env.name, environment: env.name,
approvalUrl: `${cfg.SITE_URL}/project/${project.id}/approval` approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval`
}, },
template: SmtpTemplates.AccessSecretRequestBypassed template: SmtpTemplates.AccessSecretRequestBypassed
}); });
@@ -1208,6 +1221,7 @@ export const secretApprovalRequestServiceFactory = ({
), ),
skipMultilineEncoding: createdSecret.skipMultilineEncoding, skipMultilineEncoding: createdSecret.skipMultilineEncoding,
key: createdSecret.secretKey, key: createdSecret.secretKey,
secretMetadata: createdSecret.secretMetadata,
type: SecretType.Shared type: SecretType.Shared
})) }))
); );
@@ -1263,12 +1277,14 @@ export const secretApprovalRequestServiceFactory = ({
reminderNote, reminderNote,
secretComment, secretComment,
metadata, metadata,
skipMultilineEncoding skipMultilineEncoding,
secretMetadata
}) => { }) => {
const secretId = updatingSecretsGroupByKey[secretKey][0].id; const secretId = updatingSecretsGroupByKey[secretKey][0].id;
if (tagIds?.length) commitTagIds[secretKey] = tagIds; if (tagIds?.length) commitTagIds[secretKey] = tagIds;
return { return {
...latestSecretVersions[secretId], ...latestSecretVersions[secretId],
secretMetadata,
key: newSecretName || secretKey, key: newSecretName || secretKey,
encryptedComment: setKnexStringValue( encryptedComment: setKnexStringValue(
secretComment, secretComment,
@@ -1370,7 +1386,8 @@ export const secretApprovalRequestServiceFactory = ({
reminderRepeatDays, reminderRepeatDays,
encryptedValue, encryptedValue,
secretId, secretId,
secretVersion secretVersion,
secretMetadata
}) => ({ }) => ({
version, version,
requestId: doc.id, requestId: doc.id,
@@ -1383,7 +1400,8 @@ export const secretApprovalRequestServiceFactory = ({
reminderRepeatDays, reminderRepeatDays,
reminderNote, reminderNote,
encryptedComment, encryptedComment,
key key,
secretMetadata: JSON.stringify(secretMetadata)
}) })
), ),
tx tx

View File

@@ -1,5 +1,6 @@
import { TImmutableDBKeys, TSecretApprovalPolicies, TSecretApprovalRequestsSecrets } from "@app/db/schemas"; import { TImmutableDBKeys, TSecretApprovalPolicies, TSecretApprovalRequestsSecrets } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretOperations } from "@app/services/secret/secret-types"; import { SecretOperations } from "@app/services/secret/secret-types";
export enum RequestState { export enum RequestState {
@@ -34,6 +35,7 @@ export type TApprovalCreateSecretV2Bridge = {
reminderRepeatDays?: number | null; reminderRepeatDays?: number | null;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
metadata?: Record<string, string>; metadata?: Record<string, string>;
secretMetadata?: ResourceMetadataDTO;
tagIds?: string[]; tagIds?: string[];
}; };

View File

@@ -13,6 +13,8 @@ import { ActorType } from "@app/services/auth/auth-type";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "@app/services/secret/secret-dal"; import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns"; import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns";
import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue"; import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue";
@@ -56,6 +58,7 @@ type TSecretReplicationServiceFactoryDep = {
>; >;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">; secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">;
secretVersionV2TagBridgeDAL: Pick<TSecretVersionV2TagDALFactory, "find" | "insertMany">; secretVersionV2TagBridgeDAL: Pick<TSecretVersionV2TagDALFactory, "find" | "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "replicateSecrets">; secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "replicateSecrets">;
queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById">; queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById">;
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">; secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
@@ -121,7 +124,8 @@ export const secretReplicationServiceFactory = ({
secretVersionV2TagBridgeDAL, secretVersionV2TagBridgeDAL,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
kmsService kmsService,
resourceMetadataDAL
}: TSecretReplicationServiceFactoryDep) => { }: TSecretReplicationServiceFactoryDep) => {
const $getReplicatedSecrets = ( const $getReplicatedSecrets = (
botKey: string, botKey: string,
@@ -151,8 +155,10 @@ export const secretReplicationServiceFactory = ({
}; };
const $getReplicatedSecretsV2 = ( const $getReplicatedSecretsV2 = (
localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[], localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[],
importedSecrets: { secrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[] }[] importedSecrets: {
secrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[];
}[]
) => { ) => {
const deDupe = new Set<string>(); const deDupe = new Set<string>();
const secrets = [...localSecrets]; const secrets = [...localSecrets];
@@ -178,6 +184,7 @@ export const secretReplicationServiceFactory = ({
secretPath, secretPath,
environmentSlug, environmentSlug,
projectId, projectId,
orgId,
actorId, actorId,
actor, actor,
pickOnlyImportIds, pickOnlyImportIds,
@@ -222,6 +229,7 @@ export const secretReplicationServiceFactory = ({
.map(({ folderId }) => .map(({ folderId }) =>
secretQueueService.replicateSecrets({ secretQueueService.replicateSecrets({
projectId, projectId,
orgId,
secretPath: foldersGroupedById[folderId][0]?.path as string, secretPath: foldersGroupedById[folderId][0]?.path as string,
environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string, environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string,
actorId, actorId,
@@ -267,6 +275,7 @@ export const secretReplicationServiceFactory = ({
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
: undefined : undefined
})); }));
const sourceSecrets = $getReplicatedSecretsV2(sourceDecryptedLocalSecrets, sourceImportedSecrets); const sourceSecrets = $getReplicatedSecretsV2(sourceDecryptedLocalSecrets, sourceImportedSecrets);
const sourceSecretsGroupByKey = groupBy(sourceSecrets, (i) => i.key); const sourceSecretsGroupByKey = groupBy(sourceSecrets, (i) => i.key);
@@ -333,13 +342,29 @@ export const secretReplicationServiceFactory = ({
.map((el) => ({ ...el, operation: SecretOperations.Create })); // rewrite update ops to create .map((el) => ({ ...el, operation: SecretOperations.Create })); // rewrite update ops to create
const locallyUpdatedSecrets = sourceSecrets const locallyUpdatedSecrets = sourceSecrets
.filter( .filter(({ key, secretKey, secretValue, secretMetadata }) => {
({ key, secretKey, secretValue }) => const sourceSecretMetadataJson = JSON.stringify(
(secretMetadata ?? []).map((entry) => ({
key: entry.key,
value: entry.value
}))
);
const destinationSecretMetadataJson = JSON.stringify(
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretMetadata ?? []).map((entry) => ({
key: entry.key,
value: entry.value
}))
);
return (
destinationLocalSecretsGroupedByKey[key]?.[0] && destinationLocalSecretsGroupedByKey[key]?.[0] &&
// if key or value changed // if key or value changed
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretKey !== secretKey || (destinationLocalSecretsGroupedByKey[key]?.[0]?.secretKey !== secretKey ||
destinationLocalSecretsGroupedByKey[key]?.[0]?.secretValue !== secretValue) destinationLocalSecretsGroupedByKey[key]?.[0]?.secretValue !== secretValue ||
) sourceSecretMetadataJson !== destinationSecretMetadataJson)
);
})
.map((el) => ({ ...el, operation: SecretOperations.Update })); // rewrite update ops to create .map((el) => ({ ...el, operation: SecretOperations.Update })); // rewrite update ops to create
const locallyDeletedSecrets = destinationLocalSecrets const locallyDeletedSecrets = destinationLocalSecrets
@@ -387,6 +412,7 @@ export const secretReplicationServiceFactory = ({
op: operation, op: operation,
requestId: approvalRequestDoc.id, requestId: approvalRequestDoc.id,
metadata: doc.metadata, metadata: doc.metadata,
secretMetadata: JSON.stringify(doc.secretMetadata),
key: doc.key, key: doc.key,
encryptedValue: doc.encryptedValue, encryptedValue: doc.encryptedValue,
encryptedComment: doc.encryptedComment, encryptedComment: doc.encryptedComment,
@@ -406,10 +432,12 @@ export const secretReplicationServiceFactory = ({
if (locallyCreatedSecrets.length) { if (locallyCreatedSecrets.length) {
await fnSecretV2BridgeBulkInsert({ await fnSecretV2BridgeBulkInsert({
folderId: destinationReplicationFolderId, folderId: destinationReplicationFolderId,
orgId,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
tx, tx,
secretTagDAL, secretTagDAL,
resourceMetadataDAL,
secretVersionTagDAL: secretVersionV2TagBridgeDAL, secretVersionTagDAL: secretVersionV2TagBridgeDAL,
inputSecrets: locallyCreatedSecrets.map((doc) => { inputSecrets: locallyCreatedSecrets.map((doc) => {
return { return {
@@ -419,6 +447,7 @@ export const secretReplicationServiceFactory = ({
encryptedValue: doc.encryptedValue, encryptedValue: doc.encryptedValue,
encryptedComment: doc.encryptedComment, encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding, skipMultilineEncoding: doc.skipMultilineEncoding,
secretMetadata: doc.secretMetadata,
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : [] references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
}; };
}) })
@@ -426,10 +455,12 @@ export const secretReplicationServiceFactory = ({
} }
if (locallyUpdatedSecrets.length) { if (locallyUpdatedSecrets.length) {
await fnSecretV2BridgeBulkUpdate({ await fnSecretV2BridgeBulkUpdate({
orgId,
folderId: destinationReplicationFolderId, folderId: destinationReplicationFolderId,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
tx, tx,
resourceMetadataDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL: secretVersionV2TagBridgeDAL, secretVersionTagDAL: secretVersionV2TagBridgeDAL,
inputSecrets: locallyUpdatedSecrets.map((doc) => { inputSecrets: locallyUpdatedSecrets.map((doc) => {
@@ -445,6 +476,7 @@ export const secretReplicationServiceFactory = ({
encryptedValue: doc.encryptedValue as Buffer, encryptedValue: doc.encryptedValue as Buffer,
encryptedComment: doc.encryptedComment, encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding, skipMultilineEncoding: doc.skipMultilineEncoding,
secretMetadata: doc.secretMetadata,
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : [] references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
} }
}; };
@@ -466,6 +498,7 @@ export const secretReplicationServiceFactory = ({
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId,
secretPath: destinationFolder.path, secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environmentSlug, environmentSlug: destinationFolder.environmentSlug,
actorId, actorId,
@@ -751,6 +784,7 @@ export const secretReplicationServiceFactory = ({
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId,
secretPath: destinationFolder.path, secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environmentSlug, environmentSlug: destinationFolder.environmentSlug,
actorId, actorId,

View File

@@ -1150,7 +1150,8 @@ export const INTEGRATION = {
shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.", shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.",
shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.", shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.",
shouldEnableDelete: "The flag to enable deletion of secrets.", shouldEnableDelete: "The flag to enable deletion of secrets.",
octopusDeployScopeValues: "Specifies the scope values to set on synced secrets to Octopus Deploy." octopusDeployScopeValues: "Specifies the scope values to set on synced secrets to Octopus Deploy.",
metadataSyncMode: "The mode for syncing metadata to external system"
} }
}, },
UPDATE: { UPDATE: {

View File

@@ -157,6 +157,8 @@ const envSchema = z
INFISICAL_CLOUD: zodStrBool.default("false"), INFISICAL_CLOUD: zodStrBool.default("false"),
MAINTENANCE_MODE: zodStrBool.default("false"), MAINTENANCE_MODE: zodStrBool.default("false"),
CAPTCHA_SECRET: zpStr(z.string().optional()), CAPTCHA_SECRET: zpStr(z.string().optional()),
CAPTCHA_SITE_KEY: zpStr(z.string().optional()),
INTERCOM_ID: zpStr(z.string().optional()),
// TELEMETRY // TELEMETRY
OTEL_TELEMETRY_COLLECTION_ENABLED: zodStrBool.default("false"), OTEL_TELEMETRY_COLLECTION_ENABLED: zodStrBool.default("false"),

View File

@@ -27,10 +27,10 @@ import { globalRateLimiterCfg } from "./config/rateLimiter";
import { addErrorsToResponseSchemas } from "./plugins/add-errors-to-response-schemas"; import { addErrorsToResponseSchemas } from "./plugins/add-errors-to-response-schemas";
import { apiMetrics } from "./plugins/api-metrics"; import { apiMetrics } from "./plugins/api-metrics";
import { fastifyErrHandler } from "./plugins/error-handler"; import { fastifyErrHandler } from "./plugins/error-handler";
import { registerExternalNextjs } from "./plugins/external-nextjs";
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod"; import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod";
import { fastifyIp } from "./plugins/ip"; import { fastifyIp } from "./plugins/ip";
import { maintenanceMode } from "./plugins/maintenanceMode"; import { maintenanceMode } from "./plugins/maintenanceMode";
import { registerServeUI } from "./plugins/serve-ui";
import { fastifySwagger } from "./plugins/swagger"; import { fastifySwagger } from "./plugins/swagger";
import { registerRoutes } from "./routes"; import { registerRoutes } from "./routes";
@@ -120,13 +120,10 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule }); await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule });
if (appCfg.isProductionMode) { await server.register(registerServeUI, {
await server.register(registerExternalNextjs, { standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED,
standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED, dir: path.join(__dirname, IS_PACKAGED ? "../../../" : "../../")
dir: path.join(__dirname, IS_PACKAGED ? "../../../" : "../../"), });
port: appCfg.PORT
});
}
await server.ready(); await server.ready();
server.swagger(); server.swagger();

View File

@@ -1,76 +0,0 @@
// this plugins allows to run infisical in standalone mode
// standalone mode = infisical backend and nextjs frontend in one server
// this way users don't need to deploy two things
import path from "node:path";
import { IS_PACKAGED } from "@app/lib/config/env";
// to enabled this u need to set standalone mode to true
export const registerExternalNextjs = async (
server: FastifyZodProvider,
{
standaloneMode,
dir,
port
}: {
standaloneMode?: boolean;
dir: string;
port: number;
}
) => {
if (standaloneMode) {
const frontendName = IS_PACKAGED ? "frontend" : "frontend-build";
const nextJsBuildPath = path.join(dir, frontendName);
const { default: conf } = (await import(
path.join(dir, `${frontendName}/.next/required-server-files.json`),
// @ts-expect-error type
{
assert: { type: "json" }
}
)) as { default: { config: string } };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let NextServer: any;
if (!IS_PACKAGED) {
/* eslint-disable */
const { default: nextServer } = (
await import(path.join(dir, `${frontendName}/node_modules/next/dist/server/next-server.js`))
).default;
NextServer = nextServer;
} else {
/* eslint-disable */
const nextServer = await import(path.join(dir, `${frontendName}/node_modules/next/dist/server/next-server.js`));
NextServer = nextServer.default;
}
const nextApp = new NextServer({
dev: false,
dir: nextJsBuildPath,
port,
conf: conf.config,
hostname: "local",
customServer: false
});
server.route({
method: ["GET", "PUT", "PATCH", "POST", "DELETE"],
url: "/*",
schema: {
hide: true
},
handler: (req, res) =>
nextApp
.getRequestHandler()(req.raw, res.raw)
.then(() => {
res.hijack();
})
});
server.addHook("onClose", () => nextApp.close());
await nextApp.prepare();
/* eslint-enable */
}
};

View File

@@ -0,0 +1,54 @@
import path from "node:path";
import staticServe from "@fastify/static";
import { getConfig, IS_PACKAGED } from "@app/lib/config/env";
// to enabled this u need to set standalone mode to true
export const registerServeUI = async (
server: FastifyZodProvider,
{
standaloneMode,
dir
}: {
standaloneMode?: boolean;
dir: string;
}
) => {
// use this only for frontend runtime static non-sensitive configuration in standalone mode
// that app needs before loading like posthog dsn key
// for most of the other usecase use server config
server.route({
method: "GET",
url: "/runtime-ui-env.js",
handler: (_req, res) => {
const appCfg = getConfig();
void res.type("application/javascript");
const config = {
CAPTCHA_SITE_KEY: appCfg.CAPTCHA_SITE_KEY,
POSTHOG_API_KEY: appCfg.POSTHOG_PROJECT_API_KEY,
INTERCOM_ID: appCfg.INTERCOM_ID,
TELEMETRY_CAPTURING_ENABLED: appCfg.TELEMETRY_ENABLED
};
const js = `window.__INFISICAL_RUNTIME_ENV__ = Object.freeze(${JSON.stringify(config)});`;
void res.send(js);
}
});
if (standaloneMode) {
const frontendName = IS_PACKAGED ? "frontend" : "frontend-build";
const frontendPath = path.join(dir, frontendName);
await server.register(staticServe, {
root: frontendPath,
wildcard: false
});
server.get("/*", (request, reply) => {
if (request.url.startsWith("/api")) {
reply.callNotFound();
return;
}
void reply.sendFile("index.html");
});
}
};

View File

@@ -181,6 +181,7 @@ import { projectUserMembershipRoleDALFactory } from "@app/services/project-membe
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal"; import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service"; import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { secretDALFactory } from "@app/services/secret/secret-dal"; import { secretDALFactory } from "@app/services/secret/secret-dal";
import { secretQueueFactory } from "@app/services/secret/secret-queue"; import { secretQueueFactory } from "@app/services/secret/secret-queue";
import { secretServiceFactory } from "@app/services/secret/secret-service"; import { secretServiceFactory } from "@app/services/secret/secret-service";
@@ -374,6 +375,7 @@ export const registerRoutes = async (
const externalGroupOrgRoleMappingDAL = externalGroupOrgRoleMappingDALFactory(db); const externalGroupOrgRoleMappingDAL = externalGroupOrgRoleMappingDALFactory(db);
const projectTemplateDAL = projectTemplateDALFactory(db); const projectTemplateDAL = projectTemplateDALFactory(db);
const resourceMetadataDAL = resourceMetadataDALFactory(db);
const permissionService = permissionServiceFactory({ const permissionService = permissionServiceFactory({
permissionDAL, permissionDAL,
@@ -854,7 +856,8 @@ export const registerRoutes = async (
secretApprovalRequestDAL, secretApprovalRequestDAL,
projectKeyDAL, projectKeyDAL,
projectUserMembershipRoleDAL, projectUserMembershipRoleDAL,
orgService orgService,
resourceMetadataDAL
}); });
const projectService = projectServiceFactory({ const projectService = projectServiceFactory({
@@ -980,7 +983,8 @@ export const registerRoutes = async (
secretApprovalPolicyService, secretApprovalPolicyService,
secretApprovalRequestSecretDAL, secretApprovalRequestSecretDAL,
kmsService, kmsService,
snapshotService snapshotService,
resourceMetadataDAL
}); });
const secretApprovalRequestService = secretApprovalRequestServiceFactory({ const secretApprovalRequestService = secretApprovalRequestServiceFactory({
@@ -1007,7 +1011,8 @@ export const registerRoutes = async (
projectEnvDAL, projectEnvDAL,
userDAL, userDAL,
licenseService, licenseService,
projectSlackConfigDAL projectSlackConfigDAL,
resourceMetadataDAL
}); });
const secretService = secretServiceFactory({ const secretService = secretServiceFactory({
@@ -1086,8 +1091,10 @@ export const registerRoutes = async (
kmsService, kmsService,
secretV2BridgeDAL, secretV2BridgeDAL,
secretVersionV2TagBridgeDAL: secretVersionTagV2BridgeDAL, secretVersionV2TagBridgeDAL: secretVersionTagV2BridgeDAL,
secretVersionV2BridgeDAL secretVersionV2BridgeDAL,
resourceMetadataDAL
}); });
const secretRotationQueue = secretRotationQueueFactory({ const secretRotationQueue = secretRotationQueueFactory({
telemetryService, telemetryService,
secretRotationDAL, secretRotationDAL,
@@ -1339,7 +1346,8 @@ export const registerRoutes = async (
folderDAL, folderDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
queueService, queueService,
secretV2BridgeService secretV2BridgeService,
resourceMetadataDAL
}); });
const migrationService = externalMigrationServiceFactory({ const migrationService = externalMigrationServiceFactory({

View File

@@ -63,7 +63,8 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
schema: { schema: {
response: { response: {
200: z.object({ 200: z.object({
token: z.string() token: z.string(),
organizationId: z.string().optional()
}) })
} }
}, },
@@ -115,7 +116,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
{ expiresIn: appCfg.JWT_AUTH_LIFETIME } { expiresIn: appCfg.JWT_AUTH_LIFETIME }
); );
return { token }; return { token, organizationId: decodedToken.organizationId };
} }
}); });
}; };

View File

@@ -17,6 +17,7 @@ import { getUserAgentType } from "@app/server/plugins/audit-log";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedDynamicSecretSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas"; import { SanitizedDynamicSecretSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretsOrderBy } from "@app/services/secret/secret-types"; import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@@ -116,6 +117,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema secrets: secretRawSchema
.extend({ .extend({
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({ tags: SecretTagsSchema.pick({
id: true, id: true,
slug: true, slug: true,
@@ -408,6 +410,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema secrets: secretRawSchema
.extend({ .extend({
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({ tags: SecretTagsSchema.pick({
id: true, id: true,
slug: true, slug: true,
@@ -693,6 +696,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema secrets: secretRawSchema
.extend({ .extend({
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({ tags: SecretTagsSchema.pick({
id: true, id: true,
slug: true, slug: true,
@@ -864,6 +868,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema secrets: secretRawSchema
.extend({ .extend({
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({ tags: SecretTagsSchema.pick({
id: true, id: true,
slug: true, slug: true,

View File

@@ -331,12 +331,8 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
failureAsync: async () => { failureAsync: async () => {
return res.redirect(appCfg.SITE_URL as string); return res.redirect(appCfg.SITE_URL as string);
}, },
successAsync: async (installation) => { successAsync: async () => {
const metadata = JSON.parse(installation.metadata || "") as { return res.redirect(`${appCfg.SITE_URL}/organization/settings?selectedTab=workflow-integrations`);
orgId: string;
};
return res.redirect(`${appCfg.SITE_URL}/org/${metadata.orgId}/settings?selectedTab=workflow-integrations`);
} }
}); });
} }

View File

@@ -18,6 +18,7 @@ import { getUserAgentType } from "@app/server/plugins/audit-log";
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 { ProjectFilterType } from "@app/services/project/project-types"; import { ProjectFilterType } from "@app/services/project/project-types";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretOperations, SecretProtectionType } from "@app/services/secret/secret-types"; import { SecretOperations, SecretProtectionType } from "@app/services/secret/secret-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@@ -205,6 +206,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema secrets: secretRawSchema
.extend({ .extend({
secretPath: z.string().optional(), secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({ tags: SecretTagsSchema.pick({
id: true, id: true,
slug: true, slug: true,
@@ -220,7 +222,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretPath: z.string(), secretPath: z.string(),
environment: z.string(), environment: z.string(),
folderId: z.string().optional(), folderId: z.string().optional(),
secrets: secretRawSchema.omit({ createdAt: true, updatedAt: true }).array() secrets: secretRawSchema
.omit({ createdAt: true, updatedAt: true })
.extend({
secretMetadata: ResourceMetadataSchema.optional()
})
.array()
}) })
.array() .array()
.optional() .optional()
@@ -348,7 +355,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}) })
.extend({ name: z.string() }) .extend({ name: z.string() })
.array() .array()
.optional() .optional(),
secretMetadata: ResourceMetadataSchema.optional()
}) })
}) })
} }
@@ -450,6 +458,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())) .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.describe(RAW_SECRETS.CREATE.secretValue), .describe(RAW_SECRETS.CREATE.secretValue),
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment), secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
secretMetadata: ResourceMetadataSchema.optional(),
tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds), tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding), skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type), type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type),
@@ -484,6 +493,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretValue: req.body.secretValue, secretValue: req.body.secretValue,
skipMultilineEncoding: req.body.skipMultilineEncoding, skipMultilineEncoding: req.body.skipMultilineEncoding,
secretComment: req.body.secretComment, secretComment: req.body.secretComment,
secretMetadata: req.body.secretMetadata,
tagIds: req.body.tagIds, tagIds: req.body.tagIds,
secretReminderNote: req.body.secretReminderNote, secretReminderNote: req.body.secretReminderNote,
secretReminderRepeatDays: req.body.secretReminderRepeatDays secretReminderRepeatDays: req.body.secretReminderRepeatDays
@@ -558,6 +568,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type), type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type),
tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds), tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds),
metadata: z.record(z.string()).optional(), metadata: z.record(z.string()).optional(),
secretMetadata: ResourceMetadataSchema.optional(),
secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote), secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote),
secretReminderRepeatDays: z secretReminderRepeatDays: z
.number() .number()
@@ -595,8 +606,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretReminderNote: req.body.secretReminderNote, secretReminderNote: req.body.secretReminderNote,
metadata: req.body.metadata, metadata: req.body.metadata,
newSecretName: req.body.newSecretName, newSecretName: req.body.newSecretName,
secretComment: req.body.secretComment secretComment: req.body.secretComment,
secretMetadata: req.body.secretMetadata
}); });
if (secretOperation.type === SecretProtectionType.Approval) { if (secretOperation.type === SecretProtectionType.Approval) {
return { approval: secretOperation.approval }; return { approval: secretOperation.approval };
} }
@@ -1850,6 +1863,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment), secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding), skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
metadata: z.record(z.string()).optional(), metadata: z.record(z.string()).optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds) tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds)
}) })
.array() .array()
@@ -1952,6 +1966,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName), newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName),
tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds), tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds),
secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote), secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote),
secretMetadata: ResourceMetadataSchema.optional(),
secretReminderRepeatDays: z secretReminderRepeatDays: z
.number() .number()
.optional() .optional()

View File

@@ -16,6 +16,7 @@ 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 { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectEnvServiceFactory } from "../project-env/project-env-service"; import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal"; import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@@ -35,6 +36,8 @@ export type TImportDataIntoInfisicalDTO = {
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">; secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">; secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findById">; folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findById">;
projectService: Pick<TProjectServiceFactory, "createProject">; projectService: Pick<TProjectServiceFactory, "createProject">;
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">; projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
@@ -503,6 +506,7 @@ export const importDataIntoInfisicalFn = async ({
secretTagDAL, secretTagDAL,
secretVersionTagDAL, secretVersionTagDAL,
folderDAL, folderDAL,
resourceMetadataDAL,
input: { data, actor, actorId, actorOrgId, actorAuthMethod } input: { data, actor, actorId, actorOrgId, actorAuthMethod }
}: TImportDataIntoInfisicalDTO) => { }: TImportDataIntoInfisicalDTO) => {
// Import data to infisical // Import data to infisical
@@ -762,6 +766,8 @@ export const importDataIntoInfisicalFn = async ({
}; };
}), }),
folderId: selectedFolder.id, folderId: selectedFolder.id,
orgId: actorOrgId,
resourceMetadataDAL,
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,

View File

@@ -8,6 +8,7 @@ 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 { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectEnvServiceFactory } from "../project-env/project-env-service"; import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal"; import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@@ -35,6 +36,8 @@ export type TExternalMigrationQueueFactoryDep = {
projectService: Pick<TProjectServiceFactory, "createProject">; projectService: Pick<TProjectServiceFactory, "createProject">;
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">; projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">; secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
}; };
export type TExternalMigrationQueueFactory = ReturnType<typeof externalMigrationQueueFactory>; export type TExternalMigrationQueueFactory = ReturnType<typeof externalMigrationQueueFactory>;
@@ -52,7 +55,8 @@ export const externalMigrationQueueFactory = ({
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL, secretVersionTagDAL,
folderDAL folderDAL,
resourceMetadataDAL
}: TExternalMigrationQueueFactoryDep) => { }: TExternalMigrationQueueFactoryDep) => {
const startImport = async (dto: { const startImport = async (dto: {
actorEmail: string; actorEmail: string;
@@ -109,7 +113,8 @@ export const externalMigrationQueueFactory = ({
kmsService, kmsService,
projectService, projectService,
projectEnvService, projectEnvService,
secretV2BridgeService secretV2BridgeService,
resourceMetadataDAL
}); });
if (projectsNotImported.length) { if (projectsNotImported.length) {

View File

@@ -427,3 +427,8 @@ export const getIntegrationOptions = async () => {
return INTEGRATION_OPTIONS; return INTEGRATION_OPTIONS;
}; };
export enum IntegrationMetadataSyncMode {
CUSTOM = "custom",
SECRET_METADATA = "secret-metadata"
}

View File

@@ -38,6 +38,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 { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { IntegrationAuthMetadataSchema } from "./integration-auth-schema"; import { IntegrationAuthMetadataSchema } from "./integration-auth-schema";
import { import {
CircleCiScope, CircleCiScope,
@@ -48,6 +49,7 @@ import {
import { import {
IntegrationInitialSyncBehavior, IntegrationInitialSyncBehavior,
IntegrationMappingBehavior, IntegrationMappingBehavior,
IntegrationMetadataSyncMode,
Integrations, Integrations,
IntegrationUrls IntegrationUrls
} from "./integration-list"; } from "./integration-list";
@@ -305,10 +307,16 @@ const syncSecretsAzureAppConfig = async ({
value: string; value: string;
} }
const getCompleteAzureAppConfigValues = async (url: string) => { if (!integration.app || !integration.app.endsWith(".azconfig.io"))
throw new BadRequestError({
message: "Invalid Azure App Configuration URL provided."
});
const getCompleteAzureAppConfigValues = async (baseURL: string, url: string) => {
let result: AzureAppConfigKeyValue[] = []; let result: AzureAppConfigKeyValue[] = [];
while (url) { while (url) {
const res = await request.get(url, { const res = await request.get(url, {
baseURL,
headers: { headers: {
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
}, },
@@ -319,7 +327,7 @@ const syncSecretsAzureAppConfig = async ({
}); });
result = result.concat(res.data.items); result = result.concat(res.data.items);
url = res.data.nextLink; url = res.data?.["@nextLink"];
} }
return result; return result;
@@ -327,11 +335,13 @@ const syncSecretsAzureAppConfig = async ({
const metadata = IntegrationMetadataSchema.parse(integration.metadata); const metadata = IntegrationMetadataSchema.parse(integration.metadata);
const azureAppConfigValuesUrl = `${integration.app}/kv?api-version=2023-11-01&key=${metadata.secretPrefix}*${ const azureAppConfigValuesUrl = `/kv?api-version=2023-11-01&key=${metadata.secretPrefix}*${
metadata.azureLabel ? `&label=${metadata.azureLabel}` : "" metadata.azureLabel ? `&label=${metadata.azureLabel}` : "&label=%00"
}`; }`;
const azureAppConfigSecrets = (await getCompleteAzureAppConfigValues(azureAppConfigValuesUrl)).reduce( const azureAppConfigSecrets = (
await getCompleteAzureAppConfigValues(integration.app, azureAppConfigValuesUrl)
).reduce(
(accum, entry) => { (accum, entry) => {
accum[entry.key] = entry.value; accum[entry.key] = entry.value;
@@ -1074,14 +1084,14 @@ const syncSecretsAWSSecretManager = async ({
projectId projectId
}: { }: {
integration: TIntegrations; integration: TIntegrations;
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string; secretMetadata?: ResourceMetadataDTO }>;
accessId: string | null; accessId: string | null;
accessToken: string; accessToken: string;
awsAssumeRoleArn: string | null; awsAssumeRoleArn: string | null;
projectId?: string; projectId?: string;
}) => { }) => {
const appCfg = getConfig(); const appCfg = getConfig();
const metadata = z.record(z.any()).parse(integration.metadata || {}); const metadata = IntegrationMetadataSchema.parse(integration.metadata || {});
if (!accessId && !awsAssumeRoleArn) { if (!accessId && !awsAssumeRoleArn) {
throw new Error("AWS access ID/AWS Assume Role is required"); throw new Error("AWS access ID/AWS Assume Role is required");
@@ -1129,8 +1139,25 @@ const syncSecretsAWSSecretManager = async ({
const processAwsSecret = async ( const processAwsSecret = async (
secretId: string, secretId: string,
secretValue: Record<string, string | null | undefined> | string secretValue: Record<string, string | null | undefined> | string,
secretMetadata?: ResourceMetadataDTO
) => { ) => {
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
const shouldTag =
(secretAWSTag && secretAWSTag.length) ||
(metadata.metadataSyncMode === IntegrationMetadataSyncMode.SECRET_METADATA &&
metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE);
const tagArray =
(metadata.metadataSyncMode === IntegrationMetadataSyncMode.SECRET_METADATA ? secretMetadata : secretAWSTag) ?? [];
const integrationTagObj = tagArray.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
try { try {
const awsSecretManagerSecret = await secretsManager.send( const awsSecretManagerSecret = await secretsManager.send(
new GetSecretValueCommand({ new GetSecretValueCommand({
@@ -1159,15 +1186,14 @@ const syncSecretsAWSSecretManager = async ({
} else { } else {
await secretsManager.send( await secretsManager.send(
new DeleteSecretCommand({ new DeleteSecretCommand({
SecretId: secretId SecretId: secretId,
ForceDeleteWithoutRecovery: true
}) })
); );
} }
} }
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined; if (shouldTag) {
if (secretAWSTag && secretAWSTag.length) {
const describedSecret = await secretsManager.send( const describedSecret = await secretsManager.send(
// requires secretsmanager:DescribeSecret policy // requires secretsmanager:DescribeSecret policy
new DescribeSecretCommand({ new DescribeSecretCommand({
@@ -1177,14 +1203,6 @@ const syncSecretsAWSSecretManager = async ({
if (!describedSecret.Tags) return; if (!describedSecret.Tags) return;
const integrationTagObj = secretAWSTag.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
const awsTagObj = (describedSecret.Tags || []).reduce( const awsTagObj = (describedSecret.Tags || []).reduce(
(acc, item) => { (acc, item) => {
if (item.Key && item.Value) { if (item.Key && item.Value) {
@@ -1216,7 +1234,7 @@ const syncSecretsAWSSecretManager = async ({
} }
}); });
secretAWSTag?.forEach((tag) => { tagArray.forEach((tag) => {
if (!(tag.key in awsTagObj)) { if (!(tag.key in awsTagObj)) {
// create tag in AWS secret manager // create tag in AWS secret manager
tagsToUpdate.push({ tagsToUpdate.push({
@@ -1253,8 +1271,8 @@ const syncSecretsAWSSecretManager = async ({
Name: secretId, Name: secretId,
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue), SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue),
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }), ...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
Tags: metadata.secretAWSTag Tags: shouldTag
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({ ? tagArray.map((tag: { key: string; value: string }) => ({
Key: tag.key, Key: tag.key,
Value: tag.value Value: tag.value
})) }))
@@ -1271,7 +1289,7 @@ const syncSecretsAWSSecretManager = async ({
if (metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE) { if (metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE) {
for await (const [key, value] of Object.entries(secrets)) { for await (const [key, value] of Object.entries(secrets)) {
await processAwsSecret(key, value.value); await processAwsSecret(key, value.value, value.secretMetadata);
} }
} else { } else {
await processAwsSecret(integration.app as string, getSecretKeyValuePair(secrets)); await processAwsSecret(integration.app as string, getSecretKeyValuePair(secrets));
@@ -4431,7 +4449,7 @@ export const syncIntegrationSecrets = async ({
secretPath: string; secretPath: string;
}; };
integrationAuth: TIntegrationAuths; integrationAuth: TIntegrationAuths;
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string; secretMetadata?: ResourceMetadataDTO }>;
accessId: string | null; accessId: string | null;
awsAssumeRoleArn: string | null; awsAssumeRoleArn: string | null;
accessToken: string; accessToken: string;

View File

@@ -2,7 +2,7 @@ import { z } from "zod";
import { INTEGRATION } from "@app/lib/api-docs"; import { INTEGRATION } from "@app/lib/api-docs";
import { IntegrationMappingBehavior } from "../integration-auth/integration-list"; import { IntegrationMappingBehavior, IntegrationMetadataSyncMode } from "../integration-auth/integration-list";
export const IntegrationMetadataSchema = z.object({ export const IntegrationMetadataSchema = z.object({
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir), initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
@@ -50,6 +50,11 @@ export const IntegrationMetadataSchema = z.object({
shouldMaskSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldMaskSecrets), shouldMaskSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldMaskSecrets),
shouldProtectSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldProtectSecrets), shouldProtectSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldProtectSecrets),
metadataSyncMode: z
.nativeEnum(IntegrationMetadataSyncMode)
.optional()
.describe(INTEGRATION.CREATE.metadata.metadataSyncMode),
octopusDeployScopeValues: z octopusDeployScopeValues: z
.object({ .object({
// in Octopus Deploy Scope Value Format // in Octopus Deploy Scope Value Format

View File

@@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TResourceMetadataDALFactory = ReturnType<typeof resourceMetadataDALFactory>;
export const resourceMetadataDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.ResourceMetadata);
return orm;
};

View File

@@ -0,0 +1,10 @@
import z from "zod";
export const ResourceMetadataSchema = z
.object({
key: z.string().trim().min(1),
value: z.string().trim().default("")
})
.array();
export type ResourceMetadataDTO = z.infer<typeof ResourceMetadataSchema>;

View File

@@ -1,6 +1,7 @@
import { SecretType, TSecretImports, TSecrets, TSecretsV2 } from "@app/db/schemas"; import { SecretType, TSecretImports, TSecrets, TSecretsV2 } from "@app/db/schemas";
import { groupBy, unique } from "@app/lib/fn"; import { groupBy, unique } from "@app/lib/fn";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "../secret/secret-dal"; import { TSecretDALFactory } from "../secret/secret-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@@ -39,6 +40,7 @@ type TSecretImportSecretsV2 = {
// But for somereason ts consider ? and undefined explicit as different just ts things // But for somereason ts consider ? and undefined explicit as different just ts things
secretValue: string; secretValue: string;
secretComment: string; secretComment: string;
secretMetadata?: ResourceMetadataDTO;
})[]; })[];
}; };

View File

@@ -160,6 +160,7 @@ export const secretImportServiceFactory = ({
if (secImport.isReplication && sourceFolder) { if (secImport.isReplication && sourceFolder) {
await secretQueueService.replicateSecrets({ await secretQueueService.replicateSecrets({
secretPath: secImport.importPath, secretPath: secImport.importPath,
orgId: actorOrgId,
projectId, projectId,
environmentSlug: importEnv.slug, environmentSlug: importEnv.slug,
pickOnlyImportIds: [secImport.id], pickOnlyImportIds: [secImport.id],
@@ -169,6 +170,7 @@ export const secretImportServiceFactory = ({
} else { } else {
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath, secretPath,
orgId: actorOrgId,
projectId, projectId,
environmentSlug: environment, environmentSlug: environment,
actorId, actorId,
@@ -340,6 +342,7 @@ export const secretImportServiceFactory = ({
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath, secretPath,
orgId: actorOrgId,
projectId, projectId,
environmentSlug: environment, environmentSlug: environment,
actor, actor,
@@ -415,6 +418,7 @@ export const secretImportServiceFactory = ({
if (membership && sourceFolder) { if (membership && sourceFolder) {
await secretQueueService.replicateSecrets({ await secretQueueService.replicateSecrets({
orgId: actorOrgId,
secretPath: secretImportDoc.importPath, secretPath: secretImportDoc.importPath,
projectId, projectId,
environmentSlug: secretImportDoc.importEnv.slug, environmentSlug: secretImportDoc.importEnv.slug,

View File

@@ -78,6 +78,12 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`, `${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id` `${TableName.SecretTag}.id`
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.select(selectAllTableCols(TableName.SecretV2)) .select(selectAllTableCols(TableName.SecretV2))
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
@@ -103,6 +109,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug, slug,
name: slug name: slug
}) })
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
} }
] ]
}); });
@@ -221,7 +236,9 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
const secs = await (tx || db.replicaNode())(TableName.SecretV2) const secs = await (tx || db.replicaNode())(TableName.SecretV2)
.where({ folderId }) .where({ folderId })
.where((bd) => { .where((bd) => {
void bd.whereNull("userId").orWhere({ userId: userId || null }); void bd
.whereNull(`${TableName.SecretV2}.userId`)
.orWhere({ [`${TableName.SecretV2}.userId` as "userId"]: userId || null });
}) })
.leftJoin( .leftJoin(
TableName.SecretV2JnTag, TableName.SecretV2JnTag,
@@ -233,10 +250,16 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`, `${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id` `${TableName.SecretTag}.id`
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(selectAllTableCols(TableName.SecretV2)) .select(selectAllTableCols(TableName.SecretV2))
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.orderBy("id", "asc"); .orderBy("id", "asc");
const data = sqlNestRelationships({ const data = sqlNestRelationships({
@@ -253,6 +276,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug, slug,
name: slug name: slug
}) })
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
} }
] ]
}); });
@@ -367,7 +399,9 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
} }
}) })
.where((bd) => { .where((bd) => {
void bd.whereNull(`${TableName.SecretV2}.userId`).orWhere({ userId: userId || null }); void bd
.whereNull(`${TableName.SecretV2}.userId`)
.orWhere({ [`${TableName.SecretV2}.userId` as "userId"]: userId || null });
}) })
.leftJoin( .leftJoin(
TableName.SecretV2JnTag, TableName.SecretV2JnTag,
@@ -379,13 +413,23 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`, `${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id` `${TableName.SecretTag}.id`
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select( .select(
selectAllTableCols(TableName.SecretV2), selectAllTableCols(TableName.SecretV2),
db.raw(`DENSE_RANK() OVER (ORDER BY "key" ${filters?.orderDirection ?? OrderByDirection.ASC}) as rank`) db.raw(
`DENSE_RANK() OVER (ORDER BY "${TableName.SecretV2}".key ${
filters?.orderDirection ?? OrderByDirection.ASC
}) as rank`
)
) )
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.where((bd) => { .where((bd) => {
const slugs = filters?.tagSlugs?.filter(Boolean); const slugs = filters?.tagSlugs?.filter(Boolean);
if (slugs && slugs.length > 0) { if (slugs && slugs.length > 0) {
@@ -425,6 +469,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug, slug,
name: slug name: slug
}) })
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
} }
] ]
}); });
@@ -545,10 +598,17 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`, `${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id` `${TableName.SecretTag}.id`
) )
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(selectAllTableCols(TableName.SecretV2)) .select(selectAllTableCols(TableName.SecretV2))
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId")) .select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")); .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
);
const docs = sqlNestRelationships({ const docs = sqlNestRelationships({
data: rawDocs, data: rawDocs,
key: "id", key: "id",
@@ -563,6 +623,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug, slug,
name: slug name: slug
}) })
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
} }
] ]
}); });

View File

@@ -6,6 +6,7 @@ import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types"; import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
@@ -54,9 +55,11 @@ export const getAllSecretReferences = (maybeSecretReference: string) => {
export const fnSecretBulkInsert = async ({ export const fnSecretBulkInsert = async ({
// TODO: Pick types here // TODO: Pick types here
folderId, folderId,
orgId,
inputSecrets, inputSecrets,
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
resourceMetadataDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL, secretVersionTagDAL,
tx tx
@@ -91,6 +94,7 @@ export const fnSecretBulkInsert = async ({
sanitizedInputSecrets.map((el) => ({ ...el, folderId })), sanitizedInputSecrets.map((el) => ({ ...el, folderId })),
tx tx
); );
const newSecretGroupedByKeyName = groupBy(newSecrets, (item) => item.key); const newSecretGroupedByKeyName = groupBy(newSecrets, (item) => item.key);
const newSecretTags = inputSecrets.flatMap(({ tagIds: secretTags = [], key }) => const newSecretTags = inputSecrets.flatMap(({ tagIds: secretTags = [], key }) =>
secretTags.map((tag) => ({ secretTags.map((tag) => ({
@@ -106,6 +110,7 @@ export const fnSecretBulkInsert = async ({
})), })),
tx tx
); );
await secretDAL.upsertSecretReferences( await secretDAL.upsertSecretReferences(
inputSecrets.map(({ references = [], key }) => ({ inputSecrets.map(({ references = [], key }) => ({
secretId: newSecretGroupedByKeyName[key][0].id, secretId: newSecretGroupedByKeyName[key][0].id,
@@ -113,6 +118,22 @@ export const fnSecretBulkInsert = async ({
})), })),
tx tx
); );
await resourceMetadataDAL.insertMany(
inputSecrets.flatMap(({ key: secretKey, secretMetadata }) => {
if (secretMetadata) {
return secretMetadata.map(({ key, value }) => ({
key,
value,
secretId: newSecretGroupedByKeyName[secretKey][0].id,
orgId
}));
}
return [];
}),
tx
);
if (newSecretTags.length) { if (newSecretTags.length) {
const secTags = await secretTagDAL.saveTagsToSecretV2(newSecretTags, tx); const secTags = await secretTagDAL.saveTagsToSecretV2(newSecretTags, tx);
const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId); const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);
@@ -120,6 +141,7 @@ export const fnSecretBulkInsert = async ({
[`${TableName.SecretVersionV2}Id` as const]: secVersionsGroupBySecId[secrets_v2Id][0].id, [`${TableName.SecretVersionV2}Id` as const]: secVersionsGroupBySecId[secrets_v2Id][0].id,
[`${TableName.SecretTag}Id` as const]: secret_tagsId [`${TableName.SecretTag}Id` as const]: secret_tagsId
})); }));
await secretVersionTagDAL.insertMany(newSecretVersionTags, tx); await secretVersionTagDAL.insertMany(newSecretVersionTags, tx);
} }
@@ -130,10 +152,12 @@ export const fnSecretBulkUpdate = async ({
tx, tx,
inputSecrets, inputSecrets,
folderId, folderId,
orgId,
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL secretVersionTagDAL,
resourceMetadataDAL
}: TFnSecretBulkUpdate) => { }: TFnSecretBulkUpdate) => {
const sanitizedInputSecrets = inputSecrets.map( const sanitizedInputSecrets = inputSecrets.map(
({ ({
@@ -231,6 +255,34 @@ export const fnSecretBulkUpdate = async ({
} }
} }
const inputSecretIdsWithMetadata = inputSecrets
.filter((sec) => Boolean(sec.data.secretMetadata))
.map((sec) => sec.filter.id);
await resourceMetadataDAL.delete(
{
$in: {
secretId: inputSecretIdsWithMetadata
}
},
tx
);
await resourceMetadataDAL.insertMany(
inputSecrets.flatMap(({ filter: { id }, data: { secretMetadata } }) => {
if (secretMetadata) {
return secretMetadata.map(({ key, value }) => ({
key,
value,
secretId: id,
orgId
}));
}
return [];
}),
tx
);
return newSecrets.map((secret) => ({ ...secret, _id: secret.id })); return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
}; };
@@ -570,6 +622,7 @@ export const reshapeBridgeSecret = (
color?: string | null; color?: string | null;
name: string; name: string;
}[]; }[];
secretMetadata?: ResourceMetadataDTO;
} }
) => ({ ) => ({
secretKey: secret.key, secretKey: secret.key,
@@ -588,6 +641,7 @@ export const reshapeBridgeSecret = (
secretReminderRepeatDays: secret.reminderRepeatDays, secretReminderRepeatDays: secret.reminderRepeatDays,
secretReminderNote: secret.reminderNote, secretReminderNote: secret.reminderNote,
metadata: secret.metadata, metadata: secret.metadata,
secretMetadata: secret.secretMetadata,
createdAt: secret.createdAt, createdAt: secret.createdAt,
updatedAt: secret.updatedAt updatedAt: secret.updatedAt
}); });

View File

@@ -18,6 +18,7 @@ import { ActorType } from "../auth/auth-type";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types"; import { KmsDataKey } from "../kms/kms-types";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretQueueFactory } from "../secret/secret-queue"; import { TSecretQueueFactory } from "../secret/secret-queue";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
@@ -74,6 +75,7 @@ type TSecretV2BridgeServiceFactoryDep = {
"insertV2Bridge" | "insertApprovalSecretV2Tags" "insertV2Bridge" | "insertApprovalSecretV2Tags"
>; >;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">; snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
}; };
export type TSecretV2BridgeServiceFactory = ReturnType<typeof secretV2BridgeServiceFactory>; export type TSecretV2BridgeServiceFactory = ReturnType<typeof secretV2BridgeServiceFactory>;
@@ -95,7 +97,8 @@ export const secretV2BridgeServiceFactory = ({
secretApprovalPolicyService, secretApprovalPolicyService,
secretApprovalRequestDAL, secretApprovalRequestDAL,
secretApprovalRequestSecretDAL, secretApprovalRequestSecretDAL,
kmsService kmsService,
resourceMetadataDAL
}: TSecretV2BridgeServiceFactoryDep) => { }: TSecretV2BridgeServiceFactoryDep) => {
const $validateSecretReferences = async ( const $validateSecretReferences = async (
projectId: string, projectId: string,
@@ -141,7 +144,7 @@ export const secretV2BridgeServiceFactory = ({
}, },
{ {
operator: "eq", operator: "eq",
field: "key", field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey value: el.secretKey
} }
] ]
@@ -186,6 +189,7 @@ export const secretV2BridgeServiceFactory = ({
actorAuthMethod, actorAuthMethod,
projectId, projectId,
secretPath, secretPath,
secretMetadata,
...inputSecret ...inputSecret
}: TCreateSecretDTO) => { }: TCreateSecretDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission( const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
@@ -255,6 +259,7 @@ export const secretV2BridgeServiceFactory = ({
const secret = await secretDAL.transaction((tx) => const secret = await secretDAL.transaction((tx) =>
fnSecretBulkInsert({ fnSecretBulkInsert({
folderId, folderId,
orgId: actorOrgId,
inputSecrets: [ inputSecrets: [
{ {
version: 1, version: 1,
@@ -272,9 +277,11 @@ export const secretV2BridgeServiceFactory = ({
key: secretName, key: secretName,
userId: inputSecret.type === SecretType.Personal ? actorId : null, userId: inputSecret.type === SecretType.Personal ? actorId : null,
tagIds: inputSecret.tagIds, tagIds: inputSecret.tagIds,
references: nestedReferences references: nestedReferences,
secretMetadata
} }
], ],
resourceMetadataDAL,
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
@@ -287,6 +294,7 @@ export const secretV2BridgeServiceFactory = ({
await snapshotService.performSnapshot(folderId); await snapshotService.performSnapshot(folderId);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath, secretPath,
orgId: actorOrgId,
actorId, actorId,
actor, actor,
projectId, projectId,
@@ -309,6 +317,7 @@ export const secretV2BridgeServiceFactory = ({
actorAuthMethod, actorAuthMethod,
projectId, projectId,
secretPath, secretPath,
secretMetadata,
...inputSecret ...inputSecret
}: TUpdateSecretDTO) => { }: TUpdateSecretDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission( const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
@@ -435,6 +444,8 @@ export const secretV2BridgeServiceFactory = ({
const updatedSecret = await secretDAL.transaction(async (tx) => const updatedSecret = await secretDAL.transaction(async (tx) =>
fnSecretBulkUpdate({ fnSecretBulkUpdate({
folderId, folderId,
orgId: actorOrgId,
resourceMetadataDAL,
inputSecrets: [ inputSecrets: [
{ {
filter: { id: secretId }, filter: { id: secretId },
@@ -448,6 +459,7 @@ export const secretV2BridgeServiceFactory = ({
skipMultilineEncoding: inputSecret.skipMultilineEncoding, skipMultilineEncoding: inputSecret.skipMultilineEncoding,
key: inputSecret.newSecretName || secretName, key: inputSecret.newSecretName || secretName,
tags: inputSecret.tagIds, tags: inputSecret.tagIds,
secretMetadata,
...encryptedValue ...encryptedValue
} }
} }
@@ -475,6 +487,7 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
actor, actor,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
} }
@@ -562,6 +575,7 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
actor, actor,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
} }
@@ -961,8 +975,8 @@ export const secretV2BridgeServiceFactory = ({
? secretDAL.findOneWithTags({ ? secretDAL.findOneWithTags({
folderId, folderId,
type: secretType, type: secretType,
key: secretName, [`${TableName.SecretV2}.key` as "key"]: secretName,
userId: secretType === SecretType.Personal ? actorId : null [`${TableName.SecretV2}.userId` as "userId"]: secretType === SecretType.Personal ? actorId : null
}) })
: secretVersionDAL : secretVersionDAL
.findOne({ .findOne({
@@ -1113,7 +1127,7 @@ export const secretV2BridgeServiceFactory = ({
value: [ value: [
{ {
operator: "eq", operator: "eq",
field: "key", field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey value: el.secretKey
}, },
{ {
@@ -1185,11 +1199,14 @@ export const secretV2BridgeServiceFactory = ({
key: el.secretKey, key: el.secretKey,
tagIds: el.tagIds, tagIds: el.tagIds,
references, references,
secretMetadata: el.secretMetadata,
type: SecretType.Shared type: SecretType.Shared
}; };
}), }),
folderId, folderId,
orgId: actorOrgId,
secretDAL, secretDAL,
resourceMetadataDAL,
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL, secretVersionTagDAL,
@@ -1203,6 +1220,7 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
secretPath, secretPath,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -1254,7 +1272,7 @@ export const secretV2BridgeServiceFactory = ({
value: [ value: [
{ {
operator: "eq", operator: "eq",
field: "key", field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey value: el.secretKey
}, },
{ {
@@ -1319,7 +1337,7 @@ export const secretV2BridgeServiceFactory = ({
value: [ value: [
{ {
operator: "eq", operator: "eq",
field: "key", field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey value: el.secretKey
}, },
{ {
@@ -1371,6 +1389,7 @@ export const secretV2BridgeServiceFactory = ({
const secrets = await secretDAL.transaction(async (tx) => const secrets = await secretDAL.transaction(async (tx) =>
fnSecretBulkUpdate({ fnSecretBulkUpdate({
folderId, folderId,
orgId: actorOrgId,
tx, tx,
inputSecrets: inputSecrets.map((el) => { inputSecrets: inputSecrets.map((el) => {
const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0]; const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0];
@@ -1394,6 +1413,7 @@ export const secretV2BridgeServiceFactory = ({
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
key: el.newSecretName || el.secretKey, key: el.newSecretName || el.secretKey,
tags: el.tagIds, tags: el.tagIds,
secretMetadata: el.secretMetadata,
...encryptedValue ...encryptedValue
} }
}; };
@@ -1401,7 +1421,8 @@ export const secretV2BridgeServiceFactory = ({
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL secretVersionTagDAL,
resourceMetadataDAL
}) })
); );
await snapshotService.performSnapshot(folderId); await snapshotService.performSnapshot(folderId);
@@ -1410,6 +1431,7 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
secretPath, secretPath,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -1461,7 +1483,7 @@ export const secretV2BridgeServiceFactory = ({
value: [ value: [
{ {
operator: "eq", operator: "eq",
field: "key", field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey value: el.secretKey
}, },
{ {
@@ -1512,6 +1534,7 @@ export const secretV2BridgeServiceFactory = ({
actorId, actorId,
secretPath, secretPath,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -1815,10 +1838,12 @@ export const secretV2BridgeServiceFactory = ({
if (locallyCreatedSecrets.length) { if (locallyCreatedSecrets.length) {
await fnSecretBulkInsert({ await fnSecretBulkInsert({
folderId: destinationFolder.id, folderId: destinationFolder.id,
orgId: actorOrgId,
secretVersionDAL, secretVersionDAL,
secretDAL, secretDAL,
tx, tx,
secretTagDAL, secretTagDAL,
resourceMetadataDAL,
secretVersionTagDAL, secretVersionTagDAL,
inputSecrets: locallyCreatedSecrets.map((doc) => { inputSecrets: locallyCreatedSecrets.map((doc) => {
return { return {
@@ -1830,6 +1855,7 @@ export const secretV2BridgeServiceFactory = ({
skipMultilineEncoding: doc.skipMultilineEncoding, skipMultilineEncoding: doc.skipMultilineEncoding,
reminderNote: doc.reminderNote, reminderNote: doc.reminderNote,
reminderRepeatDays: doc.reminderRepeatDays, reminderRepeatDays: doc.reminderRepeatDays,
secretMetadata: doc.secretMetadata,
references: doc.value ? getAllSecretReferences(doc.value).nestedReferences : [] references: doc.value ? getAllSecretReferences(doc.value).nestedReferences : []
}; };
}) })
@@ -1838,6 +1864,8 @@ export const secretV2BridgeServiceFactory = ({
if (locallyUpdatedSecrets.length) { if (locallyUpdatedSecrets.length) {
await fnSecretBulkUpdate({ await fnSecretBulkUpdate({
folderId: destinationFolder.id, folderId: destinationFolder.id,
orgId: actorOrgId,
resourceMetadataDAL,
secretVersionDAL, secretVersionDAL,
secretDAL, secretDAL,
tx, tx,
@@ -1855,6 +1883,7 @@ export const secretV2BridgeServiceFactory = ({
encryptedComment: doc.encryptedComment, encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding, skipMultilineEncoding: doc.skipMultilineEncoding,
reminderNote: doc.reminderNote, reminderNote: doc.reminderNote,
secretMetadata: doc.secretMetadata,
reminderRepeatDays: doc.reminderRepeatDays, reminderRepeatDays: doc.reminderRepeatDays,
...(doc.encryptedValue ...(doc.encryptedValue
? { ? {
@@ -1938,6 +1967,7 @@ export const secretV2BridgeServiceFactory = ({
await snapshotService.performSnapshot(destinationFolder.id); await snapshotService.performSnapshot(destinationFolder.id);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId: actorOrgId,
secretPath: destinationFolder.path, secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environment.slug, environmentSlug: destinationFolder.environment.slug,
actorId, actorId,
@@ -1949,6 +1979,7 @@ export const secretV2BridgeServiceFactory = ({
await snapshotService.performSnapshot(sourceFolder.id); await snapshotService.performSnapshot(sourceFolder.id);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId, projectId,
orgId: actorOrgId,
secretPath: sourceFolder.path, secretPath: sourceFolder.path,
environmentSlug: sourceFolder.environment.slug, environmentSlug: sourceFolder.environment.slug,
actorId, actorId,

View File

@@ -7,6 +7,8 @@ import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal"; import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
import { TSecretVersionV2DALFactory } from "./secret-version-dal"; import { TSecretVersionV2DALFactory } from "./secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "./secret-version-tag-dal"; import { TSecretVersionV2TagDALFactory } from "./secret-version-tag-dal";
@@ -58,6 +60,7 @@ export type TCreateSecretDTO = TProjectPermission & {
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
secretReminderRepeatDays?: number | null; secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null; secretReminderNote?: string | null;
secretMetadata?: ResourceMetadataDTO;
}; };
export type TUpdateSecretDTO = TProjectPermission & { export type TUpdateSecretDTO = TProjectPermission & {
@@ -75,6 +78,7 @@ export type TUpdateSecretDTO = TProjectPermission & {
metadata?: { metadata?: {
source?: string; source?: string;
}; };
secretMetadata?: ResourceMetadataDTO;
}; };
export type TDeleteSecretDTO = TProjectPermission & { export type TDeleteSecretDTO = TProjectPermission & {
@@ -94,6 +98,7 @@ export type TCreateManySecretDTO = Omit<TProjectPermission, "projectId"> & {
secretComment?: string; secretComment?: string;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
tagIds?: string[]; tagIds?: string[];
secretMetadata?: ResourceMetadataDTO;
metadata?: { metadata?: {
source?: string; source?: string;
}; };
@@ -113,6 +118,7 @@ export type TUpdateManySecretDTO = Omit<TProjectPermission, "projectId"> & {
tagIds?: string[]; tagIds?: string[];
secretReminderRepeatDays?: number | null; secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null; secretReminderNote?: string | null;
secretMetadata?: ResourceMetadataDTO;
}[]; }[];
}; };
@@ -136,8 +142,16 @@ export type TSecretReference = { environment: string; secretPath: string; secret
export type TFnSecretBulkInsert = { export type TFnSecretBulkInsert = {
folderId: string; folderId: string;
orgId: string;
tx?: Knex; tx?: Knex;
inputSecrets: Array<Omit<TSecretsV2Insert, "folderId"> & { tagIds?: string[]; references: TSecretReference[] }>; inputSecrets: Array<
Omit<TSecretsV2Insert, "folderId"> & {
tagIds?: string[];
references: TSecretReference[];
secretMetadata?: ResourceMetadataDTO;
}
>;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences">; secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences">;
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">; secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2">; secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2">;
@@ -156,10 +170,12 @@ type TRequireReferenceIfValue =
export type TFnSecretBulkUpdate = { export type TFnSecretBulkUpdate = {
folderId: string; folderId: string;
orgId: string;
inputSecrets: { inputSecrets: {
filter: Partial<TSecretsV2>; filter: Partial<TSecretsV2>;
data: TRequireReferenceIfValue & { tags?: string[] }; data: TRequireReferenceIfValue & { tags?: string[]; secretMetadata?: ResourceMetadataDTO };
}[]; }[];
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences">; secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences">;
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">; secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2">; secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2">;

View File

@@ -749,7 +749,8 @@ export const createManySecretsRawFnFactory = ({
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
kmsService kmsService,
resourceMetadataDAL
}: TCreateManySecretsRawFnFactory) => { }: TCreateManySecretsRawFnFactory) => {
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL); const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
const createManySecretsRawFn = async ({ const createManySecretsRawFn = async ({
@@ -760,7 +761,7 @@ export const createManySecretsRawFnFactory = ({
userId userId
}: TCreateManySecretsRawFn) => { }: TCreateManySecretsRawFn) => {
const { botKey, shouldUseSecretV2Bridge } = await getBotKeyFn(projectId); const { botKey, shouldUseSecretV2Bridge } = await getBotKeyFn(projectId);
const project = await projectDAL.findById(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) if (!folder)
throw new NotFoundError({ throw new NotFoundError({
@@ -814,7 +815,9 @@ export const createManySecretsRawFnFactory = ({
tagIds: el.tags tagIds: el.tags
})), })),
folderId, folderId,
orgId: project.orgId,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
resourceMetadataDAL,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL, secretTagDAL,
secretVersionTagDAL: secretVersionTagV2BridgeDAL, secretVersionTagDAL: secretVersionTagV2BridgeDAL,
@@ -909,6 +912,7 @@ export const updateManySecretsRawFnFactory = ({
secretVersionTagV2BridgeDAL, secretVersionTagV2BridgeDAL,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
resourceMetadataDAL,
kmsService kmsService
}: TUpdateManySecretsRawFnFactory) => { }: TUpdateManySecretsRawFnFactory) => {
const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL); const getBotKeyFn = getBotKeyFnFactory(projectBotDAL, projectDAL);
@@ -920,6 +924,7 @@ export const updateManySecretsRawFnFactory = ({
userId userId
}: TUpdateManySecretsRawFn): Promise<Array<{ id: string }>> => { }: TUpdateManySecretsRawFn): Promise<Array<{ id: string }>> => {
const { botKey, shouldUseSecretV2Bridge } = await getBotKeyFn(projectId); const { botKey, shouldUseSecretV2Bridge } = await getBotKeyFn(projectId);
const project = await projectDAL.findById(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) if (!folder)
@@ -988,11 +993,13 @@ export const updateManySecretsRawFnFactory = ({
const updatedSecrets = await secretDAL.transaction(async (tx) => const updatedSecrets = await secretDAL.transaction(async (tx) =>
fnSecretV2BridgeBulkUpdate({ fnSecretV2BridgeBulkUpdate({
folderId, folderId,
orgId: project.orgId,
tx, tx,
inputSecrets: inputSecrets.map((el) => ({ inputSecrets: inputSecrets.map((el) => ({
filter: { id: secretsToUpdateInDBGroupedByKey[el.key][0].id, type: SecretType.Shared }, filter: { id: secretsToUpdateInDBGroupedByKey[el.key][0].id, type: SecretType.Shared },
data: el data: el
})), })),
resourceMetadataDAL,
secretDAL: secretV2BridgeDAL, secretDAL: secretV2BridgeDAL,
secretVersionDAL: secretVersionV2BridgeDAL, secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL, secretTagDAL,

View File

@@ -47,6 +47,8 @@ import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
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";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; 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";
@@ -104,6 +106,7 @@ type TSecretQueueFactoryDep = {
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">; auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
orgService: Pick<TOrgServiceFactory, "addGhostUser">; orgService: Pick<TOrgServiceFactory, "addGhostUser">;
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">; projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
}; };
export type TGetSecrets = { export type TGetSecrets = {
@@ -120,7 +123,12 @@ export const uniqueSecretQueueKey = (environment: string, secretPath: string) =>
type TIntegrationSecret = Record< type TIntegrationSecret = Record<
string, string,
{ value: string; comment?: string; skipMultilineEncoding?: boolean | null | undefined } {
value: string;
comment?: string;
skipMultilineEncoding?: boolean | null | undefined;
secretMetadata?: ResourceMetadataDTO;
}
>; >;
// TODO(akhilmhdh): split this into multiple queue // TODO(akhilmhdh): split this into multiple queue
@@ -157,7 +165,8 @@ export const secretQueueFactory = ({
auditLogService, auditLogService,
orgService, orgService,
projectUserMembershipRoleDAL, projectUserMembershipRoleDAL,
projectKeyDAL projectKeyDAL,
resourceMetadataDAL
}: TSecretQueueFactoryDep) => { }: TSecretQueueFactoryDep) => {
const integrationMeter = opentelemetry.metrics.getMeter("Integrations"); const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", { const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", {
@@ -306,7 +315,8 @@ export const secretQueueFactory = ({
kmsService, kmsService,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
secretVersionTagV2BridgeDAL secretVersionTagV2BridgeDAL,
resourceMetadataDAL
}); });
const updateManySecretsRawFn = updateManySecretsRawFnFactory({ const updateManySecretsRawFn = updateManySecretsRawFnFactory({
@@ -321,7 +331,8 @@ export const secretQueueFactory = ({
kmsService, kmsService,
secretVersionV2BridgeDAL, secretVersionV2BridgeDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
secretVersionTagV2BridgeDAL secretVersionTagV2BridgeDAL,
resourceMetadataDAL
}); });
/** /**
@@ -372,6 +383,7 @@ export const secretQueueFactory = ({
} }
content[secretKey].skipMultilineEncoding = Boolean(secret.skipMultilineEncoding); content[secretKey].skipMultilineEncoding = Boolean(secret.skipMultilineEncoding);
content[secretKey].secretMetadata = secret.secretMetadata;
}) })
); );
@@ -397,7 +409,8 @@ export const secretQueueFactory = ({
content[importedSecret.key] = { content[importedSecret.key] = {
skipMultilineEncoding: importedSecret.skipMultilineEncoding, skipMultilineEncoding: importedSecret.skipMultilineEncoding,
comment: importedSecret.secretComment, comment: importedSecret.secretComment,
value: importedSecret.secretValue || "" value: importedSecret.secretValue || "",
secretMetadata: importedSecret.secretMetadata
}; };
} }
} }
@@ -597,6 +610,7 @@ export const secretQueueFactory = ({
_depth: depth, _depth: depth,
secretPath, secretPath,
projectId, projectId,
orgId,
environmentSlug: environment, environmentSlug: environment,
excludeReplication, excludeReplication,
actorId, actorId,
@@ -625,6 +639,7 @@ export const secretQueueFactory = ({
_deDupeReplicationQueue: deDupeReplicationQueue, _deDupeReplicationQueue: deDupeReplicationQueue,
_depth: depth, _depth: depth,
projectId, projectId,
orgId,
secretPath, secretPath,
actorId, actorId,
actor, actor,
@@ -681,6 +696,7 @@ export const secretQueueFactory = ({
if (!folder) { if (!folder) {
throw new Error("Secret path not found"); throw new Error("Secret path not found");
} }
const project = await projectDAL.findById(projectId);
// find all imports made with the given environment and secret path // find all imports made with the given environment and secret path
const linkSourceDto = { const linkSourceDto = {
@@ -715,6 +731,7 @@ export const secretQueueFactory = ({
.map(({ folderId }) => .map(({ folderId }) =>
syncSecrets({ syncSecrets({
projectId, projectId,
orgId: project.orgId,
secretPath: foldersGroupedById[folderId][0]?.path as string, secretPath: foldersGroupedById[folderId][0]?.path as string,
environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string, environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string,
_deDupeQueue: deDupeQueue, _deDupeQueue: deDupeQueue,
@@ -767,6 +784,7 @@ export const secretQueueFactory = ({
.map((folderId) => .map((folderId) =>
syncSecrets({ syncSecrets({
projectId, projectId,
orgId: project.orgId,
secretPath: referencedFoldersGroupedById[folderId][0]?.path as string, secretPath: referencedFoldersGroupedById[folderId][0]?.path as string,
environmentSlug: referencedFoldersGroupedById[folderId][0]?.environmentSlug as string, environmentSlug: referencedFoldersGroupedById[folderId][0]?.environmentSlug as string,
_deDupeQueue: deDupeQueue, _deDupeQueue: deDupeQueue,

View File

@@ -288,6 +288,7 @@ export const secretServiceFactory = ({
actorId, actorId,
actor, actor,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
} }
@@ -429,6 +430,7 @@ export const secretServiceFactory = ({
await snapshotService.performSnapshot(folderId); await snapshotService.performSnapshot(folderId);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath: path, secretPath: path,
orgId: actorOrgId,
actorId, actorId,
actor, actor,
projectId, projectId,
@@ -526,6 +528,7 @@ export const secretServiceFactory = ({
actorId, actorId,
actor, actor,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
} }
@@ -820,6 +823,7 @@ export const secretServiceFactory = ({
actorId, actorId,
secretPath: path, secretPath: path,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -928,6 +932,7 @@ export const secretServiceFactory = ({
actorId, actorId,
secretPath: path, secretPath: path,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -1014,6 +1019,7 @@ export const secretServiceFactory = ({
actorId, actorId,
secretPath: path, secretPath: path,
projectId, projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug environmentSlug: folder.environment.slug
}); });
@@ -1385,7 +1391,8 @@ export const secretServiceFactory = ({
skipMultilineEncoding, skipMultilineEncoding,
tagIds, tagIds,
secretReminderNote, secretReminderNote,
secretReminderRepeatDays secretReminderRepeatDays,
secretMetadata
}: TCreateSecretRawDTO) => { }: TCreateSecretRawDTO) => {
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
const policy = const policy =
@@ -1412,7 +1419,8 @@ export const secretServiceFactory = ({
secretValue, secretValue,
tagIds, tagIds,
reminderNote: secretReminderNote, reminderNote: secretReminderNote,
reminderRepeatDays: secretReminderRepeatDays reminderRepeatDays: secretReminderRepeatDays,
secretMetadata
} }
] ]
} }
@@ -1435,7 +1443,8 @@ export const secretServiceFactory = ({
tagIds, tagIds,
secretReminderNote, secretReminderNote,
skipMultilineEncoding, skipMultilineEncoding,
secretReminderRepeatDays secretReminderRepeatDays,
secretMetadata
}); });
return { secret, type: SecretProtectionType.Direct as const }; return { secret, type: SecretProtectionType.Direct as const };
} }
@@ -1525,7 +1534,8 @@ export const secretServiceFactory = ({
secretReminderRepeatDays, secretReminderRepeatDays,
metadata, metadata,
secretComment, secretComment,
newSecretName newSecretName,
secretMetadata
}: TUpdateSecretRawDTO) => { }: TUpdateSecretRawDTO) => {
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
const policy = const policy =
@@ -1553,7 +1563,8 @@ export const secretServiceFactory = ({
secretValue, secretValue,
tagIds, tagIds,
reminderNote: secretReminderNote, reminderNote: secretReminderNote,
reminderRepeatDays: secretReminderRepeatDays reminderRepeatDays: secretReminderRepeatDays,
secretMetadata
} }
] ]
} }
@@ -1577,7 +1588,8 @@ export const secretServiceFactory = ({
secretName, secretName,
newSecretName, newSecretName,
metadata, metadata,
secretValue secretValue,
secretMetadata
}); });
return { type: SecretProtectionType.Direct as const, secret }; return { type: SecretProtectionType.Direct as const, secret };
} }
@@ -1793,7 +1805,8 @@ export const secretServiceFactory = ({
secretComment: el.secretComment, secretComment: el.secretComment,
metadata: el.metadata, metadata: el.metadata,
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
secretKey: el.secretKey secretKey: el.secretKey,
secretMetadata: el.secretMetadata
})) }))
} }
}); });
@@ -1919,7 +1932,8 @@ export const secretServiceFactory = ({
secretValue: el.secretValue, secretValue: el.secretValue,
secretComment: el.secretComment, secretComment: el.secretComment,
skipMultilineEncoding: el.skipMultilineEncoding, skipMultilineEncoding: el.skipMultilineEncoding,
secretKey: el.secretKey secretKey: el.secretKey,
secretMetadata: el.secretMetadata
})) }))
} }
}); });
@@ -2262,6 +2276,7 @@ export const secretServiceFactory = ({
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath, secretPath,
projectId: project.id, projectId: project.id,
orgId: project.orgId,
environmentSlug: environment, environmentSlug: environment,
excludeReplication: true excludeReplication: true
}); });
@@ -2370,6 +2385,7 @@ export const secretServiceFactory = ({
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
secretPath, secretPath,
projectId: project.id, projectId: project.id,
orgId: project.orgId,
environmentSlug: environment, environmentSlug: environment,
excludeReplication: true excludeReplication: true
}); });
@@ -2828,6 +2844,7 @@ export const secretServiceFactory = ({
await snapshotService.performSnapshot(destinationFolder.id); await snapshotService.performSnapshot(destinationFolder.id);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId: project.id, projectId: project.id,
orgId: project.orgId,
secretPath: destinationFolder.path, secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environment.slug, environmentSlug: destinationFolder.environment.slug,
actorId, actorId,
@@ -2839,6 +2856,7 @@ export const secretServiceFactory = ({
await snapshotService.performSnapshot(sourceFolder.id); await snapshotService.performSnapshot(sourceFolder.id);
await secretQueueService.syncSecrets({ await secretQueueService.syncSecrets({
projectId: project.id, projectId: project.id,
orgId: project.orgId,
secretPath: sourceFolder.path, secretPath: sourceFolder.path,
environmentSlug: sourceFolder.environment.slug, environmentSlug: sourceFolder.environment.slug,
actorId, actorId,

View File

@@ -14,6 +14,8 @@ import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { ActorType } from "../auth/auth-type"; import { ActorType } from "../auth/auth-type";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal"; import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal"; import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
@@ -211,6 +213,7 @@ export type TCreateSecretRawDTO = TProjectPermission & {
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
secretReminderRepeatDays?: number | null; secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null; secretReminderNote?: string | null;
secretMetadata?: ResourceMetadataDTO;
}; };
export type TUpdateSecretRawDTO = TProjectPermission & { export type TUpdateSecretRawDTO = TProjectPermission & {
@@ -228,6 +231,7 @@ export type TUpdateSecretRawDTO = TProjectPermission & {
metadata?: { metadata?: {
source?: string; source?: string;
}; };
secretMetadata?: ResourceMetadataDTO;
}; };
export type TDeleteSecretRawDTO = TProjectPermission & { export type TDeleteSecretRawDTO = TProjectPermission & {
@@ -248,6 +252,7 @@ export type TCreateManySecretRawDTO = Omit<TProjectPermission, "projectId"> & {
secretComment?: string; secretComment?: string;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
tagIds?: string[]; tagIds?: string[];
secretMetadata?: ResourceMetadataDTO;
metadata?: { metadata?: {
source?: string; source?: string;
}; };
@@ -266,6 +271,7 @@ export type TUpdateManySecretRawDTO = Omit<TProjectPermission, "projectId"> & {
secretComment?: string; secretComment?: string;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
tagIds?: string[]; tagIds?: string[];
secretMetadata?: ResourceMetadataDTO;
secretReminderRepeatDays?: number | null; secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null; secretReminderNote?: string | null;
}[]; }[];
@@ -293,7 +299,13 @@ export type TSecretReference = { environment: string; secretPath: string };
export type TFnSecretBulkInsert = { export type TFnSecretBulkInsert = {
folderId: string; folderId: string;
tx?: Knex; tx?: Knex;
inputSecrets: Array<Omit<TSecretsInsert, "folderId"> & { tags?: string[]; references?: TSecretReference[] }>; inputSecrets: Array<
Omit<TSecretsInsert, "folderId"> & {
tags?: string[];
references?: TSecretReference[];
secretMetadata?: ResourceMetadataDTO;
}
>;
secretDAL: Pick<TSecretDALFactory, "insertMany" | "upsertSecretReferences">; secretDAL: Pick<TSecretDALFactory, "insertMany" | "upsertSecretReferences">;
secretVersionDAL: Pick<TSecretVersionDALFactory, "insertMany">; secretVersionDAL: Pick<TSecretVersionDALFactory, "insertMany">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecret">; secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecret">;
@@ -389,6 +401,7 @@ export type TCreateManySecretsRawFnFactory = {
>; >;
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">; secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">; secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
}; };
export type TCreateManySecretsRawFn = { export type TCreateManySecretsRawFn = {
@@ -425,6 +438,7 @@ export type TUpdateManySecretsRawFnFactory = {
>; >;
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">; secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">; secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
}; };
export type TUpdateManySecretsRawFn = { export type TUpdateManySecretsRawFn = {
@@ -460,6 +474,7 @@ export type TSyncSecretsDTO<T extends boolean = false> = {
_depth?: number; _depth?: number;
secretPath: string; secretPath: string;
projectId: string; projectId: string;
orgId: string;
environmentSlug: string; environmentSlug: string;
// cases for just doing sync integration and webhook // cases for just doing sync integration and webhook
excludeReplication?: T; excludeReplication?: T;

View File

@@ -51,7 +51,7 @@ const buildSlackPayload = (notification: TSlackNotification) => {
*Environment*: ${payload.environment} *Environment*: ${payload.environment}
*Secret path*: ${payload.secretPath || "/"} *Secret path*: ${payload.secretPath || "/"}
View the complete details <${appCfg.SITE_URL}/project/${payload.projectId}/approval?requestId=${ View the complete details <${appCfg.SITE_URL}/secret-manager/${payload.projectId}/approval?requestId=${
payload.requestId payload.requestId
}|here>.`; }|here>.`;

View File

@@ -19,7 +19,7 @@ Every new joiner has an onboarding buddy who should ideally be in the the same t
1. Join the weekly all-hands meeting. It typically happens on Monday's at 8:30am PT. 1. Join the weekly all-hands meeting. It typically happens on Monday's at 8:30am PT.
2. Ship something together on day one even if tiny! It feels great to hit the ground running, with a development environment all ready to go. 2. Ship something together on day one even if tiny! It feels great to hit the ground running, with a development environment all ready to go.
3. Check out the [Areas of Responsibility (AoR) Table](https://docs.google.com/spreadsheets/d/1RnXlGFg83Sgu0dh7ycuydsSobmFfI3A0XkGw7vrVxEI/edit?usp=sharing). This is helpful to know who you can ask about particular areas of Infisical. Feel free to add yourself to the areas you'd be most interesting to dive into. 3. Check out the [Areas of Responsibility (AoR) Table](https://docs.google.com/spreadsheets/d/1RnXlGFg83Sgu0dh7ycuydsSobmFfI3A0XkGw7vrVxEI/edit?usp=sharing). This is helpful to know who you can ask about particular areas of Infisical. Feel free to add yourself to the areas you'd be most interesting to dive into.
4. Read the [Infisical Strategy Doc](https://docs.google.com/document/d/1RaJd3RoS2QpWLFHlgfHaXnHqCCwRt6mCGZkbJ75J_D0/edit?usp=sharing). 4. Read the [Infisical Strategy Doc](https://docs.google.com/document/d/1uV9IaahYwbZ5OuzDTFdQMSa1P0mpMOnetGB-xqf4G40).
5. Update your LinkedIn profile with one of [Infisical's official banners](https://drive.google.com/drive/u/0/folders/1oSNWjbpRl9oNYwxM_98IqzKs9fAskrb2) (if you want to). You can also coordinate your social posts in the #marketing Slack channel, so that we can boost it from Infisical's official social media accounts. 5. Update your LinkedIn profile with one of [Infisical's official banners](https://drive.google.com/drive/u/0/folders/1oSNWjbpRl9oNYwxM_98IqzKs9fAskrb2) (if you want to). You can also coordinate your social posts in the #marketing Slack channel, so that we can boost it from Infisical's official social media accounts.
6. Over the first few weeks, feel free to schedule 1:1s with folks on the team to get to know them a bit better. 6. Over the first few weeks, feel free to schedule 1:1s with folks on the team to get to know them a bit better.
7. Change your Slack username in the users channel to `[NAME] (Infisical)`. 7. Change your Slack username in the users channel to `[NAME] (Infisical)`.

View File

@@ -144,9 +144,6 @@ services:
- ./frontend/src:/app/src/ # mounted whole src to avoid missing reload on new files - ./frontend/src:/app/src/ # mounted whole src to avoid missing reload on new files
- ./frontend/public:/app/public - ./frontend/public:/app/public
env_file: .env env_file: .env
environment:
- NEXT_PUBLIC_ENV=development
- INFISICAL_TELEMETRY_ENABLED=false
pgadmin: pgadmin:
image: dpage/pgadmin4 image: dpage/pgadmin4

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 580 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 KiB

After

Width:  |  Height:  |  Size: 795 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

View File

@@ -17,6 +17,7 @@ Prerequisites:
If your instance is deployed on AWS, the aws-sdk will automatically retrieve the credentials. Ensure that you assign the provided permission policy to your deployed instance, such as ECS or EC2. If your instance is deployed on AWS, the aws-sdk will automatically retrieve the credentials. Ensure that you assign the provided permission policy to your deployed instance, such as ECS or EC2.
The following steps are for instances not deployed on AWS The following steps are for instances not deployed on AWS
<Steps> <Steps>
<Step title="Create an IAM User"> <Step title="Create an IAM User">
Navigate to [Create IAM User](https://console.aws.amazon.com/iamv2/home#/users/create) in your AWS Console. Navigate to [Create IAM User](https://console.aws.amazon.com/iamv2/home#/users/create) in your AWS Console.
@@ -40,9 +41,10 @@ The following steps are for instances not deployed on AWS
<Step title="Obtain the IAM User Credentials"> <Step title="Obtain the IAM User Credentials">
Obtain the AWS access key ID and secret access key for your IAM User by navigating to IAM > Users > [Your User] > Security credentials > Access keys. Obtain the AWS access key ID and secret access key for your IAM User by navigating to IAM > Users > [Your User] > Security credentials > Access keys.
![Access Key Step 1](../../images/integrations/aws/integrations-aws-access-key-1.png) ![Access Key Step 1](../../images/integrations/aws/integrations-aws-access-key-1.png)
![Access Key Step 2](../../images/integrations/aws/integrations-aws-access-key-2.png) ![Access Key Step 2](../../images/integrations/aws/integrations-aws-access-key-2.png)
![Access Key Step 3](../../images/integrations/aws/integrations-aws-access-key-3.png) ![Access Key Step 3](../../images/integrations/aws/integrations-aws-access-key-3.png)
</Step> </Step>
<Step title="Set Up Integration Keys"> <Step title="Set Up Integration Keys">
1. Set the access key as **CLIENT_ID_AWS_INTEGRATION**. 1. Set the access key as **CLIENT_ID_AWS_INTEGRATION**.
@@ -59,6 +61,7 @@ The following steps are for instances not deployed on AWS
2. Select **AWS Account** as the **Trusted Entity Type**. 2. Select **AWS Account** as the **Trusted Entity Type**.
3. Choose **Another AWS Account** and enter **381492033652** (Infisical AWS Account ID). This restricts the role to be assumed only by Infisical. If self-hosting, provide your AWS account number instead. 3. Choose **Another AWS Account** and enter **381492033652** (Infisical AWS Account ID). This restricts the role to be assumed only by Infisical. If self-hosting, provide your AWS account number instead.
4. Optionally, enable **Require external ID** and enter your **project ID** to further enhance security. 4. Optionally, enable **Require external ID** and enter your **project ID** to further enhance security.
</Step> </Step>
<Step title="Add Required Permissions for the IAM Role"> <Step title="Add Required Permissions for the IAM Role">
@@ -89,11 +92,13 @@ The following steps are for instances not deployed on AWS
] ]
} }
``` ```
</Step> </Step>
<Step title="Copy the AWS IAM Role ARN"> <Step title="Copy the AWS IAM Role ARN">
![Copy IAM Role ARN](../../images/integrations/aws/integration-aws-iam-assume-arn.png) ![Copy IAM Role
</Step> ARN](../../images/integrations/aws/integration-aws-iam-assume-arn.png)
</Step>
<Step title="Authorize Infisical for AWS Secrets Manager"> <Step title="Authorize Infisical for AWS Secrets Manager">
1. Navigate to your project's integrations tab in Infisical. 1. Navigate to your project's integrations tab in Infisical.
@@ -104,6 +109,7 @@ The following steps are for instances not deployed on AWS
![Select Assume Role](../../images/integrations/aws/integration-aws-iam-assume-select.png) ![Select Assume Role](../../images/integrations/aws/integration-aws-iam-assume-select.png)
4. Provide the **AWS IAM Role ARN** obtained from the previous step. 4. Provide the **AWS IAM Role ARN** obtained from the previous step.
</Step> <Step title="Start integration"> </Step> <Step title="Start integration">
Select how you want to integration to work by specifying a number of parameters: Select how you want to integration to work by specifying a number of parameters:
@@ -127,6 +133,12 @@ The following steps are for instances not deployed on AWS
Optionally, you can add tags or specify the encryption key of all the secrets created via this integration: Optionally, you can add tags or specify the encryption key of all the secrets created via this integration:
<ParamField path="Tag Sync Mode" type="string" optional>
The sync mode for AWS tags. The supported options are `Secret Metadata` and `Custom`. If `Secret Metadata` is selected,
the metadata of the Infisical secrets are used as tags in AWS. If custom is selected, then the key/value of the **Secret Tag** field is used. `Secret Metadata` mode
is only supported for one-to-one integrations.
</ParamField>
<ParamField path="Secret Tag" type="string" optional> <ParamField path="Secret Tag" type="string" optional>
The Key/Value of a tag that will be added to secrets in AWS. Please note that it is possible to add multiple tags via API. The Key/Value of a tag that will be added to secrets in AWS. Please note that it is possible to add multiple tags via API.
</ParamField> </ParamField>

View File

@@ -29,6 +29,36 @@ description: "How to sync secrets from Infisical to Azure App Configuration"
![integrations](../../images/integrations/azure-app-configuration/create-integration-form.png) ![integrations](../../images/integrations/azure-app-configuration/create-integration-form.png)
Press create integration to start syncing secrets to Azure App Configuration. Press create integration to start syncing secrets to Azure App Configuration.
<Note>
The Azure App Configuration integration requires the following permissions to be set on the user / service principal
for Infisical to sync secrets to Azure App Configuration: `Read Key-Value`, `Write Key-Value`, `Delete Key-Value`.
Any role with these permissions would work such as the **App Configuration Data Owner** role. Alternatively, you can use the
**App Configuration Data Reader** role for read-only access or **App Configuration Data Contributor** role for read/write access.
</Note>
</Step>
<Step title="Additional Configuration">
#### Azure references
When adding secrets in Infisical that reference Azure Key Vault secrets, Infisical will automatically sets the content type to `application/vnd.microsoft.appconfig.keyvaultref+json;charset=utf-8` in Azure App Configuration.
The following reference formats are automatically detected when added on Infisical's side:
- `{ "uri": "https://my-key-vault.vault.azure.net/secrets/my-secret" }`
- `https://my-key-vault.vault.azure.net/secrets/my-secret`
#### Azure Labels
You can sync secrets from Infisical to Azure with custom labels by enabling the `Use Labels` option during setup:
**When enabled**: Secrets will be pushed to Azure with your specified label
**When disabled**: Secrets will be pushed with an empty (null) label
<Info>
If you have set the initial sync to `import` have behavior, the label selection affects which secrets are imported from Azure:
- With `Use Labels` disabled: Only secrets with empty labels are imported on initial sync
- With `Use Labels` enabled: Only secrets matching your specified label are imported on initial sync
</Info>
</Step> </Step>
</Steps> </Steps>

View File

@@ -17,6 +17,10 @@ description: "How to sync secrets from Infisical to Azure Key Vault"
![integrations](../../images/integrations.png) ![integrations](../../images/integrations.png)
Press on the Azure Key Vault tile and grant Infisical access to Azure Key Vault. Press on the Azure Key Vault tile and grant Infisical access to Azure Key Vault.
You can optionally authenticate against a specific tenant by providing the Azure tenant or directory ID.
![integrations](/images/integrations/azure-key-vault/integrations-azure-key-vault-tenant-select.png)
</Step> </Step>
<Step title="Start integration"> <Step title="Start integration">
Obtain the Vault URI of your key vault in the Overview tab. Obtain the Vault URI of your key vault in the Overview tab.

View File

@@ -342,14 +342,6 @@
"cli/faq" "cli/faq"
] ]
}, },
{
"group": "App Connections",
"pages": [
"integrations/app-connections/overview",
"integrations/app-connections/aws",
"integrations/app-connections/github"
]
},
{ {
"group": "Infrastructure Integrations", "group": "Infrastructure Integrations",
"pages": [ "pages": [
@@ -766,33 +758,6 @@
"api-reference/endpoints/identity-specific-privilege/list" "api-reference/endpoints/identity-specific-privilege/list"
] ]
}, },
{
"group": "App Connections",
"pages": [
"api-reference/endpoints/app-connections/list",
"api-reference/endpoints/app-connections/options",
{ "group": "AWS",
"pages": [
"api-reference/endpoints/app-connections/aws/list",
"api-reference/endpoints/app-connections/aws/get-by-id",
"api-reference/endpoints/app-connections/aws/get-by-name",
"api-reference/endpoints/app-connections/aws/create",
"api-reference/endpoints/app-connections/aws/update",
"api-reference/endpoints/app-connections/aws/delete"
]
},
{ "group": "GitHub",
"pages": [
"api-reference/endpoints/app-connections/github/list",
"api-reference/endpoints/app-connections/github/get-by-id",
"api-reference/endpoints/app-connections/github/get-by-name",
"api-reference/endpoints/app-connections/github/create",
"api-reference/endpoints/app-connections/github/update",
"api-reference/endpoints/app-connections/github/delete"
]
}
]
},
{ {
"group": "Integrations", "group": "Integrations",
"pages": [ "pages": [
@@ -975,9 +940,6 @@
] ]
} }
], ],
"integrations": {
"intercom": "hsg644ru"
},
"analytics": { "analytics": {
"koala": { "koala": {
"publicApiKey": "pk_b50d7184e0e39ddd5cdb43cf6abeadd9b97d" "publicApiKey": "pk_b50d7184e0e39ddd5cdb43cf6abeadd9b97d"

View File

@@ -1,3 +1,2 @@
node_modules node_modules
**/.next dist
.next

View File

@@ -1,108 +0,0 @@
module.exports = {
overrides: [
{
files: ["next.config.js"]
}
],
root: true,
env: {
browser: true,
es2021: true,
es6: true
},
extends: [
"airbnb",
"airbnb-typescript",
"airbnb/hooks",
"plugin:react/recommended",
"prettier",
"plugin:storybook/recommended"
],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: "./tsconfig.json",
ecmaFeatures: {
jsx: true
},
tsconfigRootDir: __dirname
},
plugins: ["react", "prettier", "simple-import-sort", "import"],
rules: {
"@typescript-eslint/no-empty-function": "off",
quotes: ["error", "double", { avoidEscape: true }],
"comma-dangle": ["error", "only-multiline"],
"react/react-in-jsx-scope": "off",
"import/prefer-default-export": "off",
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/ban-ts-comment": "warn",
"react/jsx-props-no-spreading": "off", // switched off for component building
// TODO: This rule will be switched ON after complete revamp of frontend
"@typescript-eslint/no-explicit-any": "off",
"jsx-a11y/control-has-associated-label": "off",
"no-console": "off",
"arrow-body-style": "off",
"no-underscore-dangle": [
"error",
{
allow: ["_id"]
}
],
"jsx-a11y/anchor-is-valid": "off",
// all those <a> tags must be converted to label or a p component
//
"react/require-default-props": "off",
"react/jsx-filename-extension": [
1,
{
extensions: [".tsx", ".ts"]
}
],
// TODO: turn this rule ON after migration. everything should use arrow functions
"react/function-component-definition": [
0,
{
namedComponents: "arrow-function"
}
],
"react/no-unknown-property": [
"error",
{
ignore: ["jsx"]
}
],
"@typescript-eslint/no-non-null-assertion": "off",
"simple-import-sort/exports": "warn",
"simple-import-sort/imports": [
"warn",
{
groups: [
// Node.js builtins. You could also generate this regex if you use a `.js` config.
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
// Note that if you use the `node:` prefix for Node.js builtins,
// you can avoid this complexity: You can simply use "^node:".
[
"^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)"
],
// Packages `react` related packages
["^react", "^next", "^@?\\w"],
["^@app"],
// Internal packages.
["^~(/.*|$)"],
// Relative imports
["^\\.\\.(?!/?$)", "^\\.\\./?$", "^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
// Style imports.
["^.+\\.?(css|scss)$"]
]
}
]
},
ignorePatterns: ["next.config.js", "cypress/**/*.js", "cypress.config.js"],
settings: {
"import/resolver": {
typescript: {
project: ["./tsconfig.json"]
}
}
}
};

50
frontend/.gitignore vendored
View File

@@ -1,36 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # Logs
logs
# dependencies *.log
/node_modules
/.pnp
.pnp.js
.env
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
# debug
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# local env files node_modules
.env.local dist
.env.development.local dist-ssr
.env.test.local *.local
.env.production.local
.vercel
.env.infisical
.vscode # Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -3,5 +3,8 @@
"printWidth": 100, "printWidth": 100,
"trailingComma": "none", "trailingComma": "none",
"tabWidth": 2, "tabWidth": 2,
"semi": true "semi": true,
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindStylesheet": "./src/index.css",
"tailwindFunctions": ["clsx", "twMerge"]
} }

View File

@@ -1,28 +0,0 @@
const path = require("path");
module.exports = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"storybook-dark-mode",
{
name: "@storybook/addon-styling",
options: {
postCss: {
implementation: require("postcss")
}
}
}
],
framework: {
name: "@storybook/nextjs",
options: {}
},
core: {
disableTelemetry: true
},
docs: {
autodocs: "tag"
}
};

View File

@@ -1,29 +0,0 @@
import { themes } from "@storybook/theming";
import "react-day-picker/dist/style.css";
import "../src/styles/globals.css";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
backgrounds: {
default: "dark",
values: [
{
name: "dark",
value: "rgb(14, 16, 20)"
},
{
name: "paper",
value: "rgb(30, 31, 34)"
}
]
},
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/
}
},
darkMode: {
dark: { ...themes.dark, appContentBg: "rgb(14,16,20)", appBg: "rgb(14,16,20)" }
}
};

View File

@@ -1,84 +0,0 @@
ARG POSTHOG_HOST=https://app.posthog.com
ARG POSTHOG_API_KEY=posthog-api-key
ARG INTERCOM_ID=intercom-id
ARG NEXT_INFISICAL_PLATFORM_VERSION=next-infisical-platform-version
ARG CAPTCHA_SITE_KEY=captcha-site-key
FROM node:16-alpine AS deps
# Install dependencies only when needed. Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
# RUN apk add --no-cache libc6-compat
WORKDIR /app
# Copy over dependency files
COPY package.json package-lock.json next.config.js ./
# Install dependencies
RUN npm ci --only-production --ignore-scripts
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
WORKDIR /app
# Copy dependencies
COPY --from=deps /app/node_modules ./node_modules
# Copy all files
COPY . .
ENV NODE_ENV production
ENV NEXT_PUBLIC_ENV production
ARG POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
# Build
RUN npm run build
# Production image
FROM node:16-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN mkdir -p /app/.next/cache/images && chown nextjs:nodejs /app/.next/cache/images
VOLUME /app/.next/cache/images
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
ARG SAML_ORG_SLUG
ENV NEXT_PUBLIC_SAML_ORG_SLUG=$SAML_ORG_SLUG \
BAKED_NEXT_PUBLIC_SAML_ORG_SLUG=$SAML_ORG_SLUG
ARG NEXT_INFISICAL_PLATFORM_VERSION
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION=$NEXT_INFISICAL_PLATFORM_VERSION
ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY \
BAKED_NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
COPY --chown=nextjs:nodejs --chmod=555 scripts ./scripts
COPY --from=builder /app/public ./public
RUN chown nextjs:nodejs ./public/data
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs --chmod=777 /app/.next/static ./.next/static
RUN chmod -R 777 /app/.next/server
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV NEXT_TELEMETRY_DISABLED 1
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node scripts/healthcheck.js
CMD ["/app/scripts/start.sh"]

View File

@@ -1,5 +1,5 @@
# Base layer # Base layer
FROM node:16-alpine FROM node:20-alpine
# Set the working directory # Set the working directory
WORKDIR /app WORKDIR /app
@@ -11,9 +11,6 @@ COPY package-lock.json ./
# Install # Install
RUN npm install --ignore-scripts RUN npm install --ignore-scripts
# Copy over next.js config
COPY next.config.js ./next.config.js
# Copy all files # Copy all files
COPY . . COPY . .

View File

@@ -1,22 +1,50 @@
This is the client repository for Infisical. # React + TypeScript + Vite
## Before you get started with development locally This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Please ensure you have Docker and Docker Compose installed for your OS. Currently, two official plugins are available:
### Steps to start server - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
- `CD` into the repo ## Expanding the ESLint configuration
- run command `docker-compose -f docker-compose.dev.yml up --build --force-recreate`
- Visit localhost:8080 and the website should be live
### Steps to shutdown this Docker compose If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- `CD` into this repo - Configure the top-level `parserOptions` property like this:
- run command `docker-compose -f docker-compose.dev.yml down`
### Notes ```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
Any changes made to local files in the `/components`, `/pages`, `/styles` will be hot reloaded. If would like like to watch for other files or folders live, please add them to the docker volume. - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
You will also need to ensure that a .env.local file exists with all required environment variables ```js
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```

View File

@@ -1,7 +0,0 @@
module.exports = {
e2e: {
baseUrl: 'http://localhost:8080',
viewportWidth: 1480,
viewportHeight: 920,
},
};

View File

@@ -1,47 +0,0 @@
/// <reference types="cypress" />
describe('organization Overview', () => {
beforeEach(() => {
cy.login(`test@localhost.local`, `testInfisical1`)
})
const projectName = "projectY"
it('can`t create projects with empty names', () => {
cy.get('.button').click()
cy.get('input[placeholder="Type your project name"]').type('abc').clear()
cy.intercept('*').as('anyRequest');
cy.get('@anyRequest').should('not.exist');
})
it('can delete a newly-created project', () => {
// Create a project
cy.get('.button').click()
cy.get('input[placeholder="Type your project name"]').type(`${projectName}`)
cy.contains('button', 'Create Project').click()
cy.url().should('include', '/project')
// Delete a project
cy.get(`[href^="/project/"][href$="/settings"] > a > .group`).click()
cy.contains('button', `Delete ${projectName}`).click()
cy.contains('button', 'Delete Project').should('have.attr', 'disabled')
cy.get('input[placeholder="Type to delete..."]').type('confirm')
cy.contains('button', 'Delete Project').should('not.have.attr', 'disabled')
cy.url().then((currentUrl) => {
let projectId = currentUrl.split("/")[4]
cy.intercept('DELETE', `/api/v1/workspace/${projectId}`).as('deleteProject');
cy.contains('button', 'Delete Project').click();
cy.get('@deleteProject').should('have.property', 'response').and('have.property', 'statusCode', 200);
})
})
it('can display no projects', () => {
cy.intercept('/api/v1/workspace', {
body: {
"workspaces": []
},
})
cy.get('.border-mineshaft-700 > :nth-child(2)').should('have.text', 'You are not part of any projects in this organization yet. When you are, they will appear here.')
})
})

View File

@@ -1,24 +0,0 @@
/// <reference types="cypress" />
describe('Organization Settings', () => {
let orgId;
beforeEach(() => {
cy.login(`test@localhost.local`, `testInfisical1`)
cy.url().then((currentUrl) => {
orgId = currentUrl.split("/")[4]
cy.visit(`org/${orgId}/settings`)
})
})
it('can rename org', () => {
cy.get('input[placeholder="Acme Corp"]').clear().type('ABC')
cy.intercept('PATCH', `/api/v1/organization/${orgId}/name`).as('renameOrg');
cy.get('form.p-4 > .button').click()
cy.get('@renameOrg').should('have.property', 'response').and('have.property', 'statusCode', 200);
cy.get('.pl-3').should("have.text", "ABC ")
})
})

View File

@@ -1,84 +0,0 @@
/// <reference types="cypress" />
describe('Project Overview', () => {
const projectName = "projectY"
let projectId;
let isFirstTest = true;
before(() => {
cy.login(`test@localhost.local`, `testInfisical1`)
// Create a project
cy.get('.button').click()
cy.get('input[placeholder="Type your project name"]').type(`${projectName}`)
cy.contains('button', 'Create Project').click()
cy.url().should('include', '/project').then((currentUrl) => {
projectId = currentUrl.split("/")[4]
})
})
beforeEach(() => {
if (isFirstTest) {
isFirstTest = false;
return; // Skip the rest of the beforeEach for the first test
}
cy.login(`test@localhost.local`, `testInfisical1`)
cy.visit(`/project/${projectId}/secrets/overview`)
})
it('can create secrets', () => {
cy.contains('button', 'Go to Development').click()
cy.contains('button', 'Add a new secret').click()
cy.get('input[placeholder="Type your secret name"]').type('SECRET_A')
cy.contains('button', 'Create Secret').click()
cy.get('.w-80 > .inline-flex > .input').should('have.value', 'SECRET_A')
cy.get(':nth-child(6) > .button > .w-min').should('have.text', '1 Commit')
})
it('can update secrets', () => {
cy.get(':nth-child(2) > .flex > .button').click()
cy.get('.overflow-auto > .relative > .absolute').type('VALUE_A')
cy.get('.button.text-primary > .svg-inline--fa').click()
cy.get(':nth-child(6) > .button > .w-min').should('have.text', '2 Commits')
})
it('can`t create duplicate-name secrets', () => {
cy.get(':nth-child(2) > .flex > .button').click()
cy.contains('button', 'Add Secret').click()
cy.get('input[placeholder="Type your secret name"]').type('SECRET_A')
cy.intercept('POST', `/api/v3/secrets/SECRET_A`).as('createSecret');
cy.contains('button', 'Create Secret').click()
cy.get('@createSecret').should('have.property', 'response').and('have.property', 'statusCode', 400);
})
it('can add another secret', () => {
cy.get(':nth-child(2) > .flex > .button').click()
cy.contains('button', 'Add Secret').click()
cy.get('input[placeholder="Type your secret name"]').type('SECRET_B')
cy.contains('button', 'Create Secret').click()
cy.get(':nth-child(6) > .button > .w-min').should('have.text', '3 Commits')
})
it('can delete a secret', () => {
cy.get(':nth-child(2) > .flex > .button').click()
// cy.get(':nth-child(3) > .shadow-none').trigger('mouseover')
cy.get(':nth-child(3) > .shadow-none > .group > .h-10 > .border-red').click()
cy.contains('button', 'Delete Secret').should('have.attr', 'disabled')
cy.get('input[placeholder="Type to delete..."]').type('SECRET_B')
cy.intercept('DELETE', `/api/v3/secrets/SECRET_B`).as('deleteSecret');
cy.contains('button', 'Delete Secret').should('not.have.attr', 'disabled')
cy.contains('button', 'Delete Secret').click();
cy.get('@deleteSecret').should('have.property', 'response').and('have.property', 'statusCode', 200);
})
it('can add a comment', () => {
return;
cy.get(':nth-child(2) > .flex > .button').click()
// for some reason this hover does not want to work
cy.get('.overflow-auto').trigger('mouseover').then(() => {
cy.get('.shadow-none > .group > .pl-4 > .h-8 > button[aria-label="add-comment"]').should('be.visible').click()
});
})
})

View File

@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -1,19 +0,0 @@
Cypress.Commands.add('login', (username, password) => {
cy.visit('/login')
cy.get('input[placeholder="Enter your email..."]').type(username)
cy.get('input[placeholder="Enter your password..."]').type(password)
cy.contains('Continue with Email').click()
cy.url().should('include', '/overview')
})
// Cypress.Commands.add('login', (username, password) => {
// cy.session([username, password], () => {
// cy.visit('/login')
// cy.get('input[placeholder="Enter your email..."]').type(username)
// cy.get('input[placeholder="Enter your password..."]').type(password)
// cy.contains('Continue with Email').click()
// cy.url().should('include', '/overview')
// cy.wait(2000);
// })
// })

View File

@@ -1,20 +0,0 @@
// ***********************************************************
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

137
frontend/eslint.config.js Normal file
View File

@@ -0,0 +1,137 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import eslintPluginPrettier from "eslint-plugin-prettier/recommended";
import simpleImportSort from "eslint-plugin-simple-import-sort";
import tseslint from "typescript-eslint";
import { FlatCompat } from "@eslint/eslintrc";
import stylisticPlugin from "@stylistic/eslint-plugin";
import importPlugin from "eslint-plugin-import";
import pluginRouter from "@tanstack/eslint-plugin-router";
const compat = new FlatCompat({
baseDirectory: import.meta.dirname
});
export default tseslint.config(
{ ignores: ["dist"] },
{
extends: [
...pluginRouter.configs["flat/recommended"],
js.configs.recommended,
tseslint.configs.recommended,
...compat.extends("airbnb"),
...compat.extends("@kesills/airbnb-typescript"),
eslintPluginPrettier
],
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname
}
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
"simple-import-sort": simpleImportSort,
import: importPlugin
},
settings: {
"import/resolver": {
typescript: {
project: ["./tsconfig.json"]
}
}
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": "off",
"@typescript-eslint/only-throw-error": "off",
"@typescript-eslint/no-empty-function": "off",
quotes: ["error", "double", { avoidEscape: true }],
"comma-dangle": ["error", "only-multiline"],
"react/react-in-jsx-scope": "off",
"import/prefer-default-export": "off",
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/ban-ts-comment": "warn",
"react/jsx-props-no-spreading": "off", // switched off for component building
// TODO: This rule will be switched ON after complete revamp of frontend
"@typescript-eslint/no-explicit-any": "off",
"jsx-a11y/control-has-associated-label": "off",
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: true
}
],
"no-console": "off",
"arrow-body-style": "off",
"no-underscore-dangle": [
"error",
{
allow: ["_id"]
}
],
"jsx-a11y/anchor-is-valid": "off",
// all those <a> tags must be converted to label or a p component
//
"react/require-default-props": "off",
"react/jsx-filename-extension": [
1,
{
extensions: [".tsx", ".ts"]
}
],
// TODO: turn this rule ON after migration. everything should use arrow functions
"react/function-component-definition": [
0,
{
namedComponents: "arrow-function"
}
],
"react/no-unknown-property": [
"error",
{
ignore: ["jsx"]
}
],
"@typescript-eslint/no-non-null-assertion": "off",
"simple-import-sort/exports": "warn",
"simple-import-sort/imports": [
"warn",
{
groups: [
// Node.js builtins. You could also generate this regex if you use a `.js` config.
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
// Note that if you use the `node:` prefix for Node.js builtins,
// you can avoid this complexity: You can simply use "^node:".
[
"^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)"
],
// Packages `react` related packages
["^react", "^next", "^@?\\w"],
["^@app"],
// Internal packages.
["^~(/.*|$)"],
// Relative imports
["^\\.\\.(?!/?$)", "^\\.\\./?$", "^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
// Style imports.
["^.+\\.?(css|scss)$"]
]
}
],
"import/first": "error",
"import/newline-after-import": "error",
"import/no-duplicates": "error"
}
},
{
rules: Object.fromEntries(
Object.keys(stylisticPlugin.configs["all-flat"].rules ?? {}).map((key) => [key, "off"])
)
}
);

29
frontend/index.html Normal file
View File

@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/infisical.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
content="
default-src 'self';
connect-src 'self' https://*.posthog.com http://127.0.0.1:* https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@0.38.2/dist/dotlottie-player.wasm;
script-src 'self' https://*.posthog.com https://js.stripe.com https://api.stripe.com https://widget.intercom.io https://js.intercomcdn.com https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net/npm/@lottiefiles/dotlottie-web@0.38.2/dist/dotlottie-player.wasm;
style-src 'self' 'unsafe-inline' https://hcaptcha.com https://*.hcaptcha.com;
child-src https://api.stripe.com;
frame-src https://js.stripe.com/ https://api.stripe.com https://www.youtube.com/ https://hcaptcha.com https://*.hcaptcha.com;
connect-src 'self' wss://nexus-websocket-a.intercom.io https://api-iam.intercom.io https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com https://api.pwnedpasswords.com http://127.0.0.1:* https://hcaptcha.com https://*.hcaptcha.com;
img-src 'self' https://static.intercomassets.com https://js.intercomcdn.com https://downloads.intercomcdn.com https://*.stripe.com https://i.ytimg.com/ data:;
media-src https://js.intercomcdn.com;
font-src 'self' https://fonts.intercomcdn.com/ https://fonts.gstatic.com;
"
/>
<title>Infisical</title>
<script src="/runtime-ui-env.js"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,98 +0,0 @@
const path = require("path");
const ContentSecurityPolicy = `
default-src 'self';
connect-src 'self' https://*.posthog.com http://127.0.0.1:*;
script-src 'self' https://*.posthog.com https://js.stripe.com https://api.stripe.com https://widget.intercom.io https://js.intercomcdn.com https://hcaptcha.com https://*.hcaptcha.com 'unsafe-inline' 'unsafe-eval';
style-src 'self' https://rsms.me 'unsafe-inline' https://hcaptcha.com https://*.hcaptcha.com;
child-src https://api.stripe.com;
frame-src https://js.stripe.com/ https://api.stripe.com https://www.youtube.com/ https://hcaptcha.com https://*.hcaptcha.com;
connect-src 'self' wss://nexus-websocket-a.intercom.io https://api-iam.intercom.io https://api.heroku.com/ https://id.heroku.com/oauth/authorize https://id.heroku.com/oauth/token https://checkout.stripe.com https://app.posthog.com https://api.stripe.com https://api.pwnedpasswords.com http://127.0.0.1:* https://hcaptcha.com https://*.hcaptcha.com;
img-src 'self' https://static.intercomassets.com https://js.intercomcdn.com https://downloads.intercomcdn.com https://*.stripe.com https://i.ytimg.com/ data:;
media-src https://js.intercomcdn.com;
font-src 'self' https://fonts.intercomcdn.com/ https://maxcdn.bootstrapcdn.com https://rsms.me https://fonts.gstatic.com;
`;
// You can choose which headers to add to the list
// after learning more below.
const securityHeaders = [
{
key: "X-DNS-Prefetch-Control",
value: "on"
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload"
},
{
key: "X-XSS-Protection",
value: "1; mode=block"
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN"
},
{
key: "Permissions-Policy",
value: "camera=(), microphone=()"
},
{
key: "X-Content-Type-Options",
value: "nosniff"
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin"
},
{
key: "Content-Security-Policy",
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim()
}
];
/**
* @type {import('next').NextConfig}
**/
module.exports = {
output: "standalone",
i18n: {
locales: ["en", "ko", "fr", "pt-BR", "pt-PT", "es"],
defaultLocale: "en"
},
async headers() {
return [
{
// Apply these headers to all routes in your application.
source: "/:path*",
headers: securityHeaders
}
];
},
webpack: (config, { isServer, webpack }) => {
// config
config.module.rules.push({
test: /\.wasm$/,
loader: "base64-loader",
type: "javascript/auto"
});
config.module.noParse = /\.wasm$/;
config.module.rules.forEach((rule) => {
(rule.oneOf || []).forEach((oneOf) => {
if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) {
oneOf.exclude.push(/\.wasm$/);
}
});
});
if (!isServer) {
config.resolve.fallback.fs = false;
}
// Perform customizations to webpack config
config.plugins.push(new webpack.IgnorePlugin({ resourceRegExp: /\/__tests__\// }));
// Important: return the modified config
return config;
}
};

23584
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,165 +1,134 @@
{ {
"name": "frontend-v2",
"private": true, "private": true,
"version": "0.0.0",
"type": "module",
"scripts": { "scripts": {
"prepare": "cd .. && npm install", "dev": "vite",
"dev": "next dev", "build": "tsc -b && vite build",
"build": "next build", "preview": "vite preview",
"start": "next start", "lint": "eslint ./src",
"start:docker": "next build && next start", "lint:fix": "eslint --fix ./src",
"lint": "eslint --ext js,ts,tsx ./src", "type:check": "tsc --noEmit --project ./tsconfig.app.json"
"lint:fix": "eslint --fix --ext js,ts,tsx ./src",
"type:check": "tsc --project tsconfig.json --noEmit",
"storybook": "storybook dev -p 6006 -s ./public",
"build-storybook": "storybook build"
},
"overrides": {
"@storybook/nextjs": {
"sharp": "npm:dry-uninstall"
}
}, },
"dependencies": { "dependencies": {
"@casl/ability": "^6.5.0", "@casl/ability": "^6.7.2",
"@casl/react": "^3.1.0", "@casl/react": "^4.0.0",
"@dnd-kit/core": "^6.0.8", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^6.0.1", "@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^7.0.2", "@dnd-kit/sortable": "^10.0.0",
"@emotion/css": "^11.10.0", "@fontsource/inter": "^5.1.0",
"@emotion/server": "^11.10.0", "@fortawesome/fontawesome-svg-core": "^6.7.1",
"@fontsource/inter": "^4.5.15", "@fortawesome/free-brands-svg-icons": "^6.7.1",
"@fortawesome/fontawesome-svg-core": "^6.1.2", "@fortawesome/free-regular-svg-icons": "^6.7.1",
"@fortawesome/free-brands-svg-icons": "^6.1.2", "@fortawesome/free-solid-svg-icons": "^6.7.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/react-fontawesome": "^0.2.2",
"@fortawesome/free-solid-svg-icons": "^6.1.2", "@hcaptcha/react-hcaptcha": "^1.11.0",
"@fortawesome/react-fontawesome": "^0.2.0", "@headlessui/react": "^1.7.19",
"@hcaptcha/react-hcaptcha": "^1.10.1", "@hookform/resolvers": "^3.9.1",
"@headlessui/react": "^1.7.7", "@lottiefiles/dotlottie-react": "^0.12.0",
"@hookform/resolvers": "^2.9.10", "@octokit/rest": "^21.0.2",
"@octokit/rest": "^19.0.7", "@peculiar/x509": "^1.12.3",
"@peculiar/x509": "^1.11.0", "@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.1.3",
"@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.1.3",
"@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.1.3",
"@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-hover-card": "^1.1.3",
"@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.1.3",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popper": "^1.2.1",
"@radix-ui/react-popper": "^1.1.3", "@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-radio-group": "^1.2.2",
"@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-select": "^2.1.3",
"@radix-ui/react-select": "^2.0.0", "@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-toast": "^1.2.3",
"@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.1.5",
"@radix-ui/react-tooltip": "^1.0.7", "@sindresorhus/slugify": "^2.2.1",
"@reduxjs/toolkit": "^1.8.3", "@tanstack/react-query": "^5.62.7",
"@sindresorhus/slugify": "1.1.0", "@tanstack/react-router": "^1.95.1",
"@stripe/react-stripe-js": "^1.16.3", "@tanstack/virtual-file-routes": "^1.87.6",
"@stripe/stripe-js": "^1.46.0", "@tanstack/zod-adapter": "^1.91.0",
"@tanstack/react-query": "^4.23.0", "@types/nprogress": "^0.2.3",
"@types/argon2-browser": "^1.18.1",
"@ucast/mongo2js": "^1.3.4", "@ucast/mongo2js": "^1.3.4",
"add": "^2.0.6",
"argon2-browser": "^1.18.0", "argon2-browser": "^1.18.0",
"axios": "^0.28.0", "axios": "^1.7.9",
"axios-auth-refresh": "^3.3.6", "classnames": "^2.5.1",
"base64-loader": "^1.0.0", "cva": "npm:class-variance-authority@^0.7.1",
"classnames": "^2.3.1", "date-fns": "^4.1.0",
"cookies": "^0.9.1",
"cva": "npm:class-variance-authority@^0.4.0",
"date-fns": "^2.30.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"framer-motion": "^6.2.3", "framer-motion": "^11.14.1",
"fs": "^0.0.2", "i18next": "^24.1.0",
"gray-matter": "^4.0.3", "i18next-browser-languagedetector": "^8.0.2",
"http-proxy": "^1.18.1", "i18next-http-backend": "^3.0.1",
"i18next": "^22.4.15",
"i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^2.2.0",
"infisical-node": "^1.0.37",
"jspdf": "^2.5.2", "jspdf": "^2.5.2",
"jsrp": "^0.2.4", "jsrp": "^0.2.4",
"jwt-decode": "^3.1.2", "jwt-decode": "^4.0.0",
"lottie-react": "^2.4.0",
"markdown-it": "^13.0.1",
"ms": "^2.1.3", "ms": "^2.1.3",
"next": "^12.3.4",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"picomatch": "^2.3.1", "picomatch": "^4.0.2",
"posthog-js": "^1.105.6", "posthog-js": "^1.198.0",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"query-string": "^7.1.3", "react": "^18.3.1",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1",
"react-code-input": "^3.10.1", "react-code-input": "^3.10.1",
"react-day-picker": "^8.8.0", "react-day-picker": "^9.4.3",
"react-dom": "^17.0.2", "react-dom": "^18.3.1",
"react-grid-layout": "^1.3.4", "react-helmet": "^6.1.0",
"react-hook-form": "^7.43.0", "react-hook-form": "^7.54.0",
"react-i18next": "^12.2.2", "react-i18next": "^15.2.0",
"react-icons": "^5.3.0", "react-icons": "^5.4.0",
"react-mailchimp-subscribe": "^2.1.3", "react-select": "^5.9.0",
"react-markdown": "^8.0.3", "react-toastify": "^10.0.6",
"react-redux": "^8.0.2", "redaxios": "^0.5.1",
"react-select": "^5.8.1", "tailwind-merge": "^2.5.5",
"react-table": "^7.8.0",
"react-toastify": "^9.1.3",
"sanitize-html": "^2.12.1",
"set-cookie-parser": "^2.5.1",
"sharp": "^0.33.2",
"styled-components": "^5.3.7",
"tailwind-merge": "^1.8.1",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1", "tweetnacl-util": "^0.15.1",
"uuid": "^8.3.2", "yaml": "^2.6.1",
"uuidv4": "^6.2.13", "zod": "^3.24.1",
"yaml": "^2.2.2", "zustand": "^5.0.2"
"yup": "^0.32.11",
"zod": "^3.22.3",
"zustand": "^4.5.0"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-essentials": "^7.5.2", "@eslint/eslintrc": "^3.2.0",
"@storybook/addon-interactions": "^7.0.23", "@eslint/js": "^9.15.0",
"@storybook/addon-links": "^7.0.23", "@kesills/eslint-config-airbnb-typescript": "^20.0.0",
"@storybook/addon-styling": "^1.3.0", "@stylistic/eslint-plugin": "^2.12.1",
"@storybook/blocks": "^7.0.23", "@tailwindcss/typography": "^0.5.15",
"@storybook/client-api": "^7.2.1", "@tanstack/eslint-plugin-router": "^1.87.6",
"@storybook/nextjs": "^7.0.23", "@tanstack/router-devtools": "^1.87.9",
"@storybook/react": "^7.0.23", "@tanstack/router-plugin": "^1.95.1",
"@storybook/testing-library": "^0.2.0", "@types/argon2-browser": "^1.18.4",
"@tailwindcss/typography": "^0.5.4", "@types/file-saver": "^2.0.7",
"@types/file-saver": "^2.0.5", "@types/jsrp": "^0.2.6",
"@types/jsrp": "^0.2.4", "@types/ms": "^0.7.34",
"@types/node": "^18.11.9", "@types/picomatch": "^3.0.1",
"@types/picomatch": "^2.3.0",
"@types/qrcode": "^1.5.5", "@types/qrcode": "^1.5.5",
"@types/react": "^18.0.26", "@types/react": "^18.3.12",
"@types/sanitize-html": "^2.9.0", "@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^5.48.1", "@types/react-helmet": "^6.1.11",
"@typescript-eslint/parser": "^5.45.0", "@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.7", "autoprefixer": "^10.4.20",
"cypress": "^13.3.2", "eslint": "^8.57.1",
"eslint": "^8.32.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^9.1.0",
"eslint-config-next": "^13.0.5", "eslint-import-resolver-typescript": "^3.7.0",
"eslint-config-prettier": "^8.6.0", "eslint-plugin-import": "^2.31.0",
"eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-import": "^2.27.4", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react-refresh": "^0.4.14",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-react": "^7.32.0", "globals": "^15.12.0",
"eslint-plugin-react-hooks": "^4.6.0", "postcss": "^8.4.49",
"eslint-plugin-simple-import-sort": "^8.0.0", "prettier": "3.4.2",
"eslint-plugin-storybook": "^0.6.12", "prettier-plugin-tailwindcss": "^0.6.9",
"postcss": "^8.4.39", "tailwindcss": "^3.4.16",
"prettier": "^2.8.3", "typescript": "~5.6.2",
"prettier-plugin-tailwindcss": "^0.2.2", "typescript-eslint": "^8.15.0",
"storybook": "^7.6.20", "vite": "^5.4.11",
"storybook-dark-mode": "^3.0.0", "vite-plugin-node-polyfills": "^0.22.0",
"tailwindcss": "3.2", "vite-plugin-top-level-await": "^1.4.4",
"typescript": "^4.9.3" "vite-plugin-wasm": "^3.3.0",
"vite-tsconfig-paths": "^5.1.4"
} }
} }

View File

@@ -1,6 +1,6 @@
module.exports = { export default {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
}; }

View File

@@ -1,94 +0,0 @@
interface Mapping {
[key: string]: string;
}
const integrationSlugNameMapping: Mapping = {
"azure-key-vault": "Azure Key Vault",
"aws-parameter-store": "AWS Parameter Store",
"aws-secret-manager": "AWS Secrets Manager",
heroku: "Heroku",
vercel: "Vercel",
netlify: "Netlify",
github: "GitHub",
gitlab: "GitLab",
render: "Render",
"laravel-forge": "Laravel Forge",
railway: "Railway",
flyio: "Fly.io",
circleci: "CircleCI",
databricks: "Databricks",
travisci: "TravisCI",
supabase: "Supabase",
checkly: "Checkly",
qovery: "Qovery",
"terraform-cloud": "Terraform Cloud",
teamcity: "TeamCity",
"hashicorp-vault": "Vault",
"cloudflare-pages": "Cloudflare Pages",
"cloudflare-workers": "Cloudflare Workers",
codefresh: "Codefresh",
"digital-ocean-app-platform": "Digital Ocean App Platform",
bitbucket: "Bitbucket",
"cloud-66": "Cloud 66",
northflank: "Northflank",
windmill: "Windmill",
"gcp-secret-manager": "GCP Secret Manager",
"hasura-cloud": "Hasura Cloud",
rundeck: "Rundeck",
"azure-devops": "Azure DevOps",
"azure-app-configuration": "Azure App Configuration",
"octopus-deploy": "Octopus Deploy"
};
const envMapping: Mapping = {
Development: "dev",
Staging: "staging",
Production: "prod",
Testing: "test"
};
const reverseEnvMapping: Mapping = {
dev: "Development",
staging: "Staging",
prod: "Production",
test: "Testing"
};
const contextNetlifyMapping: Mapping = {
dev: "Local development",
"branch-deploy": "Branch deploys",
"deploy-preview": "Deploy Previews",
production: "Production"
};
const reverseContextNetlifyMapping: Mapping = {
"Local development": "dev",
"Branch deploys": "branch-deploy",
"Deploy Previews": "deploy-preview",
Production: "production"
};
const plansDev: Mapping = {
starter: "prod_Mb4ATFT5QAHoPM",
team: "prod_NEpD2WMXUS2eDn",
professional: "prod_Mb4CetZ2jE7jdl",
enterprise: "licence_key_required"
};
const plansProd: Mapping = {
starter: "prod_Mb8oR5XNwyFTul",
team: "prod_NEp7fAB3UJWK6A",
professional: "prod_Mb8pUIpA0OUi5N",
enterprise: "licence_key_required"
};
const plans = plansProd || plansDev;
export {
contextNetlifyMapping,
envMapping,
integrationSlugNameMapping,
plans,
reverseContextNetlifyMapping,
reverseEnvMapping
};

View File

@@ -1,19 +0,0 @@
export interface Tag {
id: string;
name: string;
slug: string;
user: string;
workspace: string;
createdAt: string;
}
export interface SecretDataProps {
pos: number;
key: string;
value: string | undefined;
valueOverride: string | undefined;
id: string;
idOverride?: string;
comment: string;
tags: Tag[];
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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