Merge branch 'Infisical:main' into feat/create-multiple-orgs-under-same-account

This commit is contained in:
Ronit Panda
2023-10-24 05:42:31 +05:30
committed by GitHub
126 changed files with 2887 additions and 1714 deletions
+8
View File
@@ -1,2 +1,10 @@
backend/node_modules backend/node_modules
frontend/node_modules frontend/node_modules
backend/frontend-build
**/node_modules
**/.next
.dockerignore
.git
README.md
.dockerignore
**/Dockerfile
+12 -17
View File
@@ -1,24 +1,12 @@
# Keys # Keys
# Required key for platform encryption/decryption ops # Required key for platform encryption/decryption ops
# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NOT BE USED FOR PRODUCTION # THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
# JWT # JWT
# Required secrets to sign JWT tokens # Required secrets to sign JWT tokens
JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a # THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION
JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE=
JWT_AUTH_SECRET=4be6ba5602e0fa0ac6ac05c3cd4d247f
JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
JWT_SERVICE_TOKEN_SECRET=f32f716d70a42c5703f4656015e76200
JWT_PROVIDER_AUTH_SECRET=f32f716d70a42c5703f4656015e76201
# JWT lifetime
# Optional lifetimes for JWT tokens expressed in seconds or a string
# describing a time span (e.g. 60, "2 days", "10h", "7d")
JWT_AUTH_LIFETIME=
JWT_REFRESH_LIFETIME=
JWT_SIGNUP_LIFETIME=
JWT_PROVIDER_AUTH_LIFETIME=
# MongoDB # MongoDB
# Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref # Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref
@@ -68,5 +56,12 @@ SENTRY_DSN=
POSTHOG_HOST= POSTHOG_HOST=
POSTHOG_PROJECT_API_KEY= POSTHOG_PROJECT_API_KEY=
CLIENT_ID_GOOGLE= # SSO-specific variables
CLIENT_SECRET_GOOGLE= CLIENT_ID_GOOGLE_LOGIN=
CLIENT_SECRET_GOOGLE_LOGIN=
CLIENT_ID_GITHUB_LOGIN=
CLIENT_SECRET_GITHUB_LOGIN=
CLIENT_ID_GITLAB_LOGIN=
CLIENT_SECRET_GITLAB_LOGIN=
+1 -1
View File
@@ -6,7 +6,7 @@ services:
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
- mongo - mongo
image: infisical/backend:test image: infisical/infisical:test
command: npm run start command: npm run start
environment: environment:
- NODE_ENV=production - NODE_ENV=production
+1 -33
View File
@@ -1,29 +1,3 @@
# secretScanningGitApp:
# enabled: false
# deploymentAnnotations:
# secrets.infisical.com/auto-reload: "true"
# image:
# repository: infisical/staging_deployment_secret-scanning-git-app
frontend:
enabled: true
name: frontend
podAnnotations: {}
deploymentAnnotations:
secrets.infisical.com/auto-reload: "true"
replicaCount: 2
image:
repository: infisical/staging_deployment_frontend
tag: "latest"
pullPolicy: Always
kubeSecretRef: managed-secret-frontend
service:
annotations: {}
type: ClusterIP
nodePort: ""
frontendEnvironmentVariables: null
backend: backend:
enabled: true enabled: true
name: backend name: backend
@@ -32,7 +6,7 @@ backend:
secrets.infisical.com/auto-reload: "true" secrets.infisical.com/auto-reload: "true"
replicaCount: 2 replicaCount: 2
image: image:
repository: infisical/staging_deployment_backend repository: infisical/staging_infisical
tag: "latest" tag: "latest"
pullPolicy: Always pullPolicy: Always
kubeSecretRef: managed-backend-secret kubeSecretRef: managed-backend-secret
@@ -65,12 +39,6 @@ ingress:
# kubernetes.io/ingress.class: "nginx" # kubernetes.io/ingress.class: "nginx"
# cert-manager.io/issuer: letsencrypt-nginx # cert-manager.io/issuer: letsencrypt-nginx
hostName: gamma.infisical.com ## <- Replace with your own domain hostName: gamma.infisical.com ## <- Replace with your own domain
frontend:
path: /
pathType: Prefix
backend:
path: /api
pathType: Prefix
tls: tls:
[] []
# - secretName: letsencrypt-nginx # - secretName: letsencrypt-nginx
@@ -39,7 +39,7 @@ jobs:
token: ${{ secrets.DEPOT_PROJECT_TOKEN }} token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
load: true load: true
context: backend context: backend
tags: infisical/backend:test tags: infisical/infisical:test
- name: ⏻ Spawn backend container and dependencies - name: ⏻ Spawn backend container and dependencies
run: | run: |
docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
+12 -59
View File
@@ -2,7 +2,7 @@ name: Build, Publish and Deploy to Gamma
on: [workflow_dispatch] on: [workflow_dispatch]
jobs: jobs:
backend-image: infisical-image:
name: Build backend image name: Build backend image
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -32,8 +32,9 @@ jobs:
project: 64mmf0n610 project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }} token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
load: true load: true
context: backend context: .
tags: infisical/backend:test file: Dockerfile.standalone-infisical
tags: infisical/infisical:test
- name: ⏻ Spawn backend container and dependencies - name: ⏻ Spawn backend container and dependencies
run: | run: |
docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
@@ -49,68 +50,20 @@ jobs:
project: 64mmf0n610 project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }} token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true push: true
context: backend context: .
file: Dockerfile.standalone-infisical
tags: | tags: |
infisical/staging_deployment_backend:${{ steps.commit.outputs.short }} infisical/staging_infisical:${{ steps.commit.outputs.short }}
infisical/staging_deployment_backend:latest infisical/staging_infisical:latest
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
frontend-image:
name: Build frontend image
runs-on: ubuntu-latest
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Depot CLI
uses: depot/setup-action@v1
- name: 📦 Build frontend and export to Docker
uses: depot/build-push-action@v1
with:
load: true
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
project: 64mmf0n610
context: frontend
tags: infisical/staging_deployment_frontend:test
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
- name: ⏻ Spawn frontend container
run: |
docker run -d --rm --name infisical-frontend-test infisical/staging_deployment_frontend:test
- name: 🧪 Test frontend image
run: |
./.github/resources/healthcheck.sh infisical-frontend-test
- name: ⏻ Shut down frontend container
run: |
docker stop infisical-frontend-test
- name: 🏗️ Build frontend and push
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
push: true
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
context: frontend
tags: |
infisical/staging_deployment_frontend:${{ steps.commit.outputs.short }}
infisical/staging_deployment_frontend:latest
platforms: linux/amd64,linux/arm64
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
gamma-deployment: gamma-deployment:
name: Deploy to gamma name: Deploy to gamma
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [frontend-image, backend-image] needs: [infisical-image]
steps: steps:
- name: ☁️ Checkout source - name: ☁️ Checkout source
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -73,3 +73,6 @@ jobs:
infisical/infisical:${{ steps.extract_version.outputs.version }} infisical/infisical:${{ steps.extract_version.outputs.version }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
file: Dockerfile.standalone-infisical file: Dockerfile.standalone-infisical
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
+3 -1
View File
@@ -33,7 +33,7 @@ reports
junit.xml junit.xml
# next.js # next.js
/.next/ .next/
/out/ /out/
# production # production
@@ -60,3 +60,5 @@ yarn-error.log*
# Editor specific # Editor specific
.vscode/* .vscode/*
frontend-build
+53 -32
View File
@@ -1,7 +1,13 @@
ARG POSTHOG_HOST=https://app.posthog.com ARG POSTHOG_HOST=https://app.posthog.com
ARG POSTHOG_API_KEY=posthog-api-key ARG POSTHOG_API_KEY=posthog-api-key
ARG INTERCOM_ID=intercom-id
FROM node:16-alpine AS frontend-dependencies FROM node:16-alpine AS base
FROM base AS frontend-dependencies
# 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 WORKDIR /app
@@ -11,7 +17,7 @@ COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./
RUN npm ci --only-production --ignore-scripts RUN npm ci --only-production --ignore-scripts
# Rebuild the source code only when needed # Rebuild the source code only when needed
FROM node:16-alpine AS frontend-builder FROM base AS frontend-builder
WORKDIR /app WORKDIR /app
# Copy dependencies # Copy dependencies
@@ -27,41 +33,38 @@ ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
ARG INTERCOM_ID ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
ARG INFISICAL_PLATFORM_VERSION
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
# Build # Build
RUN npm run build RUN npm run build
# Production image # Production image
FROM node:16-alpine AS frontend-runner FROM base AS frontend-runner
WORKDIR /app WORKDIR /app
RUN addgroup --system --gid 1001 nodejs RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs RUN adduser --system --uid 1001 non-root-user
RUN mkdir -p /app/.next/cache/images && chown nextjs:nodejs /app/.next/cache/images RUN mkdir -p /app/.next/cache/images && chown non-root-user:nodejs /app/.next/cache/images
VOLUME /app/.next/cache/images VOLUME /app/.next/cache/images
ARG POSTHOG_API_KEY COPY --chown=non-root-user:nodejs --chmod=555 frontend/scripts ./scripts
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
COPY --chown=nextjs:nodejs --chmod=555 frontend/scripts ./scripts
COPY --from=frontend-builder /app/public ./public COPY --from=frontend-builder /app/public ./public
RUN chown nextjs:nodejs ./public/data RUN chown non-root-user:nodejs ./public/data
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/standalone ./
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/static ./.next/static
USER nextjs USER non-root-user
ENV NEXT_TELEMETRY_DISABLED 1 ENV NEXT_TELEMETRY_DISABLED 1
## ##
## BACKEND ## BACKEND
## ##
FROM node:16-alpine AS backend-build FROM base AS backend-build
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 non-root-user
WORKDIR /app WORKDIR /app
@@ -69,10 +72,11 @@ COPY backend/package*.json ./
RUN npm ci --only-production RUN npm ci --only-production
COPY /backend . COPY /backend .
COPY --chown=non-root-user:nodejs standalone-entrypoint.sh standalone-entrypoint.sh
RUN npm run build RUN npm run build
# Production stage # Production stage
FROM node:16-alpine AS backend-runner FROM base AS backend-runner
WORKDIR /app WORKDIR /app
@@ -81,27 +85,44 @@ RUN npm ci --only-production
COPY --from=backend-build /app . COPY --from=backend-build /app .
RUN mkdir frontend-build
# Production stage # Production stage
FROM node:16-alpine AS production FROM base AS production
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 non-root-user
## set pre baked keys
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=intercom-id
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
WORKDIR / WORKDIR /
# Install PM2
RUN npm install -g pm2
# Copy ecosystem.config.js
COPY ecosystem.config.js .
RUN apk add --no-cache nginx
COPY nginx/default-stand-alone-docker.conf /etc/nginx/nginx.conf
COPY --from=backend-runner /app /backend COPY --from=backend-runner /app /backend
COPY --from=frontend-runner /app/ /app/ COPY --from=frontend-runner /app ./backend/frontend-build
EXPOSE 80 ENV PORT 8080
ENV HTTPS_ENABLED false ENV HTTPS_ENABLED false
ENV NODE_ENV production
ENV STANDALONE_BUILD true
WORKDIR /backend
ENV TELEMETRY_ENABLED true
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node healthcheck.js
EXPOSE 8080
USER non-root-user
CMD ["./standalone-entrypoint.sh"]
CMD ["pm2-runtime", "start", "ecosystem.config.js"]
+2 -6
View File
@@ -17,17 +17,13 @@ export const getRootEncryptionKey = async () => {
} }
export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true" export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true"
export const getSaltRounds = async () => parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10; export const getSaltRounds = async () => parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10;
export const getAuthSecret = async () => (await client.getSecret("JWT_AUTH_SECRET")).secretValue ?? (await client.getSecret("AUTH_SECRET")).secretValue;
export const getJwtAuthLifetime = async () => (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d"; export const getJwtAuthLifetime = async () => (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d";
export const getJwtAuthSecret = async () => (await client.getSecret("JWT_AUTH_SECRET")).secretValue;
export const getJwtMfaLifetime = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m"; export const getJwtMfaLifetime = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m";
export const getJwtMfaSecret = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m";
export const getJwtRefreshLifetime = async () => (await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d"; export const getJwtRefreshLifetime = async () => (await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d";
export const getJwtRefreshSecret = async () => (await client.getSecret("JWT_REFRESH_SECRET")).secretValue; export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1)
export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue;
export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m"; export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m";
export const getJwtProviderAuthSecret = async () => (await client.getSecret("JWT_PROVIDER_AUTH_SECRET")).secretValue;
export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m"; export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m";
export const getJwtSignupSecret = async () => (await client.getSecret("JWT_SIGNUP_SECRET")).secretValue;
export const getJwtServiceTokenSecret = async () => (await client.getSecret("JWT_SERVICE_TOKEN_SECRET")).secretValue; export const getJwtServiceTokenSecret = async () => (await client.getSecret("JWT_SERVICE_TOKEN_SECRET")).secretValue;
export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue; export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue;
export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production"; export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production";
+13 -6
View File
@@ -6,15 +6,18 @@ const jsrp = require("jsrp");
import { LoginSRPDetail, TokenVersion, User } from "../../models"; import { LoginSRPDetail, TokenVersion, User } from "../../models";
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth"; import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user"; import { checkUserDevice } from "../../helpers/user";
import { ACTION_LOGIN, ACTION_LOGOUT } from "../../variables"; import {
ACTION_LOGIN,
ACTION_LOGOUT,
AuthTokenType
} from "../../variables";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { EELogService } from "../../ee/services"; import { EELogService } from "../../ee/services";
import { getUserAgentType } from "../../utils/posthog"; import { getUserAgentType } from "../../utils/posthog";
import { import {
getAuthSecret,
getHttpsEnabled, getHttpsEnabled,
getJwtAuthLifetime, getJwtAuthLifetime
getJwtAuthSecret,
getJwtRefreshSecret
} from "../../config"; } from "../../config";
import { ActorType } from "../../ee/models"; import { ActorType } from "../../ee/models";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
@@ -238,6 +241,7 @@ export const checkAuth = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const getNewToken = async (req: Request, res: Response) => { export const getNewToken = async (req: Request, res: Response) => {
const refreshToken = req.cookies.jid; const refreshToken = req.cookies.jid;
if (!refreshToken) if (!refreshToken)
@@ -245,7 +249,9 @@ export const getNewToken = async (req: Request, res: Response) => {
message: "Failed to find refresh token in request cookies" message: "Failed to find refresh token in request cookies"
}); });
const decodedToken = <jwt.UserIDJwtPayload>jwt.verify(refreshToken, await getJwtRefreshSecret()); const decodedToken = <jwt.UserIDJwtPayload>jwt.verify(refreshToken, await getAuthSecret());
if (decodedToken.authTokenType !== AuthTokenType.REFRESH_TOKEN) throw UnauthorizedRequestError();
const user = await User.findOne({ const user = await User.findOne({
_id: decodedToken.userId _id: decodedToken.userId
@@ -268,12 +274,13 @@ export const getNewToken = async (req: Request, res: Response) => {
const token = createToken({ const token = createToken({
payload: { payload: {
authTokenType: AuthTokenType.ACCESS_TOKEN,
userId: decodedToken.userId, userId: decodedToken.userId,
tokenVersionId: tokenVersion._id.toString(), tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.refreshVersion accessVersion: tokenVersion.refreshVersion
}, },
expiresIn: await getJwtAuthLifetime(), expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret() secret: await getAuthSecret()
}); });
return res.status(200).send({ return res.status(200).send({
@@ -8,11 +8,11 @@ import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
import { sendMail } from "../../helpers/nodemailer"; import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services"; import { TokenService } from "../../services";
import { EELicenseService } from "../../ee/services"; import { EELicenseService } from "../../ee/services";
import { ACCEPTED, INVITED, MEMBER, TOKEN_EMAIL_ORG_INVITATION } from "../../variables"; import { ACCEPTED, AuthTokenType, INVITED, MEMBER, TOKEN_EMAIL_ORG_INVITATION } from "../../variables";
import * as reqValidator from "../../validation/membershipOrg"; import * as reqValidator from "../../validation/membershipOrg";
import { import {
getAuthSecret,
getJwtSignupLifetime, getJwtSignupLifetime,
getJwtSignupSecret,
getSiteURL, getSiteURL,
getSmtpConfigured getSmtpConfigured
} from "../../config"; } from "../../config";
@@ -272,10 +272,11 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
// generate temporary signup token // generate temporary signup token
const token = createToken({ const token = createToken({
payload: { payload: {
authTokenType: AuthTokenType.SIGNUP_TOKEN,
userId: user._id.toString() userId: user._id.toString()
}, },
expiresIn: await getJwtSignupLifetime(), expiresIn: await getJwtSignupLifetime(),
secret: await getJwtSignupSecret() secret: await getAuthSecret()
}); });
return res.status(200).send({ return res.status(200).send({
@@ -5,12 +5,12 @@ import * as bigintConversion from "bigint-conversion";
import { BackupPrivateKey, LoginSRPDetail, User } from "../../models"; import { BackupPrivateKey, LoginSRPDetail, User } from "../../models";
import { clearTokens, createToken, sendMail } from "../../helpers"; import { clearTokens, createToken, sendMail } from "../../helpers";
import { TokenService } from "../../services"; import { TokenService } from "../../services";
import { TOKEN_EMAIL_PASSWORD_RESET } from "../../variables"; import { AuthTokenType, TOKEN_EMAIL_PASSWORD_RESET } from "../../variables";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { import {
getAuthSecret,
getHttpsEnabled, getHttpsEnabled,
getJwtSignupLifetime, getJwtSignupLifetime,
getJwtSignupSecret,
getSiteURL getSiteURL
} from "../../config"; } from "../../config";
import { ActorType } from "../../ee/models"; import { ActorType } from "../../ee/models";
@@ -88,10 +88,11 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
// generate temporary password-reset token // generate temporary password-reset token
const token = createToken({ const token = createToken({
payload: { payload: {
authTokenType: AuthTokenType.SIGNUP_TOKEN,
userId: user._id.toString() userId: user._id.toString()
}, },
expiresIn: await getJwtSignupLifetime(), expiresIn: await getJwtSignupLifetime(),
secret: await getJwtSignupSecret() secret: await getAuthSecret()
}); });
return res.status(200).send({ return res.status(200).send({
@@ -4,14 +4,15 @@ import { checkEmailVerification, sendEmailVerification } from "../../helpers/sig
import { createToken } from "../../helpers/auth"; import { createToken } from "../../helpers/auth";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
import { import {
getAuthSecret,
getInviteOnlySignup, getInviteOnlySignup,
getJwtSignupLifetime, getJwtSignupLifetime,
getJwtSignupSecret,
getSmtpConfigured getSmtpConfigured
} from "../../config"; } from "../../config";
import { validateUserEmail } from "../../validation"; import { validateUserEmail } from "../../validation";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth"; import * as reqValidator from "../../validation/auth";
import { AuthTokenType } from "../../variables";
/** /**
* Signup step 1: Initialize account for user under email [email] and send a verification code * Signup step 1: Initialize account for user under email [email] and send a verification code
@@ -95,10 +96,11 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
// generate temporary signup token // generate temporary signup token
const token = createToken({ const token = createToken({
payload: { payload: {
authTokenType: AuthTokenType.SIGNUP_TOKEN,
userId: user._id.toString() userId: user._id.toString()
}, },
expiresIn: await getJwtSignupLifetime(), expiresIn: await getJwtSignupLifetime(),
secret: await getJwtSignupSecret() secret: await getAuthSecret()
}); });
return res.status(200).send({ return res.status(200).send({
+4 -3
View File
@@ -10,9 +10,9 @@ import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services"; import { TokenService } from "../../services";
import { EELogService } from "../../ee/services"; import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors"; import { BadRequestError, InternalServerError } from "../../utils/errors";
import { ACTION_LOGIN, TOKEN_EMAIL_MFA } from "../../variables"; import { ACTION_LOGIN, AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
import { getHttpsEnabled, getJwtMfaLifetime, getJwtMfaSecret } from "../../config"; import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth"; import * as reqValidator from "../../validation/auth";
@@ -109,10 +109,11 @@ export const login2 = async (req: Request, res: Response) => {
// generate temporary MFA token // generate temporary MFA token
const token = createToken({ const token = createToken({
payload: { payload: {
authTokenType: AuthTokenType.MFA_TOKEN,
userId: user._id.toString() userId: user._id.toString()
}, },
expiresIn: await getJwtMfaLifetime(), expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret() secret: await getAuthSecret()
}); });
const code = await TokenService.createToken({ const code = await TokenService.createToken({
+4 -3
View File
@@ -10,9 +10,9 @@ import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services"; import { TokenService } from "../../services";
import { EELogService } from "../../ee/services"; import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors"; import { BadRequestError, InternalServerError } from "../../utils/errors";
import { ACTION_LOGIN, TOKEN_EMAIL_MFA } from "../../variables"; import { ACTION_LOGIN, AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
import { getHttpsEnabled, getJwtMfaLifetime, getJwtMfaSecret } from "../../config"; import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
import { AuthMethod } from "../../models/user"; import { AuthMethod } from "../../models/user";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth"; import * as reqValidator from "../../validation/auth";
@@ -134,10 +134,11 @@ export const login2 = async (req: Request, res: Response) => {
// generate temporary MFA token // generate temporary MFA token
const token = createToken({ const token = createToken({
payload: { payload: {
authTokenType: AuthTokenType.MFA_TOKEN,
userId: user._id.toString() userId: user._id.toString()
}, },
expiresIn: await getJwtMfaLifetime(), expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret() secret: await getAuthSecret()
}); });
const code = await TokenService.createToken({ const code = await TokenService.createToken({
+2
View File
@@ -1,9 +1,11 @@
import * as usersController from "./usersController";
import * as secretsController from "./secretsController"; import * as secretsController from "./secretsController";
import * as workspacesController from "./workspacesController"; import * as workspacesController from "./workspacesController";
import * as authController from "./authController"; import * as authController from "./authController";
import * as signupController from "./signupController"; import * as signupController from "./signupController";
export { export {
usersController,
authController, authController,
secretsController, secretsController,
signupController, signupController,
@@ -476,7 +476,7 @@ export const getSecrets = async (req: Request, res: Response) => {
if (folderId && folderId !== "root") { if (folderId && folderId !== "root") {
const folder = await Folder.findOne({ workspace: workspaceId, environment }); const folder = await Folder.findOne({ workspace: workspaceId, environment });
if (!folder) throw BadRequestError({ message: "Folder not found" }); if (!folder) return res.send({ secrets: [] });
secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath; secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath;
} }
@@ -673,6 +673,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
secretValueCiphertext, secretValueCiphertext,
secretValueTag, secretValueTag,
secretValueIV, secretValueIV,
secretId,
type, type,
environment, environment,
secretPath, secretPath,
@@ -741,6 +742,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
type, type,
secretId,
authData: req.authData, authData: req.authData,
newSecretName, newSecretName,
secretValueCiphertext, secretValueCiphertext,
@@ -961,6 +963,14 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
authData: req.authData authData: req.authData
}); });
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath
})
});
return res.status(200).send({ return res.status(200).send({
secrets: deletedSecrets secrets: deletedSecrets
}); });
@@ -5,10 +5,10 @@ import { MembershipOrg, User } from "../../models";
import { completeAccount } from "../../helpers/user"; import { completeAccount } from "../../helpers/user";
import { initializeDefaultOrg } from "../../helpers/signup"; import { initializeDefaultOrg } from "../../helpers/signup";
import { issueAuthTokens, validateProviderAuthToken } from "../../helpers/auth"; import { issueAuthTokens, validateProviderAuthToken } from "../../helpers/auth";
import { ACCEPTED, INVITED } from "../../variables"; import { ACCEPTED, AuthTokenType, INVITED } from "../../variables";
import { standardRequest } from "../../config/request"; import { standardRequest } from "../../config/request";
import { getHttpsEnabled, getJwtSignupSecret, getLoopsApiKey } from "../../config"; import { getAuthSecret, getHttpsEnabled, getLoopsApiKey } from "../../config";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { TelemetryService } from "../../services"; import { TelemetryService } from "../../services";
import { AuthMethod } from "../../models"; import { AuthMethod } from "../../models";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
@@ -78,12 +78,11 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
} }
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, await getJwtSignupSecret()) jwt.verify(AUTH_TOKEN_VALUE, await getAuthSecret())
); );
if (decodedToken.userId !== user.id) { if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) throw UnauthorizedRequestError();
throw BadRequestError(); if (decodedToken.userId !== user.id) throw UnauthorizedRequestError();
}
} }
// complete setting up user's account // complete setting up user's account
@@ -0,0 +1,18 @@
import { Request, Response } from "express";
import { APIKeyDataV2 } from "../../models";
/**
* Return API keys belonging to current user.
* @param req
* @param res
* @returns
*/
export const getMyAPIKeys = async (req: Request, res: Response) => {
const apiKeyData = await APIKeyDataV2.find({
user: req.user._id
});
return res.status(200).send({
apiKeyData
});
}
@@ -0,0 +1,101 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { APIKeyDataV2 } from "../../../models/apiKeyDataV2";
import { validateRequest } from "../../../helpers/validation";
import { BadRequestError } from "../../../utils/errors";
import * as reqValidator from "../../../validation";
import { createToken } from "../../../helpers";
import { AuthTokenType } from "../../../variables";
import { getAuthSecret } from "../../../config";
/**
* Create API key data v2
* @param req
* @param res
*/
export const createAPIKeyData = async (req: Request, res: Response) => {
const {
body: {
name
}
} = await validateRequest(reqValidator.CreateAPIKeyV3, req);
const apiKeyData = await new APIKeyDataV2({
name,
user: req.user._id,
usageCount: 0,
}).save();
const apiKey = createToken({
payload: {
authTokenType: AuthTokenType.API_KEY,
apiKeyDataId: apiKeyData._id.toString(),
userId: req.user._id.toString()
},
secret: await getAuthSecret()
});
return res.status(200).send({
apiKeyData,
apiKey
});
}
/**
* Update API key data v2 with id [apiKeyDataId]
* @param req
* @param res
*/
export const updateAPIKeyData = async (req: Request, res: Response) => {
const {
params: { apiKeyDataId },
body: {
name,
}
} = await validateRequest(reqValidator.UpdateAPIKeyV3, req);
const apiKeyData = await APIKeyDataV2.findOneAndUpdate(
{
_id: new Types.ObjectId(apiKeyDataId),
user: req.user._id
},
{
name
},
{
new: true
}
);
if (!apiKeyData) throw BadRequestError({
message: "Failed to update API key"
});
return res.status(200).send({
apiKeyData
});
}
/**
* Delete API key data v2 with id [apiKeyDataId]
* @param req
* @param res
*/
export const deleteAPIKeyData = async (req: Request, res: Response) => {
const {
params: { apiKeyDataId }
} = await validateRequest(reqValidator.DeleteAPIKeyV3, req);
const apiKeyData = await APIKeyDataV2.findOneAndDelete({
_id: new Types.ObjectId(apiKeyDataId),
user: req.user._id
});
if (!apiKeyData) throw BadRequestError({
message: "Failed to delete API key"
});
return res.status(200).send({
apiKeyData
});
}
+3 -1
View File
@@ -1,5 +1,7 @@
import * as serviceTokenDataController from "./serviceTokenDataController"; import * as serviceTokenDataController from "./serviceTokenDataController";
import * as apiKeyDataController from "./apiKeyDataController";
export { export {
serviceTokenDataController serviceTokenDataController,
apiKeyDataController
} }
@@ -30,7 +30,7 @@ import { EEAuditLogService, EELicenseService } from "../../services";
import { getJwtServiceTokenSecret } from "../../../config"; import { getJwtServiceTokenSecret } from "../../../config";
/** /**
* Return project key for service token * Return project key for service token V3
* @param req * @param req
* @param res * @param res
*/ */
@@ -57,7 +57,7 @@ export const getServiceTokenDataKey = async (req: Request, res: Response) => {
} }
/** /**
* Create service token data * Create service token data V3
* @param req * @param req
* @param res * @param res
* @returns * @returns
@@ -165,7 +165,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
} }
/** /**
* Update service token data with id [serviceTokenDataId] * Update service token V3 data with id [serviceTokenDataId]
* @param req * @param req
* @param res * @param res
* @returns * @returns
+4 -1
View File
@@ -13,7 +13,10 @@ router.get(
const options = { const options = {
failureRedirect: "/", failureRedirect: "/",
additionalParams: { additionalParams: {
RelayState: req.query.callback_port ?? "" RelayState: JSON.stringify({
spInitiated: true,
callbackPort: req.query.callback_port ?? ""
})
}, },
}; };
passport.authenticate("saml", options)(req, res, next); passport.authenticate("saml", options)(req, res, next);
+31
View File
@@ -0,0 +1,31 @@
import express from "express";
const router = express.Router();
import { requireAuth } from "../../../middleware";
import { AuthMode } from "../../../variables";
import { apiKeyDataController } from "../../controllers/v3";
router.post(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
apiKeyDataController.createAPIKeyData
);
router.patch(
"/:apiKeyDataId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
apiKeyDataController.updateAPIKeyData
);
router.delete(
"/:apiKeyDataId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
apiKeyDataController.deleteAPIKeyData
);
export default router;
+3 -1
View File
@@ -1,5 +1,7 @@
import serviceTokenData from "./serviceTokenData"; import serviceTokenData from "./serviceTokenData";
import apiKeyData from "./apiKeyData";
export { export {
serviceTokenData serviceTokenData,
apiKeyData
} }
+51 -31
View File
@@ -4,6 +4,7 @@ import jwt from "jsonwebtoken";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { import {
APIKeyData, APIKeyData,
APIKeyDataV2,
ITokenVersion, ITokenVersion,
IUser, IUser,
ServiceTokenData, ServiceTokenData,
@@ -19,15 +20,14 @@ import {
UnauthorizedRequestError, UnauthorizedRequestError,
} from "../utils/errors"; } from "../utils/errors";
import { import {
getAuthSecret,
getJwtAuthLifetime, getJwtAuthLifetime,
getJwtAuthSecret,
getJwtProviderAuthSecret,
getJwtRefreshLifetime, getJwtRefreshLifetime,
getJwtRefreshSecret,
getJwtServiceTokenSecret getJwtServiceTokenSecret
} from "../config"; } from "../config";
import { import {
AuthMode AuthMode,
AuthTokenType
} from "../variables"; } from "../variables";
import { import {
ServiceTokenAuthData, ServiceTokenAuthData,
@@ -51,8 +51,6 @@ export const validateAuthMode = ({
acceptedAuthModes: AuthMode[] acceptedAuthModes: AuthMode[]
}) => { }) => {
// TODO: update this to accept service token v3
const apiKey = headers["x-api-key"]; const apiKey = headers["x-api-key"];
const authHeader = headers["authorization"]; const authHeader = headers["authorization"];
@@ -108,6 +106,7 @@ export const validateAuthMode = ({
/** /**
* Return user payload corresponding to JWT token [authTokenValue] * Return user payload corresponding to JWT token [authTokenValue]
* that is either for browser / CLI or API Key
* @param {Object} obj * @param {Object} obj
* @param {String} obj.authTokenValue - JWT token value * @param {String} obj.authTokenValue - JWT token value
* @returns {User} user - user corresponding to JWT token * @returns {User} user - user corresponding to JWT token
@@ -120,9 +119,45 @@ export const getAuthUserPayload = async ({
authTokenValue: string; authTokenValue: string;
}): Promise<UserAuthData> => { }): Promise<UserAuthData> => {
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(authTokenValue, await getJwtAuthSecret()) jwt.verify(authTokenValue, await getAuthSecret())
); );
if (
decodedToken.authTokenType !== AuthTokenType.ACCESS_TOKEN &&
decodedToken.authTokenType !== AuthTokenType.API_KEY
) {
throw UnauthorizedRequestError();
}
if (decodedToken.authTokenType === AuthTokenType.ACCESS_TOKEN) {
const tokenVersion = await TokenVersion.findOneAndUpdate({
_id: new Types.ObjectId(decodedToken.tokenVersionId),
user: decodedToken.userId
}, {
lastUsed: new Date(),
});
if (!tokenVersion) throw UnauthorizedRequestError();
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError();
} else if (decodedToken.authTokenType === AuthTokenType.API_KEY) {
const apiKeyData = await APIKeyDataV2.findOneAndUpdate(
{
_id: new Types.ObjectId(decodedToken.apiKeyDataId),
user: new Types.ObjectId(decodedToken.userId)
},
{
lastUsed: new Date(),
$inc: { usageCount: 1 }
},
{
new: true
}
);
if (!apiKeyData) throw UnauthorizedRequestError();
}
const user = await User.findOne({ const user = await User.findOne({
_id: new Types.ObjectId(decodedToken.userId), _id: new Types.ObjectId(decodedToken.userId),
}).select("+publicKey +accessVersion"); }).select("+publicKey +accessVersion");
@@ -131,21 +166,6 @@ export const getAuthUserPayload = async ({
if (!user?.publicKey) throw UnauthorizedRequestError({ message: "Failed to authenticate user with partially set up account" }); if (!user?.publicKey) throw UnauthorizedRequestError({ message: "Failed to authenticate user with partially set up account" });
const tokenVersion = await TokenVersion.findOneAndUpdate({
_id: new Types.ObjectId(decodedToken.tokenVersionId),
user: user._id,
}, {
lastUsed: new Date(),
});
if (!tokenVersion) throw UnauthorizedRequestError({
message: "Failed to validate access token",
});
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({
message: "Failed to validate access token",
});
return { return {
actor: { actor: {
type: ActorType.USER, type: ActorType.USER,
@@ -159,11 +179,6 @@ export const getAuthUserPayload = async ({
userAgent: req.headers["user-agent"] ?? "", userAgent: req.headers["user-agent"] ?? "",
userAgentType: getUserAgentType(req.headers["user-agent"]) userAgentType: getUserAgentType(req.headers["user-agent"])
} }
// return ({
// user,
// tokenVersionId: tokenVersion._id, // what to do with this? // move this out
// });
} }
/** /**
@@ -404,22 +419,24 @@ export const issueAuthTokens = async ({
// issue tokens // issue tokens
const token = createToken({ const token = createToken({
payload: { payload: {
authTokenType: AuthTokenType.ACCESS_TOKEN,
userId, userId,
tokenVersionId: tokenVersion._id.toString(), tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.accessVersion, accessVersion: tokenVersion.accessVersion,
}, },
expiresIn: await getJwtAuthLifetime(), expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret(), secret: await getAuthSecret(),
}); });
const refreshToken = createToken({ const refreshToken = createToken({
payload: { payload: {
authTokenType: AuthTokenType.REFRESH_TOKEN,
userId, userId,
tokenVersionId: tokenVersion._id.toString(), tokenVersionId: tokenVersion._id.toString(),
refreshVersion: tokenVersion.refreshVersion, refreshVersion: tokenVersion.refreshVersion,
}, },
expiresIn: await getJwtRefreshLifetime(), expiresIn: await getJwtRefreshLifetime(),
secret: await getJwtRefreshSecret(), secret: await getAuthSecret(),
}); });
return { return {
@@ -451,7 +468,7 @@ export const clearTokens = async (tokenVersionId: Types.ObjectId): Promise<void>
* bearer/auth, refresh, and temporary signup tokens * bearer/auth, refresh, and temporary signup tokens
* @param {Object} obj * @param {Object} obj
* @param {Object} obj.payload - payload of (JWT) token * @param {Object} obj.payload - payload of (JWT) token
* @param {String} obj.secret - (JWT) secret such as [JWT_AUTH_SECRET] * @param {String} obj.secret - (JWT) secret such as [AUTH_SECRET]
* @param {String} obj.expiresIn - string describing time span such as '10h' or '7d' * @param {String} obj.expiresIn - string describing time span such as '10h' or '7d'
*/ */
export const createToken = ({ export const createToken = ({
@@ -479,14 +496,17 @@ export const validateProviderAuthToken = async ({
email: string; email: string;
providerAuthToken?: string; providerAuthToken?: string;
}) => { }) => {
if (!providerAuthToken) { if (!providerAuthToken) {
throw new Error("Invalid authentication request."); throw new Error("Invalid authentication request.");
} }
const decodedToken = <jwt.ProviderAuthJwtPayload>( const decodedToken = <jwt.ProviderAuthJwtPayload>(
jwt.verify(providerAuthToken, await getJwtProviderAuthSecret()) jwt.verify(providerAuthToken, await getAuthSecret())
); );
if (decodedToken.authTokenType !== AuthTokenType.PROVIDER_TOKEN) throw UnauthorizedRequestError();
if (decodedToken.email !== email) { if (decodedToken.email !== email) {
throw new Error("Invalid authentication credentials.") throw new Error("Invalid authentication credentials.")
} }
+5 -9
View File
@@ -31,14 +31,10 @@ export const initDatabaseHelper = async ({
* Close database conection * Close database conection
*/ */
export const closeDatabaseHelper = async () => { export const closeDatabaseHelper = async () => {
return Promise.all([ if (mongoose.connection && mongoose.connection.readyState === 1) {
new Promise((resolve) => { await mongoose.connection.close();
if (mongoose.connection && mongoose.connection.readyState == 1) { return "Database connection closed";
mongoose.connection.close()
.then(() => resolve("Database connection closed"));
} else { } else {
resolve("Database connection already closed"); return "Database connection already closed";
}
}),
]);
} }
};
+30 -1
View File
@@ -790,6 +790,7 @@ export const getSecretHelper = async ({
export const updateSecretHelper = async ({ export const updateSecretHelper = async ({
secretName, secretName,
workspaceId, workspaceId,
secretId,
environment, environment,
type, type,
authData, authData,
@@ -812,11 +813,20 @@ export const updateSecretHelper = async ({
workspaceId: new Types.ObjectId(workspaceId) workspaceId: new Types.ObjectId(workspaceId)
}); });
const oldSecretBlindIndex = await generateSecretBlindIndexWithSaltHelper({ let oldSecretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
secretName, secretName,
salt salt
}); });
if (secretId) {
const secret = await Secret.findOne({
workspace: workspaceId,
environment,
_id: secretId
}).select("secretBlindIndex");
if (secret && secret.secretBlindIndex) oldSecretBlindIndex = secret.secretBlindIndex;
}
let secret: ISecret | null = null; let secret: ISecret | null = null;
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath); const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
@@ -891,6 +901,9 @@ export const updateSecretHelper = async ({
skipMultilineEncoding, skipMultilineEncoding,
secretBlindIndex: newSecretNameBlindIndex, secretBlindIndex: newSecretNameBlindIndex,
$inc: { version: 1 } $inc: { version: 1 }
},
{
new: true
} }
); );
} }
@@ -1748,6 +1761,22 @@ export const deleteSecretBatchHelper = async ({
secretIds: deletedSecrets.map((secret) => secret._id) secretIds: deletedSecrets.map((secret) => secret._id)
}); });
const action = await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: deletedSecrets.map((secret) => secret._id)
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
authData, authData,
{ {
+36 -2
View File
@@ -29,6 +29,7 @@ import {
secretApprovalRequest as v1SecretApprovalRequest, secretApprovalRequest as v1SecretApprovalRequest,
secretScanning as v1SecretScanningRouter secretScanning as v1SecretScanningRouter
} from "./ee/routes/v1"; } from "./ee/routes/v1";
import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3";
import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3"; import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3";
import { import {
auth as v1AuthRouter, auth as v1AuthRouter,
@@ -68,6 +69,7 @@ import {
auth as v3AuthRouter, auth as v3AuthRouter,
secrets as v3SecretsRouter, secrets as v3SecretsRouter,
signup as v3SignupRouter, signup as v3SignupRouter,
users as v3UsersRouter,
workspaces as v3WorkspacesRouter workspaces as v3WorkspacesRouter
} from "./routes/v3"; } from "./routes/v3";
import { healthCheck } from "./routes/status"; import { healthCheck } from "./routes/status";
@@ -81,12 +83,15 @@ import {
getSecretScanningPrivateKey, getSecretScanningPrivateKey,
getSecretScanningWebhookProxy, getSecretScanningWebhookProxy,
getSecretScanningWebhookSecret, getSecretScanningWebhookSecret,
getSiteURL getSiteURL,
} from "./config"; } from "./config";
import { setup } from "./utils/setup"; import { setup } from "./utils/setup";
import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices"; import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices";
import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent"; import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent";
const SmeeClient = require("smee-client"); // eslint-disable-line const SmeeClient = require("smee-client"); // eslint-disable-line
import path from "path";
let handler: null | any = null;
const main = async () => { const main = async () => {
await setup(); await setup();
@@ -147,6 +152,27 @@ const main = async () => {
next(); next();
}); });
if ((await getNodeEnv()) === "production" && process.env.STANDALONE_BUILD === "true") {
const nextJsBuildPath = path.join(__dirname, "../frontend-build");
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-var-requires
const conf = require("../frontend-build/.next/required-server-files.json").config;
const NextServer =
// eslint-disable-next-line @typescript-eslint/no-var-requires
require("../frontend-build/node_modules/next/dist/server/next-server").default;
const nextApp = new NextServer({
dev: false,
dir: nextJsBuildPath,
port: await getPort(),
conf,
hostname: "local",
customServer: false
});
handler = nextApp.getRequestHandler();
}
// (EE) routes // (EE) routes
app.use("/api/v1/secret", eeSecretRouter); app.use("/api/v1/secret", eeSecretRouter);
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter); app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
@@ -156,7 +182,8 @@ const main = async () => {
app.use("/api/v1/organizations", eeOrganizationsRouter); app.use("/api/v1/organizations", eeOrganizationsRouter);
app.use("/api/v1/sso", eeSSORouter); app.use("/api/v1/sso", eeSSORouter);
app.use("/api/v1/cloud-products", eeCloudProductsRouter); app.use("/api/v1/cloud-products", eeCloudProductsRouter);
app.use("/api/v3/service-token", v3ServiceTokenDataRouter); app.use("/api/v3/api-key", v3apiKeyDataRouter); // new
app.use("/api/v3/service-token", v3ServiceTokenDataRouter); // new
// v1 routes // v1 routes
app.use("/api/v1/signup", v1SignupRouter); app.use("/api/v1/signup", v1SignupRouter);
@@ -202,6 +229,7 @@ const main = async () => {
app.use("/api/v3/secrets", v3SecretsRouter); app.use("/api/v3/secrets", v3SecretsRouter);
app.use("/api/v3/workspaces", v3WorkspacesRouter); app.use("/api/v3/workspaces", v3WorkspacesRouter);
app.use("/api/v3/signup", v3SignupRouter); app.use("/api/v3/signup", v3SignupRouter);
app.use("/api/v3/users", v3UsersRouter);
// api docs // api docs
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile)); app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
@@ -209,6 +237,12 @@ const main = async () => {
// server status // server status
app.use("/api", healthCheck); app.use("/api", healthCheck);
if (handler) {
app.all("*", (req, res) => {
return handler(req, res);
});
}
//* Handle unrouted requests and respond with proper error message as well as status code //* Handle unrouted requests and respond with proper error message as well as status code
app.use((req, res, next) => { app.use((req, res, next) => {
if (res.headersSent) return next(); if (res.headersSent) return next();
+187 -177
View File
@@ -25,6 +25,8 @@ import {
INTEGRATION_GITHUB, INTEGRATION_GITHUB,
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_GITLAB_API_URL, INTEGRATION_GITLAB_API_URL,
INTEGRATION_HASURA_CLOUD,
INTEGRATION_HASURA_CLOUD_API_URL,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_HEROKU_API_URL, INTEGRATION_HEROKU_API_URL,
INTEGRATION_LARAVELFORGE, INTEGRATION_LARAVELFORGE,
@@ -47,7 +49,7 @@ import {
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_VERCEL_API_URL, INTEGRATION_VERCEL_API_URL,
INTEGRATION_WINDMILL, INTEGRATION_WINDMILL,
INTEGRATION_WINDMILL_API_URL, INTEGRATION_WINDMILL_API_URL
} from "../variables"; } from "../variables";
import { IIntegrationAuth } from "../models"; import { IIntegrationAuth } from "../models";
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
@@ -73,7 +75,7 @@ const getApps = async ({
accessToken, accessToken,
accessId, accessId,
teamId, teamId,
workspaceSlug, workspaceSlug
}: { }: {
integrationAuth: IIntegrationAuth; integrationAuth: IIntegrationAuth;
accessToken: string; accessToken: string;
@@ -85,7 +87,7 @@ const getApps = async ({
switch (integrationAuth.integration) { switch (integrationAuth.integration) {
case INTEGRATION_GCP_SECRET_MANAGER: case INTEGRATION_GCP_SECRET_MANAGER:
apps = await getAppsGCPSecretManager({ apps = await getAppsGCPSecretManager({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_AZURE_KEY_VAULT: case INTEGRATION_AZURE_KEY_VAULT:
@@ -99,50 +101,50 @@ const getApps = async ({
break; break;
case INTEGRATION_HEROKU: case INTEGRATION_HEROKU:
apps = await getAppsHeroku({ apps = await getAppsHeroku({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_VERCEL: case INTEGRATION_VERCEL:
apps = await getAppsVercel({ apps = await getAppsVercel({
integrationAuth, integrationAuth,
accessToken, accessToken
}); });
break; break;
case INTEGRATION_NETLIFY: case INTEGRATION_NETLIFY:
apps = await getAppsNetlify({ apps = await getAppsNetlify({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_GITHUB: case INTEGRATION_GITHUB:
apps = await getAppsGithub({ apps = await getAppsGithub({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_GITLAB: case INTEGRATION_GITLAB:
apps = await getAppsGitlab({ apps = await getAppsGitlab({
integrationAuth, integrationAuth,
accessToken, accessToken,
teamId, teamId
}); });
break; break;
case INTEGRATION_RENDER: case INTEGRATION_RENDER:
apps = await getAppsRender({ apps = await getAppsRender({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_RAILWAY: case INTEGRATION_RAILWAY:
apps = await getAppsRailway({ apps = await getAppsRailway({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_FLYIO: case INTEGRATION_FLYIO:
apps = await getAppsFlyio({ apps = await getAppsFlyio({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_CIRCLECI: case INTEGRATION_CIRCLECI:
apps = await getAppsCircleCI({ apps = await getAppsCircleCI({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_LARAVELFORGE: case INTEGRATION_LARAVELFORGE:
@@ -154,28 +156,28 @@ const getApps = async ({
case INTEGRATION_TERRAFORM_CLOUD: case INTEGRATION_TERRAFORM_CLOUD:
apps = await getAppsTerraformCloud({ apps = await getAppsTerraformCloud({
accessToken, accessToken,
workspacesId: accessId, workspacesId: accessId
}); });
break; break;
case INTEGRATION_TRAVISCI: case INTEGRATION_TRAVISCI:
apps = await getAppsTravisCI({ apps = await getAppsTravisCI({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_TEAMCITY: case INTEGRATION_TEAMCITY:
apps = await getAppsTeamCity({ apps = await getAppsTeamCity({
integrationAuth, integrationAuth,
accessToken, accessToken
}); });
break; break;
case INTEGRATION_SUPABASE: case INTEGRATION_SUPABASE:
apps = await getAppsSupabase({ apps = await getAppsSupabase({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_CHECKLY: case INTEGRATION_CHECKLY:
apps = await getAppsCheckly({ apps = await getAppsCheckly({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_CLOUDFLARE_PAGES: case INTEGRATION_CLOUDFLARE_PAGES:
@@ -186,7 +188,7 @@ const getApps = async ({
break; break;
case INTEGRATION_NORTHFLANK: case INTEGRATION_NORTHFLANK:
apps = await getAppsNorthflank({ apps = await getAppsNorthflank({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_BITBUCKET: case INTEGRATION_BITBUCKET:
@@ -197,7 +199,7 @@ const getApps = async ({
break; break;
case INTEGRATION_CODEFRESH: case INTEGRATION_CODEFRESH:
apps = await getAppsCodefresh({ apps = await getAppsCodefresh({
accessToken, accessToken
}); });
break; break;
case INTEGRATION_WINDMILL: case INTEGRATION_WINDMILL:
@@ -212,7 +214,13 @@ const getApps = async ({
break; break;
case INTEGRATION_CLOUD_66: case INTEGRATION_CLOUD_66:
apps = await getAppsCloud66({ apps = await getAppsCloud66({
accessToken, accessToken
});
break;
case INTEGRATION_HASURA_CLOUD:
apps = await getAppsHasuraCloud({
accessToken
}); });
break; break;
} }
@@ -229,17 +237,20 @@ const getApps = async ({
* @returns {String} apps.appId - id of GCP project * @returns {String} apps.appId - id of GCP project
*/ */
const getAppsGCPSecretManager = async ({ accessToken }: { accessToken: string }) => { const getAppsGCPSecretManager = async ({ accessToken }: { accessToken: string }) => {
interface GCPApp { interface GCPApp {
projectNumber: string; projectNumber: string;
projectId: string; projectId: string;
lifecycleState: "ACTIVE" | "LIFECYCLE_STATE_UNSPECIFIED" | "DELETE_REQUESTED" | "DELETE_IN_PROGRESS"; lifecycleState:
| "ACTIVE"
| "LIFECYCLE_STATE_UNSPECIFIED"
| "DELETE_REQUESTED"
| "DELETE_IN_PROGRESS";
name: string; name: string;
createTime: string; createTime: string;
parent: { parent: {
type: "organization" | "folder" | "project"; type: "organization" | "folder" | "project";
id: string; id: string;
} };
} }
interface GCPGetProjectsRes { interface GCPGetProjectsRes {
@@ -250,7 +261,7 @@ const getAppsGCPSecretManager = async ({ accessToken }: { accessToken: string })
interface GCPGetServiceRes { interface GCPGetServiceRes {
name: string; name: string;
parent: string; parent: string;
state: "ENABLED" | "DISABLED" | "STATE_UNSPECIFIED" state: "ENABLED" | "DISABLED" | "STATE_UNSPECIFIED";
} }
let gcpApps: GCPApp[] = []; let gcpApps: GCPApp[] = [];
@@ -266,15 +277,15 @@ const getAppsGCPSecretManager = async ({ accessToken }: { accessToken: string })
...(pageToken ? { pageToken } : {}) ...(pageToken ? { pageToken } : {})
}); });
const res: GCPGetProjectsRes = (await standardRequest.get(`${INTEGRATION_GCP_API_URL}/v1/projects`, { const res: GCPGetProjectsRes = (
await standardRequest.get(`${INTEGRATION_GCP_API_URL}/v1/projects`, {
params, params,
headers: { headers: {
"Authorization": `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json" "Accept-Encoding": "application/json"
} }
}) })
) ).data;
.data;
gcpApps = gcpApps.concat(res.projects); gcpApps = gcpApps.concat(res.projects);
@@ -287,14 +298,17 @@ const getAppsGCPSecretManager = async ({ accessToken }: { accessToken: string })
for await (const gcpApp of gcpApps) { for await (const gcpApp of gcpApps) {
try { try {
const res: GCPGetServiceRes = (await standardRequest.get( const res: GCPGetServiceRes = (
`${INTEGRATION_GCP_SERVICE_USAGE_URL}/v1/projects/${gcpApp.projectId}/services/${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`, { await standardRequest.get(
`${INTEGRATION_GCP_SERVICE_USAGE_URL}/v1/projects/${gcpApp.projectId}/services/${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`,
{
headers: { headers: {
"Authorization": `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json" "Accept-Encoding": "application/json"
} }
} }
)).data; )
).data;
if (res.state === "ENABLED") { if (res.state === "ENABLED") {
apps.push({ apps.push({
@@ -322,13 +336,13 @@ const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
await standardRequest.get(`${INTEGRATION_HEROKU_API_URL}/apps`, { await standardRequest.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
headers: { headers: {
Accept: "application/vnd.heroku+json; version=3", Accept: "application/vnd.heroku+json; version=3",
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`
}, }
}) })
).data; ).data;
const apps = res.map((a: any) => ({ const apps = res.map((a: any) => ({
name: a.name, name: a.name
})); }));
return apps; return apps;
@@ -343,7 +357,7 @@ const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
*/ */
const getAppsVercel = async ({ const getAppsVercel = async ({
integrationAuth, integrationAuth,
accessToken, accessToken
}: { }: {
integrationAuth: IIntegrationAuth; integrationAuth: IIntegrationAuth;
accessToken: string; accessToken: string;
@@ -352,21 +366,21 @@ const getAppsVercel = async ({
await standardRequest.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, { await standardRequest.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, },
...(integrationAuth?.teamId ...(integrationAuth?.teamId
? { ? {
params: { params: {
teamId: integrationAuth.teamId, teamId: integrationAuth.teamId
},
} }
: {}), }
: {})
}) })
).data; ).data;
const apps = res.projects.map((a: any) => ({ const apps = res.projects.map((a: any) => ({
name: a.name, name: a.name,
appId: a.id, appId: a.id
})); }));
return apps; return apps;
@@ -390,24 +404,21 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
const params = new URLSearchParams({ const params = new URLSearchParams({
page: String(page), page: String(page),
per_page: String(perPage), per_page: String(perPage),
filter: "all", filter: "all"
}); });
const { data } = await standardRequest.get( const { data } = await standardRequest.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`,
{
params, params,
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
},
} }
); });
data.map((a: any) => { data.map((a: any) => {
apps.push({ apps.push({
name: a.name, name: a.name,
appId: a.site_id, appId: a.site_id
}); });
}); });
@@ -441,7 +452,7 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
} }
const octokit = new Octokit({ const octokit = new Octokit({
auth: accessToken, auth: accessToken
}); });
const getAllRepos = async () => { const getAllRepos = async () => {
@@ -455,7 +466,7 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
"GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}", "GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}",
{ {
per_page, per_page,
page, page
} }
); );
@@ -478,7 +489,7 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
return { return {
appId: a.id, appId: a.id,
name: a.name, name: a.name,
owner: a.owner.login, owner: a.owner.login
}; };
}); });
@@ -499,14 +510,14 @@ const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
Accept: "application/json", Accept: "application/json",
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
}) })
).data; ).data;
const apps = res.map((a: any) => ({ const apps = res.map((a: any) => ({
name: a.service.name, name: a.service.name,
appId: a.service.id, appId: a.service.id
})); }));
return apps; return apps;
@@ -540,27 +551,27 @@ const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
const { const {
data: { data: {
data: { data: {
projects: { edges }, projects: { edges }
}, }
}, }
} = await standardRequest.post( } = await standardRequest.post(
INTEGRATION_RAILWAY_API_URL, INTEGRATION_RAILWAY_API_URL,
{ {
query, query,
variables, variables
}, },
{ {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json", "Content-Type": "application/json",
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
} }
); );
const apps = edges.map((e: any) => ({ const apps = edges.map((e: any) => ({
name: e.node.name, name: e.node.name,
appId: e.node.id, appId: e.node.id
})); }));
return apps; return apps;
@@ -583,18 +594,21 @@ const getAppsLaravelForge = async ({
serverId?: string; serverId?: string;
}) => { }) => {
const res = ( const res = (
await standardRequest.get(`${INTEGRATION_LARAVELFORGE_API_URL}/api/v1/servers/${serverId}/sites`, { await standardRequest.get(
`${INTEGRATION_LARAVELFORGE_API_URL}/api/v1/servers/${serverId}/sites`,
{
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
Accept: "application/json", Accept: "application/json",
"Content-Type": "application/json", "Content-Type": "application/json"
}, }
}) }
)
).data.sites; ).data.sites;
const apps = res.map((a: any) => ({ const apps = res.map((a: any) => ({
name: a.name, name: a.name,
appId: a.id, appId: a.id
})); }));
return apps; return apps;
@@ -632,15 +646,15 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
{ {
query, query,
variables: { variables: {
role: null, role: null
}, }
}, },
{ {
headers: { headers: {
Authorization: "Bearer " + accessToken, Authorization: "Bearer " + accessToken,
Accept: "application/json", Accept: "application/json",
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
} }
) )
).data.data.apps.nodes; ).data.data.apps.nodes;
@@ -665,14 +679,14 @@ const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
await standardRequest.get(`${INTEGRATION_CIRCLECI_API_URL}/v1.1/projects`, { await standardRequest.get(`${INTEGRATION_CIRCLECI_API_URL}/v1.1/projects`, {
headers: { headers: {
"Circle-Token": accessToken, "Circle-Token": accessToken,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
}) })
).data; ).data;
const apps = res?.map((a: any) => { const apps = res?.map((a: any) => {
return { return {
name: a?.reponame, name: a?.reponame
}; };
}); });
@@ -684,15 +698,15 @@ const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
await standardRequest.get(`${INTEGRATION_TRAVISCI_API_URL}/repos`, { await standardRequest.get(`${INTEGRATION_TRAVISCI_API_URL}/repos`, {
headers: { headers: {
Authorization: `token ${accessToken}`, Authorization: `token ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
}) })
).data; ).data;
const apps = res?.map((a: any) => { const apps = res?.map((a: any) => {
return { return {
name: a?.slug?.split("/")[1], name: a?.slug?.split("/")[1],
appId: a?.id, appId: a?.id
}; };
}); });
@@ -715,27 +729,29 @@ const getAppsTerraformCloud = async ({
workspacesId?: string; workspacesId?: string;
}) => { }) => {
const res = ( const res = (
await standardRequest.get(`${INTEGRATION_TERRAFORM_CLOUD_API_URL}/api/v2/workspaces/${workspacesId}`, { await standardRequest.get(
`${INTEGRATION_TERRAFORM_CLOUD_API_URL}/api/v2/workspaces/${workspacesId}`,
{
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
Accept: "application/json", Accept: "application/json"
}, }
}) }
)
).data.data; ).data.data;
const apps = [] const apps = [];
const appsObj = { const appsObj = {
name: res?.attributes.name, name: res?.attributes.name,
appId: res?.id, appId: res?.id
}; };
apps.push(appsObj) apps.push(appsObj);
return apps; return apps;
}; };
/** /**
* Return list of repositories for GitLab integration * Return list of repositories for GitLab integration
* @param {Object} obj * @param {Object} obj
@@ -746,13 +762,15 @@ const getAppsTerraformCloud = async ({
const getAppsGitlab = async ({ const getAppsGitlab = async ({
integrationAuth, integrationAuth,
accessToken, accessToken,
teamId, teamId
}: { }: {
integrationAuth: IIntegrationAuth; integrationAuth: IIntegrationAuth;
accessToken: string; accessToken: string;
teamId?: string; teamId?: string;
}) => { }) => {
const gitLabApiUrl = integrationAuth.url ? `${integrationAuth.url}/api` : INTEGRATION_GITLAB_API_URL; const gitLabApiUrl = integrationAuth.url
? `${integrationAuth.url}/api`
: INTEGRATION_GITLAB_API_URL;
const apps: App[] = []; const apps: App[] = [];
@@ -766,24 +784,21 @@ const getAppsGitlab = async ({
while (hasMorePages) { while (hasMorePages) {
const params = new URLSearchParams({ const params = new URLSearchParams({
page: String(page), page: String(page),
per_page: String(perPage), per_page: String(perPage)
}); });
const { data } = await standardRequest.get( const { data } = await standardRequest.get(`${gitLabApiUrl}/v4/groups/${teamId}/projects`, {
`${gitLabApiUrl}/v4/groups/${teamId}/projects`,
{
params, params,
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
},
} }
); });
data.map((a: any) => { data.map((a: any) => {
apps.push({ apps.push({
name: a.name, name: a.name,
appId: a.id, appId: a.id
}); });
}); });
@@ -800,32 +815,29 @@ const getAppsGitlab = async ({
await standardRequest.get(`${gitLabApiUrl}/v4/user`, { await standardRequest.get(`${gitLabApiUrl}/v4/user`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
}) })
).data; ).data;
while (hasMorePages) { while (hasMorePages) {
const params = new URLSearchParams({ const params = new URLSearchParams({
page: String(page), page: String(page),
per_page: String(perPage), per_page: String(perPage)
}); });
const { data } = await standardRequest.get( const { data } = await standardRequest.get(`${gitLabApiUrl}/v4/users/${id}/projects`, {
`${gitLabApiUrl}/v4/users/${id}/projects`,
{
params, params,
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
},
} }
); });
data.map((a: any) => { data.map((a: any) => {
apps.push({ apps.push({
name: a.name, name: a.name,
appId: a.id, appId: a.id
}); });
}); });
@@ -849,7 +861,7 @@ const getAppsGitlab = async ({
*/ */
const getAppsTeamCity = async ({ const getAppsTeamCity = async ({
integrationAuth, integrationAuth,
accessToken, accessToken
}: { }: {
integrationAuth: IIntegrationAuth; integrationAuth: IIntegrationAuth;
accessToken: string; accessToken: string;
@@ -858,15 +870,15 @@ const getAppsTeamCity = async ({
await standardRequest.get(`${integrationAuth.url}/app/rest/projects`, { await standardRequest.get(`${integrationAuth.url}/app/rest/projects`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
Accept: "application/json", Accept: "application/json"
}, }
}) })
).data.project.slice(1); ).data.project.slice(1);
const apps = res.map((a: any) => { const apps = res.map((a: any) => {
return { return {
name: a.name, name: a.name,
appId: a.id, appId: a.id
}; };
}); });
@@ -881,20 +893,17 @@ const getAppsTeamCity = async ({
* @returns {String} apps.name - name of Supabase app * @returns {String} apps.name - name of Supabase app
*/ */
const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => { const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => {
const { data } = await standardRequest.get( const { data } = await standardRequest.get(`${INTEGRATION_SUPABASE_API_URL}/v1/projects`, {
`${INTEGRATION_SUPABASE_API_URL}/v1/projects`,
{
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
},
} }
); });
const apps = data.map((a: any) => { const apps = data.map((a: any) => {
return { return {
name: a.name, name: a.name,
appId: a.id, appId: a.id
}; };
}); });
@@ -909,20 +918,17 @@ const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.name - name of Checkly account * @returns {String} apps.name - name of Checkly account
*/ */
const getAppsCheckly = async ({ accessToken }: { accessToken: string }) => { const getAppsCheckly = async ({ accessToken }: { accessToken: string }) => {
const { data } = await standardRequest.get( const { data } = await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/accounts`, {
`${INTEGRATION_CHECKLY_API_URL}/v1/accounts`,
{
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept": "application/json", Accept: "application/json"
},
} }
); });
const apps = data.map((a: any) => { const apps = data.map((a: any) => {
return { return {
name: a.name, name: a.name,
appId: a.id, appId: a.id
}; };
}); });
@@ -948,19 +954,19 @@ const getAppsCloudflarePages = async ({
{ {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept": "application/json", Accept: "application/json"
}, }
} }
); );
const apps = data.result.map((a: any) => { const apps = data.result.map((a: any) => {
return { return {
name: a.name, name: a.name,
appId: a.id, appId: a.id
}; };
}); });
return apps; return apps;
} };
/** /**
* Return list of repositories for the BitBucket integration based on provided BitBucket workspace * Return list of repositories for the BitBucket integration based on provided BitBucket workspace
@@ -972,7 +978,7 @@ const getAppsCloudflarePages = async ({
*/ */
const getAppsBitBucket = async ({ const getAppsBitBucket = async ({
accessToken, accessToken,
workspaceSlug, workspaceSlug
}: { }: {
accessToken: string; accessToken: string;
workspaceSlug?: string; workspaceSlug?: string;
@@ -996,45 +1002,42 @@ const getAppsBitBucket = async ({
} }
if (!workspaceSlug) { if (!workspaceSlug) {
return [] return [];
} }
const repositories: Repository[] = []; const repositories: Repository[] = [];
let hasNextPage = true; let hasNextPage = true;
let repositoriesUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/repositories/${workspaceSlug}` let repositoriesUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/repositories/${workspaceSlug}`;
while (hasNextPage) { while (hasNextPage) {
const { data }: { data: RepositoriesResponse } = await standardRequest.get( const { data }: { data: RepositoriesResponse } = await standardRequest.get(repositoriesUrl, {
repositoriesUrl,
{
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept": "application/json", Accept: "application/json"
},
} }
); });
if (data?.values.length > 0) { if (data?.values.length > 0) {
data.values.forEach((repository) => { data.values.forEach((repository) => {
repositories.push(repository) repositories.push(repository);
}) });
} }
if (data.next) { if (data.next) {
repositoriesUrl = data.next repositoriesUrl = data.next;
} else { } else {
hasNextPage = false hasNextPage = false;
} }
} }
const apps = repositories.map((repository) => { const apps = repositories.map((repository) => {
return { return {
name: repository.name, name: repository.name,
appId: repository.uuid, appId: repository.uuid
}; };
}); });
return apps; return apps;
} };
/** Return list of projects for Northflank integration /** Return list of projects for Northflank integration
* @param {Object} obj * @param {Object} obj
@@ -1045,19 +1048,14 @@ const getAppsBitBucket = async ({
const getAppsNorthflank = async ({ accessToken }: { accessToken: string }) => { const getAppsNorthflank = async ({ accessToken }: { accessToken: string }) => {
const { const {
data: { data: {
data: { data: { projects }
projects
} }
} } = await standardRequest.get(`${INTEGRATION_NORTHFLANK_API_URL}/v1/projects`, {
} = await standardRequest.get(
`${INTEGRATION_NORTHFLANK_API_URL}/v1/projects`,
{
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
},
} }
); });
const apps = projects.map((a: any) => { const apps = projects.map((a: any) => {
return { return {
@@ -1076,27 +1074,22 @@ const getAppsNorthflank = async ({ accessToken }: { accessToken: string }) => {
* @returns {Object[]} apps - names of Supabase apps * @returns {Object[]} apps - names of Supabase apps
* @returns {String} apps.name - name of Supabase app * @returns {String} apps.name - name of Supabase app
*/ */
const getAppsCodefresh = async ({ const getAppsCodefresh = async ({ accessToken }: { accessToken: string }) => {
accessToken,
}: {
accessToken: string;
}) => {
const res = ( const res = (
await standardRequest.get(`${INTEGRATION_CODEFRESH_API_URL}/projects`, { await standardRequest.get(`${INTEGRATION_CODEFRESH_API_URL}/projects`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
}) })
).data; ).data;
const apps = res.projects.map((a: any) => ({ const apps = res.projects.map((a: any) => ({
name: a.projectName, name: a.projectName,
appId: a.id, appId: a.id
})); }));
return apps; return apps;
}; };
/** /**
@@ -1107,15 +1100,12 @@ const getAppsCodefresh = async ({
* @returns {String} apps.name - name of Windmill workspace * @returns {String} apps.name - name of Windmill workspace
*/ */
const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => { const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
const { data } = await standardRequest.get( const { data } = await standardRequest.get(`${INTEGRATION_WINDMILL_API_URL}/workspaces/list`, {
`${INTEGRATION_WINDMILL_API_URL}/workspaces/list`,
{
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
},
} }
); });
// check for write access of secrets in windmill workspaces // check for write access of secrets in windmill workspaces
const writeAccessCheck = data.map(async (app: any) => { const writeAccessCheck = data.map(async (app: any) => {
@@ -1134,8 +1124,8 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
{ {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
} }
); );
@@ -1150,8 +1140,8 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
{ {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
} }
); );
@@ -1162,8 +1152,8 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
{ {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
} }
); );
@@ -1172,8 +1162,8 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
{ {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json"
}, }
} }
); );
@@ -1192,12 +1182,12 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
const apps = appsWithWriteAccess.map((a: any) => { const apps = appsWithWriteAccess.map((a: any) => {
return { return {
name: a.name, name: a.name,
appId: a.id, appId: a.id
}; };
}); });
return apps; return apps;
} };
/** /**
* Return list of applications for DigitalOcean App Platform integration * Return list of applications for DigitalOcean App Platform integration
@@ -1239,7 +1229,27 @@ const getAppsDigitalOceanAppPlatform = async ({ accessToken }: { accessToken: st
name: a.spec.name, name: a.spec.name,
appId: a.id appId: a.id
})); }));
};
const getAppsHasuraCloud = async ({ accessToken }: { accessToken: string }) => {
const res = await standardRequest.post(
INTEGRATION_HASURA_CLOUD_API_URL,
{
query: "query MyQuery { projects { name tenant { id } } }"
},
{
headers: {
Authorization: `pat ${accessToken}`,
"Content-Type": "application/json"
} }
}
);
const data = (res?.data?.data?.projects ?? []).map(
({ name, tenant: { id: appId } }: { name: string; tenant: { id: string } }) => ({ name, appId })
);
return data;
};
/** /**
* Return list of applications for Cloud66 integration * Return list of applications for Cloud66 integration
@@ -1290,7 +1300,7 @@ const getAppsCloud66 = async ({ accessToken }: { accessToken: string }) => {
"Accept-Encoding": "application/json" "Accept-Encoding": "application/json"
} }
}) })
).data.response as Cloud66Apps[] ).data.response as Cloud66Apps[];
const apps = stacks.map((app) => ({ const apps = stacks.map((app) => ({
name: app.name, name: app.name,
+158 -16
View File
@@ -32,6 +32,8 @@ import {
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_GITLAB_API_URL, INTEGRATION_GITLAB_API_URL,
INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASHICORP_VAULT,
INTEGRATION_HASURA_CLOUD,
INTEGRATION_HASURA_CLOUD_API_URL,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_HEROKU_API_URL, INTEGRATION_HEROKU_API_URL,
INTEGRATION_LARAVELFORGE, INTEGRATION_LARAVELFORGE,
@@ -63,6 +65,10 @@ import { Octokit } from "@octokit/rest";
import _ from "lodash"; import _ from "lodash";
import sodium from "libsodium-wrappers"; import sodium from "libsodium-wrappers";
import { standardRequest } from "../config/request"; import { standardRequest } from "../config/request";
import {
ZGetTenantEnv,
ZUpdateTenantEnv
} from "../validation/hasuraCloudIntegration";
const getSecretKeyValuePair = ( const getSecretKeyValuePair = (
secrets: Record<string, { value: string | null; comment?: string } | null> secrets: Record<string, { value: string | null; comment?: string } | null>
@@ -95,7 +101,7 @@ const syncSecrets = async ({
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessId: string | null; accessId: string | null;
accessToken: string; accessToken: string;
appendices?: { prefix: string, suffix: string }; appendices?: { prefix: string; suffix: string };
}) => { }) => {
switch (integration.integration) { switch (integration.integration) {
case INTEGRATION_GCP_SECRET_MANAGER: case INTEGRATION_GCP_SECRET_MANAGER:
@@ -306,6 +312,14 @@ const syncSecrets = async ({
accessToken accessToken
}); });
break; break;
case INTEGRATION_HASURA_CLOUD:
await syncSecretsHasuraCloud({
integration,
secrets,
accessToken
});
break;
} }
}; };
@@ -964,7 +978,8 @@ const syncSecretsVercel = async ({
...(integration?.path ...(integration?.path
? { ? {
gitBranch: integration?.path gitBranch: integration?.path
} : {}) }
: {})
}; };
const vercelSecrets: VercelSecret[] = ( const vercelSecrets: VercelSecret[] = (
@@ -1352,7 +1367,7 @@ const syncSecretsGitHub = async ({
integration: IIntegration; integration: IIntegration;
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessToken: string; accessToken: string;
appendices?: { prefix: string, suffix: string }; appendices?: { prefix: string; suffix: string };
}) => { }) => {
interface GitHubRepoKey { interface GitHubRepoKey {
key_id: string; key_id: string;
@@ -1395,14 +1410,23 @@ const syncSecretsGitHub = async ({
{} {}
); );
encryptedSecrets = Object.keys(encryptedSecrets).reduce((result: { encryptedSecrets = Object.keys(encryptedSecrets).reduce(
(
result: {
[key: string]: GitHubSecret; [key: string]: GitHubSecret;
}, key) => { },
if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) { key
) => {
if (
(appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) &&
(appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)
) {
result[key] = encryptedSecrets[key]; result[key] = encryptedSecrets[key];
} }
return result; return result;
}, {}); },
{}
);
Object.keys(encryptedSecrets).map(async (key) => { Object.keys(encryptedSecrets).map(async (key) => {
if (!(key in secrets)) { if (!(key in secrets)) {
@@ -2095,7 +2119,7 @@ const syncSecretsCheckly = async ({
integration: IIntegration; integration: IIntegration;
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessToken: string; accessToken: string;
appendices?: { prefix: string, suffix: string }; appendices?: { prefix: string; suffix: string };
}) => { }) => {
let getSecretsRes = ( let getSecretsRes = (
await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, { await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, {
@@ -2113,14 +2137,23 @@ const syncSecretsCheckly = async ({
{} {}
); );
getSecretsRes = Object.keys(getSecretsRes).reduce((result: { getSecretsRes = Object.keys(getSecretsRes).reduce(
(
result: {
[key: string]: string; [key: string]: string;
}, key) => { },
if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) { key
) => {
if (
(appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) &&
(appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)
) {
result[key] = getSecretsRes[key]; result[key] = getSecretsRes[key];
} }
return result; return result;
}, {}); },
{}
);
// add secrets // add secrets
for await (const key of Object.keys(secrets)) { for await (const key of Object.keys(secrets)) {
@@ -2195,18 +2228,20 @@ const syncSecretsQovery = async ({
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessToken: string; accessToken: string;
}) => { }) => {
const getSecretsRes = ( const getSecretsRes = (
await standardRequest.get(`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`, { await standardRequest.get(
`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`,
{
headers: { headers: {
Authorization: `Token ${accessToken}`, Authorization: `Token ${accessToken}`,
"Accept-Encoding": "application/json" "Accept-Encoding": "application/json"
} }
}) }
)
).data.results.reduce( ).data.results.reduce(
(obj: any, secret: any) => ({ (obj: any, secret: any) => ({
...obj, ...obj,
[secret.key]: {"id": secret.id, "value": secret.value} [secret.key]: { id: secret.id, value: secret.value }
}), }),
{} {}
); );
@@ -3076,4 +3111,111 @@ const syncSecretsNorthflank = async ({
); );
}; };
/** Sync/push [secrets] to Hasura Cloud
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for Hasura Cloud integration
*/
const syncSecretsHasuraCloud = async ({
integration,
secrets,
accessToken
}: {
integration: IIntegration;
secrets: Record<string, { value: string; comment?: string }>;
accessToken: string;
}) => {
const res = await standardRequest.post(
INTEGRATION_HASURA_CLOUD_API_URL,
{
query:
"query MyQuery($tenantId: uuid!) { getTenantEnv(tenantId: $tenantId) { hash envVars } }",
variables: {
tenantId: integration.appId
}
},
{
headers: {
Authorization: `pat ${accessToken}`,
"Content-Type": "application/json"
}
}
);
const {
data: {
getTenantEnv: { hash, envVars }
}
} = ZGetTenantEnv.parse(res.data);
let currentHash = hash;
const secretsToUpdate = Object.keys(secrets).map((key) => {
return ({
key,
value: secrets[key].value
});
});
if (secretsToUpdate.length) {
// update secrets
const addRequest = await standardRequest.post(
INTEGRATION_HASURA_CLOUD_API_URL,
{
query:
"mutation MyQuery($currentHash: String!, $envs: [UpdateEnvObject!]!, $tenantId: uuid!) { updateTenantEnv(currentHash: $currentHash, envs: $envs, tenantId: $tenantId) { hash envVars} }",
variables: {
currentHash,
envs: secretsToUpdate,
tenantId: integration.appId
}
},
{
headers: {
Authorization: `pat ${accessToken}`,
"Content-Type": "application/json"
}
}
);
const addRequestResponse = ZUpdateTenantEnv.safeParse(addRequest.data);
if (addRequestResponse.success) {
currentHash = addRequestResponse.data.data.updateTenantEnv.hash;
}
}
const secretsToDelete = envVars.environment
? Object.keys(envVars.environment).filter((key) => !(key in secrets))
: [];
if (secretsToDelete.length) {
await standardRequest.post(
INTEGRATION_HASURA_CLOUD_API_URL,
{
query: `
mutation deleteTenantEnv($id: uuid!, $currentHash: String!, $env: [String!]!) {
deleteTenantEnv(tenantId: $id, currentHash: $currentHash, deleteEnvs: $env) {
hash
envVars
}
}
`,
variables: {
id: integration.appId,
currentHash,
env: secretsToDelete
}
},
{
headers: {
Authorization: `pat ${accessToken}`,
"Content-Type": "application/json"
}
}
);
}
};
export { syncSecrets }; export { syncSecrets };
@@ -44,6 +44,7 @@ export interface GetSecretParams {
export interface UpdateSecretParams { export interface UpdateSecretParams {
secretName: string; secretName: string;
newSecretName?: string; newSecretName?: string;
secretId?: string;
secretKeyCiphertext?: string; secretKeyCiphertext?: string;
secretKeyIV?: string; secretKeyIV?: string;
secretKeyTag?: string; secretKeyTag?: string;
+5 -2
View File
@@ -2,7 +2,8 @@ import jwt from "jsonwebtoken";
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import { User } from "../models"; import { User } from "../models";
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
import { getJwtMfaSecret } from "../config"; import { getAuthSecret } from "../config";
import { AuthTokenType } from "../variables";
declare module "jsonwebtoken" { declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -26,9 +27,11 @@ const requireMfaAuth = async (
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: "Missing Authorization Body in the request header"})) if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: "Missing Authorization Body in the request header"}))
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, await getJwtMfaSecret()) jwt.verify(AUTH_TOKEN_VALUE, await getAuthSecret())
); );
if (decodedToken.authTokenType !== AuthTokenType.MFA_TOKEN) throw UnauthorizedRequestError();
const user = await User.findOne({ const user = await User.findOne({
_id: decodedToken.userId, _id: decodedToken.userId,
}).select("+publicKey"); }).select("+publicKey");
+5 -2
View File
@@ -2,7 +2,8 @@ import jwt from "jsonwebtoken";
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import { User } from "../models"; import { User } from "../models";
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
import { getJwtSignupSecret } from "../config"; import { getAuthSecret } from "../config";
import { AuthTokenType } from "../variables";
declare module "jsonwebtoken" { declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -27,9 +28,11 @@ const requireSignupAuth = async (
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: "Missing Authorization Body in the request header"})) if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: "Missing Authorization Body in the request header"}))
const decodedToken = <jwt.UserIDJwtPayload>( const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, await getJwtSignupSecret()) jwt.verify(AUTH_TOKEN_VALUE, await getAuthSecret())
); );
if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) throw UnauthorizedRequestError();
const user = await User.findOne({ const user = await User.findOne({
_id: decodedToken.userId, _id: decodedToken.userId,
}).select("+publicKey"); }).select("+publicKey");
+38
View File
@@ -0,0 +1,38 @@
import { Document, Schema, Types, model } from "mongoose";
export interface IAPIKeyDataV2 extends Document {
_id: Types.ObjectId;
name: string;
user: Types.ObjectId;
lastUsed?: Date
usageCount: number;
expiresAt?: Date;
}
const apiKeyDataV2Schema = new Schema(
{
name: {
type: String,
required: true
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true
},
lastUsed: {
type: Date,
required: false
},
usageCount: {
type: Number,
default: 0,
required: true
}
},
{
timestamps: true
}
);
export const APIKeyDataV2 = model<IAPIKeyDataV2>("APIKeyDataV2", apiKeyDataV2Schema);
+4 -3
View File
@@ -24,9 +24,10 @@ export * from "./user";
export * from "./userAction"; export * from "./userAction";
export * from "./workspace"; export * from "./workspace";
export * from "./serviceTokenData"; // TODO: deprecate export * from "./serviceTokenData"; // TODO: deprecate
export * from "./apiKeyData"; export * from "./serviceTokenDataV3";
export * from "./serviceTokenDataV3Key";
export * from "./apiKeyData"; // TODO: deprecate
export * from "./apiKeyDataV2";
export * from "./loginSRPDetail"; export * from "./loginSRPDetail";
export * from "./tokenVersion"; export * from "./tokenVersion";
export * from "./webhooks"; export * from "./webhooks";
export * from "./serviceTokenDataV3";
export * from "./serviceTokenDataV3Key";
+22 -19
View File
@@ -14,6 +14,7 @@ import {
INTEGRATION_GITHUB, INTEGRATION_GITHUB,
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASHICORP_VAULT,
INTEGRATION_HASURA_CLOUD,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_LARAVELFORGE, INTEGRATION_LARAVELFORGE,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
@@ -76,7 +77,8 @@ export interface IIntegration {
| "cloud-66" | "cloud-66"
| "northflank" | "northflank"
| "windmill" | "windmill"
| "gcp-secret-manager"; | "gcp-secret-manager"
| "hasura-cloud";
integrationAuth: Types.ObjectId; integrationAuth: Types.ObjectId;
metadata: Metadata; metadata: Metadata;
} }
@@ -86,67 +88,67 @@ const integrationSchema = new Schema<IIntegration>(
workspace: { workspace: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "Workspace", ref: "Workspace",
required: true, required: true
}, },
environment: { environment: {
type: String, type: String,
required: true, required: true
}, },
isActive: { isActive: {
type: Boolean, type: Boolean,
required: true, required: true
}, },
url: { url: {
// for custom self-hosted integrations (e.g. self-hosted GitHub enterprise) // for custom self-hosted integrations (e.g. self-hosted GitHub enterprise)
type: String, type: String,
default: null, default: null
}, },
app: { app: {
// name of app in provider // name of app in provider
type: String, type: String,
default: null, default: null
}, },
appId: { appId: {
// id of app in provider // id of app in provider
type: String, type: String,
default: null, default: null
}, },
targetEnvironment: { targetEnvironment: {
// target environment // target environment
type: String, type: String,
default: null, default: null
}, },
targetEnvironmentId: { targetEnvironmentId: {
type: String, type: String,
default: null, default: null
}, },
targetService: { targetService: {
// railway-specific service // railway-specific service
// qovery-specific project // qovery-specific project
type: String, type: String,
default: null, default: null
}, },
targetServiceId: { targetServiceId: {
// railway-specific service // railway-specific service
// qovery specific project // qovery specific project
type: String, type: String,
default: null, default: null
}, },
owner: { owner: {
// github-specific repo owner-login // github-specific repo owner-login
type: String, type: String,
default: null, default: null
}, },
path: { path: {
// aws-parameter-store-specific path // aws-parameter-store-specific path
// (also) vercel preview-branch // (also) vercel preview-branch
type: String, type: String,
default: null, default: null
}, },
region: { region: {
// aws-parameter-store-specific path // aws-parameter-store-specific path
type: String, type: String,
default: null, default: null
}, },
scope: { scope: {
// qovery-specific scope // qovery-specific scope
@@ -183,19 +185,20 @@ const integrationSchema = new Schema<IIntegration>(
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66, INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK, INTEGRATION_NORTHFLANK,
INTEGRATION_GCP_SECRET_MANAGER INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_HASURA_CLOUD
], ],
required: true, required: true
}, },
integrationAuth: { integrationAuth: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "IntegrationAuth", ref: "IntegrationAuth",
required: true, required: true
}, },
secretPath: { secretPath: {
type: String, type: String,
required: true, required: true,
default: "/", default: "/"
}, },
metadata: { metadata: {
type: Schema.Types.Mixed, type: Schema.Types.Mixed,
@@ -203,7 +206,7 @@ const integrationSchema = new Schema<IIntegration>(
} }
}, },
{ {
timestamps: true, timestamps: true
} }
); );
@@ -16,6 +16,7 @@ import {
INTEGRATION_GITHUB, INTEGRATION_GITHUB,
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASHICORP_VAULT,
INTEGRATION_HASURA_CLOUD,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_LARAVELFORGE, INTEGRATION_LARAVELFORGE,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
@@ -62,7 +63,8 @@ import {
| "teamcity" | "teamcity"
| "northflank" | "northflank"
| "windmill" | "windmill"
| "gcp-secret-manager"; | "gcp-secret-manager"
| "hasura-cloud";
teamId: string; teamId: string;
accountId: string; accountId: string;
url: string; url: string;
@@ -87,7 +89,7 @@ import {
workspace: { workspace: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: "Workspace", ref: "Workspace",
required: true, required: true
}, },
integration: { integration: {
type: String, type: String,
@@ -117,89 +119,85 @@ import {
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66, INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK, INTEGRATION_NORTHFLANK,
INTEGRATION_GCP_SECRET_MANAGER INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_HASURA_CLOUD
], ],
required: true, required: true
}, },
teamId: { teamId: {
// vercel-specific integration param // vercel-specific integration param
type: String, type: String
}, },
url: { url: {
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault) // for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
type: String, type: String
}, },
namespace: { namespace: {
// hashicorp-vault-specific integration param // hashicorp-vault-specific integration param
type: String, type: String
}, },
accountId: { accountId: {
// netlify-specific integration param // netlify-specific integration param
type: String, type: String
}, },
refreshCiphertext: { refreshCiphertext: {
type: String, type: String,
select: false, select: false
}, },
refreshIV: { refreshIV: {
type: String, type: String,
select: false, select: false
}, },
refreshTag: { refreshTag: {
type: String, type: String,
select: false, select: false
}, },
accessIdCiphertext: { accessIdCiphertext: {
type: String, type: String,
select: false, select: false
}, },
accessIdIV: { accessIdIV: {
type: String, type: String,
select: false, select: false
}, },
accessIdTag: { accessIdTag: {
type: String, type: String,
select: false, select: false
}, },
accessCiphertext: { accessCiphertext: {
type: String, type: String,
select: false, select: false
}, },
accessIV: { accessIV: {
type: String, type: String,
select: false, select: false
}, },
accessTag: { accessTag: {
type: String, type: String,
select: false, select: false
}, },
accessExpiresAt: { accessExpiresAt: {
type: Date, type: Date,
select: false, select: false
}, },
algorithm: { // the encryption algorithm used algorithm: {
// the encryption algorithm used
type: String, type: String,
enum: [ALGORITHM_AES_256_GCM], enum: [ALGORITHM_AES_256_GCM],
required: true, required: true
}, },
keyEncoding: { keyEncoding: {
type: String, type: String,
enum: [ enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
ENCODING_SCHEME_UTF8, required: true
ENCODING_SCHEME_BASE64,
],
required: true,
}, },
metadata: { metadata: {
type: Schema.Types.Mixed type: Schema.Types.Mixed
} }
}, },
{ {
timestamps: true, timestamps: true
} }
); );
export const IntegrationAuth = model<IIntegrationAuth>( export const IntegrationAuth = model<IIntegrationAuth>("IntegrationAuth", integrationAuthSchema);
"IntegrationAuth",
integrationAuthSchema
);
+1
View File
@@ -54,6 +54,7 @@ const serviceTokenDataV3Schema = new Schema(
}, },
isActive: { isActive: {
type: Boolean, type: Boolean,
default: true,
required: true required: true
}, },
lastUsed: { lastUsed: {
@@ -67,7 +67,7 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
}) })
syncSecretsToThirdPartyServices.on("error", (error) => { syncSecretsToThirdPartyServices.on("error", (error) => {
console.log("QUEUE ERROR:", error) // eslint-disable-line // console.log("QUEUE ERROR:", error) // eslint-disable-line
}) })
export const syncSecretsToActiveIntegrationsQueue = (jobDetails: TSyncSecretsToThirdPartyServices) => { export const syncSecretsToActiveIntegrationsQueue = (jobDetails: TSyncSecretsToThirdPartyServices) => {
+5 -5
View File
@@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
router.post( router.post(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
}), }),
secretImpsController.createSecretImp secretImpsController.createSecretImp
); );
@@ -15,7 +15,7 @@ router.post(
router.put( router.put(
"/:id", "/:id",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
}), }),
secretImpsController.updateSecretImport secretImpsController.updateSecretImport
); );
@@ -23,7 +23,7 @@ router.put(
router.delete( router.delete(
"/:id", "/:id",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
}), }),
secretImpsController.deleteSecretImport secretImpsController.deleteSecretImport
); );
@@ -31,7 +31,7 @@ router.delete(
router.get( router.get(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
}), }),
secretImpsController.getSecretImports secretImpsController.getSecretImports
); );
@@ -39,7 +39,7 @@ router.get(
router.get( router.get(
"/secrets", "/secrets",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
}), }),
secretImpsController.getAllSecretsFromImport secretImpsController.getAllSecretsFromImport
); );
+4 -4
View File
@@ -12,7 +12,7 @@ import { AuthMode } from "../../variables";
router.post( router.post(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
}), }),
createFolder createFolder
); );
@@ -20,7 +20,7 @@ router.post(
router.patch( router.patch(
"/:folderName", "/:folderName",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
}), }),
updateFolderById updateFolderById
); );
@@ -28,7 +28,7 @@ router.patch(
router.delete( router.delete(
"/:folderName", "/:folderName",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
}), }),
deleteFolder deleteFolder
); );
@@ -36,7 +36,7 @@ router.delete(
router.get( router.get(
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
}), }),
getFolders getFolders
); );
+3 -3
View File
@@ -6,7 +6,7 @@ import {
import { AuthMode } from "../../variables"; import { AuthMode } from "../../variables";
import { serviceTokenDataController } from "../../controllers/v2"; import { serviceTokenDataController } from "../../controllers/v2";
router.get( router.get( // TODO: deprecate (moving to ST V3)
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.SERVICE_TOKEN] acceptedAuthModes: [AuthMode.SERVICE_TOKEN]
@@ -14,7 +14,7 @@ router.get(
serviceTokenDataController.getServiceTokenData serviceTokenDataController.getServiceTokenData
); );
router.post( router.post( // TODO: deprecate (moving to ST V3)
"/", "/",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
@@ -22,7 +22,7 @@ router.post(
serviceTokenDataController.createServiceTokenData serviceTokenDataController.createServiceTokenData
); );
router.delete( router.delete( // TODO: deprecate (moving to ST V3)
"/:serviceTokenDataId", "/:serviceTokenDataId",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
+1 -1
View File
@@ -36,7 +36,7 @@ router.get(
usersController.getMyOrganizations usersController.getMyOrganizations
); );
router.get( router.get( // TODO: deprecate (moving to API Key V2)
"/me/api-keys", "/me/api-keys",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.JWT] acceptedAuthModes: [AuthMode.JWT]
+2
View File
@@ -1,10 +1,12 @@
import auth from "./auth"; import auth from "./auth";
import users from "./users";
import secrets from "./secrets"; import secrets from "./secrets";
import workspaces from "./workspaces"; import workspaces from "./workspaces";
import signup from "./signup"; import signup from "./signup";
export { export {
auth, auth,
users,
secrets, secrets,
signup, signup,
workspaces workspaces
+15
View File
@@ -0,0 +1,15 @@
import express from "express";
const router = express.Router();
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
import { usersController } from "../../controllers/v3";
router.get(
"/me/api-keys",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
usersController.getMyAPIKeys
);
export default router;
+19 -11
View File
@@ -13,6 +13,7 @@ import {
} from "../models"; } from "../models";
import { createToken } from "../helpers/auth"; import { createToken } from "../helpers/auth";
import { import {
getAuthSecret,
getClientIdGitHubLogin, getClientIdGitHubLogin,
getClientIdGitLabLogin, getClientIdGitLabLogin,
getClientIdGoogleLogin, getClientIdGoogleLogin,
@@ -20,13 +21,12 @@ import {
getClientSecretGitLabLogin, getClientSecretGitLabLogin,
getClientSecretGoogleLogin, getClientSecretGoogleLogin,
getJwtProviderAuthLifetime, getJwtProviderAuthLifetime,
getJwtProviderAuthSecret,
getSiteURL, getSiteURL,
getUrlGitLabLogin getUrlGitLabLogin
} from "../config"; } from "../config";
import { getSSOConfigHelper } from "../ee/helpers/organizations"; import { getSSOConfigHelper } from "../ee/helpers/organizations";
import { InternalServerError, OrganizationNotFoundError } from "./errors"; import { InternalServerError, OrganizationNotFoundError } from "./errors";
import { ACCEPTED, INTEGRATION_GITHUB_API_URL, INVITED, MEMBER } from "../variables"; import { ACCEPTED, AuthTokenType, INTEGRATION_GITHUB_API_URL, INVITED, MEMBER } from "../variables";
import { standardRequest } from "../config/request"; import { standardRequest } from "../config/request";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
@@ -131,6 +131,7 @@ const initializePassport = async () => {
const isUserCompleted = !!user.publicKey; const isUserCompleted = !!user.publicKey;
const providerAuthToken = createToken({ const providerAuthToken = createToken({
payload: { payload: {
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user._id.toString(), userId: user._id.toString(),
email: user.email, email: user.email,
firstName: user.firstName, firstName: user.firstName,
@@ -143,7 +144,7 @@ const initializePassport = async () => {
} : {}) } : {})
}, },
expiresIn: await getJwtProviderAuthLifetime(), expiresIn: await getJwtProviderAuthLifetime(),
secret: await getJwtProviderAuthSecret(), secret: await getAuthSecret(),
}); });
req.isUserCompleted = isUserCompleted; req.isUserCompleted = isUserCompleted;
@@ -204,6 +205,7 @@ const initializePassport = async () => {
const isUserCompleted = !!user.publicKey; const isUserCompleted = !!user.publicKey;
const providerAuthToken = createToken({ const providerAuthToken = createToken({
payload: { payload: {
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user._id.toString(), userId: user._id.toString(),
email: user.email, email: user.email,
firstName: user.firstName, firstName: user.firstName,
@@ -216,7 +218,7 @@ const initializePassport = async () => {
} : {}) } : {})
}, },
expiresIn: await getJwtProviderAuthLifetime(), expiresIn: await getJwtProviderAuthLifetime(),
secret: await getJwtProviderAuthSecret(), secret: await getAuthSecret(),
}); });
req.isUserCompleted = isUserCompleted; req.isUserCompleted = isUserCompleted;
@@ -258,6 +260,7 @@ const initializePassport = async () => {
const isUserCompleted = !!user.publicKey; const isUserCompleted = !!user.publicKey;
const providerAuthToken = createToken({ const providerAuthToken = createToken({
payload: { payload: {
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user._id.toString(), userId: user._id.toString(),
email: user.email, email: user.email,
firstName: user.firstName, firstName: user.firstName,
@@ -270,7 +273,7 @@ const initializePassport = async () => {
} : {}) } : {})
}, },
expiresIn: await getJwtProviderAuthLifetime(), expiresIn: await getJwtProviderAuthLifetime(),
secret: await getJwtProviderAuthSecret(), secret: await getAuthSecret(),
}); });
req.isUserCompleted = isUserCompleted; req.isUserCompleted = isUserCompleted;
@@ -291,8 +294,7 @@ const initializePassport = async () => {
}); });
interface ISAMLConfig { interface ISAMLConfig {
path: string; callbackUrl: string;
callbackURL: string;
entryPoint: string; entryPoint: string;
issuer: string; issuer: string;
cert: string; cert: string;
@@ -301,8 +303,7 @@ const initializePassport = async () => {
} }
const samlConfig: ISAMLConfig = ({ const samlConfig: ISAMLConfig = ({
path: `${await getSiteURL()}/api/v1/sso/saml2/${ssoIdentifier}`, callbackUrl: `${await getSiteURL()}/api/v1/sso/saml2/${ssoIdentifier}`,
callbackURL: `${await getSiteURL()}/api/v1/sso/saml2${ssoIdentifier}`,
entryPoint: ssoConfig.entryPoint, entryPoint: ssoConfig.entryPoint,
issuer: ssoConfig.issuer, issuer: ssoConfig.issuer,
cert: ssoConfig.cert, cert: ssoConfig.cert,
@@ -313,6 +314,12 @@ const initializePassport = async () => {
samlConfig.wantAuthnResponseSigned = false; samlConfig.wantAuthnResponseSigned = false;
} }
if (ssoConfig.authProvider.toString() === AuthMethod.AZURE_SAML.toString()) {
if (req.body.RelayState && JSON.parse(req.body.RelayState).spInitiated) {
samlConfig.audience = `spn:${ssoConfig.issuer}`;
}
}
req.ssoConfig = ssoConfig; req.ssoConfig = ssoConfig;
done(null, samlConfig); done(null, samlConfig);
@@ -397,6 +404,7 @@ const initializePassport = async () => {
const isUserCompleted = !!user.publicKey; const isUserCompleted = !!user.publicKey;
const providerAuthToken = createToken({ const providerAuthToken = createToken({
payload: { payload: {
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user._id.toString(), userId: user._id.toString(),
email: user.email, email: user.email,
firstName, firstName,
@@ -405,11 +413,11 @@ const initializePassport = async () => {
authMethod: req.ssoConfig.authProvider, authMethod: req.ssoConfig.authProvider,
isUserCompleted, isUserCompleted,
...(req.body.RelayState ? { ...(req.body.RelayState ? {
callbackPort: req.body.RelayState as string callbackPort: JSON.parse(req.body.RelayState).callbackPort as string
} : {}) } : {})
}, },
expiresIn: await getJwtProviderAuthLifetime(), expiresIn: await getJwtProviderAuthLifetime(),
secret: await getJwtProviderAuthSecret(), secret: await getAuthSecret(),
}); });
req.isUserCompleted = isUserCompleted; req.isUserCompleted = isUserCompleted;
-3
View File
@@ -55,9 +55,6 @@ export const setup = async () => {
// initializing global feature set // initializing global feature set
await EELicenseService.initGlobalFeatureSet(); await EELicenseService.initGlobalFeatureSet();
// initializing the database connection
await DatabaseService.initDatabase(await getMongoURL());
await initializePassport(); await initializePassport();
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY // re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
+22
View File
@@ -0,0 +1,22 @@
import { z } from "zod";
export const CreateAPIKeyV3 = z.object({
body: z.object({
name: z.string().trim()
})
});
export const UpdateAPIKeyV3 = z.object({
params: z.object({
apiKeyDataId: z.string().trim()
}),
body: z.object({
name: z.string().trim()
})
});
export const DeleteAPIKeyV3 = z.object({
params: z.object({
apiKeyDataId: z.string().trim()
})
});
@@ -0,0 +1,21 @@
import * as z from "zod";
export const ZGetTenantEnv = z.object({
data: z.object({
getTenantEnv: z.object({
hash: z.string(),
envVars: z.object({
environment: z.record(z.any()).optional()
})
})
})
});
export const ZUpdateTenantEnv = z.object({
data: z.object({
updateTenantEnv: z.object({
hash: z.string(),
envVars: z.record(z.any())
})
})
});
+1
View File
@@ -10,3 +10,4 @@ export * from "./secrets";
export * from "./serviceAccount"; export * from "./serviceAccount";
export * from "./serviceTokenData"; export * from "./serviceTokenData";
export * from "./serviceTokenDataV3"; export * from "./serviceTokenDataV3";
export * from "./apiKeyDataV3";
+1
View File
@@ -353,6 +353,7 @@ export const UpdateSecretByNameV3 = z.object({
body: z.object({ body: z.object({
workspaceId: z.string().trim(), workspaceId: z.string().trim(),
environment: z.string().trim(), environment: z.string().trim(),
secretId: z.string().trim().optional(),
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]), type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
secretPath: z.string().trim().default("/"), secretPath: z.string().trim().default("/"),
secretValueCiphertext: z.string().trim(), secretValueCiphertext: z.string().trim(),
+9
View File
@@ -1,3 +1,12 @@
export enum AuthTokenType {
ACCESS_TOKEN = "accessToken",
REFRESH_TOKEN = "refreshToken",
SIGNUP_TOKEN = "signupToken",
MFA_TOKEN = "mfaToken",
PROVIDER_TOKEN = "providerToken",
API_KEY = "apiKey"
}
export enum AuthMode { export enum AuthMode {
JWT = "jwt", JWT = "jwt",
SERVICE_TOKEN = "serviceToken", SERVICE_TOKEN = "serviceToken",
+48 -36
View File
@@ -22,7 +22,7 @@ export const INTEGRATION_GITLAB = "gitlab";
export const INTEGRATION_RENDER = "render"; export const INTEGRATION_RENDER = "render";
export const INTEGRATION_RAILWAY = "railway"; export const INTEGRATION_RAILWAY = "railway";
export const INTEGRATION_FLYIO = "flyio"; export const INTEGRATION_FLYIO = "flyio";
export const INTEGRATION_LARAVELFORGE = "laravel-forge" export const INTEGRATION_LARAVELFORGE = "laravel-forge";
export const INTEGRATION_CIRCLECI = "circleci"; export const INTEGRATION_CIRCLECI = "circleci";
export const INTEGRATION_TRAVISCI = "travisci"; export const INTEGRATION_TRAVISCI = "travisci";
export const INTEGRATION_TEAMCITY = "teamcity"; export const INTEGRATION_TEAMCITY = "teamcity";
@@ -38,6 +38,7 @@ export const INTEGRATION_WINDMILL = "windmill";
export const INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM = "digital-ocean-app-platform"; export const INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM = "digital-ocean-app-platform";
export const INTEGRATION_CLOUD_66 = "cloud-66"; export const INTEGRATION_CLOUD_66 = "cloud-66";
export const INTEGRATION_NORTHFLANK = "northflank"; export const INTEGRATION_NORTHFLANK = "northflank";
export const INTEGRATION_HASURA_CLOUD = "hasura-cloud";
export const INTEGRATION_SET = new Set([ export const INTEGRATION_SET = new Set([
INTEGRATION_GCP_SECRET_MANAGER, INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
@@ -63,7 +64,8 @@ export const INTEGRATION_SET = new Set([
INTEGRATION_BITBUCKET, INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM, INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66, INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK INTEGRATION_NORTHFLANK,
INTEGRATION_HASURA_CLOUD
]); ]);
// integration types // integration types
@@ -71,15 +73,14 @@ export const INTEGRATION_OAUTH2 = "oauth2";
// integration oauth endpoints // integration oauth endpoints
export const INTEGRATION_GCP_TOKEN_URL = "https://oauth2.googleapis.com/token"; export const INTEGRATION_GCP_TOKEN_URL = "https://oauth2.googleapis.com/token";
export const INTEGRATION_AZURE_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token"; export const INTEGRATION_AZURE_TOKEN_URL =
"https://login.microsoftonline.com/common/oauth2/v2.0/token";
export const INTEGRATION_HEROKU_TOKEN_URL = "https://id.heroku.com/oauth/token"; export const INTEGRATION_HEROKU_TOKEN_URL = "https://id.heroku.com/oauth/token";
export const INTEGRATION_VERCEL_TOKEN_URL = export const INTEGRATION_VERCEL_TOKEN_URL = "https://api.vercel.com/v2/oauth/access_token";
"https://api.vercel.com/v2/oauth/access_token";
export const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token"; export const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token";
export const INTEGRATION_GITHUB_TOKEN_URL = export const INTEGRATION_GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
"https://github.com/login/oauth/access_token";
export const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token"; export const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token";
export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token" export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token";
// integration apps endpoints // integration apps endpoints
export const INTEGRATION_GCP_API_URL = "https://cloudresourcemanager.googleapis.com"; export const INTEGRATION_GCP_API_URL = "https://cloudresourcemanager.googleapis.com";
@@ -106,11 +107,13 @@ export const INTEGRATION_WINDMILL_API_URL = "https://app.windmill.dev/api";
export const INTEGRATION_DIGITAL_OCEAN_API_URL = "https://api.digitalocean.com"; export const INTEGRATION_DIGITAL_OCEAN_API_URL = "https://api.digitalocean.com";
export const INTEGRATION_CLOUD_66_API_URL = "https://app.cloud66.com/api"; export const INTEGRATION_CLOUD_66_API_URL = "https://app.cloud66.com/api";
export const INTEGRATION_NORTHFLANK_API_URL = "https://api.northflank.com"; export const INTEGRATION_NORTHFLANK_API_URL = "https://api.northflank.com";
export const INTEGRATION_HASURA_CLOUD_API_URL = "https://data.pro.hasura.io/v1/graphql";
export const INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com" export const INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com";
export const INTEGRATION_GCP_SECRET_MANAGER_URL = `https://${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`; export const INTEGRATION_GCP_SECRET_MANAGER_URL = `https://${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`;
export const INTEGRATION_GCP_SERVICE_USAGE_URL = "https://serviceusage.googleapis.com"; export const INTEGRATION_GCP_SERVICE_USAGE_URL = "https://serviceusage.googleapis.com";
export const INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform"; export const INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE =
"https://www.googleapis.com/auth/cloud-platform";
export const getIntegrationOptions = async () => { export const getIntegrationOptions = async () => {
const INTEGRATION_OPTIONS = [ const INTEGRATION_OPTIONS = [
@@ -121,7 +124,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "oauth", type: "oauth",
clientId: await getClientIdHeroku(), clientId: await getClientIdHeroku(),
docsLink: "", docsLink: ""
}, },
{ {
name: "Vercel", name: "Vercel",
@@ -131,7 +134,7 @@ export const getIntegrationOptions = async () => {
type: "oauth", type: "oauth",
clientId: "", clientId: "",
clientSlug: await getClientSlugVercel(), clientSlug: await getClientSlugVercel(),
docsLink: "", docsLink: ""
}, },
{ {
name: "Netlify", name: "Netlify",
@@ -140,7 +143,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "oauth", type: "oauth",
clientId: await getClientIdNetlify(), clientId: await getClientIdNetlify(),
docsLink: "", docsLink: ""
}, },
{ {
name: "GitHub", name: "GitHub",
@@ -149,7 +152,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "oauth", type: "oauth",
clientId: await getClientIdGitHub(), clientId: await getClientIdGitHub(),
docsLink: "", docsLink: ""
}, },
{ {
name: "Render", name: "Render",
@@ -158,7 +161,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Railway", name: "Railway",
@@ -167,7 +170,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Fly.io", name: "Fly.io",
@@ -176,7 +179,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "AWS Parameter Store", name: "AWS Parameter Store",
@@ -185,7 +188,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "custom", type: "custom",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Laravel Forge", name: "Laravel Forge",
@@ -194,7 +197,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "AWS Secrets Manager", name: "AWS Secrets Manager",
@@ -203,7 +206,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "custom", type: "custom",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Azure Key Vault", name: "Azure Key Vault",
@@ -212,7 +215,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "oauth", type: "oauth",
clientId: await getClientIdAzure(), clientId: await getClientIdAzure(),
docsLink: "", docsLink: ""
}, },
{ {
name: "Circle CI", name: "Circle CI",
@@ -221,7 +224,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "GitLab", name: "GitLab",
@@ -230,7 +233,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "custom", type: "custom",
clientId: await getClientIdGitLab(), clientId: await getClientIdGitLab(),
docsLink: "", docsLink: ""
}, },
{ {
name: "Terraform Cloud", name: "Terraform Cloud",
@@ -239,7 +242,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
cliendId: "", cliendId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Travis CI", name: "Travis CI",
@@ -248,7 +251,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "TeamCity", name: "TeamCity",
@@ -257,7 +260,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Supabase", name: "Supabase",
@@ -266,7 +269,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Checkly", name: "Checkly",
@@ -275,7 +278,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Qovery", name: "Qovery",
@@ -284,7 +287,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "HashiCorp Vault", name: "HashiCorp Vault",
@@ -293,7 +296,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "GCP Secret Manager", name: "GCP Secret Manager",
@@ -329,7 +332,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Windmill", name: "Windmill",
@@ -338,7 +341,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Digital Ocean App Platform", name: "Digital Ocean App Platform",
@@ -347,7 +350,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Cloud 66", name: "Cloud 66",
@@ -356,7 +359,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true, isAvailable: true,
type: "pat", type: "pat",
clientId: "", clientId: "",
docsLink: "", docsLink: ""
}, },
{ {
name: "Northflank", name: "Northflank",
@@ -367,7 +370,16 @@ export const getIntegrationOptions = async () => {
clientId: "", clientId: "",
docsLink: "" docsLink: ""
}, },
] {
name: "Hasura Cloud",
slug: "hasura-cloud",
image: "Hasura.svg",
isAvailable: true,
type: "pat",
clientId: "",
docsLink: ""
}
];
return INTEGRATION_OPTIONS; return INTEGRATION_OPTIONS;
} };
+8 -4
View File
@@ -4,6 +4,7 @@ Copyright (c) 2023 Infisical Inc.
package cmd package cmd
import ( import (
"crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"regexp" "regexp"
@@ -11,8 +12,6 @@ import (
"strings" "strings"
"unicode" "unicode"
"crypto/sha256"
"github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/crypto" "github.com/Infisical/infisical-merge/packages/crypto"
"github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/models"
@@ -441,6 +440,11 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
} }
} }
secretsPath, err := cmd.Flags().GetString("path")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
infisicalToken, err := cmd.Flags().GetString("token") infisicalToken, err := cmd.Flags().GetString("token")
if err != nil { if err != nil {
util.HandleError(err, "Unable to parse flag") util.HandleError(err, "Unable to parse flag")
@@ -451,7 +455,7 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
util.HandleError(err, "Unable to parse flag") util.HandleError(err, "Unable to parse flag")
} }
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs}) secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath})
if err != nil { if err != nil {
util.HandleError(err, "To fetch all secrets") util.HandleError(err, "To fetch all secrets")
} }
@@ -650,8 +654,8 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod
} }
func init() { func init() {
secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token") secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
secretsGenerateExampleEnvCmd.Flags().String("path", "/", "Fetch secrets from within a folder path")
secretsCmd.AddCommand(secretsGenerateExampleEnvCmd) secretsCmd.AddCommand(secretsGenerateExampleEnvCmd)
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token") secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
+3 -29
View File
@@ -1,46 +1,20 @@
version: "3" version: "3"
services: services:
nginx:
container_name: infisical-nginx
image: nginx
restart: always
ports:
- 80:80
- 443:443
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- frontend
- backend
networks:
- infisical
backend: backend:
container_name: infisical-backend container_name: infisical-backend
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
- mongo - mongo
image: infisical/backend image: infisical/infisical:latest
env_file: .env env_file: .env
ports:
- 80:8080
environment: environment:
- NODE_ENV=production - NODE_ENV=production
networks: networks:
- infisical - infisical
frontend:
container_name: infisical-frontend
restart: unless-stopped
depends_on:
- backend
image: infisical/frontend
env_file: .env
environment:
# - NEXT_PUBLIC_POSTHOG_API_KEY=${POSTHOG_PROJECT_API_KEY}
- INFISICAL_TELEMETRY_ENABLED=${TELEMETRY_ENABLED}
networks:
- infisical
redis: redis:
image: redis image: redis
container_name: infisical-dev-redis container_name: infisical-dev-redis
+24
View File
@@ -0,0 +1,24 @@
# Contributing to the documentation
## Getting familiar with Mintlify
New to Mintlify. [Start Here](https://mintlify.com/docs/quickstart)
## 👩‍💻 Development
Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command
```
npm i -g mintlify
```
Run the following command at the root of your documentation (where mint.json is)
```
mintlify dev
```
## Troubleshooting
- Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies.
- Page loads as a 404 - Make sure you are running in a folder with `mint.json`. Check the `/docs` folder
+9 -4
View File
@@ -63,12 +63,17 @@ description: "Configure Azure SAML for Infisical SSO"
7. Get IdP values: 7. Get IdP values:
Back in the **Set up Single Sign-On with SAML** screen, copy the **Login URL**, **Azure AD Identifier** and **SAML Certificate** to use when finishing configuring Azure SAML in Infisical. In the **Set up Single Sign-On with SAML** screen, copy the **Login URL** and **SAML Certificate** to use when finishing configuring Azure SAML in Infisical.
Back in Infisical, set **Login URL** and **Azure AD Identifier** from above. Once you've done that, press **Update** to complete the required configuration. ![Azure SAML identity provider values 1](../../../images/sso/azure/idp-values.png)
![Azure SAML identity provider values](../../../images/sso/azure/idp-values.png) In the **Properties** screen, copy the **Application ID** to use when finishing configuring Azure SAML in Infisical.
![Azure SAML paste identity provider values](../../../images/sso/azure/idp-values-2.png)
![Azure SAML identity provider values 2](../../../images/sso/azure/idp-values-2.png)
Back in Infisical, set **Login URL**, **Azure Application ID**, and **SAML Certificate** from above. Once you've done that, press **Update** to complete the required configuration.
![Azure SAML paste identity provider values](../../../images/sso/azure/idp-values-3.png)
<Note> <Note>
When pasting the certificate into Infisical, you'll want to retain `-----BEGIN When pasting the certificate into Infisical, you'll want to retain `-----BEGIN
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 521 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

+36
View File
@@ -0,0 +1,36 @@
---
title: "Hasura Cloud"
description: "How to sync secrets from Infisical to Hasura Cloud"
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Enter your Hasura Cloud Access Token
Obtain a Hasura Cloud Access Token in My Account > Access Tokens
![integrations hasura cloud tokens](../../images/integrations/hasura-cloud/integrations-hasura-cloud-tokens.png)
Press on the Hasura Cloud tile and input your Hasura Cloud access token to grant Infisical access to your Hasura Cloud account.
![integrations hasura cloud authorization](../../images/integrations/hasura-cloud/integrations-hasura-cloud-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to which Hasura Cloud project and press create integration to start syncing secrets to Hasura Cloud.
![integrations hasura cloud](../../images/integrations/hasura-cloud/integrations-hasura-cloud-create.png)
![integrations hasura cloud](../../images/integrations/hasura-cloud/integrations-hasura-cloud.png)
+5 -10
View File
@@ -141,10 +141,10 @@
"group": "Deployment options", "group": "Deployment options",
"pages": [ "pages": [
"self-hosting/overview", "self-hosting/overview",
"self-hosting/deployment-options/standalone-infisical",
"self-hosting/deployment-options/kubernetes-helm", "self-hosting/deployment-options/kubernetes-helm",
"self-hosting/deployment-options/aws-ec2", "self-hosting/deployment-options/aws-ec2",
"self-hosting/deployment-options/docker-compose", "self-hosting/deployment-options/docker-compose",
"self-hosting/deployment-options/standalone-infisical",
"self-hosting/deployment-options/digital-ocean-marketplace" "self-hosting/deployment-options/digital-ocean-marketplace"
] ]
}, },
@@ -189,9 +189,7 @@
}, },
{ {
"group": "Integrations", "group": "Integrations",
"pages": [ "pages": ["integrations/overview"]
"integrations/overview"
]
}, },
{ {
"group": "Infrastructure Integrations", "group": "Infrastructure Integrations",
@@ -221,9 +219,7 @@
}, },
{ {
"group": "Digital Ocean", "group": "Digital Ocean",
"pages": [ "pages": ["integrations/cloud/digital-ocean-app-platform"]
"integrations/cloud/digital-ocean-app-platform"
]
}, },
"integrations/cloud/heroku", "integrations/cloud/heroku",
"integrations/cloud/vercel", "integrations/cloud/vercel",
@@ -234,6 +230,7 @@
"integrations/cloud/laravel-forge", "integrations/cloud/laravel-forge",
"integrations/cloud/supabase", "integrations/cloud/supabase",
"integrations/cloud/northflank", "integrations/cloud/northflank",
"integrations/cloud/hasura-cloud",
"integrations/cloud/terraform-cloud", "integrations/cloud/terraform-cloud",
"integrations/cloud/teamcity", "integrations/cloud/teamcity",
"integrations/cloud/cloudflare-pages", "integrations/cloud/cloudflare-pages",
@@ -277,9 +274,7 @@
}, },
{ {
"group": "Build Tool Integrations", "group": "Build Tool Integrations",
"pages": [ "pages": ["integrations/build-tools/gradle"]
"integrations/build-tools/gradle"
]
}, },
{ {
"group": "Overview", "group": "Overview",
+3 -46
View File
@@ -3,7 +3,7 @@ title: "All environment variables"
description: "Configure your environment variables when self-hosting Infisical." description: "Configure your environment variables when self-hosting Infisical."
--- ---
## Backend environment variables ## Environment variables
Depending on your chosen self hosted deployment method, you may need to configured at least the required environment variable listed below. Depending on your chosen self hosted deployment method, you may need to configured at least the required environment variable listed below.
Other environment variables are listed below to increase the functionality of your self hosted instance based on your use case. Other environment variables are listed below to increase the functionality of your self hosted instance based on your use case.
@@ -14,39 +14,8 @@ Other environment variables are listed below to increase the functionality of yo
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField> </ParamField>
{" "} <ParamField query="AUTH_SECRET" type="string" default="none" required>
Must be a random 32 byte base64 string. Can be generated with `openssl rand -base64 32`
<ParamField query="JWT_SIGNUP_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex
16`
</ParamField>
{" "}
<ParamField query="JWT_REFRESH_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex
16`
</ParamField>
{" "}
<ParamField query="JWT_AUTH_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex
16`
</ParamField>
{" "}
<ParamField query="JWT_MFA_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex
16`
</ParamField>
{" "}
<ParamField query="JWT_SERVICE_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex
16`
</ParamField> </ParamField>
<ParamField query="MONGO_URL" type="string" default="none" required> <ParamField query="MONGO_URL" type="string" default="none" required>
@@ -140,9 +109,6 @@ Other environment variables are listed below to increase the functionality of yo
</Tab> </Tab>
<Tab title="Auth Integrations"> <Tab title="Auth Integrations">
To integrate with external auth providers, provide value for the related keys To integrate with external auth providers, provide value for the related keys
<ParamField query="JWT_PROVIDER_AUTH_SECRET" type="string" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField>
<ParamField query="CLIENT_ID_GOOGLE_LOGIN" type="string" default="none" optional> <ParamField query="CLIENT_ID_GOOGLE_LOGIN" type="string" default="none" optional>
OAuth2 client ID for Google login OAuth2 client ID for Google login
</ParamField> </ParamField>
@@ -232,12 +198,3 @@ Infisical uses Sentry to report error logs
<ParamField query="TELEMETRY_ENABLED" type="string" default="true" optional></ParamField> <ParamField query="TELEMETRY_ENABLED" type="string" default="true" optional></ParamField>
</Tab> </Tab>
</Tabs> </Tabs>
## Frontend environment variables
<ParamField
query="TELEMETRY_ENABLED"
type="string"
default="true"
optional
></ParamField>
@@ -31,7 +31,7 @@ primary_region = "iad"
MONGO_URL = <> MONGO_URL = <>
[http_service] [http_service]
internal_port = 80 internal_port = 8080
``` ```
@@ -23,40 +23,27 @@ helm repo update
## Add Helm values ## Add Helm values
Create a values.yaml file to configure various installation settings, such as the docker image tags and environment variables for both the frontend and backend. To explore all configurable properties for your values file, [visit this page](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical). Create a values.yaml file to configure various installation settings, such as the docker image tags and environment variables. To explore all configurable properties for your values file, [visit this page](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical).
#### Set image tags #### Set image tags
By default, the application will use the latest tag to retrieve the required Docker images, which may be appropriate for most cases. By default, the application will use the `latest` docker image tag. This is okay for test environments; however, for production deployments it is important to pin your deployment to a particular docker image tag to prevent receiving unintended changes.
However, it's important to specify a particular version of Infisical during installation to prevent any significant updates from disrupting your deployment.
View [properties for frontend and backend](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical#parameters).
<Tip> <Tip>
To find the latest version number of Infisical, follow the links below To find the latest version number of Infisical, click [here](https://hub.docker.com/r/infisical/infisical/tags)
- [frontend Docker image](https://hub.docker.com/r/infisical/frontend/tags)
- [backend Docker image](https://hub.docker.com/r/infisical/backend/tags)
</Tip> </Tip>
```yaml simple-values-example.yaml ```yaml simple-values-example.yaml
frontend:
name: frontend
replicaCount: 2
image:
repository: infisical/frontend
tag: "v0.34.2" # <--- frontend version
pullPolicy: Always
backend: backend:
replicaCount: 2 replicaCount: 2
image: image:
repository: infisical/backend repository: infisical/infisical
tag: "v0.34.2" # <--- backend version tag: "v0.39.5"
pullPolicy: Always pullPolicy: Always
``` ```
#### Configure environment variables #### Configure environment variables
You can configure environment variables for the frontend and backend in your Helm values file under the property `frontendEnvironmentVariables` and `backendEnvironmentVariables` respectively. View configurable [environment variables](../configuration/envars). You can configure environment variables for your instance of Infisical though the Helm values file under the property `backendEnvironmentVariables`. View configurable [environment variables](../configuration/envars).
Infisical requires the following backend environment variables to be defined: _`ENCRYPTION_KEY`_, _`JWT_SIGNUP_SECRET`_, _`JWT_REFRESH_SECRET`_, _`JWT_AUTH_SECRET`_, _`JWT_MFA_SECRET`_ and _`JWT_SERVICE_SECRET`_. Infisical requires the following backend environment variables to be defined: _`ENCRYPTION_KEY`_, _`JWT_SIGNUP_SECRET`_, _`JWT_REFRESH_SECRET`_, _`JWT_AUTH_SECRET`_, _`JWT_MFA_SECRET`_ and _`JWT_SERVICE_SECRET`_.
@@ -87,38 +74,30 @@ Infisical uses Nginx to route external traffic. You can install Nginx along with
... ...
ingress: ingress:
nginx: nginx:
enabled: false #<-- if you would like to install nginx along with Infisical enabled: true #<-- if you would like to install nginx along with Infisical
``` ```
#### Database #### Database
Infisical uses a document database as its persistence layer. With this Helm chart, you spin up a MongoDB instance power by Bitnami along side other Infisical services in your cluster. Infisical uses a MongoDB as its persistence layer. With this Helm chart, a MongoDB instance is automatically spun up for use with Infisical.
When persistence is enabled, the data will be stored as Kubernetes Persistence Volume. View all [properties for mongodb](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical). When persistence is enabled, the data will be stored as Kubernetes Persistence Volume. View all [properties for mongodb](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical).
```yaml simple-values-example.yaml ```yaml simple-values-example.yaml
mongodb: mongodb:
enabled: false enabled: true
persistence: persistence:
enabled: false enabled: false
``` ```
To increase data redundancy, we recommend that you use a managed document database service such as AWS Document DB, MongoDB or similar services instead. To achieve high availability and data redundancy, we recommend that you use a managed document database service such as AWS Document DB, MongoDB or similar services instead of the in cluster database.
Managed database connection string can be set in the `backendEnvironmentVariables`. Managed database connection string can be set in the `backendEnvironmentVariables`.
#### Example helm values #### Example helm values
```yaml simple-values-example.yaml ```yaml simple-values-example.yaml
frontend:
name: frontend
replicaCount: 2
image:
repository: infisical/frontend
tag: "v0.34.2" # <--- frontend version
pullPolicy: Always
backend: backend:
replicaCount: 2 replicaCount: 2
image: image:
repository: infisical/backend repository: infisical/infisical
tag: "v0.34.2" # <--- backend version tag: "v0.39.5"
pullPolicy: Always pullPolicy: Always
backendEnvironmentVariables: backendEnvironmentVariables:
@@ -126,7 +105,7 @@ backendEnvironmentVariables:
ingress: ingress:
nginx: nginx:
enabled: true #<-- if you would like to install nginx along with Infisical enabled: true
``` ```
@@ -136,22 +115,6 @@ ingress:
nginx: nginx:
enabled: true enabled: true
frontend:
enabled: true
name: frontend
podAnnotations: {}
deploymentAnnotations: {}
replicaCount: 4
image:
repository: infisical/frontend
tag: "v0.34.2" # <--- frontend version
pullPolicy: IfNotPresent
kubeSecretRef: null
service:
annotations: {}
type: ClusterIP
nodePort: ""
backend: backend:
enabled: true enabled: true
name: backend name: backend
@@ -159,8 +122,8 @@ ingress:
deploymentAnnotations: {} deploymentAnnotations: {}
replicaCount: 4 replicaCount: 4
image: image:
repository: infisical/backend repository: infisical/infisical
tag: "v0.34.2" # <--- backend version tag: "v0.39.5"
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
kubeSecretRef: null kubeSecretRef: null
service: service:
@@ -176,26 +139,9 @@ ingress:
## Mongo DB persistence ## Mongo DB persistence
mongodb: mongodb:
enabled: false
persistence:
enabled: false
ingress:
enabled: true enabled: true
annotations: persistence:
cert-manager.io/cluster-issuer: "letsencrypt-prod" # <-- if you are setting up HTTPS enabled: true
hostName: app.infisical.com ## <- Replace with your own domain
frontend:
path: /
pathType: Prefix
backend:
path: /api
pathType: Prefix
tls: # <-- if you are setting up HTTPS
- secretName: echo-tls
hosts:
- app.infisical.com
``` ```
</Accordion> </Accordion>
@@ -3,11 +3,15 @@ title: "Docker"
description: "Learn to install Infisical purely on docker" description: "Learn to install Infisical purely on docker"
--- ---
The Infisical standalone version combines all the essential components of the application into a single container, making deployment and management more straightforward than using Kubernetes or Docker Compose. The Infisical standalone version combines all the essential components into a single container, making deployment and management more straightforward than other methods.
Since all the components are bundled into one image, running this version of Infisical requires a minimum of **230MB of memory**. ## Prerequisites
This guide assumes you have basic knowledge of Docker and have it installed on your system. If you don't have Docker installed, please follow the official installation guide: https://docs.docker.com/get-docker/ This guide assumes you have basic knowledge of Docker and have it installed on your system. If you don't have Docker installed, please follow the official installation guide [here](https://docs.docker.com/get-docker/).
#### System requirements
To have a functional deployment, we recommended compute with **2GB of RAM** and **1 CPU**.
However, depending on your usage, you may need to further scale up system resources to meet demand.
## Pull the Infisical Docker image ## Pull the Infisical Docker image
@@ -18,59 +22,39 @@ docker pull infisical/infisical:latest
``` ```
## Run with docker ## Run with docker
The Infisical Docker image requires several required environment variables. To run Infisical, we'll need to configure the required configs listed below.
Add the required environment variables listed below to your docker run command. View [all configurable environment variables](../configuration/envars) Other configs can be found [here](../configuration/envars)
<ParamField query="ENCRYPTION_KEY" type="string" default="none" required> <ParamField query="ENCRYPTION_KEY" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField> </ParamField>
<ParamField query="JWT_SIGNUP_SECRET" type="string" default="none" required> <ParamField query="AUTH_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField> </ParamField>
<ParamField query="JWT_REFRESH_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField>
<ParamField query="JWT_AUTH_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField>
<ParamField query="JWT_MFA_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField>
<ParamField query="JWT_SERVICE_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField>
<ParamField query="REDIS_URL" type="string" default="none" required>
Redis connection string
</ParamField>
<ParamField query="MONGO_URL" type="string" default="none" required> <ParamField query="MONGO_URL" type="string" default="none" required>
A MongoDB connection string. Can use any MongoDB PaaS such as Mongo Atlas, AWS Document DB, etc.
*TLS based connection string is not yet supported *TLS based connection string is not yet supported
</ParamField> </ParamField>
<ParamField query="REDIS_URL" type="string" default="none">
Redis connection string. Only required if you plan to use web integrations.
</ParamField>
Once you have added the required environment variables to your docker run command, execute it in your terminal. Once you have added the required environment variables to your docker run command, execute it in your terminal.
```bash ```bash
docker run -p 80:80 \ docker run -p 80:8080 \
-e ENCRYPTION_KEY=f40c9178624764ad85a6830b37ce239a \ -e ENCRYPTION_KEY=f40c9178624764ad85a6830b37ce239a \
-e JWT_SIGNUP_SECRET=38ea90fb7998b92176080f457d890392 \ -e AUTH_SECRET=5239fea3a4720c0e524f814a540e14a2 \
-e JWT_REFRESH_SECRET=7764c7bbf3928ad501591a3e005eb364 \
-e JWT_AUTH_SECRET=5239fea3a4720c0e524f814a540e14a2 \
-e JWT_SERVICE_SECRET=8509fb8b90c9b53e9e61d1e35826dcb5 \
-e MONGO_URL="<>" \ -e MONGO_URL="<>" \
-e REDIS_URL="<>" \
infisical/infisical:latest infisical/infisical:latest
``` ```
<Warning> <Warning>
The sample environment variables listed above are only to be used as an example and should not be used in production The above environment variable values are only to be used as an example and should not be used in production
</Warning> </Warning>
## Verify the installation: ## Verify the installation:
+8 -8
View File
@@ -8,11 +8,11 @@ Self-hosted Infisical allows you to maintain your sensitive information within y
Choose from a variety of deployment options listed below to get started. Choose from a variety of deployment options listed below to get started.
<Card <Card
title="Kubernetes (recommended)" title="Docker"
color="#ea5a0c" color="#0285c7"
href="deployment-options/kubernetes-helm" href="deployment-options/standalone-infisical"
> >
Use our Helm chart to Install Infisical on your Kubernetes cluster Use the fully packaged docker image to deploy Infisical anywhere
</Card> </Card>
<CardGroup cols={2}> <CardGroup cols={2}>
<Card <Card
@@ -33,10 +33,10 @@ Choose from a variety of deployment options listed below to get started.
Install Infisical using our Docker Compose template Install Infisical using our Docker Compose template
</Card> </Card>
<Card <Card
title="Docker" title="Kubernetes"
color="#0285c7" color="#ea5a0c"
href="deployment-options/standalone-infisical" href="deployment-options/kubernetes-helm"
> >
Use the fully packaged, single docker image Infisical to deploy anywhere Use our Helm chart to Install Infisical on your Kubernetes cluster
</Card> </Card>
</CardGroup> </CardGroup>
-32
View File
@@ -1,32 +0,0 @@
module.exports = {
apps: [
{
name: 'frontend',
script: "./scripts/start.sh",
instances: 1,
cwd: "./app",
interpreter: 'sh',
exec_mode: "fork",
autorestart: true,
watch: false,
max_memory_restart: '500M',
},
{
name: 'backend',
script: 'npm',
args: 'run start',
cwd: "./backend",
instances: 1,
exec_mode: "fork",
autorestart: true,
watch: false,
max_memory_restart: '500M',
},
{
name: "nginx",
script: "nginx",
args: "-g 'daemon off;'",
exec_interpreter: "none",
},
],
};
+1 -1
View File
@@ -8,7 +8,7 @@ module.exports = {
env: { env: {
browser: true, browser: true,
es2021: true, es2021: true,
"es6": true es6: true
}, },
extends: [ extends: [
"airbnb", "airbnb",
+3 -5
View File
@@ -1,8 +1,4 @@
// @ts-check
/**
* @type {import('next').NextConfig}
**/
const path = require("path"); const path = require("path");
const ContentSecurityPolicy = ` const ContentSecurityPolicy = `
@@ -53,7 +49,9 @@ const securityHeaders = [
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim() value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim()
} }
]; ];
/**
* @type {import('next').NextConfig}
**/
module.exports = { module.exports = {
output: "standalone", output: "standalone",
i18n: { i18n: {
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "npm-proj-1695919945735-0.225773463026700768rr1Oh", "name": "frontend",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
+40 -38
View File
@@ -6,74 +6,75 @@ const integrationSlugNameMapping: Mapping = {
"azure-key-vault": "Azure Key Vault", "azure-key-vault": "Azure Key Vault",
"aws-parameter-store": "AWS Parameter Store", "aws-parameter-store": "AWS Parameter Store",
"aws-secret-manager": "AWS Secrets Manager", "aws-secret-manager": "AWS Secrets Manager",
"heroku": "Heroku", heroku: "Heroku",
"vercel": "Vercel", vercel: "Vercel",
"netlify": "Netlify", netlify: "Netlify",
"github": "GitHub", github: "GitHub",
"gitlab": "GitLab", gitlab: "GitLab",
"render": "Render", render: "Render",
"laravel-forge": "Laravel Forge", "laravel-forge": "Laravel Forge",
"railway": "Railway", railway: "Railway",
"flyio": "Fly.io", flyio: "Fly.io",
"circleci": "CircleCI", circleci: "CircleCI",
"travisci": "TravisCI", travisci: "TravisCI",
"supabase": "Supabase", supabase: "Supabase",
"checkly": "Checkly", checkly: "Checkly",
"qovery": "Qovery", qovery: "Qovery",
"terraform-cloud": "Terraform Cloud", "terraform-cloud": "Terraform Cloud",
"teamcity": "TeamCity", teamcity: "TeamCity",
"hashicorp-vault": "Vault", "hashicorp-vault": "Vault",
"cloudflare-pages": "Cloudflare Pages", "cloudflare-pages": "Cloudflare Pages",
"codefresh": "Codefresh", codefresh: "Codefresh",
"digital-ocean-app-platform": "Digital Ocean App Platform", "digital-ocean-app-platform": "Digital Ocean App Platform",
"bitbucket": "BitBucket", bitbucket: "BitBucket",
"cloud-66": "Cloud 66", "cloud-66": "Cloud 66",
"northflank": "Northflank", northflank: "Northflank",
"windmill": "Windmill", windmill: "Windmill",
"gcp-secret-manager": "GCP Secret Manager" "gcp-secret-manager": "GCP Secret Manager",
} "hasura-cloud": "Hasura Cloud"
};
const envMapping: Mapping = { const envMapping: Mapping = {
Development: "dev", Development: "dev",
Staging: "staging", Staging: "staging",
Production: "prod", Production: "prod",
Testing: "test", Testing: "test"
}; };
const reverseEnvMapping: Mapping = { const reverseEnvMapping: Mapping = {
dev: "Development", dev: "Development",
staging: "Staging", staging: "Staging",
prod: "Production", prod: "Production",
test: "Testing", test: "Testing"
}; };
const contextNetlifyMapping: Mapping = { const contextNetlifyMapping: Mapping = {
"dev": "Local development", dev: "Local development",
"branch-deploy": "Branch deploys", "branch-deploy": "Branch deploys",
"deploy-preview": "Deploy Previews", "deploy-preview": "Deploy Previews",
"production": "Production" production: "Production"
} };
const reverseContextNetlifyMapping: Mapping = { const reverseContextNetlifyMapping: Mapping = {
"Local development": "dev", "Local development": "dev",
"Branch deploys": "branch-deploy", "Branch deploys": "branch-deploy",
"Deploy Previews": "deploy-preview", "Deploy Previews": "deploy-preview",
"Production": "production" Production: "production"
} };
const plansDev: Mapping = { const plansDev: Mapping = {
"starter": "prod_Mb4ATFT5QAHoPM", starter: "prod_Mb4ATFT5QAHoPM",
"team": "prod_NEpD2WMXUS2eDn", team: "prod_NEpD2WMXUS2eDn",
"professional": "prod_Mb4CetZ2jE7jdl", professional: "prod_Mb4CetZ2jE7jdl",
"enterprise": "licence_key_required" enterprise: "licence_key_required"
} };
const plansProd: Mapping = { const plansProd: Mapping = {
"starter": "prod_Mb8oR5XNwyFTul", starter: "prod_Mb8oR5XNwyFTul",
"team": "prod_NEp7fAB3UJWK6A", team: "prod_NEp7fAB3UJWK6A",
"professional": "prod_Mb8pUIpA0OUi5N", professional: "prod_Mb8pUIpA0OUi5N",
"enterprise": "licence_key_required" enterprise: "licence_key_required"
} };
const plans = plansProd || plansDev; const plans = plansProd || plansDev;
@@ -83,4 +84,5 @@ export {
integrationSlugNameMapping, integrationSlugNameMapping,
plans, plans,
reverseContextNetlifyMapping, reverseContextNetlifyMapping,
reverseEnvMapping} reverseEnvMapping
};
@@ -0,0 +1,11 @@
<svg width="81" height="84" viewBox="-20 -20 121 124" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_5273_21928)">
<path d="M79.7186 28.6019C82.1218 21.073 80.6778 6.03601 76.0158 0.487861C75.4073 -0.238064 74.2624 -0.134361 73.757 0.664158L68.0121 9.72786C66.5887 11.5427 64.0308 11.9575 62.1124 10.6923C55.8827 6.59601 48.4359 4.21082 40.4322 4.21082C32.4285 4.21082 24.9817 6.59601 18.752 10.6923C16.8336 11.9575 14.2757 11.5323 12.8523 9.72786L7.10738 0.664158C6.60199 -0.134361 5.45712 -0.238064 4.84859 0.487861C0.186621 6.03601 -1.25735 21.073 1.14583 28.6019C1.94002 31.1012 2.16693 33.7456 1.69248 36.3279C1.22834 38.879 0.753897 41.9693 0.753897 44.1056C0.753897 66.1323 18.5251 84.0004 40.4322 84.0004C62.3497 84.0004 80.1105 66.1427 80.1105 44.1056C80.1105 41.959 79.6464 38.879 79.1719 36.3279C78.6975 33.7456 78.9244 31.1012 79.7186 28.6019ZM40.4322 75.0819C23.4965 75.0819 9.71684 61.2271 9.71684 44.199C9.71684 43.639 9.73747 43.0893 9.7581 42.5397C10.3769 30.9353 17.3802 21.0108 27.3024 16.2819C31.2836 14.3738 35.7393 13.316 40.4322 13.316C45.1251 13.316 49.5808 14.3842 53.5724 16.2923C63.4945 21.0212 70.4978 30.9456 71.1166 42.5397C71.1476 43.0893 71.1579 43.639 71.1579 44.199C71.1476 61.2271 57.3679 75.0819 40.4322 75.0819Z" fill="#1EB4D4"/>
<path d="M53.7371 56.083L45.8881 42.4044L39.153 30.997C38.9983 30.7274 38.7095 30.5615 38.3898 30.5615H31.9538C31.634 30.5615 31.3452 30.7378 31.1905 31.0074C31.0358 31.2874 31.0358 31.6296 31.2008 31.8993L37.6368 42.7881L28.9936 56.0415C28.8183 56.3111 28.7977 56.6637 28.9524 56.9541C29.1071 57.2444 29.4062 57.4207 29.7259 57.4207H36.2032C36.5023 57.4207 36.7808 57.2652 36.9458 57.0163L41.6181 49.6741L45.8056 56.9748C45.9603 57.2548 46.2594 57.4207 46.5688 57.4207H52.9533C53.273 57.4207 53.5618 57.2548 53.7165 56.9748C53.9022 56.6948 53.9022 56.363 53.7371 56.083Z" fill="#1EB4D4"/>
</g>
<defs>
<clipPath id="clip0_5273_21928">
<rect width="81" height="84" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
scripts/replace-standalone-build-variable.sh "$BAKED_NEXT_PUBLIC_POSTHOG_API_KEY" "$NEXT_PUBLIC_POSTHOG_API_KEY"
scripts/replace-standalone-build-variable.sh "$BAKED_NEXT_PUBLIC_INTERCOM_ID" "$NEXT_PUBLIC_INTERCOM_ID"
if [ "$TELEMETRY_ENABLED" != "false" ]; then
echo "Telemetry is enabled"
scripts/set-standalone-build-telemetry.sh true
else
echo "Client opted out of telemetry"
scripts/set-standalone-build-telemetry.sh false
fi
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
ORIGINAL=$1
REPLACEMENT=$2
if [ "${ORIGINAL}" = "${REPLACEMENT}" ]; then
echo "Environment variable replacement is the same, skipping.."
exit 0
fi
echo "Replacing pre-baked value.."
find public .next -type f -name "*.js" |
while read file; do
sed -i "s|$ORIGINAL|$REPLACEMENT|g" "$file"
done
View File
@@ -0,0 +1,8 @@
#!/bin/sh
VALUE=$1
find public .next -type f -name "*.js" |
while read file; do
sed -i "s|TELEMETRY_CAPTURING_ENABLED|$VALUE|g" "$file"
done
View File
+1 -1
View File
@@ -10,7 +10,7 @@ export const initPostHog = () => {
try { try {
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
// @ts-ignore // @ts-ignore
if (ENV === "production" && TELEMETRY_CAPTURING_ENABLED) { if (ENV === "production" && TELEMETRY_CAPTURING_ENABLED === "true") {
posthog.init(POSTHOG_API_KEY, { posthog.init(POSTHOG_API_KEY, {
api_host: POSTHOG_HOST api_host: POSTHOG_HOST
}); });
@@ -13,7 +13,7 @@ class Capturer {
} }
capture(item: string) { capture(item: string) {
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED) { if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED === "true") {
try { try {
this.api.capture(item); this.api.capture(item);
} catch (error) { } catch (error) {
@@ -23,7 +23,7 @@ class Capturer {
} }
identify(id: string, email?: string) { identify(id: string, email?: string) {
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED) { if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED === "true") {
try { try {
this.api.identify(id, { this.api.identify(id, {
email: email email: email
@@ -0,0 +1,61 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import type { Meta, StoryObj } from "@storybook/react";
import { Alert, AlertDescription } from "./Alert";
const meta: Meta<typeof Alert> = {
title: "Components/Alert",
component: Alert,
tags: ["v2"]
};
export default meta;
type Story = StoryObj<typeof Alert>;
const ExampleComponent = () => <AlertDescription>this is a description</AlertDescription>;
export const Default: Story = {
args: {
children: <ExampleComponent />
}
};
export const Warning: Story = {
args: {
children: <ExampleComponent />,
variant: "warning"
}
};
export const Danger: Story = {
args: {
children: <ExampleComponent />,
variant: "danger"
}
};
export const WithCustomIcon: Story = {
args: {
children: <ExampleComponent />,
variant: "warning",
icon: <FontAwesomeIcon icon={faPlus} />
}
};
export const WithOutIcon: Story = {
args: {
children: <ExampleComponent />,
variant: "warning",
icon: null
}
};
export const WithOutTitle: Story = {
args: {
children: <ExampleComponent />,
variant: "warning",
hideTitle: true
}
};
@@ -0,0 +1,85 @@
import { forwardRef } from "react";
import {
faExclamationCircle,
faExclamationTriangle,
faInfoCircle
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { type VariantProps, cva } from "cva";
import { twMerge } from "tailwind-merge";
const alertVariants = cva(
"w-full bg-mineshaft-800 rounded-lg border px-4 py-3 text-sm flex items-center gap-x-4",
{
variants: {
variant: {
default: "",
danger: "text-red border-red",
warning: "text-yellow border-yellow"
}
},
defaultVariants: {
variant: "default"
}
}
);
type AlertProps = {
title?: string;
hideTitle?: boolean;
icon?: React.ReactNode;
};
const variantTitleMap = {
default: "Info",
danger: "Danger",
warning: "Warning"
};
const variantIconMap = {
default: faInfoCircle,
danger: faExclamationCircle,
warning: faExclamationTriangle
};
const Alert = forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants> & AlertProps
>(({ className, variant, title, icon, hideTitle = false, children, ...props }, ref) => {
const defaultTitle = title ?? variantTitleMap[variant ?? "default"];
return (
<div
ref={ref}
role="alert"
className={twMerge(alertVariants({ variant }), className)}
{...props}
>
<div>
{typeof icon !== "undefined" ? (
<>{icon} </>
) : (
<FontAwesomeIcon className="text-lg" icon={variantIconMap[variant ?? "default"]} />
)}
</div>
<div className="flex flex-col gap-y-1">
{hideTitle ? null : (
<h5 className="font-medium leading-none tracking-tight" {...props}>
{defaultTitle}
</h5>
)}
{children}
</div>
</div>
);
});
Alert.displayName = "Alert";
const AlertDescription = forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={twMerge("text-sm [&_p]:leading-relaxed", className)} {...props} />
));
AlertDescription.displayName = "AlertDescription";
export { Alert, AlertDescription };
@@ -0,0 +1 @@
export { Alert, AlertDescription } from "./Alert";
+1
View File
@@ -1,4 +1,5 @@
export * from "./Accordion"; export * from "./Accordion";
export * from "./Alert";
export * from "./Button"; export * from "./Button";
export * from "./Card"; export * from "./Card";
export * from "./Checkbox"; export * from "./Checkbox";
+4
View File
@@ -0,0 +1,4 @@
export {
useCreateAPIKeyV2,
useDeleteAPIKeyV2,
useUpdateAPIKeyV2} from "./queries";
@@ -0,0 +1,62 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { userKeys } from "../users/queries";
import {
APIKeyDataV2,
CreateAPIKeyDataV2DTO,
CreateServiceTokenDataV3Res,
DeleteAPIKeyDataV2DTO,
UpdateAPIKeyDataV2DTO} from "./types";
export const useCreateAPIKeyV2 = () => {
const queryClient = useQueryClient();
return useMutation<CreateServiceTokenDataV3Res, {}, CreateAPIKeyDataV2DTO>({
mutationFn: async ({
name
}) => {
const { data } = await apiRequest.post("/api/v3/api-key", {
name
});
return data;
},
onSuccess: () => {
queryClient.invalidateQueries(userKeys.myAPIKeysV2);
}
});
};
export const useUpdateAPIKeyV2 = () => {
const queryClient = useQueryClient();
return useMutation<APIKeyDataV2, {}, UpdateAPIKeyDataV2DTO>({
mutationFn: async ({
apiKeyDataId,
name
}) => {
const { data: { apiKeyData } } = await apiRequest.patch(`/api/v3/api-key/${apiKeyDataId}`, {
name
});
return apiKeyData;
},
onSuccess: () => {
queryClient.invalidateQueries(userKeys.myAPIKeysV2);
}
});
};
export const useDeleteAPIKeyV2 = () => {
const queryClient = useQueryClient();
return useMutation<APIKeyDataV2, {}, DeleteAPIKeyDataV2DTO>({
mutationFn: async ({
apiKeyDataId
}) => {
const { data: { apiKeyData } } = await apiRequest.delete(`/api/v3/api-key/${apiKeyDataId}`);
return apiKeyData;
},
onSuccess: () => {
queryClient.invalidateQueries(userKeys.myAPIKeysV2);
}
});
};
+27
View File
@@ -0,0 +1,27 @@
export type APIKeyDataV2 = {
_id: string;
name: string;
user: string;
lastUsed?: string;
usageCount: number;
createdAt: string;
updatedAt: string;
};
export type CreateAPIKeyDataV2DTO = {
name: string;
}
export type CreateServiceTokenDataV3Res = {
apiKeyData: APIKeyDataV2;
apiKey: string;
}
export type UpdateAPIKeyDataV2DTO = {
apiKeyDataId: string;
name: string;
}
export type DeleteAPIKeyDataV2DTO = {
apiKeyDataId: string;
}
+1
View File
@@ -1,3 +1,4 @@
export * from "./apiKeys";
export * from "./auditLogs"; export * from "./auditLogs";
export * from "./auth"; export * from "./auth";
export * from "./bots"; export * from "./bots";
@@ -131,6 +131,7 @@ export const useUpdateSecretV3 = ({
mutationFn: async ({ mutationFn: async ({
secretPath = "/", secretPath = "/",
type, type,
secretId,
environment, environment,
workspaceId, workspaceId,
secretName, secretName,
@@ -157,6 +158,7 @@ export const useUpdateSecretV3 = ({
environment, environment,
type, type,
secretPath, secretPath,
secretId,
...encryptSecret(randomBytes, newSecretName ?? secretName, secretValue, secretComment), ...encryptSecret(randomBytes, newSecretName ?? secretName, secretValue, secretComment),
tags, tags,
skipMultilineEncoding, skipMultilineEncoding,
+1
View File
@@ -109,6 +109,7 @@ export type TUpdateSecretsV3DTO = {
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
newSecretName?: string; newSecretName?: string;
secretName: string; secretName: string;
secretId?: string;
secretValue: string; secretValue: string;
secretComment?: string; secretComment?: string;
tags?: string[]; tags?: string[];
@@ -4,4 +4,5 @@ export {
useDeleteServiceToken, useDeleteServiceToken,
useDeleteServiceTokenV3, useDeleteServiceTokenV3,
useGetUserWsServiceTokens, useGetUserWsServiceTokens,
useUpdateServiceTokenV3} from "./queries"; useUpdateServiceTokenV3
} from "./queries";
+1
View File
@@ -7,6 +7,7 @@ export {
useDeleteOrgMembership, useDeleteOrgMembership,
useDeleteUser, useDeleteUser,
useGetMyAPIKeys, useGetMyAPIKeys,
useGetMyAPIKeysV2,
useGetMyIp, useGetMyIp,
useGetMyOrganizationProjects, useGetMyOrganizationProjects,
useGetMySessions, useGetMySessions,
+17 -4
View File
@@ -7,6 +7,7 @@ import {
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { setAuthToken } from "@app/reactQuery"; import { setAuthToken } from "@app/reactQuery";
import { APIKeyDataV2 } from "../apiKeys/types";
import { useUploadWsKey } from "../keys/queries"; import { useUploadWsKey } from "../keys/queries";
import { workspaceKeys } from "../workspace/queries"; import { workspaceKeys } from "../workspace/queries";
import { import {
@@ -24,12 +25,13 @@ import {
User User
} from "./types"; } from "./types";
const userKeys = { export const userKeys = {
getUser: ["user"] as const, getUser: ["user"] as const,
userAction: ["user-action"] as const, userAction: ["user-action"] as const,
getOrgUsers: (orgId: string) => [{ orgId }, "user"], getOrgUsers: (orgId: string) => [{ orgId }, "user"],
myIp: ["ip"] as const, myIp: ["ip"] as const,
myAPIKeys: ["api-keys"] as const, myAPIKeys: ["api-keys"] as const,
myAPIKeysV2: ["api-keys-v2"] as const,
mySessions: ["sessions"] as const, mySessions: ["sessions"] as const,
myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const myOrganizationProjects: (orgId: string) => [{ orgId }, "organization-projects"] as const
}; };
@@ -270,7 +272,7 @@ export const useGetMyIp = () => {
}); });
}; };
export const useGetMyAPIKeys = () => { export const useGetMyAPIKeys = () => { // TODO: deprecate (moving to API Key V2)
return useQuery({ return useQuery({
queryKey: userKeys.myAPIKeys, queryKey: userKeys.myAPIKeys,
queryFn: async () => { queryFn: async () => {
@@ -281,7 +283,18 @@ export const useGetMyAPIKeys = () => {
}); });
}; };
export const useCreateAPIKey = () => { export const useGetMyAPIKeysV2 = () => {
return useQuery({
queryKey: userKeys.myAPIKeysV2,
queryFn: async () => {
const { data: { apiKeyData } } = await apiRequest.get<{ apiKeyData: APIKeyDataV2[] }>("/api/v3/users/me/api-keys");
return apiKeyData;
},
enabled: true
});
};
export const useCreateAPIKey = () => { // TODO: deprecate (moving to API Key V2)
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ name, expiresIn }: { name: string; expiresIn: number }) => { mutationFn: async ({ name, expiresIn }: { name: string; expiresIn: number }) => {
@@ -298,7 +311,7 @@ export const useCreateAPIKey = () => {
}); });
}; };
export const useDeleteAPIKey = () => { export const useDeleteAPIKey = () => { // TODO: deprecate (moving to API Key V2)
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async (apiKeyDataId: string) => { mutationFn: async (apiKeyDataId: string) => {
+1 -1
View File
@@ -726,7 +726,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
{infisicalPlatformVersion && ( {infisicalPlatformVersion && (
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200"> <div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" /> <FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
Platform Version: {infisicalPlatformVersion} Version: {infisicalPlatformVersion}
</div> </div>
)} )}
</div> </div>
@@ -41,7 +41,6 @@ export default function FlyioAuthorizeIntegrationPage() {
const onFormSubmit = async ({ const onFormSubmit = async ({
accessToken accessToken
}: FormData) => { }: FormData) => {
console.log("onFormSubmit accessToken: ", accessToken);
try { try {
setIsLoading(true); setIsLoading(true);

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