Merge branch 'Infisical:main' into feat/create-multiple-orgs-under-same-account
@@ -1,2 +1,10 @@
|
||||
backend/node_modules
|
||||
frontend/node_modules
|
||||
frontend/node_modules
|
||||
backend/frontend-build
|
||||
**/node_modules
|
||||
**/.next
|
||||
.dockerignore
|
||||
.git
|
||||
README.md
|
||||
.dockerignore
|
||||
**/Dockerfile
|
||||
|
||||
29
.env.example
@@ -1,24 +1,12 @@
|
||||
# Keys
|
||||
# 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
|
||||
|
||||
# JWT
|
||||
# Required secrets to sign JWT tokens
|
||||
JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a
|
||||
JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff
|
||||
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=
|
||||
# THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION
|
||||
AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE=
|
||||
|
||||
# MongoDB
|
||||
# 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_PROJECT_API_KEY=
|
||||
|
||||
CLIENT_ID_GOOGLE=
|
||||
CLIENT_SECRET_GOOGLE=
|
||||
# SSO-specific variables
|
||||
CLIENT_ID_GOOGLE_LOGIN=
|
||||
CLIENT_SECRET_GOOGLE_LOGIN=
|
||||
|
||||
CLIENT_ID_GITHUB_LOGIN=
|
||||
CLIENT_SECRET_GITHUB_LOGIN=
|
||||
|
||||
CLIENT_ID_GITLAB_LOGIN=
|
||||
CLIENT_SECRET_GITLAB_LOGIN=
|
||||
|
||||
2
.github/resources/docker-compose.be-test.yml
vendored
@@ -6,7 +6,7 @@ services:
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- mongo
|
||||
image: infisical/backend:test
|
||||
image: infisical/infisical:test
|
||||
command: npm run start
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
|
||||
36
.github/values.yaml
vendored
@@ -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:
|
||||
enabled: true
|
||||
name: backend
|
||||
@@ -32,7 +6,7 @@ backend:
|
||||
secrets.infisical.com/auto-reload: "true"
|
||||
replicaCount: 2
|
||||
image:
|
||||
repository: infisical/staging_deployment_backend
|
||||
repository: infisical/staging_infisical
|
||||
tag: "latest"
|
||||
pullPolicy: Always
|
||||
kubeSecretRef: managed-backend-secret
|
||||
@@ -63,14 +37,8 @@ ingress:
|
||||
enabled: true
|
||||
# annotations:
|
||||
# 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
|
||||
frontend:
|
||||
path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
path: /api
|
||||
pathType: Prefix
|
||||
tls:
|
||||
[]
|
||||
# - secretName: letsencrypt-nginx
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||
load: true
|
||||
context: backend
|
||||
tags: infisical/backend:test
|
||||
tags: infisical/infisical:test
|
||||
- name: ⏻ Spawn backend container and dependencies
|
||||
run: |
|
||||
docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
|
||||
|
||||
71
.github/workflows/build-staging-img.yml
vendored
@@ -2,7 +2,7 @@ name: Build, Publish and Deploy to Gamma
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
backend-image:
|
||||
infisical-image:
|
||||
name: Build backend image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -32,8 +32,9 @@ jobs:
|
||||
project: 64mmf0n610
|
||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||
load: true
|
||||
context: backend
|
||||
tags: infisical/backend:test
|
||||
context: .
|
||||
file: Dockerfile.standalone-infisical
|
||||
tags: infisical/infisical:test
|
||||
- name: ⏻ Spawn backend container and dependencies
|
||||
run: |
|
||||
docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
|
||||
@@ -49,68 +50,20 @@ jobs:
|
||||
project: 64mmf0n610
|
||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||
push: true
|
||||
context: backend
|
||||
context: .
|
||||
file: Dockerfile.standalone-infisical
|
||||
tags: |
|
||||
infisical/staging_deployment_backend:${{ steps.commit.outputs.short }}
|
||||
infisical/staging_deployment_backend:latest
|
||||
infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||
infisical/staging_infisical:latest
|
||||
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:
|
||||
name: Deploy to gamma
|
||||
runs-on: ubuntu-latest
|
||||
needs: [frontend-image, backend-image]
|
||||
needs: [infisical-image]
|
||||
steps:
|
||||
- name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
|
||||
@@ -73,3 +73,6 @@ jobs:
|
||||
infisical/infisical:${{ steps.extract_version.outputs.version }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: Dockerfile.standalone-infisical
|
||||
build-args: |
|
||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||
INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
||||
|
||||
6
.gitignore
vendored
@@ -33,7 +33,7 @@ reports
|
||||
junit.xml
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
@@ -59,4 +59,6 @@ yarn-error.log*
|
||||
.infisical.json
|
||||
|
||||
# Editor specific
|
||||
.vscode/*
|
||||
.vscode/*
|
||||
|
||||
frontend-build
|
||||
@@ -1,7 +1,13 @@
|
||||
ARG POSTHOG_HOST=https://app.posthog.com
|
||||
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
|
||||
|
||||
@@ -11,7 +17,7 @@ COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./
|
||||
RUN npm ci --only-production --ignore-scripts
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM node:16-alpine AS frontend-builder
|
||||
FROM base AS frontend-builder
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependencies
|
||||
@@ -27,41 +33,38 @@ ARG POSTHOG_API_KEY
|
||||
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
|
||||
ARG INTERCOM_ID
|
||||
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
|
||||
ARG INFISICAL_PLATFORM_VERSION
|
||||
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
||||
|
||||
# Build
|
||||
RUN npm run build
|
||||
|
||||
# Production image
|
||||
FROM node:16-alpine AS frontend-runner
|
||||
FROM base AS frontend-runner
|
||||
WORKDIR /app
|
||||
|
||||
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
|
||||
|
||||
ARG POSTHOG_API_KEY
|
||||
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
|
||||
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
|
||||
ARG INTERCOM_ID
|
||||
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
||||
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
|
||||
|
||||
COPY --chown=nextjs:nodejs --chmod=555 frontend/scripts ./scripts
|
||||
COPY --chown=non-root-user:nodejs --chmod=555 frontend/scripts ./scripts
|
||||
COPY --from=frontend-builder /app/public ./public
|
||||
RUN chown nextjs:nodejs ./public/data
|
||||
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
RUN chown non-root-user:nodejs ./public/data
|
||||
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/standalone ./
|
||||
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
USER non-root-user
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
##
|
||||
## 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
|
||||
|
||||
@@ -69,10 +72,11 @@ COPY backend/package*.json ./
|
||||
RUN npm ci --only-production
|
||||
|
||||
COPY /backend .
|
||||
COPY --chown=non-root-user:nodejs standalone-entrypoint.sh standalone-entrypoint.sh
|
||||
RUN npm run build
|
||||
|
||||
# Production stage
|
||||
FROM node:16-alpine AS backend-runner
|
||||
FROM base AS backend-runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -81,27 +85,44 @@ RUN npm ci --only-production
|
||||
|
||||
COPY --from=backend-build /app .
|
||||
|
||||
RUN mkdir frontend-build
|
||||
|
||||
# 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 /
|
||||
|
||||
# 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=frontend-runner /app/ /app/
|
||||
COPY --from=frontend-runner /app ./backend/frontend-build
|
||||
|
||||
EXPOSE 80
|
||||
ENV PORT 8080
|
||||
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"]
|
||||
|
||||
|
||||
|
||||
@@ -17,17 +17,13 @@ export const getRootEncryptionKey = async () => {
|
||||
}
|
||||
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 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 getJwtAuthSecret = async () => (await client.getSecret("JWT_AUTH_SECRET")).secretValue;
|
||||
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 getJwtRefreshSecret = async () => (await client.getSecret("JWT_REFRESH_SECRET")).secretValue;
|
||||
export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue;
|
||||
export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1)
|
||||
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 getJwtSignupSecret = async () => (await client.getSecret("JWT_SIGNUP_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 getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production";
|
||||
|
||||
@@ -6,15 +6,18 @@ const jsrp = require("jsrp");
|
||||
import { LoginSRPDetail, TokenVersion, User } from "../../models";
|
||||
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
|
||||
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 { EELogService } from "../../ee/services";
|
||||
import { getUserAgentType } from "../../utils/posthog";
|
||||
import {
|
||||
getAuthSecret,
|
||||
getHttpsEnabled,
|
||||
getJwtAuthLifetime,
|
||||
getJwtAuthSecret,
|
||||
getJwtRefreshSecret
|
||||
getJwtAuthLifetime
|
||||
} from "../../config";
|
||||
import { ActorType } from "../../ee/models";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
@@ -238,6 +241,7 @@ export const checkAuth = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const getNewToken = async (req: Request, res: Response) => {
|
||||
|
||||
const refreshToken = req.cookies.jid;
|
||||
|
||||
if (!refreshToken)
|
||||
@@ -245,7 +249,9 @@ export const getNewToken = async (req: Request, res: Response) => {
|
||||
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({
|
||||
_id: decodedToken.userId
|
||||
@@ -268,12 +274,13 @@ export const getNewToken = async (req: Request, res: Response) => {
|
||||
|
||||
const token = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.ACCESS_TOKEN,
|
||||
userId: decodedToken.userId,
|
||||
tokenVersionId: tokenVersion._id.toString(),
|
||||
accessVersion: tokenVersion.refreshVersion
|
||||
},
|
||||
expiresIn: await getJwtAuthLifetime(),
|
||||
secret: await getJwtAuthSecret()
|
||||
secret: await getAuthSecret()
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
|
||||
@@ -8,11 +8,11 @@ import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
||||
import { sendMail } from "../../helpers/nodemailer";
|
||||
import { TokenService } from "../../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 {
|
||||
getAuthSecret,
|
||||
getJwtSignupLifetime,
|
||||
getJwtSignupSecret,
|
||||
getSiteURL,
|
||||
getSmtpConfigured
|
||||
} from "../../config";
|
||||
@@ -272,10 +272,11 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
|
||||
// generate temporary signup token
|
||||
const token = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.SIGNUP_TOKEN,
|
||||
userId: user._id.toString()
|
||||
},
|
||||
expiresIn: await getJwtSignupLifetime(),
|
||||
secret: await getJwtSignupSecret()
|
||||
secret: await getAuthSecret()
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
|
||||
@@ -5,12 +5,12 @@ import * as bigintConversion from "bigint-conversion";
|
||||
import { BackupPrivateKey, LoginSRPDetail, User } from "../../models";
|
||||
import { clearTokens, createToken, sendMail } from "../../helpers";
|
||||
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 {
|
||||
getAuthSecret,
|
||||
getHttpsEnabled,
|
||||
getJwtSignupLifetime,
|
||||
getJwtSignupSecret,
|
||||
getSiteURL
|
||||
} from "../../config";
|
||||
import { ActorType } from "../../ee/models";
|
||||
@@ -88,10 +88,11 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
|
||||
// generate temporary password-reset token
|
||||
const token = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.SIGNUP_TOKEN,
|
||||
userId: user._id.toString()
|
||||
},
|
||||
expiresIn: await getJwtSignupLifetime(),
|
||||
secret: await getJwtSignupSecret()
|
||||
secret: await getAuthSecret()
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
|
||||
@@ -4,14 +4,15 @@ import { checkEmailVerification, sendEmailVerification } from "../../helpers/sig
|
||||
import { createToken } from "../../helpers/auth";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import {
|
||||
getAuthSecret,
|
||||
getInviteOnlySignup,
|
||||
getJwtSignupLifetime,
|
||||
getJwtSignupSecret,
|
||||
getSmtpConfigured
|
||||
} from "../../config";
|
||||
import { validateUserEmail } from "../../validation";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
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
|
||||
@@ -95,10 +96,11 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
|
||||
// generate temporary signup token
|
||||
const token = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.SIGNUP_TOKEN,
|
||||
userId: user._id.toString()
|
||||
},
|
||||
expiresIn: await getJwtSignupLifetime(),
|
||||
secret: await getJwtSignupSecret()
|
||||
secret: await getAuthSecret()
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
|
||||
@@ -10,9 +10,9 @@ import { sendMail } from "../../helpers/nodemailer";
|
||||
import { TokenService } from "../../services";
|
||||
import { EELogService } from "../../ee/services";
|
||||
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 { getHttpsEnabled, getJwtMfaLifetime, getJwtMfaSecret } from "../../config";
|
||||
import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
import * as reqValidator from "../../validation/auth";
|
||||
|
||||
@@ -109,10 +109,11 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
// generate temporary MFA token
|
||||
const token = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.MFA_TOKEN,
|
||||
userId: user._id.toString()
|
||||
},
|
||||
expiresIn: await getJwtMfaLifetime(),
|
||||
secret: await getJwtMfaSecret()
|
||||
secret: await getAuthSecret()
|
||||
});
|
||||
|
||||
const code = await TokenService.createToken({
|
||||
|
||||
@@ -10,9 +10,9 @@ import { sendMail } from "../../helpers/nodemailer";
|
||||
import { TokenService } from "../../services";
|
||||
import { EELogService } from "../../ee/services";
|
||||
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 { getHttpsEnabled, getJwtMfaLifetime, getJwtMfaSecret } from "../../config";
|
||||
import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
|
||||
import { AuthMethod } from "../../models/user";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
import * as reqValidator from "../../validation/auth";
|
||||
@@ -134,10 +134,11 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
// generate temporary MFA token
|
||||
const token = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.MFA_TOKEN,
|
||||
userId: user._id.toString()
|
||||
},
|
||||
expiresIn: await getJwtMfaLifetime(),
|
||||
secret: await getJwtMfaSecret()
|
||||
secret: await getAuthSecret()
|
||||
});
|
||||
|
||||
const code = await TokenService.createToken({
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import * as usersController from "./usersController";
|
||||
import * as secretsController from "./secretsController";
|
||||
import * as workspacesController from "./workspacesController";
|
||||
import * as authController from "./authController";
|
||||
import * as signupController from "./signupController";
|
||||
|
||||
export {
|
||||
usersController,
|
||||
authController,
|
||||
secretsController,
|
||||
signupController,
|
||||
|
||||
@@ -476,7 +476,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
if (folderId && folderId !== "root") {
|
||||
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;
|
||||
}
|
||||
@@ -673,6 +673,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueTag,
|
||||
secretValueIV,
|
||||
secretId,
|
||||
type,
|
||||
environment,
|
||||
secretPath,
|
||||
@@ -741,6 +742,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
secretId,
|
||||
authData: req.authData,
|
||||
newSecretName,
|
||||
secretValueCiphertext,
|
||||
@@ -961,6 +963,14 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
authData: req.authData
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath
|
||||
})
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secrets: deletedSecrets
|
||||
});
|
||||
|
||||
@@ -5,10 +5,10 @@ import { MembershipOrg, User } from "../../models";
|
||||
import { completeAccount } from "../../helpers/user";
|
||||
import { initializeDefaultOrg } from "../../helpers/signup";
|
||||
import { issueAuthTokens, validateProviderAuthToken } from "../../helpers/auth";
|
||||
import { ACCEPTED, INVITED } from "../../variables";
|
||||
import { ACCEPTED, AuthTokenType, INVITED } from "../../variables";
|
||||
import { standardRequest } from "../../config/request";
|
||||
import { getHttpsEnabled, getJwtSignupSecret, getLoopsApiKey } from "../../config";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { getAuthSecret, getHttpsEnabled, getLoopsApiKey } from "../../config";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { TelemetryService } from "../../services";
|
||||
import { AuthMethod } from "../../models";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
@@ -78,12 +78,11 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(AUTH_TOKEN_VALUE, await getJwtSignupSecret())
|
||||
jwt.verify(AUTH_TOKEN_VALUE, await getAuthSecret())
|
||||
);
|
||||
|
||||
if (decodedToken.userId !== user.id) {
|
||||
throw BadRequestError();
|
||||
}
|
||||
|
||||
if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) throw UnauthorizedRequestError();
|
||||
if (decodedToken.userId !== user.id) throw UnauthorizedRequestError();
|
||||
}
|
||||
|
||||
// complete setting up user's account
|
||||
|
||||
18
backend/src/controllers/v3/usersController.ts
Normal file
@@ -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
|
||||
});
|
||||
}
|
||||
101
backend/src/ee/controllers/v3/apiKeyDataController.ts
Normal file
@@ -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
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import * as serviceTokenDataController from "./serviceTokenDataController";
|
||||
import * as apiKeyDataController from "./apiKeyDataController";
|
||||
|
||||
export {
|
||||
serviceTokenDataController
|
||||
serviceTokenDataController,
|
||||
apiKeyDataController
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import { EEAuditLogService, EELicenseService } from "../../services";
|
||||
import { getJwtServiceTokenSecret } from "../../../config";
|
||||
|
||||
/**
|
||||
* Return project key for service token
|
||||
* Return project key for service token V3
|
||||
* @param req
|
||||
* @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 res
|
||||
* @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 res
|
||||
* @returns
|
||||
|
||||
@@ -13,7 +13,10 @@ router.get(
|
||||
const options = {
|
||||
failureRedirect: "/",
|
||||
additionalParams: {
|
||||
RelayState: req.query.callback_port ?? ""
|
||||
RelayState: JSON.stringify({
|
||||
spInitiated: true,
|
||||
callbackPort: req.query.callback_port ?? ""
|
||||
})
|
||||
},
|
||||
};
|
||||
passport.authenticate("saml", options)(req, res, next);
|
||||
|
||||
31
backend/src/ee/routes/v3/apiKeyData.ts
Normal 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;
|
||||
@@ -1,5 +1,7 @@
|
||||
import serviceTokenData from "./serviceTokenData";
|
||||
import apiKeyData from "./apiKeyData";
|
||||
|
||||
export {
|
||||
serviceTokenData
|
||||
serviceTokenData,
|
||||
apiKeyData
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import jwt from "jsonwebtoken";
|
||||
import bcrypt from "bcrypt";
|
||||
import {
|
||||
APIKeyData,
|
||||
APIKeyDataV2,
|
||||
ITokenVersion,
|
||||
IUser,
|
||||
ServiceTokenData,
|
||||
@@ -19,15 +20,14 @@ import {
|
||||
UnauthorizedRequestError,
|
||||
} from "../utils/errors";
|
||||
import {
|
||||
getAuthSecret,
|
||||
getJwtAuthLifetime,
|
||||
getJwtAuthSecret,
|
||||
getJwtProviderAuthSecret,
|
||||
getJwtRefreshLifetime,
|
||||
getJwtRefreshSecret,
|
||||
getJwtServiceTokenSecret
|
||||
} from "../config";
|
||||
import {
|
||||
AuthMode
|
||||
AuthMode,
|
||||
AuthTokenType
|
||||
} from "../variables";
|
||||
import {
|
||||
ServiceTokenAuthData,
|
||||
@@ -51,8 +51,6 @@ export const validateAuthMode = ({
|
||||
acceptedAuthModes: AuthMode[]
|
||||
}) => {
|
||||
|
||||
// TODO: update this to accept service token v3
|
||||
|
||||
const apiKey = headers["x-api-key"];
|
||||
const authHeader = headers["authorization"];
|
||||
|
||||
@@ -108,6 +106,7 @@ export const validateAuthMode = ({
|
||||
|
||||
/**
|
||||
* Return user payload corresponding to JWT token [authTokenValue]
|
||||
* that is either for browser / CLI or API Key
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.authTokenValue - JWT token value
|
||||
* @returns {User} user - user corresponding to JWT token
|
||||
@@ -120,9 +119,45 @@ export const getAuthUserPayload = async ({
|
||||
authTokenValue: string;
|
||||
}): Promise<UserAuthData> => {
|
||||
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({
|
||||
_id: new Types.ObjectId(decodedToken.userId),
|
||||
}).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" });
|
||||
|
||||
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 {
|
||||
actor: {
|
||||
type: ActorType.USER,
|
||||
@@ -159,11 +179,6 @@ export const getAuthUserPayload = async ({
|
||||
userAgent: 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
|
||||
const token = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.ACCESS_TOKEN,
|
||||
userId,
|
||||
tokenVersionId: tokenVersion._id.toString(),
|
||||
accessVersion: tokenVersion.accessVersion,
|
||||
},
|
||||
expiresIn: await getJwtAuthLifetime(),
|
||||
secret: await getJwtAuthSecret(),
|
||||
secret: await getAuthSecret(),
|
||||
});
|
||||
|
||||
const refreshToken = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.REFRESH_TOKEN,
|
||||
userId,
|
||||
tokenVersionId: tokenVersion._id.toString(),
|
||||
refreshVersion: tokenVersion.refreshVersion,
|
||||
},
|
||||
expiresIn: await getJwtRefreshLifetime(),
|
||||
secret: await getJwtRefreshSecret(),
|
||||
secret: await getAuthSecret(),
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -451,7 +468,7 @@ export const clearTokens = async (tokenVersionId: Types.ObjectId): Promise<void>
|
||||
* bearer/auth, refresh, and temporary signup tokens
|
||||
* @param {Object} obj
|
||||
* @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'
|
||||
*/
|
||||
export const createToken = ({
|
||||
@@ -479,13 +496,16 @@ export const validateProviderAuthToken = async ({
|
||||
email: string;
|
||||
providerAuthToken?: string;
|
||||
}) => {
|
||||
|
||||
if (!providerAuthToken) {
|
||||
throw new Error("Invalid authentication request.");
|
||||
}
|
||||
|
||||
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) {
|
||||
throw new Error("Invalid authentication credentials.")
|
||||
|
||||
@@ -14,7 +14,7 @@ export const initDatabaseHelper = async ({
|
||||
}) => {
|
||||
try {
|
||||
await mongoose.connect(mongoURL);
|
||||
|
||||
|
||||
// allow empty strings to pass the required validator
|
||||
mongoose.Schema.Types.String.checkRequired(v => typeof v === "string");
|
||||
|
||||
@@ -31,14 +31,10 @@ export const initDatabaseHelper = async ({
|
||||
* Close database conection
|
||||
*/
|
||||
export const closeDatabaseHelper = async () => {
|
||||
return Promise.all([
|
||||
new Promise((resolve) => {
|
||||
if (mongoose.connection && mongoose.connection.readyState == 1) {
|
||||
mongoose.connection.close()
|
||||
.then(() => resolve("Database connection closed"));
|
||||
} else {
|
||||
resolve("Database connection already closed");
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
if (mongoose.connection && mongoose.connection.readyState === 1) {
|
||||
await mongoose.connection.close();
|
||||
return "Database connection closed";
|
||||
} else {
|
||||
return "Database connection already closed";
|
||||
}
|
||||
};
|
||||
@@ -790,6 +790,7 @@ export const getSecretHelper = async ({
|
||||
export const updateSecretHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
secretId,
|
||||
environment,
|
||||
type,
|
||||
authData,
|
||||
@@ -812,11 +813,20 @@ export const updateSecretHelper = async ({
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const oldSecretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||
let oldSecretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||
secretName,
|
||||
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;
|
||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
|
||||
@@ -891,6 +901,9 @@ export const updateSecretHelper = async ({
|
||||
skipMultilineEncoding,
|
||||
secretBlindIndex: newSecretNameBlindIndex,
|
||||
$inc: { version: 1 }
|
||||
},
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1748,6 +1761,22 @@ export const deleteSecretBatchHelper = async ({
|
||||
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(
|
||||
authData,
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
secretApprovalRequest as v1SecretApprovalRequest,
|
||||
secretScanning as v1SecretScanningRouter
|
||||
} from "./ee/routes/v1";
|
||||
import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3";
|
||||
import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3";
|
||||
import {
|
||||
auth as v1AuthRouter,
|
||||
@@ -68,6 +69,7 @@ import {
|
||||
auth as v3AuthRouter,
|
||||
secrets as v3SecretsRouter,
|
||||
signup as v3SignupRouter,
|
||||
users as v3UsersRouter,
|
||||
workspaces as v3WorkspacesRouter
|
||||
} from "./routes/v3";
|
||||
import { healthCheck } from "./routes/status";
|
||||
@@ -81,12 +83,15 @@ import {
|
||||
getSecretScanningPrivateKey,
|
||||
getSecretScanningWebhookProxy,
|
||||
getSecretScanningWebhookSecret,
|
||||
getSiteURL
|
||||
getSiteURL,
|
||||
} from "./config";
|
||||
import { setup } from "./utils/setup";
|
||||
import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices";
|
||||
import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent";
|
||||
const SmeeClient = require("smee-client"); // eslint-disable-line
|
||||
import path from "path";
|
||||
|
||||
let handler: null | any = null;
|
||||
|
||||
const main = async () => {
|
||||
await setup();
|
||||
@@ -147,6 +152,27 @@ const main = async () => {
|
||||
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
|
||||
app.use("/api/v1/secret", eeSecretRouter);
|
||||
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
||||
@@ -156,7 +182,8 @@ const main = async () => {
|
||||
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
||||
app.use("/api/v1/sso", eeSSORouter);
|
||||
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
|
||||
app.use("/api/v1/signup", v1SignupRouter);
|
||||
@@ -202,6 +229,7 @@ const main = async () => {
|
||||
app.use("/api/v3/secrets", v3SecretsRouter);
|
||||
app.use("/api/v3/workspaces", v3WorkspacesRouter);
|
||||
app.use("/api/v3/signup", v3SignupRouter);
|
||||
app.use("/api/v3/users", v3UsersRouter);
|
||||
|
||||
// api docs
|
||||
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
|
||||
@@ -209,6 +237,12 @@ const main = async () => {
|
||||
// server status
|
||||
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
|
||||
app.use((req, res, next) => {
|
||||
if (res.headersSent) return next();
|
||||
|
||||
@@ -32,6 +32,8 @@ import {
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_GITLAB_API_URL,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HASURA_CLOUD,
|
||||
INTEGRATION_HASURA_CLOUD_API_URL,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_HEROKU_API_URL,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
@@ -63,6 +65,10 @@ import { Octokit } from "@octokit/rest";
|
||||
import _ from "lodash";
|
||||
import sodium from "libsodium-wrappers";
|
||||
import { standardRequest } from "../config/request";
|
||||
import {
|
||||
ZGetTenantEnv,
|
||||
ZUpdateTenantEnv
|
||||
} from "../validation/hasuraCloudIntegration";
|
||||
|
||||
const getSecretKeyValuePair = (
|
||||
secrets: Record<string, { value: string | null; comment?: string } | null>
|
||||
@@ -95,7 +101,7 @@ const syncSecrets = async ({
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
appendices?: { prefix: string, suffix: string };
|
||||
appendices?: { prefix: string; suffix: string };
|
||||
}) => {
|
||||
switch (integration.integration) {
|
||||
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||
@@ -306,6 +312,14 @@ const syncSecrets = async ({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
|
||||
case INTEGRATION_HASURA_CLOUD:
|
||||
await syncSecretsHasuraCloud({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -963,8 +977,9 @@ const syncSecretsVercel = async ({
|
||||
: {}),
|
||||
...(integration?.path
|
||||
? {
|
||||
gitBranch: integration?.path
|
||||
} : {})
|
||||
gitBranch: integration?.path
|
||||
}
|
||||
: {})
|
||||
};
|
||||
|
||||
const vercelSecrets: VercelSecret[] = (
|
||||
@@ -992,7 +1007,7 @@ const syncSecretsVercel = async ({
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
const res: { [key: string]: VercelSecret } = {};
|
||||
|
||||
for await (const vercelSecret of vercelSecrets) {
|
||||
@@ -1352,7 +1367,7 @@ const syncSecretsGitHub = async ({
|
||||
integration: IIntegration;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
appendices?: { prefix: string, suffix: string };
|
||||
appendices?: { prefix: string; suffix: string };
|
||||
}) => {
|
||||
interface GitHubRepoKey {
|
||||
key_id: string;
|
||||
@@ -1395,14 +1410,23 @@ const syncSecretsGitHub = async ({
|
||||
{}
|
||||
);
|
||||
|
||||
encryptedSecrets = Object.keys(encryptedSecrets).reduce((result: {
|
||||
[key: string]: GitHubSecret;
|
||||
}, key) => {
|
||||
if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) {
|
||||
result[key] = encryptedSecrets[key];
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
encryptedSecrets = Object.keys(encryptedSecrets).reduce(
|
||||
(
|
||||
result: {
|
||||
[key: string]: GitHubSecret;
|
||||
},
|
||||
key
|
||||
) => {
|
||||
if (
|
||||
(appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) &&
|
||||
(appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)
|
||||
) {
|
||||
result[key] = encryptedSecrets[key];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
Object.keys(encryptedSecrets).map(async (key) => {
|
||||
if (!(key in secrets)) {
|
||||
@@ -2095,7 +2119,7 @@ const syncSecretsCheckly = async ({
|
||||
integration: IIntegration;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
appendices?: { prefix: string, suffix: string };
|
||||
appendices?: { prefix: string; suffix: string };
|
||||
}) => {
|
||||
let getSecretsRes = (
|
||||
await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, {
|
||||
@@ -2113,14 +2137,23 @@ const syncSecretsCheckly = async ({
|
||||
{}
|
||||
);
|
||||
|
||||
getSecretsRes = Object.keys(getSecretsRes).reduce((result: {
|
||||
[key: string]: string;
|
||||
}, key) => {
|
||||
if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) {
|
||||
result[key] = getSecretsRes[key];
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
getSecretsRes = Object.keys(getSecretsRes).reduce(
|
||||
(
|
||||
result: {
|
||||
[key: string]: string;
|
||||
},
|
||||
key
|
||||
) => {
|
||||
if (
|
||||
(appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) &&
|
||||
(appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)
|
||||
) {
|
||||
result[key] = getSecretsRes[key];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
// add secrets
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
@@ -2195,18 +2228,20 @@ const syncSecretsQovery = async ({
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
|
||||
const getSecretsRes = (
|
||||
await standardRequest.get(`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`, {
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
).data.results.reduce(
|
||||
(obj: any, secret: any) => ({
|
||||
...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 };
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface GetSecretParams {
|
||||
export interface UpdateSecretParams {
|
||||
secretName: string;
|
||||
newSecretName?: string;
|
||||
secretId?: string;
|
||||
secretKeyCiphertext?: string;
|
||||
secretKeyIV?: string;
|
||||
secretKeyTag?: string;
|
||||
|
||||
@@ -2,7 +2,8 @@ import jwt from "jsonwebtoken";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { User } from "../models";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { getJwtMfaSecret } from "../config";
|
||||
import { getAuthSecret } from "../config";
|
||||
import { AuthTokenType } from "../variables";
|
||||
|
||||
declare module "jsonwebtoken" {
|
||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||
@@ -26,8 +27,10 @@ const requireMfaAuth = async (
|
||||
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: "Missing Authorization Body in the request header"}))
|
||||
|
||||
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({
|
||||
_id: decodedToken.userId,
|
||||
|
||||
@@ -2,7 +2,8 @@ import jwt from "jsonwebtoken";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import { User } from "../models";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { getJwtSignupSecret } from "../config";
|
||||
import { getAuthSecret } from "../config";
|
||||
import { AuthTokenType } from "../variables";
|
||||
|
||||
declare module "jsonwebtoken" {
|
||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||
@@ -27,8 +28,10 @@ const requireSignupAuth = async (
|
||||
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: "Missing Authorization Body in the request header"}))
|
||||
|
||||
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({
|
||||
_id: decodedToken.userId,
|
||||
|
||||
38
backend/src/models/apiKeyDataV2.ts
Normal 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);
|
||||
@@ -24,9 +24,10 @@ export * from "./user";
|
||||
export * from "./userAction";
|
||||
export * from "./workspace";
|
||||
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 "./tokenVersion";
|
||||
export * from "./webhooks";
|
||||
export * from "./serviceTokenDataV3";
|
||||
export * from "./serviceTokenDataV3Key";
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HASURA_CLOUD,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
@@ -76,7 +77,8 @@ export interface IIntegration {
|
||||
| "cloud-66"
|
||||
| "northflank"
|
||||
| "windmill"
|
||||
| "gcp-secret-manager";
|
||||
| "gcp-secret-manager"
|
||||
| "hasura-cloud";
|
||||
integrationAuth: Types.ObjectId;
|
||||
metadata: Metadata;
|
||||
}
|
||||
@@ -86,67 +88,67 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
environment: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
// for custom self-hosted integrations (e.g. self-hosted GitHub enterprise)
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
app: {
|
||||
// name of app in provider
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
appId: {
|
||||
// id of app in provider
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
targetEnvironment: {
|
||||
// target environment
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
targetEnvironmentId: {
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
targetService: {
|
||||
// railway-specific service
|
||||
// qovery-specific project
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
targetServiceId: {
|
||||
// railway-specific service
|
||||
// qovery specific project
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
owner: {
|
||||
// github-specific repo owner-login
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
path: {
|
||||
// aws-parameter-store-specific path
|
||||
// (also) vercel preview-branch
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
region: {
|
||||
// aws-parameter-store-specific path
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
},
|
||||
scope: {
|
||||
// qovery-specific scope
|
||||
@@ -183,19 +185,20 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_GCP_SECRET_MANAGER
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_HASURA_CLOUD
|
||||
],
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
integrationAuth: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "IntegrationAuth",
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
secretPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "/",
|
||||
default: "/"
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed,
|
||||
@@ -203,8 +206,8 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
export const Integration = model<IIntegration>("Integration", integrationSchema);
|
||||
export const Integration = model<IIntegration>("Integration", integrationSchema);
|
||||
|
||||
@@ -1,205 +1,203 @@
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_WINDMILL
|
||||
} from "../../variables";
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
import { IntegrationAuthMetadata } from "./types";
|
||||
|
||||
export interface IIntegrationAuth extends Document {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
integration:
|
||||
| "heroku"
|
||||
| "vercel"
|
||||
| "netlify"
|
||||
| "github"
|
||||
| "gitlab"
|
||||
| "render"
|
||||
| "railway"
|
||||
| "flyio"
|
||||
| "azure-key-vault"
|
||||
| "laravel-forge"
|
||||
| "circleci"
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "aws-parameter-store"
|
||||
| "aws-secret-manager"
|
||||
| "checkly"
|
||||
| "qovery"
|
||||
| "cloudflare-pages"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
| "bitbucket"
|
||||
| "cloud-66"
|
||||
| "terraform-cloud"
|
||||
| "teamcity"
|
||||
| "northflank"
|
||||
| "windmill"
|
||||
| "gcp-secret-manager";
|
||||
teamId: string;
|
||||
accountId: string;
|
||||
url: string;
|
||||
namespace: string;
|
||||
refreshCiphertext?: string;
|
||||
refreshIV?: string;
|
||||
refreshTag?: string;
|
||||
accessIdCiphertext?: string;
|
||||
accessIdIV?: string;
|
||||
accessIdTag?: string;
|
||||
accessCiphertext?: string;
|
||||
accessIV?: string;
|
||||
accessTag?: string;
|
||||
algorithm?: "aes-256-gcm";
|
||||
keyEncoding?: "utf8" | "base64";
|
||||
accessExpiresAt?: Date;
|
||||
metadata?: IntegrationAuthMetadata;
|
||||
}
|
||||
|
||||
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true,
|
||||
},
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_GCP_SECRET_MANAGER
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
teamId: {
|
||||
// vercel-specific integration param
|
||||
type: String,
|
||||
},
|
||||
url: {
|
||||
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
|
||||
type: String,
|
||||
},
|
||||
namespace: {
|
||||
// hashicorp-vault-specific integration param
|
||||
type: String,
|
||||
},
|
||||
accountId: {
|
||||
// netlify-specific integration param
|
||||
type: String,
|
||||
},
|
||||
refreshCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
refreshIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
refreshTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessExpiresAt: {
|
||||
type: Date,
|
||||
select: false,
|
||||
},
|
||||
algorithm: { // the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true,
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
}
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HASURA_CLOUD,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_WINDMILL
|
||||
} from "../../variables";
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
import { IntegrationAuthMetadata } from "./types";
|
||||
|
||||
export interface IIntegrationAuth extends Document {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
integration:
|
||||
| "heroku"
|
||||
| "vercel"
|
||||
| "netlify"
|
||||
| "github"
|
||||
| "gitlab"
|
||||
| "render"
|
||||
| "railway"
|
||||
| "flyio"
|
||||
| "azure-key-vault"
|
||||
| "laravel-forge"
|
||||
| "circleci"
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "aws-parameter-store"
|
||||
| "aws-secret-manager"
|
||||
| "checkly"
|
||||
| "qovery"
|
||||
| "cloudflare-pages"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
| "bitbucket"
|
||||
| "cloud-66"
|
||||
| "terraform-cloud"
|
||||
| "teamcity"
|
||||
| "northflank"
|
||||
| "windmill"
|
||||
| "gcp-secret-manager"
|
||||
| "hasura-cloud";
|
||||
teamId: string;
|
||||
accountId: string;
|
||||
url: string;
|
||||
namespace: string;
|
||||
refreshCiphertext?: string;
|
||||
refreshIV?: string;
|
||||
refreshTag?: string;
|
||||
accessIdCiphertext?: string;
|
||||
accessIdIV?: string;
|
||||
accessIdTag?: string;
|
||||
accessCiphertext?: string;
|
||||
accessIV?: string;
|
||||
accessTag?: string;
|
||||
algorithm?: "aes-256-gcm";
|
||||
keyEncoding?: "utf8" | "base64";
|
||||
accessExpiresAt?: Date;
|
||||
metadata?: IntegrationAuthMetadata;
|
||||
}
|
||||
|
||||
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_HASURA_CLOUD
|
||||
],
|
||||
required: true
|
||||
},
|
||||
teamId: {
|
||||
// vercel-specific integration param
|
||||
type: String
|
||||
},
|
||||
url: {
|
||||
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
|
||||
type: String
|
||||
},
|
||||
namespace: {
|
||||
// hashicorp-vault-specific integration param
|
||||
type: String
|
||||
},
|
||||
accountId: {
|
||||
// netlify-specific integration param
|
||||
type: String
|
||||
},
|
||||
refreshCiphertext: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
refreshIV: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
refreshTag: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIdCiphertext: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIdIV: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIdTag: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessCiphertext: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessIV: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessTag: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
accessExpiresAt: {
|
||||
type: Date,
|
||||
select: false
|
||||
},
|
||||
algorithm: {
|
||||
// the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
|
||||
required: true
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
}
|
||||
);
|
||||
|
||||
export const IntegrationAuth = model<IIntegrationAuth>(
|
||||
"IntegrationAuth",
|
||||
integrationAuthSchema
|
||||
);
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
export const IntegrationAuth = model<IIntegrationAuth>("IntegrationAuth", integrationAuthSchema);
|
||||
|
||||
@@ -54,6 +54,7 @@ const serviceTokenDataV3Schema = new Schema(
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
required: true
|
||||
},
|
||||
lastUsed: {
|
||||
|
||||
@@ -40,9 +40,9 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
|
||||
const prefix = (integration.metadata?.secretPrefix || "");
|
||||
const suffix = (integration.metadata?.secretSuffix || "");
|
||||
const newKey = prefix + key + suffix;
|
||||
|
||||
|
||||
suffixedSecrets[newKey] = secrets[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
|
||||
@@ -67,7 +67,7 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => {
|
||||
})
|
||||
|
||||
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) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
|
||||
router.post(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
}),
|
||||
secretImpsController.createSecretImp
|
||||
);
|
||||
@@ -15,7 +15,7 @@ router.post(
|
||||
router.put(
|
||||
"/:id",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
}),
|
||||
secretImpsController.updateSecretImport
|
||||
);
|
||||
@@ -23,7 +23,7 @@ router.put(
|
||||
router.delete(
|
||||
"/:id",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
}),
|
||||
secretImpsController.deleteSecretImport
|
||||
);
|
||||
@@ -31,7 +31,7 @@ router.delete(
|
||||
router.get(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
}),
|
||||
secretImpsController.getSecretImports
|
||||
);
|
||||
@@ -39,7 +39,7 @@ router.get(
|
||||
router.get(
|
||||
"/secrets",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
}),
|
||||
secretImpsController.getAllSecretsFromImport
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ import { AuthMode } from "../../variables";
|
||||
router.post(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
}),
|
||||
createFolder
|
||||
);
|
||||
@@ -20,7 +20,7 @@ router.post(
|
||||
router.patch(
|
||||
"/:folderName",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
}),
|
||||
updateFolderById
|
||||
);
|
||||
@@ -28,7 +28,7 @@ router.patch(
|
||||
router.delete(
|
||||
"/:folderName",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
}),
|
||||
deleteFolder
|
||||
);
|
||||
@@ -36,7 +36,7 @@ router.delete(
|
||||
router.get(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
}),
|
||||
getFolders
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import { AuthMode } from "../../variables";
|
||||
import { serviceTokenDataController } from "../../controllers/v2";
|
||||
|
||||
router.get(
|
||||
router.get( // TODO: deprecate (moving to ST V3)
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.SERVICE_TOKEN]
|
||||
@@ -14,7 +14,7 @@ router.get(
|
||||
serviceTokenDataController.getServiceTokenData
|
||||
);
|
||||
|
||||
router.post(
|
||||
router.post( // TODO: deprecate (moving to ST V3)
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
@@ -22,7 +22,7 @@ router.post(
|
||||
serviceTokenDataController.createServiceTokenData
|
||||
);
|
||||
|
||||
router.delete(
|
||||
router.delete( // TODO: deprecate (moving to ST V3)
|
||||
"/:serviceTokenDataId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
@@ -30,4 +30,4 @@ router.delete(
|
||||
serviceTokenDataController.deleteServiceTokenData
|
||||
);
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
@@ -36,7 +36,7 @@ router.get(
|
||||
usersController.getMyOrganizations
|
||||
);
|
||||
|
||||
router.get(
|
||||
router.get( // TODO: deprecate (moving to API Key V2)
|
||||
"/me/api-keys",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import auth from "./auth";
|
||||
import users from "./users";
|
||||
import secrets from "./secrets";
|
||||
import workspaces from "./workspaces";
|
||||
import signup from "./signup";
|
||||
|
||||
export {
|
||||
auth,
|
||||
users,
|
||||
secrets,
|
||||
signup,
|
||||
workspaces
|
||||
|
||||
15
backend/src/routes/v3/users.ts
Normal 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;
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "../models";
|
||||
import { createToken } from "../helpers/auth";
|
||||
import {
|
||||
getAuthSecret,
|
||||
getClientIdGitHubLogin,
|
||||
getClientIdGitLabLogin,
|
||||
getClientIdGoogleLogin,
|
||||
@@ -20,13 +21,12 @@ import {
|
||||
getClientSecretGitLabLogin,
|
||||
getClientSecretGoogleLogin,
|
||||
getJwtProviderAuthLifetime,
|
||||
getJwtProviderAuthSecret,
|
||||
getSiteURL,
|
||||
getUrlGitLabLogin
|
||||
} from "../config";
|
||||
import { getSSOConfigHelper } from "../ee/helpers/organizations";
|
||||
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";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
@@ -131,6 +131,7 @@ const initializePassport = async () => {
|
||||
const isUserCompleted = !!user.publicKey;
|
||||
const providerAuthToken = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.PROVIDER_TOKEN,
|
||||
userId: user._id.toString(),
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
@@ -143,7 +144,7 @@ const initializePassport = async () => {
|
||||
} : {})
|
||||
},
|
||||
expiresIn: await getJwtProviderAuthLifetime(),
|
||||
secret: await getJwtProviderAuthSecret(),
|
||||
secret: await getAuthSecret(),
|
||||
});
|
||||
|
||||
req.isUserCompleted = isUserCompleted;
|
||||
@@ -204,6 +205,7 @@ const initializePassport = async () => {
|
||||
const isUserCompleted = !!user.publicKey;
|
||||
const providerAuthToken = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.PROVIDER_TOKEN,
|
||||
userId: user._id.toString(),
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
@@ -216,7 +218,7 @@ const initializePassport = async () => {
|
||||
} : {})
|
||||
},
|
||||
expiresIn: await getJwtProviderAuthLifetime(),
|
||||
secret: await getJwtProviderAuthSecret(),
|
||||
secret: await getAuthSecret(),
|
||||
});
|
||||
|
||||
req.isUserCompleted = isUserCompleted;
|
||||
@@ -258,6 +260,7 @@ const initializePassport = async () => {
|
||||
const isUserCompleted = !!user.publicKey;
|
||||
const providerAuthToken = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.PROVIDER_TOKEN,
|
||||
userId: user._id.toString(),
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
@@ -270,7 +273,7 @@ const initializePassport = async () => {
|
||||
} : {})
|
||||
},
|
||||
expiresIn: await getJwtProviderAuthLifetime(),
|
||||
secret: await getJwtProviderAuthSecret(),
|
||||
secret: await getAuthSecret(),
|
||||
});
|
||||
|
||||
req.isUserCompleted = isUserCompleted;
|
||||
@@ -291,8 +294,7 @@ const initializePassport = async () => {
|
||||
});
|
||||
|
||||
interface ISAMLConfig {
|
||||
path: string;
|
||||
callbackURL: string;
|
||||
callbackUrl: string;
|
||||
entryPoint: string;
|
||||
issuer: string;
|
||||
cert: string;
|
||||
@@ -301,8 +303,7 @@ const initializePassport = async () => {
|
||||
}
|
||||
|
||||
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,
|
||||
issuer: ssoConfig.issuer,
|
||||
cert: ssoConfig.cert,
|
||||
@@ -313,6 +314,12 @@ const initializePassport = async () => {
|
||||
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;
|
||||
|
||||
done(null, samlConfig);
|
||||
@@ -397,6 +404,7 @@ const initializePassport = async () => {
|
||||
const isUserCompleted = !!user.publicKey;
|
||||
const providerAuthToken = createToken({
|
||||
payload: {
|
||||
authTokenType: AuthTokenType.PROVIDER_TOKEN,
|
||||
userId: user._id.toString(),
|
||||
email: user.email,
|
||||
firstName,
|
||||
@@ -405,11 +413,11 @@ const initializePassport = async () => {
|
||||
authMethod: req.ssoConfig.authProvider,
|
||||
isUserCompleted,
|
||||
...(req.body.RelayState ? {
|
||||
callbackPort: req.body.RelayState as string
|
||||
callbackPort: JSON.parse(req.body.RelayState).callbackPort as string
|
||||
} : {})
|
||||
},
|
||||
expiresIn: await getJwtProviderAuthLifetime(),
|
||||
secret: await getJwtProviderAuthSecret(),
|
||||
secret: await getAuthSecret(),
|
||||
});
|
||||
|
||||
req.isUserCompleted = isUserCompleted;
|
||||
|
||||
@@ -55,9 +55,6 @@ export const setup = async () => {
|
||||
// initializing global feature set
|
||||
await EELicenseService.initGlobalFeatureSet();
|
||||
|
||||
// initializing the database connection
|
||||
await DatabaseService.initDatabase(await getMongoURL());
|
||||
|
||||
await initializePassport();
|
||||
|
||||
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
|
||||
|
||||
22
backend/src/validation/apiKeyDataV3.ts
Normal 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()
|
||||
})
|
||||
});
|
||||
21
backend/src/validation/hasuraCloudIntegration.ts
Normal file
@@ -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())
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -10,3 +10,4 @@ export * from "./secrets";
|
||||
export * from "./serviceAccount";
|
||||
export * from "./serviceTokenData";
|
||||
export * from "./serviceTokenDataV3";
|
||||
export * from "./apiKeyDataV3";
|
||||
|
||||
@@ -353,6 +353,7 @@ export const UpdateSecretByNameV3 = z.object({
|
||||
body: z.object({
|
||||
workspaceId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretId: z.string().trim().optional(),
|
||||
type: z.enum([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
secretPath: z.string().trim().default("/"),
|
||||
secretValueCiphertext: z.string().trim(),
|
||||
|
||||
@@ -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 {
|
||||
JWT = "jwt",
|
||||
SERVICE_TOKEN = "serviceToken",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGCPSecretManager,
|
||||
getClientIdGitHub,
|
||||
getClientIdGitLab,
|
||||
getClientIdHeroku,
|
||||
getClientIdNetlify,
|
||||
getClientSlugVercel
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGCPSecretManager,
|
||||
getClientIdGitHub,
|
||||
getClientIdGitLab,
|
||||
getClientIdHeroku,
|
||||
getClientIdNetlify,
|
||||
getClientSlugVercel
|
||||
} from "../config";
|
||||
|
||||
// integrations
|
||||
@@ -22,7 +22,7 @@ export const INTEGRATION_GITLAB = "gitlab";
|
||||
export const INTEGRATION_RENDER = "render";
|
||||
export const INTEGRATION_RAILWAY = "railway";
|
||||
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_TRAVISCI = "travisci";
|
||||
export const INTEGRATION_TEAMCITY = "teamcity";
|
||||
@@ -38,32 +38,34 @@ export const INTEGRATION_WINDMILL = "windmill";
|
||||
export const INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM = "digital-ocean-app-platform";
|
||||
export const INTEGRATION_CLOUD_66 = "cloud-66";
|
||||
export const INTEGRATION_NORTHFLANK = "northflank";
|
||||
export const INTEGRATION_HASURA_CLOUD = "hasura-cloud";
|
||||
export const INTEGRATION_SET = new Set([
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_HASURA_CLOUD
|
||||
]);
|
||||
|
||||
// integration types
|
||||
@@ -71,15 +73,14 @@ export const INTEGRATION_OAUTH2 = "oauth2";
|
||||
|
||||
// integration oauth endpoints
|
||||
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_VERCEL_TOKEN_URL =
|
||||
"https://api.vercel.com/v2/oauth/access_token";
|
||||
export const INTEGRATION_VERCEL_TOKEN_URL = "https://api.vercel.com/v2/oauth/access_token";
|
||||
export const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token";
|
||||
export const INTEGRATION_GITHUB_TOKEN_URL =
|
||||
"https://github.com/login/oauth/access_token";
|
||||
export const INTEGRATION_GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_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
|
||||
export const INTEGRATION_GCP_API_URL = "https://cloudresourcemanager.googleapis.com";
|
||||
@@ -106,268 +107,279 @@ 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_CLOUD_66_API_URL = "https://app.cloud66.com/api";
|
||||
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_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 () => {
|
||||
const INTEGRATION_OPTIONS = [
|
||||
{
|
||||
name: "Heroku",
|
||||
slug: "heroku",
|
||||
image: "Heroku.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdHeroku(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Vercel",
|
||||
slug: "vercel",
|
||||
image: "Vercel.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: "",
|
||||
clientSlug: await getClientSlugVercel(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Netlify",
|
||||
slug: "netlify",
|
||||
image: "Netlify.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdNetlify(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "GitHub",
|
||||
slug: "github",
|
||||
image: "GitHub.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdGitHub(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Render",
|
||||
slug: "render",
|
||||
image: "Render.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Railway",
|
||||
slug: "railway",
|
||||
image: "Railway.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Fly.io",
|
||||
slug: "flyio",
|
||||
image: "Flyio.svg",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "AWS Parameter Store",
|
||||
slug: "aws-parameter-store",
|
||||
image: "Amazon Web Services.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Laravel Forge",
|
||||
slug: "laravel-forge",
|
||||
image: "Laravel Forge.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "AWS Secrets Manager",
|
||||
slug: "aws-secret-manager",
|
||||
image: "Amazon Web Services.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Azure Key Vault",
|
||||
slug: "azure-key-vault",
|
||||
image: "Microsoft Azure.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdAzure(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Circle CI",
|
||||
slug: "circleci",
|
||||
image: "Circle CI.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "GitLab",
|
||||
slug: "gitlab",
|
||||
image: "GitLab.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: await getClientIdGitLab(),
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Terraform Cloud",
|
||||
slug: "terraform-cloud",
|
||||
image: "Terraform Cloud.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
cliendId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Travis CI",
|
||||
slug: "travisci",
|
||||
image: "Travis CI.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "TeamCity",
|
||||
slug: "teamcity",
|
||||
image: "TeamCity.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Supabase",
|
||||
slug: "supabase",
|
||||
image: "Supabase.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Checkly",
|
||||
slug: "checkly",
|
||||
image: "Checkly.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Qovery",
|
||||
slug: "qovery",
|
||||
image: "Qovery.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "HashiCorp Vault",
|
||||
slug: "hashicorp-vault",
|
||||
image: "Vault.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "GCP Secret Manager",
|
||||
slug: "gcp-secret-manager",
|
||||
image: "Google Cloud Platform.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdGCPSecretManager(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Cloudflare Pages",
|
||||
slug: "cloudflare-pages",
|
||||
image: "Cloudflare.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "BitBucket",
|
||||
slug: "bitbucket",
|
||||
image: "BitBucket.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdBitBucket(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Codefresh",
|
||||
slug: "codefresh",
|
||||
image: "Codefresh.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Windmill",
|
||||
slug: "windmill",
|
||||
image: "Windmill.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Digital Ocean App Platform",
|
||||
slug: "digital-ocean-app-platform",
|
||||
image: "Digital Ocean.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Cloud 66",
|
||||
slug: "cloud-66",
|
||||
image: "Cloud 66.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Northflank",
|
||||
slug: "northflank",
|
||||
image: "Northflank.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
]
|
||||
|
||||
return INTEGRATION_OPTIONS;
|
||||
}
|
||||
const INTEGRATION_OPTIONS = [
|
||||
{
|
||||
name: "Heroku",
|
||||
slug: "heroku",
|
||||
image: "Heroku.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdHeroku(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Vercel",
|
||||
slug: "vercel",
|
||||
image: "Vercel.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: "",
|
||||
clientSlug: await getClientSlugVercel(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Netlify",
|
||||
slug: "netlify",
|
||||
image: "Netlify.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdNetlify(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "GitHub",
|
||||
slug: "github",
|
||||
image: "GitHub.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdGitHub(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Render",
|
||||
slug: "render",
|
||||
image: "Render.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Railway",
|
||||
slug: "railway",
|
||||
image: "Railway.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Fly.io",
|
||||
slug: "flyio",
|
||||
image: "Flyio.svg",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "AWS Parameter Store",
|
||||
slug: "aws-parameter-store",
|
||||
image: "Amazon Web Services.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Laravel Forge",
|
||||
slug: "laravel-forge",
|
||||
image: "Laravel Forge.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "AWS Secrets Manager",
|
||||
slug: "aws-secret-manager",
|
||||
image: "Amazon Web Services.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Azure Key Vault",
|
||||
slug: "azure-key-vault",
|
||||
image: "Microsoft Azure.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdAzure(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Circle CI",
|
||||
slug: "circleci",
|
||||
image: "Circle CI.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "GitLab",
|
||||
slug: "gitlab",
|
||||
image: "GitLab.png",
|
||||
isAvailable: true,
|
||||
type: "custom",
|
||||
clientId: await getClientIdGitLab(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Terraform Cloud",
|
||||
slug: "terraform-cloud",
|
||||
image: "Terraform Cloud.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
cliendId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Travis CI",
|
||||
slug: "travisci",
|
||||
image: "Travis CI.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "TeamCity",
|
||||
slug: "teamcity",
|
||||
image: "TeamCity.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Supabase",
|
||||
slug: "supabase",
|
||||
image: "Supabase.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Checkly",
|
||||
slug: "checkly",
|
||||
image: "Checkly.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Qovery",
|
||||
slug: "qovery",
|
||||
image: "Qovery.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "HashiCorp Vault",
|
||||
slug: "hashicorp-vault",
|
||||
image: "Vault.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "GCP Secret Manager",
|
||||
slug: "gcp-secret-manager",
|
||||
image: "Google Cloud Platform.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdGCPSecretManager(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Cloudflare Pages",
|
||||
slug: "cloudflare-pages",
|
||||
image: "Cloudflare.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "BitBucket",
|
||||
slug: "bitbucket",
|
||||
image: "BitBucket.png",
|
||||
isAvailable: true,
|
||||
type: "oauth",
|
||||
clientId: await getClientIdBitBucket(),
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Codefresh",
|
||||
slug: "codefresh",
|
||||
image: "Codefresh.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Windmill",
|
||||
slug: "windmill",
|
||||
image: "Windmill.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Digital Ocean App Platform",
|
||||
slug: "digital-ocean-app-platform",
|
||||
image: "Digital Ocean.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Cloud 66",
|
||||
slug: "cloud-66",
|
||||
image: "Cloud 66.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Northflank",
|
||||
slug: "northflank",
|
||||
image: "Northflank.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
},
|
||||
{
|
||||
name: "Hasura Cloud",
|
||||
slug: "hasura-cloud",
|
||||
image: "Hasura.svg",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: ""
|
||||
}
|
||||
];
|
||||
|
||||
return INTEGRATION_OPTIONS;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ Copyright (c) 2023 Infisical Inc.
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"regexp"
|
||||
@@ -11,8 +12,6 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/api"
|
||||
"github.com/Infisical/infisical-merge/packages/crypto"
|
||||
"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")
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
|
||||
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 {
|
||||
util.HandleError(err, "To fetch all secrets")
|
||||
}
|
||||
@@ -650,8 +654,8 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||
secretsGenerateExampleEnvCmd.Flags().String("path", "/", "Fetch secrets from within a folder path")
|
||||
secretsCmd.AddCommand(secretsGenerateExampleEnvCmd)
|
||||
|
||||
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
||||
|
||||
@@ -1,46 +1,20 @@
|
||||
version: "3"
|
||||
|
||||
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:
|
||||
container_name: infisical-backend
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- mongo
|
||||
image: infisical/backend
|
||||
image: infisical/infisical:latest
|
||||
env_file: .env
|
||||
ports:
|
||||
- 80:8080
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
networks:
|
||||
- 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:
|
||||
image: redis
|
||||
container_name: infisical-dev-redis
|
||||
|
||||
24
docs/CONTRIBUTING.MD
Normal 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
|
||||
@@ -63,12 +63,17 @@ description: "Configure Azure SAML for Infisical SSO"
|
||||
|
||||
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.
|
||||

|
||||
|
||||

|
||||

|
||||
In the **Properties** screen, copy the **Application ID** to use when finishing configuring Azure SAML in Infisical.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
<Note>
|
||||
When pasting the certificate into Infisical, you'll want to retain `-----BEGIN
|
||||
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 678 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 521 KiB After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/sso/azure/idp-values-3.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
36
docs/integrations/cloud/hasura-cloud.mdx
Normal 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
|
||||
|
||||

|
||||
|
||||
## Enter your Hasura Cloud Access Token
|
||||
|
||||
Obtain a Hasura Cloud Access Token in My Account > Access Tokens
|
||||
|
||||

|
||||
|
||||
Press on the Hasura Cloud tile and input your Hasura Cloud access token to grant Infisical access to your Hasura Cloud account.
|
||||
|
||||

|
||||
|
||||
<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.
|
||||
|
||||

|
||||

|
||||
@@ -141,10 +141,10 @@
|
||||
"group": "Deployment options",
|
||||
"pages": [
|
||||
"self-hosting/overview",
|
||||
"self-hosting/deployment-options/standalone-infisical",
|
||||
"self-hosting/deployment-options/kubernetes-helm",
|
||||
"self-hosting/deployment-options/aws-ec2",
|
||||
"self-hosting/deployment-options/docker-compose",
|
||||
"self-hosting/deployment-options/standalone-infisical",
|
||||
"self-hosting/deployment-options/digital-ocean-marketplace"
|
||||
]
|
||||
},
|
||||
@@ -189,9 +189,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"pages": [
|
||||
"integrations/overview"
|
||||
]
|
||||
"pages": ["integrations/overview"]
|
||||
},
|
||||
{
|
||||
"group": "Infrastructure Integrations",
|
||||
@@ -221,9 +219,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Digital Ocean",
|
||||
"pages": [
|
||||
"integrations/cloud/digital-ocean-app-platform"
|
||||
]
|
||||
"pages": ["integrations/cloud/digital-ocean-app-platform"]
|
||||
},
|
||||
"integrations/cloud/heroku",
|
||||
"integrations/cloud/vercel",
|
||||
@@ -234,6 +230,7 @@
|
||||
"integrations/cloud/laravel-forge",
|
||||
"integrations/cloud/supabase",
|
||||
"integrations/cloud/northflank",
|
||||
"integrations/cloud/hasura-cloud",
|
||||
"integrations/cloud/terraform-cloud",
|
||||
"integrations/cloud/teamcity",
|
||||
"integrations/cloud/cloudflare-pages",
|
||||
@@ -277,9 +274,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Build Tool Integrations",
|
||||
"pages": [
|
||||
"integrations/build-tools/gradle"
|
||||
]
|
||||
"pages": ["integrations/build-tools/gradle"]
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "All environment variables"
|
||||
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.
|
||||
Other environment variables are listed below to increase the functionality of your self hosted instance based on your use case.
|
||||
@@ -11,43 +11,12 @@ Other environment variables are listed below to increase the functionality of yo
|
||||
<Tabs>
|
||||
<Tab title="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`
|
||||
</ParamField>
|
||||
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
|
||||
</ParamField>
|
||||
|
||||
{" "}
|
||||
|
||||
<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 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>
|
||||
|
||||
<ParamField query="MONGO_URL" type="string" default="none" required>
|
||||
*TLS based connection string is not yet supported
|
||||
@@ -140,9 +109,6 @@ Other environment variables are listed below to increase the functionality of yo
|
||||
</Tab>
|
||||
<Tab title="Auth Integrations">
|
||||
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>
|
||||
OAuth2 client ID for Google login
|
||||
</ParamField>
|
||||
@@ -232,12 +198,3 @@ Infisical uses Sentry to report error logs
|
||||
<ParamField query="TELEMETRY_ENABLED" type="string" default="true" optional></ParamField>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Frontend environment variables
|
||||
|
||||
<ParamField
|
||||
query="TELEMETRY_ENABLED"
|
||||
type="string"
|
||||
default="true"
|
||||
optional
|
||||
></ParamField>
|
||||
|
||||
@@ -31,7 +31,7 @@ primary_region = "iad"
|
||||
MONGO_URL = <>
|
||||
|
||||
[http_service]
|
||||
internal_port = 80
|
||||
internal_port = 8080
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -23,40 +23,27 @@ helm repo update
|
||||
|
||||
## 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
|
||||
|
||||
By default, the application will use the latest tag to retrieve the required Docker images, which may be appropriate for most cases.
|
||||
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).
|
||||
|
||||
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.
|
||||
<Tip>
|
||||
To find the latest version number of Infisical, follow the links below
|
||||
- [frontend Docker image](https://hub.docker.com/r/infisical/frontend/tags)
|
||||
- [backend Docker image](https://hub.docker.com/r/infisical/backend/tags)
|
||||
To find the latest version number of Infisical, click [here](https://hub.docker.com/r/infisical/infisical/tags)
|
||||
</Tip>
|
||||
|
||||
```yaml simple-values-example.yaml
|
||||
frontend:
|
||||
name: frontend
|
||||
replicaCount: 2
|
||||
image:
|
||||
repository: infisical/frontend
|
||||
tag: "v0.34.2" # <--- frontend version
|
||||
pullPolicy: Always
|
||||
|
||||
backend:
|
||||
replicaCount: 2
|
||||
image:
|
||||
repository: infisical/backend
|
||||
tag: "v0.34.2" # <--- backend version
|
||||
repository: infisical/infisical
|
||||
tag: "v0.39.5"
|
||||
pullPolicy: Always
|
||||
```
|
||||
|
||||
#### 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`_.
|
||||
|
||||
@@ -87,38 +74,30 @@ Infisical uses Nginx to route external traffic. You can install Nginx along with
|
||||
...
|
||||
ingress:
|
||||
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
|
||||
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).
|
||||
|
||||
```yaml simple-values-example.yaml
|
||||
mongodb:
|
||||
enabled: false
|
||||
enabled: true
|
||||
persistence:
|
||||
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`.
|
||||
|
||||
#### Example helm values
|
||||
```yaml simple-values-example.yaml
|
||||
frontend:
|
||||
name: frontend
|
||||
replicaCount: 2
|
||||
image:
|
||||
repository: infisical/frontend
|
||||
tag: "v0.34.2" # <--- frontend version
|
||||
pullPolicy: Always
|
||||
|
||||
backend:
|
||||
replicaCount: 2
|
||||
image:
|
||||
repository: infisical/backend
|
||||
tag: "v0.34.2" # <--- backend version
|
||||
repository: infisical/infisical
|
||||
tag: "v0.39.5"
|
||||
pullPolicy: Always
|
||||
|
||||
backendEnvironmentVariables:
|
||||
@@ -126,7 +105,7 @@ backendEnvironmentVariables:
|
||||
|
||||
ingress:
|
||||
nginx:
|
||||
enabled: true #<-- if you would like to install nginx along with Infisical
|
||||
enabled: true
|
||||
|
||||
```
|
||||
|
||||
@@ -136,22 +115,6 @@ ingress:
|
||||
nginx:
|
||||
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:
|
||||
enabled: true
|
||||
name: backend
|
||||
@@ -159,8 +122,8 @@ ingress:
|
||||
deploymentAnnotations: {}
|
||||
replicaCount: 4
|
||||
image:
|
||||
repository: infisical/backend
|
||||
tag: "v0.34.2" # <--- backend version
|
||||
repository: infisical/infisical
|
||||
tag: "v0.39.5"
|
||||
pullPolicy: IfNotPresent
|
||||
kubeSecretRef: null
|
||||
service:
|
||||
@@ -176,26 +139,9 @@ ingress:
|
||||
|
||||
## Mongo DB persistence
|
||||
mongodb:
|
||||
enabled: false
|
||||
persistence:
|
||||
enabled: false
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod" # <-- if you are setting up HTTPS
|
||||
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
|
||||
|
||||
persistence:
|
||||
enabled: true
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
|
||||
@@ -3,11 +3,15 @@ title: "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
|
||||
|
||||
@@ -18,59 +22,39 @@ docker pull infisical/infisical:latest
|
||||
```
|
||||
|
||||
## Run with docker
|
||||
The Infisical Docker image requires several required environment variables.
|
||||
Add the required environment variables listed below to your docker run command. View [all configurable environment variables](../configuration/envars)
|
||||
|
||||
To run Infisical, we'll need to configure the required configs listed below.
|
||||
Other configs can be found [here](../configuration/envars)
|
||||
|
||||
<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`
|
||||
</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`
|
||||
</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>
|
||||
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
|
||||
</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.
|
||||
|
||||
```bash
|
||||
docker run -p 80:80 \
|
||||
docker run -p 80:8080 \
|
||||
-e ENCRYPTION_KEY=f40c9178624764ad85a6830b37ce239a \
|
||||
-e JWT_SIGNUP_SECRET=38ea90fb7998b92176080f457d890392 \
|
||||
-e JWT_REFRESH_SECRET=7764c7bbf3928ad501591a3e005eb364 \
|
||||
-e JWT_AUTH_SECRET=5239fea3a4720c0e524f814a540e14a2 \
|
||||
-e JWT_SERVICE_SECRET=8509fb8b90c9b53e9e61d1e35826dcb5 \
|
||||
-e AUTH_SECRET=5239fea3a4720c0e524f814a540e14a2 \
|
||||
-e MONGO_URL="<>" \
|
||||
-e REDIS_URL="<>" \
|
||||
infisical/infisical:latest
|
||||
```
|
||||
|
||||
<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>
|
||||
|
||||
## Verify the installation:
|
||||
|
||||
@@ -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.
|
||||
|
||||
<Card
|
||||
title="Kubernetes (recommended)"
|
||||
color="#ea5a0c"
|
||||
href="deployment-options/kubernetes-helm"
|
||||
title="Docker"
|
||||
color="#0285c7"
|
||||
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>
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
@@ -33,10 +33,10 @@ Choose from a variety of deployment options listed below to get started.
|
||||
Install Infisical using our Docker Compose template
|
||||
</Card>
|
||||
<Card
|
||||
title="Docker"
|
||||
color="#0285c7"
|
||||
href="deployment-options/standalone-infisical"
|
||||
>
|
||||
Use the fully packaged, single docker image Infisical to deploy anywhere
|
||||
</Card>
|
||||
title="Kubernetes"
|
||||
color="#ea5a0c"
|
||||
href="deployment-options/kubernetes-helm"
|
||||
>
|
||||
Use our Helm chart to Install Infisical on your Kubernetes cluster
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -8,7 +8,7 @@ module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
"es6": true
|
||||
es6: true
|
||||
},
|
||||
extends: [
|
||||
"airbnb",
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
**/
|
||||
const path = require("path");
|
||||
|
||||
const ContentSecurityPolicy = `
|
||||
@@ -53,7 +49,9 @@ const securityHeaders = [
|
||||
value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim()
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
**/
|
||||
module.exports = {
|
||||
output: "standalone",
|
||||
i18n: {
|
||||
|
||||
2
frontend/package-lock.json
generated
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "npm-proj-1695919945735-0.225773463026700768rr1Oh",
|
||||
"name": "frontend",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -6,74 +6,75 @@ const integrationSlugNameMapping: Mapping = {
|
||||
"azure-key-vault": "Azure Key Vault",
|
||||
"aws-parameter-store": "AWS Parameter Store",
|
||||
"aws-secret-manager": "AWS Secrets Manager",
|
||||
"heroku": "Heroku",
|
||||
"vercel": "Vercel",
|
||||
"netlify": "Netlify",
|
||||
"github": "GitHub",
|
||||
"gitlab": "GitLab",
|
||||
"render": "Render",
|
||||
heroku: "Heroku",
|
||||
vercel: "Vercel",
|
||||
netlify: "Netlify",
|
||||
github: "GitHub",
|
||||
gitlab: "GitLab",
|
||||
render: "Render",
|
||||
"laravel-forge": "Laravel Forge",
|
||||
"railway": "Railway",
|
||||
"flyio": "Fly.io",
|
||||
"circleci": "CircleCI",
|
||||
"travisci": "TravisCI",
|
||||
"supabase": "Supabase",
|
||||
"checkly": "Checkly",
|
||||
"qovery": "Qovery",
|
||||
railway: "Railway",
|
||||
flyio: "Fly.io",
|
||||
circleci: "CircleCI",
|
||||
travisci: "TravisCI",
|
||||
supabase: "Supabase",
|
||||
checkly: "Checkly",
|
||||
qovery: "Qovery",
|
||||
"terraform-cloud": "Terraform Cloud",
|
||||
"teamcity": "TeamCity",
|
||||
teamcity: "TeamCity",
|
||||
"hashicorp-vault": "Vault",
|
||||
"cloudflare-pages": "Cloudflare Pages",
|
||||
"codefresh": "Codefresh",
|
||||
codefresh: "Codefresh",
|
||||
"digital-ocean-app-platform": "Digital Ocean App Platform",
|
||||
"bitbucket": "BitBucket",
|
||||
bitbucket: "BitBucket",
|
||||
"cloud-66": "Cloud 66",
|
||||
"northflank": "Northflank",
|
||||
"windmill": "Windmill",
|
||||
"gcp-secret-manager": "GCP Secret Manager"
|
||||
}
|
||||
northflank: "Northflank",
|
||||
windmill: "Windmill",
|
||||
"gcp-secret-manager": "GCP Secret Manager",
|
||||
"hasura-cloud": "Hasura Cloud"
|
||||
};
|
||||
|
||||
const envMapping: Mapping = {
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
Testing: "test"
|
||||
};
|
||||
|
||||
const reverseEnvMapping: Mapping = {
|
||||
dev: "Development",
|
||||
staging: "Staging",
|
||||
prod: "Production",
|
||||
test: "Testing",
|
||||
test: "Testing"
|
||||
};
|
||||
|
||||
const contextNetlifyMapping: Mapping = {
|
||||
"dev": "Local development",
|
||||
dev: "Local development",
|
||||
"branch-deploy": "Branch deploys",
|
||||
"deploy-preview": "Deploy Previews",
|
||||
"production": "Production"
|
||||
}
|
||||
production: "Production"
|
||||
};
|
||||
|
||||
const reverseContextNetlifyMapping: Mapping = {
|
||||
"Local development": "dev",
|
||||
"Branch deploys": "branch-deploy",
|
||||
"Deploy Previews": "deploy-preview",
|
||||
"Production": "production"
|
||||
}
|
||||
Production: "production"
|
||||
};
|
||||
|
||||
const plansDev: Mapping = {
|
||||
"starter": "prod_Mb4ATFT5QAHoPM",
|
||||
"team": "prod_NEpD2WMXUS2eDn",
|
||||
"professional": "prod_Mb4CetZ2jE7jdl",
|
||||
"enterprise": "licence_key_required"
|
||||
}
|
||||
starter: "prod_Mb4ATFT5QAHoPM",
|
||||
team: "prod_NEpD2WMXUS2eDn",
|
||||
professional: "prod_Mb4CetZ2jE7jdl",
|
||||
enterprise: "licence_key_required"
|
||||
};
|
||||
|
||||
const plansProd: Mapping = {
|
||||
"starter": "prod_Mb8oR5XNwyFTul",
|
||||
"team": "prod_NEp7fAB3UJWK6A",
|
||||
"professional": "prod_Mb8pUIpA0OUi5N",
|
||||
"enterprise": "licence_key_required"
|
||||
}
|
||||
starter: "prod_Mb8oR5XNwyFTul",
|
||||
team: "prod_NEp7fAB3UJWK6A",
|
||||
professional: "prod_Mb8pUIpA0OUi5N",
|
||||
enterprise: "licence_key_required"
|
||||
};
|
||||
|
||||
const plans = plansProd || plansDev;
|
||||
|
||||
@@ -83,4 +84,5 @@ export {
|
||||
integrationSlugNameMapping,
|
||||
plans,
|
||||
reverseContextNetlifyMapping,
|
||||
reverseEnvMapping}
|
||||
reverseEnvMapping
|
||||
};
|
||||
|
||||
11
frontend/public/images/integrations/Hasura.svg
Normal file
@@ -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
frontend/scripts/initialize-standalone-build.sh
Executable 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
frontend/scripts/replace-standalone-build-variable.sh
Executable 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
|
||||
0
frontend/scripts/replace-variable.sh
Normal file → Executable file
8
frontend/scripts/set-standalone-build-telemetry.sh
Normal 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
|
||||
0
frontend/scripts/set-telemetry.sh
Normal file → Executable file
@@ -10,7 +10,7 @@ export const initPostHog = () => {
|
||||
try {
|
||||
if (typeof window !== "undefined") {
|
||||
// @ts-ignore
|
||||
if (ENV === "production" && TELEMETRY_CAPTURING_ENABLED) {
|
||||
if (ENV === "production" && TELEMETRY_CAPTURING_ENABLED === "true") {
|
||||
posthog.init(POSTHOG_API_KEY, {
|
||||
api_host: POSTHOG_HOST
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ class Capturer {
|
||||
}
|
||||
|
||||
capture(item: string) {
|
||||
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED) {
|
||||
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED === "true") {
|
||||
try {
|
||||
this.api.capture(item);
|
||||
} catch (error) {
|
||||
@@ -23,7 +23,7 @@ class Capturer {
|
||||
}
|
||||
|
||||
identify(id: string, email?: string) {
|
||||
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED) {
|
||||
if (ENV === 'production' && TELEMETRY_CAPTURING_ENABLED === "true") {
|
||||
try {
|
||||
this.api.identify(id, {
|
||||
email: email
|
||||
|
||||
61
frontend/src/components/v2/Alert/Alert.stories.tsx
Normal file
@@ -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
|
||||
}
|
||||
};
|
||||
85
frontend/src/components/v2/Alert/Alert.tsx
Normal file
@@ -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 };
|
||||
1
frontend/src/components/v2/Alert/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { Alert, AlertDescription } from "./Alert";
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./Accordion";
|
||||
export * from "./Alert";
|
||||
export * from "./Button";
|
||||
export * from "./Card";
|
||||
export * from "./Checkbox";
|
||||
|
||||
4
frontend/src/hooks/api/apiKeys/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export {
|
||||
useCreateAPIKeyV2,
|
||||
useDeleteAPIKeyV2,
|
||||
useUpdateAPIKeyV2} from "./queries";
|
||||
62
frontend/src/hooks/api/apiKeys/queries.tsx
Normal file
@@ -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
frontend/src/hooks/api/apiKeys/types.ts
Normal 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,3 +1,4 @@
|
||||
export * from "./apiKeys";
|
||||
export * from "./auditLogs";
|
||||
export * from "./auth";
|
||||
export * from "./bots";
|
||||
|
||||
@@ -131,6 +131,7 @@ export const useUpdateSecretV3 = ({
|
||||
mutationFn: async ({
|
||||
secretPath = "/",
|
||||
type,
|
||||
secretId,
|
||||
environment,
|
||||
workspaceId,
|
||||
secretName,
|
||||
@@ -157,6 +158,7 @@ export const useUpdateSecretV3 = ({
|
||||
environment,
|
||||
type,
|
||||
secretPath,
|
||||
secretId,
|
||||
...encryptSecret(randomBytes, newSecretName ?? secretName, secretValue, secretComment),
|
||||
tags,
|
||||
skipMultilineEncoding,
|
||||
|
||||
@@ -109,6 +109,7 @@ export type TUpdateSecretsV3DTO = {
|
||||
skipMultilineEncoding?: boolean;
|
||||
newSecretName?: string;
|
||||
secretName: string;
|
||||
secretId?: string;
|
||||
secretValue: string;
|
||||
secretComment?: string;
|
||||
tags?: string[];
|
||||
|
||||
@@ -4,4 +4,5 @@ export {
|
||||
useDeleteServiceToken,
|
||||
useDeleteServiceTokenV3,
|
||||
useGetUserWsServiceTokens,
|
||||
useUpdateServiceTokenV3} from "./queries";
|
||||
useUpdateServiceTokenV3
|
||||
} from "./queries";
|
||||
|
||||
@@ -7,6 +7,7 @@ export {
|
||||
useDeleteOrgMembership,
|
||||
useDeleteUser,
|
||||
useGetMyAPIKeys,
|
||||
useGetMyAPIKeysV2,
|
||||
useGetMyIp,
|
||||
useGetMyOrganizationProjects,
|
||||
useGetMySessions,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import { apiRequest } from "@app/config/request";
|
||||
import { setAuthToken } from "@app/reactQuery";
|
||||
|
||||
import { APIKeyDataV2 } from "../apiKeys/types";
|
||||
import { useUploadWsKey } from "../keys/queries";
|
||||
import { workspaceKeys } from "../workspace/queries";
|
||||
import {
|
||||
@@ -24,12 +25,13 @@ import {
|
||||
User
|
||||
} from "./types";
|
||||
|
||||
const userKeys = {
|
||||
export const userKeys = {
|
||||
getUser: ["user"] as const,
|
||||
userAction: ["user-action"] as const,
|
||||
getOrgUsers: (orgId: string) => [{ orgId }, "user"],
|
||||
myIp: ["ip"] as const,
|
||||
myAPIKeys: ["api-keys"] as const,
|
||||
myAPIKeysV2: ["api-keys-v2"] as const,
|
||||
mySessions: ["sessions"] 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({
|
||||
queryKey: userKeys.myAPIKeys,
|
||||
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();
|
||||
return useMutation({
|
||||
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();
|
||||
return useMutation({
|
||||
mutationFn: async (apiKeyDataId: string) => {
|
||||
|
||||
@@ -726,7 +726,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
{infisicalPlatformVersion && (
|
||||
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
|
||||
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
|
||||
Platform Version: {infisicalPlatformVersion}
|
||||
Version: {infisicalPlatformVersion}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,6 @@ export default function FlyioAuthorizeIntegrationPage() {
|
||||
const onFormSubmit = async ({
|
||||
accessToken
|
||||
}: FormData) => {
|
||||
console.log("onFormSubmit accessToken: ", accessToken);
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||