1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-28 15:29:21 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
121adf924b fix: upgrade framer-motion from 6.2.3 to 6.5.1
Snyk has created this PR to upgrade framer-motion from 6.2.3 to 6.5.1.

See this package in npm:
https://www.npmjs.com/package/framer-motion

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-04-28 20:41:48 +00:00
329 changed files with 15274 additions and 11608 deletions
.env.example
.github/workflows
.goreleaser.yamlDockerfile.standalone-infisicalREADME.mdSECURITY.md
backend
cli/packages
docs
ecosystem.config.js
frontend
next-i18next.config.jsnext.config.jspackage-lock.jsonpackage.json
public/locales
en
es
fr
ko
pt-BR
tr
src
components
ee/components
hooks/api/secrets
i18n.ts
layouts/AppLayout
AppLayout.tsx
components/NavBar
pages
reactQuery.ts
views
DashboardPage
Settings
OrgSettingsPage
OrgSettingsPage.tsx
components
OrgMembersTable
OrgNameChangeSection
ProjectSettingsPage
ProjectSettingsPage.tsx
components
AutoCapitalizationSection
CopyProjectIDSection
ProjectNameChangeSection
ServiceTokenSection
i18n
nginx
render.yaml

@ -1,6 +1,5 @@
# Keys
# Required key for platform encryption/decryption ops
# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NOT BE USED FOR PRODUCTION
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
# JWT
@ -31,12 +30,14 @@ MONGO_PASSWORD=example
# Required
SITE_URL=http://localhost:8080
# Mail/SMTP
SMTP_HOST=
SMTP_PORT=
SMTP_NAME=
SMTP_USERNAME=
SMTP_PASSWORD=
# Mail/SMTP
SMTP_HOST=
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_PORT=587
SMTP_SECURE=false
SMTP_FROM_ADDRESS=
SMTP_FROM_NAME=Infisical
# Integration
# Optional only if integration is used

@ -1,17 +1,12 @@
name: Build, Publish and Deploy to Gamma
on:
push:
tags:
- "infisical/v*.*.*"
on: [workflow_dispatch]
jobs:
backend-image:
name: Build backend image
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: extract_version
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical/}"
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: 📦 Install dependencies to test all dependencies
@ -56,19 +51,15 @@ jobs:
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true
context: backend
tags: |
infisical/backend:${{ steps.commit.outputs.short }}
tags: infisical/backend:${{ steps.commit.outputs.short }},
infisical/backend:latest
infisical/backend:${{ steps.extract_version.outputs.version }}
platforms: linux/amd64,linux/arm64
frontend-image:
name: Build frontend image
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: extract_version
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical/}"
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: Save commit hashes for tag
@ -109,10 +100,8 @@ jobs:
push: true
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
context: frontend
tags: |
infisical/frontend:${{ steps.commit.outputs.short }}
tags: infisical/frontend:${{ steps.commit.outputs.short }},
infisical/frontend:latest
infisical/frontend:${{ steps.extract_version.outputs.version }}
platforms: linux/amd64,linux/arm64
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}

@ -1,68 +0,0 @@
name: Release standalone docker image
on: [workflow_dispatch]
jobs:
infisical-standalone:
name: Build infisical standalone image
runs-on: ubuntu-latest
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: 📦 Install dependencies to test all dependencies
run: npm ci --only-production
working-directory: backend
- uses: paulhatch/semantic-version@v5.0.2
id: version
with:
# The prefix to use to identify tags
tag_prefix: "infisical-standalone/v"
# A string which, if present in a git commit, indicates that a change represents a
# major (breaking) change, supports regular expressions wrapped with '/'
major_pattern: "(MAJOR)"
# Same as above except indicating a minor change, supports regular expressions wrapped with '/'
minor_pattern: "(MINOR)"
# A string to determine the format of the version output
version_format: "${major}.${minor}.${patch}-prerelease${increment}"
# Optional path to check for changes. If any changes are detected in the path the
# 'changed' output will true. Enter multiple paths separated by spaces.
change_path: "backend,frontend"
# Prevents pre-v1.0.0 version from automatically incrementing the major version.
# If enabled, when the major version is 0, major releases will be treated as minor and minor as patch. Note that the version_type output is unchanged.
enable_prerelease_mode: true
# - name: 🧪 Run tests
# run: npm run test:ci
# working-directory: backend
- name: version output
run: |
echo "Output Value: ${{ steps.version.outputs.major }}"
echo "Output Value: ${{ steps.version.outputs.minor }}"
echo "Output Value: ${{ steps.version.outputs.patch }}"
echo "Output Value: ${{ steps.version.outputs.version }}"
echo "Output Value: ${{ steps.version.outputs.version_type }}"
echo "Output Value: ${{ steps.version.outputs.increment }}"
- 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 backend and export to Docker
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true
context: .
tags: |
infisical/infisical:latest
infisical/infisical:${{ steps.commit.outputs.short }}
platforms: linux/amd64,linux/arm64
file: Dockerfile.standalone-infisical

@ -4,7 +4,7 @@ on:
push:
# run only against tags
tags:
- "infisical-cli/v*.*.*"
- "v*"
permissions:
contents: write
@ -41,14 +41,13 @@ jobs:
git clone https://github.com/plentico/osxcross-target.git ../../osxcross/target
- uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser-pro
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- uses: actions/setup-python@v4
- run: pip install --upgrade cloudsmith-cli
- name: Publish to CloudSmith

@ -11,10 +11,6 @@ before:
- ./cli/scripts/completions.sh
- ./cli/scripts/manpages.sh
monorepo:
tag_prefix: infisical-cli/
dir: cli
builds:
- id: darwin-build
binary: infisical
@ -65,10 +61,10 @@ archives:
- goos: windows
format: zip
files:
- ../README*
- ../LICENSE*
- ../manpages/*
- ../completions/*
- README*
- LICENSE*
- manpages/*
- completions/*
release:
replace_existing_draft: true
@ -78,7 +74,14 @@ checksum:
name_template: "checksums.txt"
snapshot:
name_template: "{{ .Version }}-devel"
name_template: "{{ incpatch .Version }}-devel"
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
# publishers:
# - name: fury.io
@ -161,7 +164,7 @@ aurs:
mkdir -p "${pkgdir}/usr/share/zsh/site-functions/"
mkdir -p "${pkgdir}/usr/share/fish/vendor_completions.d/"
install -Dm644 "./completions/infisical.bash" "${pkgdir}/usr/share/bash-completion/completions/infisical"
install -Dm644 "./completions/infisical.zsh" "${pkgdir}/usr/share/zsh/site-functions/_infisical"
install -Dm644 "./completions/infisical.zsh" "${pkgdir}/usr/share/zsh/site-functions/infisical"
install -Dm644 "./completions/infisical.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/infisical.fish"
# man pages
install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz"

@ -1,102 +0,0 @@
ARG POSTHOG_HOST=https://app.posthog.com
ARG POSTHOG_API_KEY=posthog-api-key
FROM node:16-alpine AS frontend-dependencies
WORKDIR /app
COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./
# Install dependencies
RUN npm ci --only-production --ignore-scripts
# Rebuild the source code only when needed
FROM node:16-alpine AS frontend-builder
WORKDIR /app
# Copy dependencies
COPY --from=frontend-dependencies /app/node_modules ./node_modules
# Copy all files
COPY /frontend .
ENV NODE_ENV production
ENV NEXT_PUBLIC_ENV production
ARG POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
# Build
RUN npm run build
# Production image
FROM node:16-alpine AS frontend-runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN mkdir -p /app/.next/cache/images && chown nextjs:nodejs /app/.next/cache/images
VOLUME /app/.next/cache/images
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
COPY --chown=nextjs: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
USER nextjs
ENV NEXT_TELEMETRY_DISABLED 1
##
## BACKEND
##
FROM node:16-alpine AS backend-build
WORKDIR /app
COPY backend/package*.json ./
RUN npm ci --only-production
COPY /backend .
RUN npm run build
# Production stage
FROM node:16-alpine AS backend-runner
WORKDIR /app
COPY backend/package*.json ./
RUN npm ci --only-production
COPY --from=backend-build /app .
# Production stage
FROM node:14-alpine AS production
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/
EXPOSE 80
ENV HTTPS_ENABLED false
CMD ["pm2-runtime", "start", "ecosystem.config.js"]

356
README.md

File diff suppressed because one or more lines are too long

@ -1,13 +1,9 @@
# Security Policy
## Supported versions
## Supported Versions
We always recommend using the latest version of Infisical to ensure you get all security updates.
## Reporting vulnerabilities
## Reporting a Vulnerability
Please do not file GitHub issues or post on our public forum for security vulnerabilities, as they are public!
Infisical takes security issues very seriously. If you have any concerns about Infisical or believe you have uncovered a vulnerability, please get in touch via the e-mail address security@infisical.com. In the message, try to provide a description of the issue and ideally a way of reproducing it. The security team will get back to you as soon as possible.
Note that this security address should be used only for undisclosed vulnerabilities. Please report any security problems to us before disclosing it publicly.
Please report security vulnerabilities or concerns to team@infisical.com.

1793
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,19 +1,19 @@
{
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.317.0",
"@godaddy/terminus": "^4.12.0",
"@aws-sdk/client-secrets-manager": "^3.306.0",
"@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.41.0",
"@sentry/tracing": "^7.48.0",
"@sentry/tracing": "^7.47.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",
"argon2": "^0.30.3",
"await-to-js": "^3.0.0",
"aws-sdk": "^2.1360.0",
"aws-sdk": "^2.1338.0",
"axios": "^1.3.5",
"axios-retry": "^3.4.0",
"bcrypt": "^5.1.0",
"bigint-conversion": "^2.4.0",
"bigint-conversion": "^2.2.2",
"builder-pattern": "^2.2.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
@ -30,7 +30,7 @@
"jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21",
"mongoose": "^6.10.5",
"mongoose": "^6.10.4",
"nodemailer": "^6.8.0",
"posthog-node": "^2.6.0",
"query-string": "^7.1.3",

@ -5,7 +5,7 @@ const client = new InfisicalClient({
});
export const getPort = async () => (await client.getSecret('PORT')).secretValue || 4000;
export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue == undefined ? false : (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue;
export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue == undefined ? false : await client.getSecret('INVITE_ONLY_SIGNUP');
export const getEncryptionKey = async () => (await client.getSecret('ENCRYPTION_KEY')).secretValue;
export const getSaltRounds = async () => parseInt((await client.getSecret('SALT_ROUNDS')).secretValue) || 10;
export const getJwtAuthLifetime = async () => (await client.getSecret('JWT_AUTH_LIFETIME')).secretValue || '10d';

@ -188,7 +188,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
});
if (!(await getSmtpConfigured())) {
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}&organization_id=${organization._id}`
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}`
}
}
@ -217,10 +217,10 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
export const verifyUserToOrganization = async (req: Request, res: Response) => {
let user, token;
try {
const {
email,
const {
email,
organizationId,
code
code
} = req.body;
user = await User.findOne({ email }).select('+publicKey');

@ -86,22 +86,4 @@ export const deleteFolder = async (req: Request, res: Response) => {
}
res.send()
}
// TODO: validate workspace
export const getFolderById = async (req: Request, res: Response) => {
const { folderId } = req.params
const folder = await Folder.findById(folderId);
if (!folder) {
throw BadRequestError({ message: "The folder doesn't exist" })
}
// check that user is a member of the workspace
await validateMembership({
userId: req.user._id.toString(),
workspaceId: folder.workspace as any,
acceptedRoles: [ADMIN, MEMBER]
});
res.send({ folder })
}

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { Action } from '../models';
import {
@ -35,25 +36,33 @@ const createActionUpdateSecret = async ({
workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[];
}) => {
const latestSecretVersions = (await getLatestNSecretSecretVersionIds({
secretIds,
n: 2
}))
.map((s) => ({
oldSecretVersion: s.versions[0]._id,
newSecretVersion: s.versions[1]._id
}));
const action = await new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions
}
}).save();
let action;
try {
const latestSecretVersions = (await getLatestNSecretSecretVersionIds({
secretIds,
n: 2
}))
.map((s) => ({
oldSecretVersion: s.versions[0]._id,
newSecretVersion: s.versions[1]._id
}));
action = await new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions
}
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create update secret action');
}
return action;
}
@ -81,25 +90,33 @@ const createActionSecret = async ({
workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[];
}) => {
// case: action is adding, deleting, or reading secrets
// -> add new secret versions
const latestSecretVersions = (await getLatestSecretVersionIds({
secretIds
}))
.map((s) => ({
newSecretVersion: s.versionId
}));
const action = await new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions
}
}).save();
let action;
try {
// case: action is adding, deleting, or reading secrets
// -> add new secret versions
const latestSecretVersions = (await getLatestSecretVersionIds({
secretIds
}))
.map((s) => ({
newSecretVersion: s.versionId
}));
action = await new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions
}
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create action create/read/delete secret action');
}
return action;
}
@ -123,12 +140,19 @@ const createActionClient = ({
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
}) => {
const action = new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId
}).save();
let action;
try {
action = new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create client action');
}
return action;
}
@ -157,34 +181,40 @@ const createActionHelper = async ({
secretIds?: Types.ObjectId[];
}) => {
let action;
switch (name) {
case ACTION_LOGIN:
case ACTION_LOGOUT:
action = await createActionClient({
name,
userId
});
break;
case ACTION_ADD_SECRETS:
case ACTION_READ_SECRETS:
case ACTION_DELETE_SECRETS:
if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret');
action = await createActionSecret({
name,
userId,
workspaceId,
secretIds
});
break;
case ACTION_UPDATE_SECRETS:
if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret');
action = await createActionUpdateSecret({
name,
userId,
workspaceId,
secretIds
});
break;
try {
switch (name) {
case ACTION_LOGIN:
case ACTION_LOGOUT:
action = await createActionClient({
name,
userId
});
break;
case ACTION_ADD_SECRETS:
case ACTION_READ_SECRETS:
case ACTION_DELETE_SECRETS:
if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret');
action = await createActionSecret({
name,
userId,
workspaceId,
secretIds
});
break;
case ACTION_UPDATE_SECRETS:
if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret');
action = await createActionUpdateSecret({
name,
userId,
workspaceId,
secretIds
});
break;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create action');
}
return action;
@ -192,4 +222,4 @@ const createActionHelper = async ({
export {
createActionHelper
};
};

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import {
Log,
@ -31,20 +32,27 @@ const createLogHelper = async ({
channel: string;
ipAddress: string;
}) => {
const log = await new Log({
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId ?? undefined,
actionNames: actions.map((a) => a.name),
actions,
channel,
ipAddress
}).save();
let log;
try {
log = await new Log({
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId ?? undefined,
actionNames: actions.map((a) => a.name),
actions,
channel,
ipAddress
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create log');
}
return log;
}
export {
createLogHelper
}
}

@ -1,6 +1,14 @@
import { Types } from "mongoose";
import { Secret, ISecret } from "../../models";
import { SecretSnapshot, SecretVersion, ISecretVersion } from "../models";
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Secret,
ISecret,
} from '../../models';
import {
SecretSnapshot,
SecretVersion,
ISecretVersion
} from '../models';
/**
* Save a secret snapshot that is a copy of the current state of secrets in workspace with id
@ -11,53 +19,56 @@ import { SecretSnapshot, SecretVersion, ISecretVersion } from "../models";
* @returns {SecretSnapshot} secretSnapshot - new secret snapshot
*/
const takeSecretSnapshotHelper = async ({
workspaceId,
workspaceId
}: {
workspaceId: Types.ObjectId;
workspaceId: Types.ObjectId;
}) => {
const secretIds = (
await Secret.find(
{
workspace: workspaceId,
},
"_id"
)
).map((s) => s._id);
const latestSecretVersions = (
await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds,
},
},
},
{
$group: {
_id: "$secret",
version: { $max: "$version" },
versionId: { $max: "$_id" }, // secret version id
},
},
{
$sort: { version: -1 },
},
]).exec()
).map((s) => s.versionId);
let secretSnapshot;
try {
const secretIds = (await Secret.find({
workspace: workspaceId
}, '_id')).map((s) => s._id);
const latestSecretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId,
}).sort({ version: -1 });
const latestSecretVersions = (await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds
}
}
},
{
$group: {
_id: '$secret',
version: { $max: '$version' },
versionId: { $max: '$_id' } // secret version id
}
},
{
$sort: { version: -1 }
}
])
.exec())
.map((s) => s.versionId);
const secretSnapshot = await new SecretSnapshot({
workspace: workspaceId,
version: latestSecretSnapshot ? latestSecretSnapshot.version + 1 : 1,
secretVersions: latestSecretVersions,
}).save();
const latestSecretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId
}).sort({ version: -1 });
return secretSnapshot;
};
secretSnapshot = await new SecretSnapshot({
workspace: workspaceId,
version: latestSecretSnapshot ? latestSecretSnapshot.version + 1 : 1,
secretVersions: latestSecretVersions
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to take a secret snapshot');
}
return secretSnapshot;
}
/**
* Add secret versions [secretVersions] to the SecretVersion collection.
@ -66,79 +77,93 @@ const takeSecretSnapshotHelper = async ({
* @returns {SecretVersion[]} newSecretVersions - new secret versions
*/
const addSecretVersionsHelper = async ({
secretVersions,
secretVersions
}: {
secretVersions: ISecretVersion[];
secretVersions: ISecretVersion[]
}) => {
const newSecretVersions = await SecretVersion.insertMany(secretVersions);
let newSecretVersions;
try {
newSecretVersions = await SecretVersion.insertMany(secretVersions);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(`Failed to add secret versions [err=${err}]`);
}
return newSecretVersions;
};
return newSecretVersions;
}
const markDeletedSecretVersionsHelper = async ({
secretIds,
secretIds
}: {
secretIds: Types.ObjectId[];
secretIds: Types.ObjectId[];
}) => {
await SecretVersion.updateMany(
{
secret: { $in: secretIds },
},
{
isDeleted: true,
},
{
new: true,
}
);
};
try {
await SecretVersion.updateMany({
secret: { $in: secretIds }
}, {
isDeleted: true
}, {
new: true
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to mark secret versions as deleted');
}
}
/**
* Initialize secret versioning by setting previously unversioned
* secrets to version 1 and begin populating secret versions.
*/
const initSecretVersioningHelper = async () => {
await Secret.updateMany(
{ version: { $exists: false } },
{ $set: { version: 1 } }
);
try {
const unversionedSecrets: ISecret[] = await Secret.aggregate([
{
$lookup: {
from: "secretversions",
localField: "_id",
foreignField: "secret",
as: "versions",
},
},
{
$match: {
versions: { $size: 0 },
},
},
]);
await Secret.updateMany(
{ version: { $exists: false } },
{ $set: { version: 1 } }
);
if (unversionedSecrets.length > 0) {
await addSecretVersionsHelper({
secretVersions: unversionedSecrets.map(
(s, idx) =>
new SecretVersion({
...s,
secret: s._id,
version: s.version ? s.version : 1,
isDeleted: false,
workspace: s.workspace,
environment: s.environment,
})
),
});
}
};
const unversionedSecrets: ISecret[] = await Secret.aggregate([
{
$lookup: {
from: 'secretversions',
localField: '_id',
foreignField: 'secret',
as: 'versions',
},
},
{
$match: {
versions: { $size: 0 },
},
},
]);
if (unversionedSecrets.length > 0) {
await addSecretVersionsHelper({
secretVersions: unversionedSecrets.map((s, idx) => new SecretVersion({
...s,
secret: s._id,
version: s.version ? s.version : 1,
isDeleted: false,
workspace: s.workspace,
environment: s.environment
}))
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to ensure that secrets are versioned');
}
}
export {
takeSecretSnapshotHelper,
addSecretVersionsHelper,
markDeletedSecretVersionsHelper,
initSecretVersioningHelper,
};
takeSecretSnapshotHelper,
addSecretVersionsHelper,
markDeletedSecretVersionsHelper,
initSecretVersioningHelper
}

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { SecretVersion } from '../models';
@ -12,32 +13,41 @@ const getLatestSecretVersionIds = async ({
}: {
secretIds: Types.ObjectId[];
}) => {
interface LatestSecretVersionId {
_id: Types.ObjectId;
version: number;
versionId: Types.ObjectId;
}
const latestSecretVersionIds = (await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds
}
let latestSecretVersionIds: LatestSecretVersionId[];
try {
latestSecretVersionIds = (await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds
}
}
},
{
$group: {
_id: '$secret',
version: { $max: '$version' },
versionId: { $max: '$_id' } // id of latest secret version
}
},
{
$sort: { version: -1 }
}
},
{
$group: {
_id: '$secret',
version: { $max: '$version' },
versionId: { $max: '$_id' } // id of latest secret version
}
},
{
$sort: { version: -1 }
}
])
.exec());
])
.exec());
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get latest secret versions');
}
return latestSecretVersionIds;
}
@ -56,32 +66,40 @@ const getLatestNSecretSecretVersionIds = async ({
secretIds: Types.ObjectId[];
n: number;
}) => {
// TODO: optimize query
const latestNSecretVersions = (await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds,
let latestNSecretVersions;
try {
latestNSecretVersions = (await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds,
},
},
},
{
$sort: { version: -1 },
},
},
{
$sort: { version: -1 },
},
{
$group: {
_id: "$secret",
versions: { $push: "$$ROOT" },
{
$group: {
_id: "$secret",
versions: { $push: "$$ROOT" },
},
},
},
{
$project: {
_id: 0,
secret: "$_id",
versions: { $slice: ["$versions", n] },
},
}
]));
{
$project: {
_id: 0,
secret: "$_id",
versions: { $slice: ["$versions", n] },
},
}
]));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get latest n secret versions');
}
return latestNSecretVersions;
}

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';

@ -1,34 +1,41 @@
import { Types } from "mongoose";
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import {
Bot,
BotKey,
Secret,
ISecret,
IUser,
User,
IServiceAccount,
ServiceAccount,
IServiceTokenData,
ServiceTokenData,
} from "../models";
Bot,
BotKey,
Secret,
ISecret,
IUser,
User,
IServiceAccount,
ServiceAccount,
IServiceTokenData,
ServiceTokenData
} from '../models';
import {
generateKeyPair,
encryptSymmetric,
decryptSymmetric,
decryptAsymmetric
} from '../utils/crypto';
import {
generateKeyPair,
encryptSymmetric,
decryptSymmetric,
decryptAsymmetric,
} from "../utils/crypto";
SECRET_SHARED,
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY
} from '../variables';
import { getEncryptionKey } from '../config';
import { BotNotFoundError, UnauthorizedRequestError } from '../utils/errors';
import {
SECRET_SHARED,
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY,
} from "../variables";
import { getEncryptionKey } from "../config";
import { BotNotFoundError, UnauthorizedRequestError } from "../utils/errors";
import { validateMembership } from "../helpers/membership";
import { validateUserClientForWorkspace } from "../helpers/user";
import { validateServiceAccountClientForWorkspace } from "../helpers/serviceAccount";
validateMembership
} from '../helpers/membership';
import {
validateUserClientForWorkspace
} from '../helpers/user';
import {
validateServiceAccountClientForWorkspace
} from '../helpers/serviceAccount';
/**
* Validate authenticated clients for bot with id [botId] based
@ -39,104 +46,99 @@ import { validateServiceAccountClientForWorkspace } from "../helpers/serviceAcco
* @param {Array<'admin' | 'member'>} obj.acceptedRoles - accepted workspace roles
*/
const validateClientForBot = async ({
authData,
botId,
acceptedRoles,
authData,
botId,
acceptedRoles
}: {
authData: {
authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData;
};
botId: Types.ObjectId;
acceptedRoles: Array<"admin" | "member">;
authData: {
authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData;
};
botId: Types.ObjectId;
acceptedRoles: Array<'admin' | 'member'>;
}) => {
const bot = await Bot.findById(botId);
const bot = await Bot.findById(botId);
if (!bot) throw BotNotFoundError();
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
await validateUserClientForWorkspace({
user: authData.authPayload,
workspaceId: bot.workspace,
acceptedRoles
});
return bot;
}
if (!bot) throw BotNotFoundError();
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
await validateServiceAccountClientForWorkspace({
serviceAccount: authData.authPayload,
workspaceId: bot.workspace
});
if (
authData.authMode === AUTH_MODE_JWT &&
authData.authPayload instanceof User
) {
await validateUserClientForWorkspace({
user: authData.authPayload,
workspaceId: bot.workspace,
acceptedRoles,
return bot;
}
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
throw UnauthorizedRequestError({
message: 'Failed service token authorization for bot'
});
}
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
await validateUserClientForWorkspace({
user: authData.authPayload,
workspaceId: bot.workspace,
acceptedRoles
});
return bot;
}
throw BotNotFoundError({
message: 'Failed client authorization for bot'
});
return bot;
}
if (
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
authData.authPayload instanceof ServiceAccount
) {
await validateServiceAccountClientForWorkspace({
serviceAccount: authData.authPayload,
workspaceId: bot.workspace,
});
return bot;
}
if (
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
authData.authPayload instanceof ServiceTokenData
) {
throw UnauthorizedRequestError({
message: "Failed service token authorization for bot",
});
}
if (
authData.authMode === AUTH_MODE_API_KEY &&
authData.authPayload instanceof User
) {
await validateUserClientForWorkspace({
user: authData.authPayload,
workspaceId: bot.workspace,
acceptedRoles,
});
return bot;
}
throw BotNotFoundError({
message: "Failed client authorization for bot",
});
};
}
/**
* Create an inactive bot with name [name] for workspace with id [workspaceId]
* @param {Object} obj
* @param {Object} obj
* @param {String} obj.name - name of bot
* @param {String} obj.workspaceId - id of workspace that bot belongs to
*/
const createBot = async ({
name,
workspaceId,
}: {
name: string;
workspaceId: Types.ObjectId;
}) => {
const { publicKey, privateKey } = generateKeyPair();
const { ciphertext, iv, tag } = encryptSymmetric({
plaintext: privateKey,
key: await getEncryptionKey(),
});
const bot = await new Bot({
name,
workspace: workspaceId,
isActive: false,
publicKey,
encryptedPrivateKey: ciphertext,
iv,
tag,
}).save();
workspaceId,
}: {
name: string;
workspaceId: Types.ObjectId;
}) => {
let bot;
try {
const { publicKey, privateKey } = generateKeyPair();
const { ciphertext, iv, tag } = encryptSymmetric({
plaintext: privateKey,
key: await getEncryptionKey()
});
return bot;
};
bot = await new Bot({
name,
workspace: workspaceId,
isActive: false,
publicKey,
encryptedPrivateKey: ciphertext,
iv,
tag
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create bot');
}
return bot;
}
/**
* Return decrypted secrets for workspace with id [workspaceId]
@ -146,105 +148,125 @@ const createBot = async ({
* @param {String} obj.environment - environment
*/
const getSecretsHelper = async ({
workspaceId,
environment,
workspaceId,
environment
}: {
workspaceId: Types.ObjectId;
environment: string;
workspaceId: Types.ObjectId;
environment: string;
}) => {
const content = {} as any;
const key = await getKey({ workspaceId: workspaceId.toString() });
const secrets = await Secret.find({
workspace: workspaceId,
environment,
type: SECRET_SHARED,
});
const content = {} as any;
try {
const key = await getKey({ workspaceId });
const secrets = await Secret.find({
workspace: workspaceId,
environment,
type: SECRET_SHARED
});
secrets.forEach((secret: ISecret) => {
const secretKey = decryptSymmetric({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key
});
secrets.forEach((secret: ISecret) => {
const secretKey = decryptSymmetric({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key,
});
const secretValue = decryptSymmetric({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key
});
const secretValue = decryptSymmetric({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key,
});
content[secretKey] = secretValue;
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get secrets');
}
content[secretKey] = secretValue;
});
return content;
};
return content;
}
/**
* Return bot's copy of the workspace key for workspace
* Return bot's copy of the workspace key for workspace
* with id [workspaceId]
* @param {Object} obj
* @param {String} obj.workspaceId - id of workspace
* @returns {String} key - decrypted workspace key
*/
const getKey = async ({ workspaceId }: { workspaceId: string }) => {
const botKey = await BotKey.findOne({
workspace: workspaceId,
}).populate<{ sender: IUser }>("sender", "publicKey");
if (!botKey) throw new Error("Failed to find bot key");
const bot = await Bot.findOne({
workspace: workspaceId,
}).select("+encryptedPrivateKey +iv +tag");
if (!bot) throw new Error("Failed to find bot");
if (!bot.isActive) throw new Error("Bot is not active");
const privateKeyBot = decryptSymmetric({
ciphertext: bot.encryptedPrivateKey,
iv: bot.iv,
tag: bot.tag,
key: await getEncryptionKey(),
});
const key = decryptAsymmetric({
ciphertext: botKey.encryptedKey,
nonce: botKey.nonce,
publicKey: botKey.sender.publicKey as string,
privateKey: privateKeyBot,
});
return key;
};
const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) => {
let key;
try {
const botKey = await BotKey.findOne({
workspace: workspaceId
}).populate<{ sender: IUser }>('sender', 'publicKey');
if (!botKey) throw new Error('Failed to find bot key');
const bot = await Bot.findOne({
workspace: workspaceId
}).select('+encryptedPrivateKey +iv +tag');
if (!bot) throw new Error('Failed to find bot');
if (!bot.isActive) throw new Error('Bot is not active');
const privateKeyBot = decryptSymmetric({
ciphertext: bot.encryptedPrivateKey,
iv: bot.iv,
tag: bot.tag,
key: await getEncryptionKey()
});
key = decryptAsymmetric({
ciphertext: botKey.encryptedKey,
nonce: botKey.nonce,
publicKey: botKey.sender.publicKey as string,
privateKey: privateKeyBot
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get workspace key');
}
return key;
}
/**
* Return symmetrically encrypted [plaintext] using the
* key for workspace with id [workspaceId]
* key for workspace with id [workspaceId]
* @param {Object} obj1
* @param {String} obj1.workspaceId - id of workspace
* @param {String} obj1.plaintext - plaintext to encrypt
*/
const encryptSymmetricHelper = async ({
workspaceId,
plaintext,
workspaceId,
plaintext
}: {
workspaceId: Types.ObjectId;
plaintext: string;
workspaceId: Types.ObjectId;
plaintext: string;
}) => {
const key = await getKey({ workspaceId: workspaceId.toString() });
const { ciphertext, iv, tag } = encryptSymmetric({
plaintext,
key,
});
return {
ciphertext,
iv,
tag,
};
};
try {
const key = await getKey({ workspaceId });
const { ciphertext, iv, tag } = encryptSymmetric({
plaintext,
key
});
return ({
ciphertext,
iv,
tag
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform symmetric encryption with bot');
}
}
/**
* Return symmetrically decrypted [ciphertext] using the
* key for workspace with id [workspaceId]
@ -255,31 +277,40 @@ const encryptSymmetricHelper = async ({
* @param {String} obj.tag - tag
*/
const decryptSymmetricHelper = async ({
workspaceId,
ciphertext,
iv,
tag,
}: {
workspaceId: Types.ObjectId;
ciphertext: string;
iv: string;
tag: string;
}) => {
const key = await getKey({ workspaceId: workspaceId.toString() });
const plaintext = decryptSymmetric({
workspaceId,
ciphertext,
iv,
tag,
key,
});
return plaintext;
};
tag
}: {
workspaceId: Types.ObjectId;
ciphertext: string;
iv: string;
tag: string;
}) => {
let plaintext;
try {
const key = await getKey({ workspaceId });
const plaintext = decryptSymmetric({
ciphertext,
iv,
tag,
key
});
return plaintext;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform symmetric decryption with bot');
}
return plaintext;
}
export {
validateClientForBot,
createBot,
getSecretsHelper,
encryptSymmetricHelper,
decryptSymmetricHelper,
};
validateClientForBot,
createBot,
getSecretsHelper,
encryptSymmetricHelper,
decryptSymmetricHelper
}

@ -1,13 +1,14 @@
import { Types } from "mongoose";
import { Bot, IBot } from "../models";
import { EVENT_PUSH_SECRETS } from "../variables";
import { IntegrationService } from "../services";
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import { Bot, IBot } from '../models';
import { EVENT_PUSH_SECRETS } from '../variables';
import { IntegrationService } from '../services';
interface Event {
name: string;
workspaceId: Types.ObjectId;
environment?: string;
payload: any;
name: string;
workspaceId: Types.ObjectId;
environment?: string;
payload: any;
}
/**
@ -18,25 +19,39 @@ interface Event {
* @param {String} obj.event.workspaceId - id of workspace that event is part of
* @param {Object} obj.event.payload - payload of event (depends on event)
*/
const handleEventHelper = async ({ event }: { event: Event }) => {
const { workspaceId, environment } = event;
// TODO: moduralize bot check into separate function
const bot = await Bot.findOne({
workspace: workspaceId,
isActive: true,
});
if (!bot) return;
switch (event.name) {
case EVENT_PUSH_SECRETS:
IntegrationService.syncIntegrations({
const handleEventHelper = async ({
event
}: {
event: Event;
}) => {
const {
workspaceId,
environment,
});
break;
}
};
environment
} = event;
// TODO: moduralize bot check into separate function
const bot = await Bot.findOne({
workspace: workspaceId,
isActive: true
});
if (!bot) return;
try {
switch (event.name) {
case EVENT_PUSH_SECRETS:
IntegrationService.syncIntegrations({
workspaceId,
environment
});
break;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
}
}
export { handleEventHelper };
export {
handleEventHelper
}

@ -256,7 +256,7 @@ const syncIntegrationsHelper = async ({
integration,
integrationAuth,
secrets,
accessId: access.accessId === undefined ? null : access.accessId,
accessId: access.accessId,
accessToken: access.accessToken
});
}
@ -482,4 +482,4 @@ export {
getIntegrationAuthAccessHelper,
setIntegrationAuthRefreshHelper,
setIntegrationAuthAccessHelper
}
}

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/node';
import { Key, IKey } from '../models';
interface Key {
@ -26,30 +27,36 @@ const pushKeys = async ({
workspaceId: string;
keys: Key[];
}): Promise<void> => {
// filter out already-inserted keys
const keysSet = new Set(
(
await Key.find(
{
workspace: workspaceId
},
'receiver'
)
).map((k: IKey) => k.receiver.toString())
);
try {
// filter out already-inserted keys
const keysSet = new Set(
(
await Key.find(
{
workspace: workspaceId
},
'receiver'
)
).map((k: IKey) => k.receiver.toString())
);
keys = keys.filter((key) => !keysSet.has(key.userId));
keys = keys.filter((key) => !keysSet.has(key.userId));
// add new shared keys only
await Key.insertMany(
keys.map((k) => ({
encryptedKey: k.encryptedKey,
nonce: k.nonce,
sender: userId,
receiver: k.userId,
workspace: workspaceId
}))
);
// add new shared keys only
await Key.insertMany(
keys.map((k) => ({
encryptedKey: k.encryptedKey,
nonce: k.nonce,
sender: userId,
receiver: k.userId,
workspace: workspaceId
}))
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to push access keys');
}
};
export { pushKeys };

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import {
MembershipOrg,
@ -143,7 +144,15 @@ const validateMembershipOrg = async ({
* @return {Object} membershipOrg - membership
*/
const findMembershipOrg = (queryObj: any) => {
const membershipOrg = MembershipOrg.findOne(queryObj);
let membershipOrg;
try {
membershipOrg = MembershipOrg.findOne(queryObj);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to find organization membership');
}
return membershipOrg;
};
@ -166,27 +175,33 @@ const addMembershipsOrg = async ({
roles: string[];
statuses: string[];
}) => {
const operations = userIds.map((userId, idx) => {
return {
updateOne: {
filter: {
user: userId,
organization: organizationId,
role: roles[idx],
status: statuses[idx]
},
update: {
user: userId,
organization: organizationId,
role: roles[idx],
status: statuses[idx]
},
upsert: true
}
};
});
try {
const operations = userIds.map((userId, idx) => {
return {
updateOne: {
filter: {
user: userId,
organization: organizationId,
role: roles[idx],
status: statuses[idx]
},
update: {
user: userId,
organization: organizationId,
role: roles[idx],
status: statuses[idx]
},
upsert: true
}
};
});
await MembershipOrg.bulkWrite(operations as any);
await MembershipOrg.bulkWrite(operations as any);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to add users to organization');
}
};
/**
@ -199,36 +214,43 @@ const deleteMembershipOrg = async ({
}: {
membershipOrgId: string;
}) => {
const deletedMembershipOrg = await MembershipOrg.findOneAndDelete({
_id: membershipOrgId
});
let deletedMembershipOrg;
try {
deletedMembershipOrg = await MembershipOrg.findOneAndDelete({
_id: membershipOrgId
});
if (!deletedMembershipOrg) throw new Error('Failed to delete organization membership');
if (!deletedMembershipOrg) throw new Error('Failed to delete organization membership');
// delete keys associated with organization membership
if (deletedMembershipOrg?.user) {
// case: organization membership had a registered user
// delete keys associated with organization membership
if (deletedMembershipOrg?.user) {
// case: organization membership had a registered user
const workspaces = (
await Workspace.find({
organization: deletedMembershipOrg.organization
})
).map((w) => w._id.toString());
const workspaces = (
await Workspace.find({
organization: deletedMembershipOrg.organization
})
).map((w) => w._id.toString());
await Membership.deleteMany({
user: deletedMembershipOrg.user,
workspace: {
$in: workspaces
}
});
await Membership.deleteMany({
user: deletedMembershipOrg.user,
workspace: {
$in: workspaces
}
});
await Key.deleteMany({
receiver: deletedMembershipOrg.user,
workspace: {
$in: workspaces
}
});
}
await Key.deleteMany({
receiver: deletedMembershipOrg.user,
workspace: {
$in: workspaces
}
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to delete organization membership');
}
return deletedMembershipOrg;
};

@ -1,34 +1,39 @@
import Stripe from "stripe";
import { Types } from "mongoose";
import * as Sentry from '@sentry/node';
import Stripe from 'stripe';
import { Types } from 'mongoose';
import {
IUser,
User,
IServiceAccount,
ServiceAccount,
IServiceTokenData,
ServiceTokenData,
} from "../models";
import { Organization, MembershipOrg } from "../models";
IUser,
User,
IServiceAccount,
ServiceAccount,
IServiceTokenData,
ServiceTokenData
} from '../models';
import { Organization, MembershipOrg } from '../models';
import {
ACCEPTED,
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY,
OWNER
} from '../variables';
import {
getStripeSecretKey,
getStripeProductPro,
getStripeProductTeam,
getStripeProductStarter
} from '../config';
import {
ACCEPTED,
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY,
OWNER,
} from "../variables";
UnauthorizedRequestError,
OrganizationNotFoundError
} from '../utils/errors';
import {
getStripeSecretKey,
getStripeProductPro,
getStripeProductTeam,
getStripeProductStarter,
} from "../config";
validateUserClientForOrganization
} from '../helpers/user';
import {
UnauthorizedRequestError,
OrganizationNotFoundError,
} from "../utils/errors";
import { validateUserClientForOrganization } from "../helpers/user";
import { validateServiceAccountClientForOrganization } from "../helpers/serviceAccount";
validateServiceAccountClientForOrganization
} from '../helpers/serviceAccount';
/**
* Validate accepted clients for organization with id [organizationId]
@ -37,80 +42,69 @@ import { validateServiceAccountClientForOrganization } from "../helpers/serviceA
* @param {Types.ObjectId} obj.organizationId - id of organization to validate against
*/
const validateClientForOrganization = async ({
authData,
organizationId,
acceptedRoles,
acceptedStatuses,
authData,
organizationId,
acceptedRoles,
acceptedStatuses
}: {
authData: {
authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData;
};
organizationId: Types.ObjectId;
acceptedRoles: Array<"owner" | "admin" | "member">;
acceptedStatuses: Array<"invited" | "accepted">;
authData: {
authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData;
},
organizationId: Types.ObjectId;
acceptedRoles: Array<'owner' | 'admin' | 'member'>;
acceptedStatuses: Array<'invited' | 'accepted'>;
}) => {
const organization = await Organization.findById(organizationId);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: 'Failed to find organization'
});
}
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
const membershipOrg = await validateUserClientForOrganization({
user: authData.authPayload,
organization,
acceptedRoles,
acceptedStatuses
});
return ({ organization, membershipOrg });
}
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization",
});
}
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
await validateServiceAccountClientForOrganization({
serviceAccount: authData.authPayload,
organization
});
return ({ organization });
}
if (
authData.authMode === AUTH_MODE_JWT &&
authData.authPayload instanceof User
) {
const membershipOrg = await validateUserClientForOrganization({
user: authData.authPayload,
organization,
acceptedRoles,
acceptedStatuses,
});
if (authData.authMode === AUTH_MODE_SERVICE_TOKEN && authData.authPayload instanceof ServiceTokenData) {
throw UnauthorizedRequestError({
message: 'Failed service token authorization for organization'
});
}
return { organization, membershipOrg };
}
if (
authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
authData.authPayload instanceof ServiceAccount
) {
await validateServiceAccountClientForOrganization({
serviceAccount: authData.authPayload,
organization,
});
return { organization };
}
if (
authData.authMode === AUTH_MODE_SERVICE_TOKEN &&
authData.authPayload instanceof ServiceTokenData
) {
throw UnauthorizedRequestError({
message: "Failed service token authorization for organization",
});
}
if (
authData.authMode === AUTH_MODE_API_KEY &&
authData.authPayload instanceof User
) {
const membershipOrg = await validateUserClientForOrganization({
user: authData.authPayload,
organization,
acceptedRoles,
acceptedStatuses,
});
return { organization, membershipOrg };
}
throw UnauthorizedRequestError({
message: "Failed client authorization for organization",
});
};
if (authData.authMode === AUTH_MODE_API_KEY && authData.authPayload instanceof User) {
const membershipOrg = await validateUserClientForOrganization({
user: authData.authPayload,
organization,
acceptedRoles,
acceptedStatuses
});
return ({ organization, membershipOrg });
}
throw UnauthorizedRequestError({
message: 'Failed client authorization for organization'
});
}
/**
* Create an organization with name [name]
@ -120,37 +114,43 @@ const validateClientForOrganization = async ({
* @param {Object} organization - new organization
*/
const createOrganization = async ({
name,
email,
name,
email
}: {
name: string;
email: string;
name: string;
email: string;
}) => {
let organization;
// register stripe account
const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: "2022-08-01",
});
let organization;
try {
// register stripe account
const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01'
});
if (await getStripeSecretKey()) {
const customer = await stripe.customers.create({
email,
description: name,
});
if (await getStripeSecretKey()) {
const customer = await stripe.customers.create({
email,
description: name
});
organization = await new Organization({
name,
customerId: customer.id,
}).save();
} else {
organization = await new Organization({
name,
}).save();
}
organization = await new Organization({
name,
customerId: customer.id
}).save();
} else {
organization = await new Organization({
name
}).save();
}
await initSubscriptionOrg({ organizationId: organization._id });
await initSubscriptionOrg({ organizationId: organization._id });
} catch (err) {
Sentry.setUser({ email });
Sentry.captureException(err);
throw new Error(`Failed to create organization [err=${err}]`);
}
return organization;
return organization;
};
/**
@ -162,52 +162,57 @@ const createOrganization = async ({
* @return {Subscription} obj.subscription - new subscription
*/
const initSubscriptionOrg = async ({
organizationId,
organizationId
}: {
organizationId: Types.ObjectId;
organizationId: Types.ObjectId;
}) => {
let stripeSubscription;
let subscription;
let stripeSubscription;
let subscription;
try {
// find organization
const organization = await Organization.findOne({
_id: organizationId
});
// find organization
const organization = await Organization.findOne({
_id: organizationId,
});
if (organization) {
if (organization.customerId) {
// initialize starter subscription with quantity of 0
const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01'
});
if (organization) {
if (organization.customerId) {
// initialize starter subscription with quantity of 0
const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: "2022-08-01",
});
const productToPriceMap = {
starter: await getStripeProductStarter(),
team: await getStripeProductTeam(),
pro: await getStripeProductPro()
};
const productToPriceMap = {
starter: await getStripeProductStarter(),
team: await getStripeProductTeam(),
pro: await getStripeProductPro(),
};
stripeSubscription = await stripe.subscriptions.create({
customer: organization.customerId,
items: [
{
price: productToPriceMap['starter'],
quantity: 1
}
],
payment_behavior: 'default_incomplete',
proration_behavior: 'none',
expand: ['latest_invoice.payment_intent']
});
}
} else {
throw new Error('Failed to initialize free organization subscription');
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to initialize free organization subscription');
}
stripeSubscription = await stripe.subscriptions.create({
customer: organization.customerId,
items: [
{
price: productToPriceMap["starter"],
quantity: 1,
},
],
payment_behavior: "default_incomplete",
proration_behavior: "none",
expand: ["latest_invoice.payment_intent"],
});
}
} else {
throw new Error("Failed to initialize free organization subscription");
}
return {
stripeSubscription,
subscription,
};
return {
stripeSubscription,
subscription
};
};
/**
@ -217,49 +222,54 @@ const initSubscriptionOrg = async ({
* @param {Number} obj.organizationId - id of subscription's organization
*/
const updateSubscriptionOrgQuantity = async ({
organizationId,
organizationId
}: {
organizationId: string;
organizationId: string;
}) => {
let stripeSubscription;
// find organization
const organization = await Organization.findOne({
_id: organizationId,
});
let stripeSubscription;
try {
// find organization
const organization = await Organization.findOne({
_id: organizationId
});
if (organization && organization.customerId) {
const quantity = await MembershipOrg.countDocuments({
organization: organizationId,
status: ACCEPTED,
});
if (organization && organization.customerId) {
const quantity = await MembershipOrg.countDocuments({
organization: organizationId,
status: ACCEPTED
});
const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: "2022-08-01",
});
const stripe = new Stripe(await getStripeSecretKey(), {
apiVersion: '2022-08-01'
});
const subscription = (
await stripe.subscriptions.list({
customer: organization.customerId,
})
).data[0];
const subscription = (
await stripe.subscriptions.list({
customer: organization.customerId
})
).data[0];
stripeSubscription = await stripe.subscriptions.update(subscription.id, {
items: [
{
id: subscription.items.data[0].id,
price: subscription.items.data[0].price.id,
quantity,
},
],
});
}
stripeSubscription = await stripe.subscriptions.update(subscription.id, {
items: [
{
id: subscription.items.data[0].id,
price: subscription.items.data[0].price.id,
quantity
}
]
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
}
return stripeSubscription;
return stripeSubscription;
};
export {
validateClientForOrganization,
createOrganization,
initSubscriptionOrg,
updateSubscriptionOrgQuantity,
};
validateClientForOrganization,
createOrganization,
initSubscriptionOrg,
updateSubscriptionOrgQuantity
};

File diff suppressed because it is too large Load Diff

@ -1,15 +1,16 @@
import { Types } from "mongoose";
import { TokenData } from "../models";
import crypto from "crypto";
import bcrypt from "bcrypt";
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { TokenData } from '../models';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import {
TOKEN_EMAIL_CONFIRMATION,
TOKEN_EMAIL_MFA,
TOKEN_EMAIL_ORG_INVITATION,
TOKEN_EMAIL_PASSWORD_RESET,
} from "../variables";
import { UnauthorizedRequestError } from "../utils/errors";
import { getSaltRounds } from "../config";
TOKEN_EMAIL_CONFIRMATION,
TOKEN_EMAIL_MFA,
TOKEN_EMAIL_ORG_INVITATION,
TOKEN_EMAIL_PASSWORD_RESET
} from '../variables';
import { UnauthorizedRequestError } from '../utils/errors';
import { getSaltRounds } from '../config';
/**
* Create and store a token in the database for purpose [type]
@ -21,197 +22,194 @@ import { getSaltRounds } from "../config";
* @returns {String} token - the created token
*/
const createTokenHelper = async ({
type,
email,
phoneNumber,
organizationId,
}: {
type:
| "emailConfirmation"
| "emailMfa"
| "organizationInvitation"
| "passwordReset";
email?: string;
phoneNumber?: string;
organizationId?: Types.ObjectId;
}) => {
let token, expiresAt, triesLeft;
// generate random token based on specified token use-case
// type [type]
switch (type) {
case TOKEN_EMAIL_CONFIRMATION:
// generate random 6-digit code
token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1));
expiresAt = new Date(new Date().getTime() + 86400000);
break;
case TOKEN_EMAIL_MFA:
// generate random 6-digit code
token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1));
triesLeft = 5;
expiresAt = new Date(new Date().getTime() + 300000);
break;
case TOKEN_EMAIL_ORG_INVITATION:
// generate random hex
token = crypto.randomBytes(16).toString("hex");
expiresAt = new Date(new Date().getTime() + 259200000);
break;
case TOKEN_EMAIL_PASSWORD_RESET:
// generate random hex
token = crypto.randomBytes(16).toString("hex");
expiresAt = new Date(new Date().getTime() + 86400000);
break;
default:
token = crypto.randomBytes(16).toString("hex");
expiresAt = new Date();
break;
}
interface TokenDataQuery {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
interface TokenDataUpdate {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
tokenHash: string;
triesLeft?: number;
expiresAt: Date;
}
const query: TokenDataQuery = { type };
const update: TokenDataUpdate = {
type,
tokenHash: await bcrypt.hash(token, await getSaltRounds()),
expiresAt,
};
email,
phoneNumber,
organizationId
}: {
type: 'emailConfirmation' | 'emailMfa' | 'organizationInvitation' | 'passwordReset';
email?: string;
phoneNumber?: string;
organizationId?: Types.ObjectId
}) => {
let token, expiresAt, triesLeft;
try {
// generate random token based on specified token use-case
// type [type]
switch (type) {
case TOKEN_EMAIL_CONFIRMATION:
// generate random 6-digit code
token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1));
expiresAt = new Date((new Date()).getTime() + 86400000);
break;
case TOKEN_EMAIL_MFA:
// generate random 6-digit code
token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1));
triesLeft = 5;
expiresAt = new Date((new Date()).getTime() + 300000);
break;
case TOKEN_EMAIL_ORG_INVITATION:
// generate random hex
token = crypto.randomBytes(16).toString('hex');
expiresAt = new Date((new Date()).getTime() + 259200000);
break;
case TOKEN_EMAIL_PASSWORD_RESET:
// generate random hex
token = crypto.randomBytes(16).toString('hex');
expiresAt = new Date((new Date()).getTime() + 86400000);
break;
default:
token = crypto.randomBytes(16).toString('hex');
expiresAt = new Date();
break;
}
interface TokenDataQuery {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
interface TokenDataUpdate {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
tokenHash: string;
triesLeft?: number;
expiresAt: Date;
}
if (email) {
query.email = email;
update.email = email;
}
if (phoneNumber) {
query.phoneNumber = phoneNumber;
update.phoneNumber = phoneNumber;
}
if (organizationId) {
query.organization = organizationId;
update.organization = organizationId;
}
const query: TokenDataQuery = { type };
const update: TokenDataUpdate = {
type,
tokenHash: await bcrypt.hash(token, await getSaltRounds()),
expiresAt
}
if (triesLeft) {
update.triesLeft = triesLeft;
}
await TokenData.findOneAndUpdate(query, update, {
new: true,
upsert: true,
});
return token;
};
if (email) {
query.email = email;
update.email = email;
}
if (phoneNumber) {
query.phoneNumber = phoneNumber;
update.phoneNumber = phoneNumber;
}
if (organizationId) {
query.organization = organizationId
update.organization = organizationId
}
if (triesLeft) {
update.triesLeft = triesLeft;
}
await TokenData.findOneAndUpdate(
query,
update,
{
new: true,
upsert: true
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(
"Failed to create token"
);
}
return token;
}
/**
*
*
* @param {Object} obj
* @param {String} obj.email - email associated with the token
* @param {String} obj.token - value of the token
*/
const validateTokenHelper = async ({
type,
email,
phoneNumber,
organizationId,
token,
type,
email,
phoneNumber,
organizationId,
token
}: {
type:
| "emailConfirmation"
| "emailMfa"
| "organizationInvitation"
| "passwordReset";
email?: string;
phoneNumber?: string;
organizationId?: Types.ObjectId;
token: string;
}) => {
interface Query {
type: string;
type: 'emailConfirmation' | 'emailMfa' | 'organizationInvitation' | 'passwordReset';
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
const query: Query = { type };
if (email) {
query.email = email;
}
if (phoneNumber) {
query.phoneNumber = phoneNumber;
}
if (organizationId) {
query.organization = organizationId;
}
const tokenData = await TokenData.findOne(query).select("+tokenHash");
if (!tokenData) throw new Error("Failed to find token to validate");
if (tokenData.expiresAt < new Date()) {
// case: token expired
await TokenData.findByIdAndDelete(tokenData._id);
throw UnauthorizedRequestError({
message: "MFA session expired. Please log in again",
context: {
code: "mfa_expired",
},
});
}
const isValid = await bcrypt.compare(token, tokenData.tokenHash);
if (!isValid) {
// case: token is not valid
if (tokenData?.triesLeft !== undefined) {
// case: token has a try-limit
if (tokenData.triesLeft === 1) {
// case: token is out of tries
await TokenData.findByIdAndDelete(tokenData._id);
} else {
// case: token has more than 1 try left
await TokenData.findByIdAndUpdate(
tokenData._id,
{
triesLeft: tokenData.triesLeft - 1,
},
{
new: true,
}
);
}
throw UnauthorizedRequestError({
message: "MFA code is invalid",
context: {
code: "mfa_invalid",
triesLeft: tokenData.triesLeft - 1,
},
});
organizationId?: Types.ObjectId;
token: string;
}) => {
interface Query {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
throw UnauthorizedRequestError({
message: "MFA code is invalid",
context: {
code: "mfa_invalid",
},
});
}
const query: Query = { type };
// case: token is valid
await TokenData.findByIdAndDelete(tokenData._id);
};
if (email) { query.email = email; }
if (phoneNumber) { query.phoneNumber = phoneNumber; }
if (organizationId) { query.organization = organizationId; }
export { createTokenHelper, validateTokenHelper };
const tokenData = await TokenData.findOne(query).select('+tokenHash');
if (!tokenData) throw new Error('Failed to find token to validate');
if (tokenData.expiresAt < new Date()) {
// case: token expired
await TokenData.findByIdAndDelete(tokenData._id);
throw UnauthorizedRequestError({
message: 'MFA session expired. Please log in again',
context: {
code: 'mfa_expired'
}
});
}
const isValid = await bcrypt.compare(token, tokenData.tokenHash);
if (!isValid) {
// case: token is not valid
if (tokenData?.triesLeft !== undefined) {
// case: token has a try-limit
if (tokenData.triesLeft === 1) {
// case: token is out of tries
await TokenData.findByIdAndDelete(tokenData._id);
} else {
// case: token has more than 1 try left
await TokenData.findByIdAndUpdate(tokenData._id, {
triesLeft: tokenData.triesLeft - 1
}, {
new: true
});
}
throw UnauthorizedRequestError({
message: 'MFA code is invalid',
context: {
code: 'mfa_invalid',
triesLeft: tokenData.triesLeft - 1
}
});
}
throw UnauthorizedRequestError({
message: 'MFA code is invalid',
context: {
code: 'mfa_invalid'
}
});
}
// case: token is valid
await TokenData.findByIdAndDelete(tokenData._id);
}
export {
createTokenHelper,
validateTokenHelper
}

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import {
IUser,
@ -27,9 +28,16 @@ import {
* @returns {Object} user - the initialized user
*/
const setupAccount = async ({ email }: { email: string }) => {
const user = await new User({
email
}).save();
let user;
try {
user = await new User({
email
}).save();
} catch (err) {
Sentry.setUser({ email });
Sentry.captureException(err);
throw new Error('Failed to set up account');
}
return user;
};
@ -81,27 +89,34 @@ const completeAccount = async ({
salt: string;
verifier: string;
}) => {
const options = {
new: true
};
const user = await User.findByIdAndUpdate(
userId,
{
firstName,
lastName,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},
options
);
let user;
try {
const options = {
new: true
};
user = await User.findByIdAndUpdate(
userId,
{
firstName,
lastName,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},
options
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to complete account set up');
}
return user;
};

@ -1,6 +1,7 @@
import * as Sentry from "@sentry/node";
import { Octokit } from "@octokit/rest";
import { IIntegrationAuth } from "../models";
import request from "../config/request";
import request from '../config/request';
import {
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
@ -25,7 +26,7 @@ import {
INTEGRATION_FLYIO_API_URL,
INTEGRATION_CIRCLECI_API_URL,
INTEGRATION_TRAVISCI_API_URL,
INTEGRATION_SUPABASE_API_URL,
INTEGRATION_SUPABASE_API_URL
} from "../variables";
interface App {
@ -46,80 +47,87 @@ interface App {
const getApps = async ({
integrationAuth,
accessToken,
teamId,
teamId
}: {
integrationAuth: IIntegrationAuth;
accessToken: string;
teamId?: string;
}) => {
let apps: App[] = [];
switch (integrationAuth.integration) {
case INTEGRATION_AZURE_KEY_VAULT:
apps = [];
break;
case INTEGRATION_AWS_PARAMETER_STORE:
apps = [];
break;
case INTEGRATION_AWS_SECRET_MANAGER:
apps = [];
break;
case INTEGRATION_HEROKU:
apps = await getAppsHeroku({
accessToken,
});
break;
case INTEGRATION_VERCEL:
apps = await getAppsVercel({
integrationAuth,
accessToken,
});
break;
case INTEGRATION_NETLIFY:
apps = await getAppsNetlify({
accessToken,
});
break;
case INTEGRATION_GITHUB:
apps = await getAppsGithub({
accessToken,
});
break;
case INTEGRATION_GITLAB:
apps = await getAppsGitlab({
accessToken,
teamId,
});
break;
case INTEGRATION_RENDER:
apps = await getAppsRender({
accessToken,
});
break;
case INTEGRATION_RAILWAY:
apps = await getAppsRailway({
accessToken,
});
break;
case INTEGRATION_FLYIO:
apps = await getAppsFlyio({
accessToken,
});
break;
case INTEGRATION_CIRCLECI:
apps = await getAppsCircleCI({
accessToken,
});
break;
case INTEGRATION_TRAVISCI:
apps = await getAppsTravisCI({
accessToken,
});
break;
case INTEGRATION_SUPABASE:
apps = await getAppsSupabase({
accessToken,
});
break;
try {
switch (integrationAuth.integration) {
case INTEGRATION_AZURE_KEY_VAULT:
apps = [];
break;
case INTEGRATION_AWS_PARAMETER_STORE:
apps = [];
break;
case INTEGRATION_AWS_SECRET_MANAGER:
apps = [];
break;
case INTEGRATION_HEROKU:
apps = await getAppsHeroku({
accessToken,
});
break;
case INTEGRATION_VERCEL:
apps = await getAppsVercel({
integrationAuth,
accessToken,
});
break;
case INTEGRATION_NETLIFY:
apps = await getAppsNetlify({
accessToken,
});
break;
case INTEGRATION_GITHUB:
apps = await getAppsGithub({
accessToken,
});
break;
case INTEGRATION_GITLAB:
apps = await getAppsGitlab({
accessToken,
teamId
});
break;
case INTEGRATION_RENDER:
apps = await getAppsRender({
accessToken,
});
break;
case INTEGRATION_RAILWAY:
apps = await getAppsRailway({
accessToken
});
break;
case INTEGRATION_FLYIO:
apps = await getAppsFlyio({
accessToken,
});
break;
case INTEGRATION_CIRCLECI:
apps = await getAppsCircleCI({
accessToken,
});
break;
case INTEGRATION_TRAVISCI:
apps = await getAppsTravisCI({
accessToken,
})
break;
case INTEGRATION_SUPABASE:
apps = await getAppsSupabase({
accessToken
});
break;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get integration apps");
}
return apps;
@ -133,18 +141,25 @@ const getApps = async ({
* @returns {String} apps.name - name of Heroku app
*/
const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
const res = (
await request.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
headers: {
Accept: "application/vnd.heroku+json; version=3",
Authorization: `Bearer ${accessToken}`,
},
})
).data;
let apps;
try {
const res = (
await request.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
headers: {
Accept: "application/vnd.heroku+json; version=3",
Authorization: `Bearer ${accessToken}`,
},
})
).data;
const apps = res.map((a: any) => ({
name: a.name,
}));
apps = res.map((a: any) => ({
name: a.name,
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Heroku integration apps");
}
return apps;
};
@ -163,26 +178,33 @@ const getAppsVercel = async ({
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
const res = (
await request.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
...(integrationAuth?.teamId
? {
params: {
teamId: integrationAuth.teamId,
},
}
: {}),
})
).data;
let apps;
try {
const res = (
await request.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
...(integrationAuth?.teamId
? {
params: {
teamId: integrationAuth.teamId,
},
}
: {}),
})
).data;
const apps = res.projects.map((a: any) => ({
name: a.name,
appId: a.id,
}));
apps = res.projects.map((a: any) => ({
name: a.name,
appId: a.id
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Vercel integration apps");
}
return apps;
};
@ -196,41 +218,43 @@ const getAppsVercel = async ({
*/
const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
const apps: any = [];
let page = 1;
const perPage = 10;
let hasMorePages = true;
try {
let page = 1;
const perPage = 10;
let hasMorePages = true;
// paginate through all sites
while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage)
});
// paginate through all sites
while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage),
filter: 'all'
});
const { data } = await request.get(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`,
{
const { data } = await request.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
);
data.map((a: any) => {
apps.push({
name: a.name,
appId: a.site_id,
'Accept-Encoding': 'application/json'
}
});
});
data.map((a: any) => {
apps.push({
name: a.name,
appId: a.site_id
});
});
if (data.length < perPage) {
hasMorePages = false;
}
if (data.length < perPage) {
hasMorePages = false;
page++;
}
page++;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Netlify integration apps");
}
return apps;
@ -244,59 +268,67 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.name - name of Github site
*/
const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
interface GitHubApp {
id: string;
name: string;
permissions: {
admin: boolean;
};
owner: {
login: string;
};
}
const octokit = new Octokit({
auth: accessToken,
});
const getAllRepos = async () => {
let repos: GitHubApp[] = [];
let page = 1;
const per_page = 100;
let hasMore = true;
while (hasMore) {
const response = await octokit.request(
"GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}",
{
per_page,
page,
}
);
if (response.data.length > 0) {
repos = repos.concat(response.data);
page++;
} else {
hasMore = false;
let apps;
try {
interface GitHubApp {
id: string;
name: string;
permissions: {
admin: boolean;
};
owner: {
login: string;
}
}
return repos;
};
const repos = await getAllRepos();
const apps = repos
.filter((a: GitHubApp) => a.permissions.admin === true)
.map((a: GitHubApp) => {
return {
appId: a.id,
name: a.name,
owner: a.owner.login,
};
const octokit = new Octokit({
auth: accessToken,
});
const getAllRepos = async () => {
let repos: GitHubApp[] = [];
let page = 1;
const per_page = 100;
let hasMore = true;
while (hasMore) {
const response = await octokit.request(
"GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}",
{
per_page,
page,
}
);
if (response.data.length > 0) {
repos = repos.concat(response.data);
page++;
} else {
hasMore = false;
}
}
return repos;
};
const repos = await getAllRepos();
apps = repos
.filter((a: GitHubApp) => a.permissions.admin === true)
.map((a: GitHubApp) => {
return {
appId: a.id,
name: a.name,
owner: a.owner.login,
};
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Github repos");
}
return apps;
};
@ -309,20 +341,29 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.appId - id of Render service
*/
const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
const res = (
await request.get(`${INTEGRATION_RENDER_API_URL}/v1/services`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"Accept-Encoding": "application/json",
},
})
).data;
const apps = res.map((a: any) => ({
name: a.service.name,
appId: a.service.id,
}));
let apps: any;
try {
const res = (
await request.get(`${INTEGRATION_RENDER_API_URL}/v1/services`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json',
'Accept-Encoding': 'application/json',
},
})
).data;
apps = res
.map((a: any) => ({
name: a.service.name,
appId: a.service.id
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Render services");
}
return apps;
};
@ -335,51 +376,49 @@ const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.name - name of Railway project
* @returns {String} apps.appId - id of Railway project
*
*/
*/
const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
const query = `
query GetProjects($userId: String, $teamId: String) {
projects(userId: $userId, teamId: $teamId) {
edges {
node {
id
name
let apps: any[] = [];
try {
const query = `
query GetProjects($userId: String, $teamId: String) {
projects(userId: $userId, teamId: $teamId) {
edges {
node {
id
name
}
}
}
}
}
`;
`;
const variables = {};
const variables = {};
const {
data: {
data: {
projects: { edges },
},
},
} = await request.post(
INTEGRATION_RAILWAY_API_URL,
{
const { data: { data: { projects: { edges }}} } = await request.post(INTEGRATION_RAILWAY_API_URL, {
query,
variables,
},
{
}, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Accept-Encoding": "application/json",
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'Accept-Encoding': 'application/json'
},
}
);
const apps = edges.map((e: any) => ({
name: e.node.name,
appId: e.node.id,
}));
});
apps = edges.map((e: any) => ({
name: e.node.name,
appId: e.node.id
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Railway services");
}
return apps;
};
}
/**
* Return list of apps for Fly.io integration
@ -389,40 +428,41 @@ const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.name - name of Fly.io apps
*/
const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
const query = `
query($role: String) {
apps(type: "container", first: 400, role: $role) {
nodes {
id
name
hostname
let apps;
try {
const query = `
query($role: String) {
apps(type: "container", first: 400, role: $role) {
nodes {
id
name
hostname
}
}
}
}
`;
`;
const res = (
await request.post(
INTEGRATION_FLYIO_API_URL,
{
query,
variables: {
role: null,
},
const res = (await request.post(INTEGRATION_FLYIO_API_URL, {
query,
variables: {
role: null,
},
{
headers: {
Authorization: "Bearer " + accessToken,
Accept: "application/json",
"Accept-Encoding": "application/json",
},
}
)
).data.data.apps.nodes;
}, {
headers: {
Authorization: "Bearer " + accessToken,
'Accept': 'application/json',
'Accept-Encoding': 'application/json',
},
})).data.data.apps.nodes;
const apps = res.map((a: any) => ({
name: a.name,
}));
apps = res.map((a: any) => ({
name: a.name,
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get Fly.io apps");
}
return apps;
};
@ -435,43 +475,63 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
* @returns {String} apps.name - name of CircleCI apps
*/
const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
const res = (
await request.get(`${INTEGRATION_CIRCLECI_API_URL}/v1.1/projects`, {
headers: {
"Circle-Token": accessToken,
"Accept-Encoding": "application/json",
},
})
).data;
const apps = res?.map((a: any) => {
return {
name: a?.reponame,
};
});
let apps: any;
try {
const res = (
await request.get(
`${INTEGRATION_CIRCLECI_API_URL}/v1.1/projects`,
{
headers: {
"Circle-Token": accessToken,
"Accept-Encoding": "application/json",
},
}
)
).data
apps = res?.map((a: any) => {
return {
name: a?.reponame
}
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get CircleCI projects");
}
return apps;
};
const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
const res = (
await request.get(`${INTEGRATION_TRAVISCI_API_URL}/repos`, {
headers: {
Authorization: `token ${accessToken}`,
"Accept-Encoding": "application/json",
},
})
).data;
const apps = res?.map((a: any) => {
return {
name: a?.slug?.split("/")[1],
appId: a?.id,
};
});
let apps: any;
try {
const res = (
await request.get(
`${INTEGRATION_TRAVISCI_API_URL}/repos`,
{
headers: {
"Authorization": `token ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
).data;
apps = res?.map((a: any) => {
return {
name: a?.slug?.split("/")[1],
appId: a?.id,
}
});
}catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get TravisCI projects");
}
return apps;
};
}
/**
* Return list of repositories for GitLab integration
@ -480,98 +540,112 @@ const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
* @returns {Object[]} apps - names of GitLab sites
* @returns {String} apps.name - name of GitLab site
*/
const getAppsGitlab = async ({
const getAppsGitlab = async ({
accessToken,
teamId,
teamId
}: {
accessToken: string;
teamId?: string;
}) => {
const apps: App[] = [];
let page = 1;
const perPage = 10;
let hasMorePages = true;
try {
if (teamId) {
// case: fetch projects for group with id [teamId] in GitLab
while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage),
});
const { data } = await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`,
{
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
);
data.map((a: any) => {
apps.push({
name: a.name,
appId: a.id,
if (teamId) {
// case: fetch projects for group with id [teamId] in GitLab
while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage)
});
});
if (data.length < perPage) {
hasMorePages = false;
}
const { data } = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`,
{
params,
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
);
page++;
}
} else {
// case: fetch projects for individual in GitLab
const { id } = (
await request.get(`${INTEGRATION_GITLAB_API_URL}/v4/user`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
})
).data;
while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage),
});
const { data } = await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
{
params,
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
);
data.map((a: any) => {
apps.push({
name: a.name,
appId: a.id,
data.map((a: any) => {
apps.push({
name: a.name,
appId: a.id
});
});
});
if (data.length < perPage) {
hasMorePages = false;
if (data.length < perPage) {
hasMorePages = false;
}
page++;
}
} else {
// case: fetch projects for individual in GitLab
const { id } = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/user`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
).data;
while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage)
});
page++;
const { data } = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
{
params,
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
);
data.map((a: any) => {
apps.push({
name: a.name,
appId: a.id
});
});
if (data.length < perPage) {
hasMorePages = false;
}
page++;
}
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get GitLab projects");
}
return apps;
};
}
/**
* Return list of projects for Supabase integration
@ -581,23 +655,30 @@ const getAppsGitlab = async ({
* @returns {String} apps.name - name of Supabase app
*/
const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => {
const { data } = await request.get(
`${INTEGRATION_SUPABASE_API_URL}/v1/projects`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
);
const apps = data.map((a: any) => {
return {
name: a.name,
appId: a.id,
};
});
let apps: any;
try {
const { data } = await request.get(
`${INTEGRATION_SUPABASE_API_URL}/v1/projects`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
}
);
apps = data.map((a: any) => {
return {
name: a.name,
appId: a.id
};
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get Supabase projects');
}
return apps;
};

@ -1,4 +1,5 @@
import request from "../config/request";
import * as Sentry from '@sentry/node';
import request from '../config/request';
import {
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU,
@ -11,8 +12,8 @@ import {
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITLAB_TOKEN_URL,
} from "../variables";
INTEGRATION_GITLAB_TOKEN_URL
} from '../variables';
import {
getSiteURL,
getClientIdAzure,
@ -25,8 +26,8 @@ import {
getClientIdGitHub,
getClientSecretGitHub,
getClientIdGitLab,
getClientSecretGitLab,
} from "../config";
getClientSecretGitLab
} from '../config';
interface ExchangeCodeAzureResponse {
token_type: string;
@ -92,43 +93,49 @@ interface ExchangeCodeGitlabResponse {
*/
const exchangeCode = async ({
integration,
code,
code
}: {
integration: string;
code: string;
}) => {
let obj = {} as any;
switch (integration) {
case INTEGRATION_AZURE_KEY_VAULT:
obj = await exchangeCodeAzure({
code,
});
break;
case INTEGRATION_HEROKU:
obj = await exchangeCodeHeroku({
code,
});
break;
case INTEGRATION_VERCEL:
obj = await exchangeCodeVercel({
code,
});
break;
case INTEGRATION_NETLIFY:
obj = await exchangeCodeNetlify({
code,
});
break;
case INTEGRATION_GITHUB:
obj = await exchangeCodeGithub({
code,
});
break;
case INTEGRATION_GITLAB:
obj = await exchangeCodeGitlab({
code,
});
try {
switch (integration) {
case INTEGRATION_AZURE_KEY_VAULT:
obj = await exchangeCodeAzure({
code
});
break;
case INTEGRATION_HEROKU:
obj = await exchangeCodeHeroku({
code
});
break;
case INTEGRATION_VERCEL:
obj = await exchangeCodeVercel({
code
});
break;
case INTEGRATION_NETLIFY:
obj = await exchangeCodeNetlify({
code
});
break;
case INTEGRATION_GITHUB:
obj = await exchangeCodeGithub({
code
});
break;
case INTEGRATION_GITLAB:
obj = await exchangeCodeGitlab({
code
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange');
}
return obj;
@ -136,33 +143,43 @@ const exchangeCode = async ({
/**
* Return [accessToken] for Azure OAuth2 code-token exchange
* @param param0
* @param param0
*/
const exchangeCodeAzure = async ({ code }: { code: string }) => {
const exchangeCodeAzure = async ({
code
}: {
code: string;
}) => {
const accessExpiresAt = new Date();
const res: ExchangeCodeAzureResponse = (
await request.post(
let res: ExchangeCodeAzureResponse;
try {
res = (await request.post(
INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({
grant_type: "authorization_code",
grant_type: 'authorization_code',
code: code,
scope: "https://vault.azure.net/.default openid offline_access",
scope: 'https://vault.azure.net/.default openid offline_access',
client_id: await getClientIdAzure(),
client_secret: await getClientSecretAzure(),
redirect_uri: `${await getSiteURL()}/integrations/azure-key-vault/oauth2/callback`,
redirect_uri: `${await getSiteURL()}/integrations/azure-key-vault/oauth2/callback`
} as any)
)
).data;
)).data;
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Azure');
}
return {
return ({
accessToken: res.access_token,
refreshToken: res.refresh_token,
accessExpiresAt,
};
};
accessExpiresAt
});
}
/**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Heroku
@ -174,28 +191,38 @@ const exchangeCodeAzure = async ({ code }: { code: string }) => {
* @returns {String} obj2.refreshToken - refresh token for Heroku API
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/
const exchangeCodeHeroku = async ({ code }: { code: string }) => {
const exchangeCodeHeroku = async ({
code
}: {
code: string;
}) => {
let res: ExchangeCodeHerokuResponse;
const accessExpiresAt = new Date();
const res: ExchangeCodeHerokuResponse = (
await request.post(
try {
res = (await request.post(
INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({
grant_type: "authorization_code",
grant_type: 'authorization_code',
code: code,
client_secret: await getClientSecretHeroku(),
client_secret: await getClientSecretHeroku()
} as any)
)
).data;
)).data;
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Heroku');
}
return {
return ({
accessToken: res.access_token,
refreshToken: res.refresh_token,
accessExpiresAt,
};
};
accessExpiresAt
});
}
/**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel
@ -208,23 +235,30 @@ const exchangeCodeHeroku = async ({ code }: { code: string }) => {
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/
const exchangeCodeVercel = async ({ code }: { code: string }) => {
const res: ExchangeCodeVercelResponse = (
await request.post(
INTEGRATION_VERCEL_TOKEN_URL,
new URLSearchParams({
code: code,
client_id: await getClientIdVercel(),
client_secret: await getClientSecretVercel(),
redirect_uri: `${await getSiteURL()}/integrations/vercel/oauth2/callback`,
} as any)
)
).data;
let res: ExchangeCodeVercelResponse;
try {
res = (
await request.post(
INTEGRATION_VERCEL_TOKEN_URL,
new URLSearchParams({
code: code,
client_id: await getClientIdVercel(),
client_secret: await getClientSecretVercel(),
redirect_uri: `${await getSiteURL()}/integrations/vercel/oauth2/callback`
} as any)
)
).data;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(`Failed OAuth2 code-token exchange with Vercel [err=${err}]`);
}
return {
accessToken: res.access_token,
refreshToken: null,
accessExpiresAt: null,
teamId: res.team_id,
teamId: res.team_id
};
};
@ -239,39 +273,47 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => {
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/
const exchangeCodeNetlify = async ({ code }: { code: string }) => {
const res: ExchangeCodeNetlifyResponse = (
await request.post(
INTEGRATION_NETLIFY_TOKEN_URL,
new URLSearchParams({
grant_type: "authorization_code",
code: code,
client_id: await getClientIdNetlify(),
client_secret: await getClientSecretNetlify(),
redirect_uri: `${await getSiteURL()}/integrations/netlify/oauth2/callback`,
} as any)
)
).data;
let res: ExchangeCodeNetlifyResponse;
let accountId;
try {
res = (
await request.post(
INTEGRATION_NETLIFY_TOKEN_URL,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: await getClientIdNetlify(),
client_secret: await getClientSecretNetlify(),
redirect_uri: `${await getSiteURL()}/integrations/netlify/oauth2/callback`
} as any)
)
).data;
const res2 = await request.get("https://api.netlify.com/api/v1/sites", {
headers: {
Authorization: `Bearer ${res.access_token}`,
},
});
const res3 = (
await request.get("https://api.netlify.com/api/v1/accounts", {
const res2 = await request.get('https://api.netlify.com/api/v1/sites', {
headers: {
Authorization: `Bearer ${res.access_token}`,
},
})
).data;
Authorization: `Bearer ${res.access_token}`
}
});
const accountId = res3[0].id;
const res3 = (
await request.get('https://api.netlify.com/api/v1/accounts', {
headers: {
Authorization: `Bearer ${res.access_token}`
}
})
).data;
accountId = res3[0].id;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Netlify');
}
return {
accessToken: res.access_token,
refreshToken: res.refresh_token,
accountId,
accountId
};
};
@ -286,25 +328,33 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/
const exchangeCodeGithub = async ({ code }: { code: string }) => {
const res: ExchangeCodeGithubResponse = (
await request.get(INTEGRATION_GITHUB_TOKEN_URL, {
params: {
client_id: await getClientIdGitHub(),
client_secret: await getClientSecretGitHub(),
code: code,
redirect_uri: `${await getSiteURL()}/integrations/github/oauth2/callback`,
},
headers: {
Accept: "application/json",
"Accept-Encoding": "application/json",
},
})
).data;
let res: ExchangeCodeGithubResponse;
try {
res = (
await request.get(INTEGRATION_GITHUB_TOKEN_URL, {
params: {
client_id: await getClientIdGitHub(),
client_secret: await getClientSecretGitHub(),
code: code,
redirect_uri: `${await getSiteURL()}/integrations/github/oauth2/callback`
},
headers: {
'Accept': 'application/json',
'Accept-Encoding': 'application/json'
}
})
).data;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Github');
}
return {
accessToken: res.access_token,
refreshToken: null,
accessExpiresAt: null,
accessExpiresAt: null
};
};
@ -319,32 +369,42 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/
const exchangeCodeGitlab = async ({ code }: { code: string }) => {
let res: ExchangeCodeGitlabResponse;
const accessExpiresAt = new Date();
const res: ExchangeCodeGitlabResponse = (
await request.post(
INTEGRATION_GITLAB_TOKEN_URL,
new URLSearchParams({
grant_type: "authorization_code",
code: code,
client_id: await getClientIdGitLab(),
client_secret: await getClientSecretGitLab(),
redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback`,
} as any),
{
headers: {
"Accept-Encoding": "application/json",
},
}
)
).data;
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
try {
res = (
await request.post(
INTEGRATION_GITLAB_TOKEN_URL,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: await getClientIdGitLab(),
client_secret: await getClientSecretGitLab(),
redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback`
} as any),
{
headers: {
"Accept-Encoding": "application/json",
}
}
)
).data;
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Gitlab');
}
return {
accessToken: res.access_token,
refreshToken: res.refresh_token,
accessExpiresAt,
accessExpiresAt
};
};
}
export { exchangeCode };

@ -1,24 +1,29 @@
import request from "../config/request";
import { IIntegrationAuth } from "../models";
import * as Sentry from '@sentry/node';
import request from '../config/request';
import {
INTEGRATION_AZURE_KEY_VAULT,
IIntegrationAuth
} from '../models';
import {
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU,
INTEGRATION_GITLAB,
} from "../variables";
} from '../variables';
import {
INTEGRATION_AZURE_TOKEN_URL,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_GITLAB_TOKEN_URL,
} from "../variables";
import { IntegrationService } from "../services";
INTEGRATION_GITLAB_TOKEN_URL
} from '../variables';
import {
IntegrationService
} from '../services';
import {
getSiteURL,
getClientIdAzure,
getClientSecretAzure,
getClientSecretHeroku,
getClientIdGitLab,
getClientSecretGitLab,
} from "../config";
getClientSecretGitLab
} from '../config';
interface RefreshTokenAzureResponse {
token_type: string;
@ -55,57 +60,60 @@ interface RefreshTokenGitLabResponse {
*/
const exchangeRefresh = async ({
integrationAuth,
refreshToken,
refreshToken
}: {
integrationAuth: IIntegrationAuth;
refreshToken: string;
}) => {
interface TokenDetails {
accessToken: string;
refreshToken: string;
accessExpiresAt: Date;
}
let tokenDetails: TokenDetails;
switch (integrationAuth.integration) {
case INTEGRATION_AZURE_KEY_VAULT:
tokenDetails = await exchangeRefreshAzure({
refreshToken,
try {
switch (integrationAuth.integration) {
case INTEGRATION_AZURE_KEY_VAULT:
tokenDetails = await exchangeRefreshAzure({
refreshToken
});
break;
case INTEGRATION_HEROKU:
tokenDetails = await exchangeRefreshHeroku({
refreshToken
});
break;
case INTEGRATION_GITLAB:
tokenDetails = await exchangeRefreshGitLab({
refreshToken
});
break;
default:
throw new Error('Failed to exchange token for incompatible integration');
}
if (tokenDetails?.accessToken && tokenDetails?.refreshToken && tokenDetails?.accessExpiresAt) {
await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessId: null,
accessToken: tokenDetails.accessToken,
accessExpiresAt: tokenDetails.accessExpiresAt
});
break;
case INTEGRATION_HEROKU:
tokenDetails = await exchangeRefreshHeroku({
refreshToken,
await IntegrationService.setIntegrationAuthRefresh({
integrationAuthId: integrationAuth._id.toString(),
refreshToken: tokenDetails.refreshToken
});
break;
case INTEGRATION_GITLAB:
tokenDetails = await exchangeRefreshGitLab({
refreshToken,
});
break;
default:
throw new Error("Failed to exchange token for incompatible integration");
}
return tokenDetails.accessToken;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get new OAuth2 access token');
}
if (
tokenDetails?.accessToken &&
tokenDetails?.refreshToken &&
tokenDetails?.accessExpiresAt
) {
await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessId: null,
accessToken: tokenDetails.accessToken,
accessExpiresAt: tokenDetails.accessExpiresAt,
});
await IntegrationService.setIntegrationAuthRefresh({
integrationAuthId: integrationAuth._id.toString(),
refreshToken: tokenDetails.refreshToken,
});
}
return tokenDetails.accessToken;
};
/**
@ -116,30 +124,38 @@ const exchangeRefresh = async ({
* @returns
*/
const exchangeRefreshAzure = async ({
refreshToken,
refreshToken
}: {
refreshToken: string;
}) => {
const accessExpiresAt = new Date();
const { data }: { data: RefreshTokenAzureResponse } = await request.post(
INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({
client_id: await getClientIdAzure(),
scope: "openid offline_access",
refresh_token: refreshToken,
grant_type: "refresh_token",
client_secret: await getClientSecretAzure(),
} as any)
);
try {
const accessExpiresAt = new Date();
const { data }: { data: RefreshTokenAzureResponse } = await request.post(
INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({
client_id: await getClientIdAzure(),
scope: 'openid offline_access',
refresh_token: refreshToken,
grant_type: 'refresh_token',
client_secret: await getClientSecretAzure()
} as any)
);
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + data.expires_in
);
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
accessExpiresAt,
};
};
return ({
accessToken: data.access_token,
refreshToken: data.refresh_token,
accessExpiresAt
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get refresh OAuth2 access token for Azure');
}
}
/**
* Return new access token by exchanging refresh token [refreshToken] for the
@ -149,31 +165,39 @@ const exchangeRefreshAzure = async ({
* @returns
*/
const exchangeRefreshHeroku = async ({
refreshToken,
refreshToken
}: {
refreshToken: string;
}) => {
const accessExpiresAt = new Date();
const {
data,
}: {
data: RefreshTokenHerokuResponse;
} = await request.post(
INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_secret: await getClientSecretHeroku(),
} as any)
);
try {
const accessExpiresAt = new Date();
const {
data
}: {
data: RefreshTokenHerokuResponse
} = await request.post(
INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_secret: await getClientSecretHeroku()
} as any)
);
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + data.expires_in
);
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
accessExpiresAt,
};
return ({
accessToken: data.access_token,
refreshToken: data.refresh_token,
accessExpiresAt
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to refresh OAuth2 access token for Heroku');
}
};
/**
@ -184,38 +208,45 @@ const exchangeRefreshHeroku = async ({
* @returns
*/
const exchangeRefreshGitLab = async ({
refreshToken,
refreshToken
}: {
refreshToken: string;
}) => {
const accessExpiresAt = new Date();
const {
data,
}: {
data: RefreshTokenGitLabResponse;
} = await request.post(
INTEGRATION_GITLAB_TOKEN_URL,
new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: await getClientIdGitLab(),
client_secret: await getClientSecretGitLab(),
redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback`,
} as any),
{
headers: {
"Accept-Encoding": "application/json",
},
}
);
try {
const accessExpiresAt = new Date();
const {
data
}: {
data: RefreshTokenGitLabResponse
} = await request.post(
INTEGRATION_GITLAB_TOKEN_URL,
new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: await getClientIdGitLab,
client_secret: await getClientSecretGitLab(),
redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback`
} as any),
{
headers: {
"Accept-Encoding": "application/json",
}
});
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + data.expires_in
);
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
accessExpiresAt,
};
return ({
accessToken: data.access_token,
refreshToken: data.refresh_token,
accessExpiresAt
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to refresh OAuth2 access token for GitLab');
}
};
export { exchangeRefresh };

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/node';
import {
IIntegrationAuth,
IntegrationAuth,
@ -21,28 +22,34 @@ const revokeAccess = async ({
accessToken: string;
}) => {
let deletedIntegrationAuth;
// add any integration-specific revocation logic
switch (integrationAuth.integration) {
case INTEGRATION_HEROKU:
break;
case INTEGRATION_VERCEL:
break;
case INTEGRATION_NETLIFY:
break;
case INTEGRATION_GITHUB:
break;
case INTEGRATION_GITLAB:
break;
}
try {
// add any integration-specific revocation logic
switch (integrationAuth.integration) {
case INTEGRATION_HEROKU:
break;
case INTEGRATION_VERCEL:
break;
case INTEGRATION_NETLIFY:
break;
case INTEGRATION_GITHUB:
break;
case INTEGRATION_GITLAB:
break;
}
deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
_id: integrationAuth._id
});
if (deletedIntegrationAuth) {
await Integration.deleteMany({
integrationAuth: deletedIntegrationAuth._id
deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
_id: integrationAuth._id
});
if (deletedIntegrationAuth) {
await Integration.deleteMany({
integrationAuth: deletedIntegrationAuth._id
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to delete integration authorization');
}
return deletedIntegrationAuth;

@ -1,3 +1,4 @@
import * as Sentry from "@sentry/node";
import {
IIntegrationAuth
} from '../models';
@ -30,15 +31,21 @@ const getTeams = async ({
}) => {
let teams: Team[] = [];
switch (integrationAuth.integration) {
case INTEGRATION_GITLAB:
teams = await getTeamsGitLab({
accessToken
});
break;
try {
switch (integrationAuth.integration) {
case INTEGRATION_GITLAB:
teams = await getTeamsGitLab({
accessToken
});
break;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get integration teams');
}
return teams;
}
@ -56,24 +63,30 @@ const getTeamsGitLab = async ({
accessToken: string;
}) => {
let teams: Team[] = [];
const res = (await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
try {
const res = (await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
}
)).data;
teams = res.map((t: any) => ({
name: t.name,
teamId: t.id
}));
)).data;
teams = res.map((t: any) => ({
name: t.name,
teamId: t.id
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get GitLab integration teams");
}
return teams;
}
export {
getTeams
}
}

@ -1,3 +1,4 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { Request, Response, NextFunction } from 'express';
import { IntegrationAuth, IWorkspace } from '../models';

@ -6,7 +6,7 @@ import {
validateRequest
} from '../../middleware';
import { body, param } from 'express-validator';
import { createFolder, deleteFolder, getFolderById } from '../../controllers/v1/secretsFolderController';
import { createFolder, deleteFolder } from '../../controllers/v1/secretsFolderController';
import { ADMIN, MEMBER } from '../../variables';
router.post(
@ -36,15 +36,5 @@ router.delete(
deleteFolder
);
router.get(
'/:folderId',
requireAuth({
acceptedAuthModes: ['jwt']
}),
param('folderId').exists(),
validateRequest,
getFolderById
);
export default router;

@ -3,8 +3,7 @@ import {
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS,
SMTP_HOST_ZOHOMAIL,
SMTP_HOST_GMAIL
SMTP_HOST_ZOHOMAIL
} from '../variables';
import SMTPConnection from 'nodemailer/lib/smtp-connection';
import * as Sentry from '@sentry/node';
@ -47,12 +46,6 @@ export const initSmtp = async () => {
}
break;
case SMTP_HOST_ZOHOMAIL:
mailOpts.requireTLS = true;
mailOpts.tls = {
ciphers: 'TLSv1.2'
}
break;
case SMTP_HOST_GMAIL:
mailOpts.requireTLS = true;
mailOpts.tls = {
ciphers: 'TLSv1.2'

@ -1,6 +1,7 @@
import nacl from 'tweetnacl';
import util from 'tweetnacl-util';
import AesGCM from './aes-gcm';
import * as Sentry from '@sentry/node';
/**
* Return new base64, NaCl, public-private key pair.
@ -37,13 +38,20 @@ const encryptAsymmetric = ({
publicKey: string;
privateKey: string;
}) => {
const nonce = nacl.randomBytes(24);
const ciphertext = nacl.box(
util.decodeUTF8(plaintext),
nonce,
util.decodeBase64(publicKey),
util.decodeBase64(privateKey)
);
let nonce, ciphertext;
try {
nonce = nacl.randomBytes(24);
ciphertext = nacl.box(
util.decodeUTF8(plaintext),
nonce,
util.decodeBase64(publicKey),
util.decodeBase64(privateKey)
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform asymmetric encryption');
}
return {
ciphertext: util.encodeBase64(ciphertext),
@ -72,12 +80,19 @@ const decryptAsymmetric = ({
publicKey: string;
privateKey: string;
}): string => {
const plaintext: any = nacl.box.open(
util.decodeBase64(ciphertext),
util.decodeBase64(nonce),
util.decodeBase64(publicKey),
util.decodeBase64(privateKey)
);
let plaintext: any;
try {
plaintext = nacl.box.open(
util.decodeBase64(ciphertext),
util.decodeBase64(nonce),
util.decodeBase64(publicKey),
util.decodeBase64(privateKey)
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform asymmetric decryption');
}
return util.encodeUTF8(plaintext);
};
@ -95,8 +110,17 @@ const encryptSymmetric = ({
plaintext: string;
key: string;
}) => {
const obj = AesGCM.encrypt(plaintext, key);
const { ciphertext, iv, tag } = obj;
let ciphertext, iv, tag;
try {
const obj = AesGCM.encrypt(plaintext, key);
ciphertext = obj.ciphertext;
iv = obj.iv;
tag = obj.tag;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform symmetric encryption');
}
return {
ciphertext,
@ -126,7 +150,15 @@ const decryptSymmetric = ({
tag: string;
key: string;
}): string => {
const plaintext = AesGCM.decrypt(ciphertext, iv, tag, key);
let plaintext;
try {
plaintext = AesGCM.decrypt(ciphertext, iv, tag, key);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to perform symmetric decryption');
}
return plaintext;
};

@ -55,8 +55,7 @@ import {
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS,
SMTP_HOST_ZOHOMAIL,
SMTP_HOST_GMAIL
SMTP_HOST_ZOHOMAIL
} from './smtp';
import { PLAN_STARTER, PLAN_PRO } from './stripe';
import {
@ -139,7 +138,6 @@ export {
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS,
SMTP_HOST_ZOHOMAIL,
SMTP_HOST_GMAIL,
PLAN_STARTER,
PLAN_PRO,
MFA_METHOD_EMAIL,

@ -2,12 +2,10 @@ const SMTP_HOST_SENDGRID = 'smtp.sendgrid.net';
const SMTP_HOST_MAILGUN = 'smtp.mailgun.org';
const SMTP_HOST_SOCKETLABS = 'smtp.socketlabs.com';
const SMTP_HOST_ZOHOMAIL = 'smtp.zoho.com';
const SMTP_HOST_GMAIL = 'smtp.gmail.com';
export {
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS,
SMTP_HOST_ZOHOMAIL,
SMTP_HOST_GMAIL
SMTP_HOST_ZOHOMAIL
}

@ -28,14 +28,14 @@ describe('Crypto', () => {
test('should throw error if publicKey is undefined', () => {
expect(() => {
encryptAsymmetric({ plaintext, publicKey, privateKey });
}).toThrowError('invalid encoding');
}).toThrowError('Failed to perform asymmetric encryption');
});
test('should throw error if publicKey is empty string', () => {
publicKey = '';
expect(() => {
encryptAsymmetric({ plaintext, publicKey, privateKey });
}).toThrowError('bad public key size');
}).toThrowError('Failed to perform asymmetric encryption');
});
});
@ -47,14 +47,14 @@ describe('Crypto', () => {
test('should throw error if privateKey is undefined', () => {
expect(() => {
encryptAsymmetric({ plaintext, publicKey, privateKey });
}).toThrowError('invalid encoding');
}).toThrowError('Failed to perform asymmetric encryption');
});
test('should throw error if privateKey is empty string', () => {
privateKey = '';
expect(() => {
encryptAsymmetric({ plaintext, publicKey, privateKey });
}).toThrowError('bad secret key size');
}).toThrowError('Failed to perform asymmetric encryption');
});
});
@ -66,7 +66,7 @@ describe('Crypto', () => {
test('should throw error if plaintext is undefined', () => {
expect(() => {
encryptAsymmetric({ plaintext, publicKey, privateKey });
}).toThrowError('expected string');
}).toThrowError('Failed to perform asymmetric encryption');
});
test('should encrypt plaintext containing special characters', () => {
@ -130,7 +130,7 @@ describe('Crypto', () => {
publicKey,
privateKey
});
}).toThrowError('invalid encoding');
}).toThrowError('Failed to perform asymmetric decryption');
});
test('should throw error if nonce is modified', () => {
@ -149,7 +149,7 @@ describe('Crypto', () => {
publicKey,
privateKey
});
}).toThrowError('invalid encoding');
}).toThrowError('Failed to perform asymmetric decryption');
});
});
});
@ -170,7 +170,7 @@ describe('Crypto', () => {
const invalidKey = 'invalid-key';
expect(() => {
encryptSymmetric({ plaintext, key: invalidKey });
}).toThrowError('Invalid key length');
}).toThrowError('Failed to perform symmetric encryption');
});
test('should throw an error when invalid key is provided', () => {
@ -179,7 +179,7 @@ describe('Crypto', () => {
expect(() => {
encryptSymmetric({ plaintext, key: invalidKey });
}).toThrowError('Invalid key length');
}).toThrowError('Failed to perform symmetric encryption');
});
});
@ -209,7 +209,7 @@ describe('Crypto', () => {
tag,
key
});
}).toThrowError('Unsupported state or unable to authenticate data');
}).toThrowError('Failed to perform symmetric decryption');
});
test('should fail if iv is modified', () => {
@ -221,7 +221,7 @@ describe('Crypto', () => {
tag,
key
});
}).toThrowError('Unsupported state or unable to authenticate data');
}).toThrowError('Failed to perform symmetric decryption');
});
test('should fail if tag is modified', () => {
@ -233,7 +233,7 @@ describe('Crypto', () => {
tag: modifiedTag,
key
});
}).toThrowError(/Invalid authentication tag length: \d+/);
}).toThrowError('Failed to perform symmetric decryption');
});
test('should throw an error when decryption fails', () => {
@ -245,7 +245,7 @@ describe('Crypto', () => {
tag,
key: invalidKey
});
}).toThrowError('Invalid key length');
}).toThrowError('Failed to perform symmetric decryption');
});
});
});

@ -67,7 +67,7 @@ var loginCmd = &cobra.Command{
//override domain
domainQuery := true
if config.INFISICAL_URL_MANUAL_OVERRIDE != "" && config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {
if config.INFISICAL_URL_MANUAL_OVERRIDE != util.INFISICAL_DEFAULT_API_URL {
overrideDomain, err := DomainOverridePrompt()
if err != nil {
util.HandleError(err)

@ -34,9 +34,7 @@ func init() {
rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging")
rootCmd.PersistentFlags().StringVar(&config.INFISICAL_URL, "domain", util.INFISICAL_DEFAULT_API_URL, "Point the CLI to your own backend [can also set via environment variable name: INFISICAL_API_URL]")
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
if !util.IsRunningInDocker() {
util.CheckForUpdate()
}
util.CheckForUpdate()
}
// if config.INFISICAL_URL is set to the default value, check if INFISICAL_URL is set in the environment

@ -4,15 +4,12 @@ import (
"encoding/json"
"errors"
"fmt"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"runtime"
"strings"
log "github.com/sirupsen/logrus"
"github.com/fatih/color"
)
@ -52,7 +49,7 @@ func CheckForUpdate() {
}
func getLatestTag(repoOwner string, repoName string) (string, error) {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", repoOwner, repoName)
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/tags", repoOwner, repoName)
resp, err := http.Get(url)
if err != nil {
return "", err
@ -68,20 +65,15 @@ func getLatestTag(repoOwner string, repoName string) (string, error) {
return "", err
}
var releaseTag struct {
TagName string `json:"tag_name"`
var tags []struct {
Name string `json:"name"`
}
if err := json.Unmarshal(body, &releaseTag); err != nil {
if err := json.Unmarshal(body, &tags); err != nil {
return "", fmt.Errorf("failed to unmarshal github response: %w", err)
}
tag_prefix := "infisical-cli/v"
// Extract the version from the first valid tag
version := strings.TrimPrefix(releaseTag.TagName, tag_prefix)
return version, nil
return tags[0].Name[1:], nil
}
func GetUpdateInstructions() string {
@ -133,16 +125,3 @@ func getLinuxPackageManager() string {
return ""
}
func IsRunningInDocker() bool {
if _, err := os.Stat("/.dockerenv"); err == nil {
return true
}
cgroup, err := ioutil.ReadFile("/proc/self/cgroup")
if err != nil {
return false
}
return strings.Contains(string(cgroup), "docker")
}

@ -5,11 +5,11 @@ description: "How to authenticate with the Infisical Public API"
## Essentials
The Public API accepts multiple modes of authentication being via API Key, Service Account credentials, or [Infisical Token](/documentation/platform/token).
The Public API accepts multiple modes of authentication being via API Key, Service Account credentials, or [Infisical Token](../../getting-started/dashboard/token).
- API Key: Provides full access to all endpoints representing the user.
- Service Account: Provides scoped access to an organization and select projects representing a machine such as a VM or application client.
- [Infisical Token](/documentation/platform/token): Provides short-lived, scoped CRUD access to the secrets of a specific project and environment.
- [Service Account](): Provides scoped access to an organization and select projects representing a machine such as a VM or application client.
- [Infisical Token](../../getting-started/dashboard/token): Provides short-lived, scoped CRUD access to the secrets of a specific project and environment.
<AccordionGroup>
<Accordion title="API Key">

@ -6,7 +6,7 @@ description: "How to add a secret using an Infisical Token scoped to a project a
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](/documentation/platform/token) for your project and environment with write access enabled.
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment with write access enabled.
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
- [Ensure that your project is blind-indexed](../blind-indices).

@ -6,7 +6,7 @@ description: "How to delete a secret using an Infisical Token scoped to a projec
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create either an [API Key](/api-reference/overview/authentication) or [Infisical Token](/documentation/platform/token) for your project and environment with write access enabled.
- Create either an [API Key](/api-reference/overview/authentication) or [Infisical Token](../../../getting-started/dashboard/token) for your project and environment with write access enabled.
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
- [Ensure that your project is blind-indexed](../blind-indices).

@ -6,7 +6,7 @@ description: "How to get a secret using an Infisical Token scoped to a project a
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](/documentation/platform/token) for your project and environment.
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
- [Ensure that your project is blind-indexed](../blind-indices).

@ -6,7 +6,7 @@ description: "How to get all secrets using an Infisical Token scoped to a projec
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](/documentation/platform/token) for your project and environment.
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
- [Ensure that your project is blind-indexed](../blind-indices).

@ -6,7 +6,7 @@ description: "How to update a secret using an Infisical Token scoped to a projec
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](/documentation/platform/token) for your project and environment with write access enabled.
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment with write access enabled.
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
- [Ensure that your project is blind-indexed](../blind-indices).

@ -37,7 +37,7 @@ Export environment variables from the platform into a file format.
### Environment variables
<Accordion title="INFISICAL_TOKEN">
Used to fetch secrets via a [service token](/documentation/platform/token) apposed to logged in credentials. Simply, export this variable in the terminal before running this command.
Used to fetch secrets via a [service token](/getting-started/dashboard/token) apposed to logged in credentials. Simply, export this variable in the terminal before running this command.
```bash
# Example

@ -42,7 +42,7 @@ Inject secrets from Infisical into your application process.
### Environment variables
<Accordion title="INFISICAL_TOKEN">
Used to fetch secrets via a [service token](/documentation/platform/token) apposed to logged in credentials. Simply, export this variable in the terminal before running this command.
Used to fetch secrets via a [service token](/getting-started/dashboard/token) apposed to logged in credentials. Simply, export this variable in the terminal before running this command.
```bash
# Example
@ -72,7 +72,7 @@ Inject secrets from Infisical into your application process.
</Accordion>
<Accordion title="--token">
If you are using a [service token](/documentation/platform/token) to authenticate, you can pass the token as a flag
If you are using a [service token](../../getting-started/dashboard/token) to authenticate, you can pass the token as a flag
```bash
# Example

@ -20,7 +20,7 @@ This command enables you to perform CRUD (create, read, update, delete) operatio
### Environment variables
<Accordion title="INFISICAL_TOKEN">
Used to fetch secrets via a [service token](/documentation/platform/token) apposed to logged in credentials. Simply, export this variable in the terminal before running this command.
Used to fetch secrets via a [service token](/getting-started/dashboard/token) apposed to logged in credentials. Simply, export this variable in the terminal before running this command.
```bash
# Example

@ -3,7 +3,7 @@ title: "Infisical Token"
description: "How to use Infical service token within the CLI."
---
Prerequisite: [Infisical Token and How to Generate One](/documentation/platform/token).
Prerequisite: [Infisical Token and How to Generate One](../../getting-started/dashboard/token).
It's possible to use the CLI to sync environment varialbes without manually entering login credentials by using a service token in the prerequisite link above.

@ -18,7 +18,7 @@ Prerequisite: [Install the CLI](/cli/overview)
<Tab title="Infisical Token">
To use Infisical CLI in environments where you cannot run the `infisical login` command, you can authenticate via a
Infisical Token instead. Learn more about [Infisical Token](/documentation/platform/token).
Infisical Token instead. Learn more about [Infisical Token](../getting-started/dashboard/token).
</Tab>
</Tabs>

@ -1,128 +0,0 @@
---
title: "CLI"
---
The Infisical CLI can be used to inject secrets into any framework like Next.js, Express, Django and more in local development.
It can also be used to expose secrets from Infisical as environment variables in CI/CD pipelines and [Docker containers](/documentation/getting-started/docker)
Prerequisites:
- Have a project with secrets ready in [Infisical Cloud](https://app.infisical.com).
## Installation
Follow the instructions for your operating system to install the Infisical CLI.
<Tabs>
<Tab title="MacOS">
Use [brew](https://brew.sh/) package manager
```console
$ brew install infisical/get-cli/infisical
```
</Tab>
<Tab title="Windows">
Use [Scoop](https://scoop.sh/) package manager
```console
$ scoop bucket add org https://github.com/Infisical/scoop-infisical.git
```
```console
$ scoop install infisical
```
</Tab>
<Tab title="Alpine">
Install prerequisite
```console
$ apk add --no-cache bash sudo
```
Add Infisical repository
```console
$ curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' \
| bash
```
Then install CLI
```console
$ apk update && sudo apk add infisical
```
</Tab>
<Tab title="RedHat/CentOs/Amazon">
Add Infisical repository
```console
$ curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.rpm.sh' \
| sudo -E bash
```
Then install CLI
```console
$ sudo yum install infisical
```
</Tab>
<Tab title="Debian/Ubuntu">
Add Infisical repository
```console
$ curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' \
| sudo -E bash
```
Then install CLI
```console
$ sudo apt-get update && sudo apt-get install -y infisical
```
</Tab>
<Tab title="Arch Linux">
Use the `yay` package manager to install from the [Arch User Repository](https://aur.archlinux.org/packages/infisical-bin)
```console
$ yay -S infisical-bin
```
</Tab>
</Tabs>
## Login
Authenticate the CLI with the Infisical platform using your email and password.
```console
$ infisical login
```
## Initialization
Navigate to the root of your project directory and run the `init` command. This step connects your local project to the project on the Infisical platform and creates a `infisical.json` file containing a reference to that latter project.
```console
$ infisical init
```
## Start your app with environment variables injected
```console
$ infisical run -- <your_application_start_command>
```
## Example Start Commands
```console
$ infisical run -- npm run dev
$ infisical run -- flask run
$ infisical run -- ./your_bash_script.sh
```
Your app should now be running with the secrets from Infisical injected as environment variables.
See also:
- [Full documentation for the Infisical CLI](/cli/overview)

@ -1,185 +0,0 @@
---
title: "Docker"
---
The [Infisical CLI](/cli/overview) can be added to Dockerfiles to fetch secrets from Infisical and make them available as environment variables within containers at runtime.
Prerequisites:
- Have a project with secrets ready in [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](/documentation/platform/token) scoped to an environment in your project in Infisical.
<Tabs>
<Tab title="Docker">
## Dockerfile Modification
Follow the instruction for your specific Linux distrubtion to add the Infisical CLI to your Dockerfile.
<Tabs>
<Tab title="Alpine">
```dockerfile
RUN apk add --no-cache bash curl && curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
&& apk add infisical
```
</Tab>
<Tab title="RedHat/CentOs/Amazon-linux">
```dockerfile
RUN curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.rpm.sh' | sh \
&& yum install -y infisical
```
</Tab>
<Tab title="Debian/Ubuntu">
```dockerfile
RUN apt-get update && apt-get install -y bash curl && curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash \
&& apt-get update && apt-get install -y infisical
```
</Tab>
</Tabs>
Next, modify the start command of your Dockerfile:
```dockerfile
CMD ["infisical", "run", "--", "[your service start command]"]
```
## Launch
Spin up your container with the `docker run` command and feed in your Infisical Token.
```console
docker run --env INFISICAL_TOKEN=<your_infisical_token> <DOCKER-IMAGE>
```
Your containerized application should now be up and running with secrets from Infisical exposed as environment variables within your application's process.
## Example Dockerfile
```dockerfile
# Select your base image (based on your Linux distribution, e.g., Alpine, Debian, Ubuntu, etc.)
FROM alpine
# Add the Infisical CLI to your Dockerfile (choose the appropriate block based on your base image)
RUN apk add --no-cache bash curl && curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
&& apk add infisical
# Install any additional dependencies or packages your service requires
# RUN <additional commands for your service>
# Copy your service files to the container
COPY . /app
# Set the working directory
WORKDIR /app
# Modify the start command of your Dockerfile
CMD ["infisical", "run", "--", "npm run start"]
```
</Tab>
<Tab title="Docker Compose">
## Dockerfile Modifications
Follow the instruction for your specific Linux distributions to add the Infisical CLI to your Dockerfiles.
<Tabs>
<Tab title="Alpine">
```dockerfile
RUN apk add --no-cache bash curl && curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
&& apk add infisical
```
</Tab>
<Tab title="RedHat/CentOs/Amazon-linux">
```dockerfile
RUN curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.rpm.sh' | sh \
&& yum install -y infisical
```
</Tab>
<Tab title="Debian/Ubuntu">
```dockerfile
RUN apt-get update && apt-get install -y bash curl && curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash \
&& apt-get update && apt-get install -y infisical
```
</Tab>
</Tabs>
Next, modify the start commands of your Dockerfiles:
```dockerfile
CMD ["infisical", "run", "--", "[your service start command]"]
```
## Example Dockerfile
```dockerfile
# Select your base image (based on your Linux distribution, e.g., Alpine, Debian, Ubuntu, etc.)
FROM alpine
# Add the Infisical CLI to your Dockerfile (choose the appropriate block based on your base image)
RUN apk add --no-cache bash curl && curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
&& apk add infisical
# Install any additional dependencies or packages your service requires
# RUN <additional commands for your service>
# Copy your service files to the container
COPY . /app
# Set the working directory
WORKDIR /app
# Modify the start command of your Dockerfile
CMD ["infisical", "run", "--", "[your service start command]"]
```
## Docker Compose File Modification
For each service you want to inject secrets into, set an environment variable called `INFISICAL_TOKEN` equal to a unique identifier variable. For example:
```yaml
services:
api:
build: .
image: example-service-2
environment:
- INFISICAL_TOKEN=${INFISICAL_TOKEN_FOR_API}
...
```
## Export shell variables
Next, set the shell variables you defined in your compose file. Continuing from the previous example:
```console
export INFISICAL_TOKEN_FOR_API=<your_infisical_token>
```
## Launch
Spin up your containers with the `docker-compose up` command.
```console
docker-compose up
```
Your containers should now be running with the secrets from Infisical available inside as environment variables.
</Tab>
</Tabs>
See also:
- [Documentation for Docker](/integrations/platforms/docker)
- [Documentation for Docker Compose](/integrations/platforms/docker-compose)

@ -1,90 +0,0 @@
---
title: "Introduction"
---
Infisical is an [open-source](https://opensource.com/resources/what-open-source), [end-to-end encrypted](https://en.wikipedia.org/wiki/End-to-end_encryption) secret management platform that enables teams to easily manage and sync their environment variables.
Start syncing environment variables with [Infisical Cloud](https://app.infisical.com) or learn how to [host Infisical](/self-hosting/overview) yourself.
## Learn about Infisical
<Card
href="/documentation/getting-started/platform"
title="Platform"
icon="laptop"
color="#dc2626"
>
Store secrets like API keys, database credentials, environment variables with Infisical
</Card>
## Integrate with Infisical
<CardGroup cols={2}>
<Card
title="SDKs"
href="/documentation/getting-started/sdks"
icon="boxes-stacked"
color="#3c8639"
>
Fetch secrets with any programming language on demand
</Card>
<Card href="/documentation/getting-started/cli" title="Command Line Interface" icon="square-terminal" color="#3775a9">
Inject secrets into any application process/environment
</Card>
<Card href="/documentation/getting-started/docker" title="Docker" icon="docker" color="#0078d3">
Inject secrets into Docker containers
</Card>
<Card
href="/documentation/getting-started/kubernetes"
title="Kubernetes"
icon="server"
color="#3775a9"
>
Fetch and save secrets as native Kubernetes secrets
</Card>
</CardGroup>
## Resources
<CardGroup cols={2}>
<Card
href="/self-hosting/overview"
title="Self-hosting"
icon="server"
color="#0285c7"
>
Learn how to configure and deploy Infisical
</Card>
<Card
href="/documentation/guides/introduction"
title="Guide"
icon="book-open"
color="#dc2626"
>
Explore guides for every language and stack
</Card>
<Card
href="/integrations/overview"
title="Native Integrations"
icon="clouds"
color="#dc2626"
>
Explore integrations for GitHub, Vercel, Netlify, and more
</Card>
<Card
href="/integrations/overview"
title="Frameworks"
icon="plug"
color="#dc2626"
>
Explore integrations for Next.js, Express, Django, and more
</Card>
<Card
href="https://calendly.com/team-infisical/infisical-demo"
title="Contact Us"
icon="user-headset"
color="#0285c7"
>
Questions? Need help setting up? Book a 1x1 meeting with us
</Card>
</CardGroup>

@ -1,83 +0,0 @@
---
title: "Kubernetes"
---
The Infisical Secrets Operator fetches secrets from Infisical and saves them as Kubernetes secrets using the custom `InfisicalSecret` resource to define authentication and storage methods.
The operator updates secrets continuously and can reload dependent deployments automatically on secret changes.
Prerequisites:
- Connected to your cluster via kubectl
- Have a project with secrets ready in [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](/documentation/platform/token) scoped to an environment in your project in Infisical.
## Installation
Follow the instructions for either [Helm](https://helm.sh/) or [kubectl](https://github.com/kubernetes/kubectl) to install the Infisical Secrets Operator.
<Tabs>
<Tab title="Helm">
Install the Infisical Helm repository
```console
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
helm repo update
```
Install the Helm chart
```console
helm install --generate-name infisical-helm-charts/secrets-operator
```
</Tab>
<Tab title="Kubectl">
The operator will be installed in `infisical-operator-system` namespace
```
kubectl apply -f https://raw.githubusercontent.com/Infisical/infisical/main/k8-operator/kubectl-install/install-secrets-operator.yaml
```
</Tab>
</Tabs>
## Usage
**Step 1: Create Kubernetes secret containing service token**
Once you have generated the service token, create a Kubernetes secret containing the service token you generated by running the command below.
``` bash
kubectl create secret generic service-token --from-literal=infisicalToken=<your-service-token-here>
```
**Step 2: Fill out the InfisicalSecrets CRD and apply it to your cluster**
```yaml infisical-secrets-config.yaml
apiVersion: secrets.infisical.com/v1alpha1
kind: InfisicalSecret
metadata:
# Name of of this InfisicalSecret resource
name: infisicalsecret-sample
spec:
# The host that should be used to pull secrets from. If left empty, the value specified in Global configuration will be used
hostAPI: https://app.infisical.com/api
authentication:
serviceToken:
serviceTokenSecretReference: # <-- The secret's namespaced name that holds the project token for authentication in step 1
secretName: service-token
secretNamespace: option
managedSecretReference:
secretName: managed-secret # <-- the name of kubernetes secret that will be created
secretNamespace: default # <-- in what namespace it will be created in
```
```
kubectl apply -f infisical-secrets-config.yaml
```
You should now see a new kubernetes secret automatically created in the namespace you defined in the `managedSecretReference` property above.
See also:
- [Documentation for the Infisical Kubernetes Operator](../../integrations/platforms/kubernetes)

@ -1,57 +0,0 @@
---
title: "Platform"
---
Infisical is an [open-source](https://opensource.com/resources/what-open-source), [end-to-end encrypted](https://en.wikipedia.org/wiki/End-to-end_encryption) secret management platform that enables teams to easily store, manage, and sync secrets like API keys, database credentials, and environment variables across their apps and infrastructure.
This quickstart provides an overview of the functionalities offered by Infisical.
## Projects
Projects hold secrets for applications, which are further organized into environments such as development, testing and production.
### Secrets Overview
The secrets overview provides a bird's-eye view of all the secrets in a project and is particularly useful for identifying missing secrets across environments.
![dashboard secrets overview](../../images/dashboard-secrets-overview.png)
### Secrets Dashboard
The secrets dashboard lets you manage secrets for a specific environment in a project.
Here, developers can [override secrets](//project#personal-overrides), [version secrets](/documentation/platform/secret-versioning), [rollback projects to any point in time](/documentation/platform/pit-recovery), and much more.
![dashboard](../../images/dashboard.png)
### Integrations
The integrations page provides native integrations to sync secrets from a project environment to a [host of ever-expanding integrations](/integrations/overview).
<Tip>
Depending on your infrastructure setup and compliance requirements, you may or may not prefer to use these native integrations since they break end-to-end encryption (E2EE).
You will learn about various ways to integrate with Infisical and maintain E2EE in subsequent quickstart sections.
</Tip>
![integrations](../../images/integrations.png)
### Access Control
The members page lets you add/remove members for a project and provision them access to environments (access levels include `No Access`, `Read Only`, and `Read and Write`).
![project members](../../images/project-members.png)
## Organizations
Organizations house projects and members.
### Organization Settings
At the organization-level, you can add/remove members and manage their access to projects.
![organization name modal open](../../images/dashboard-name-modal-organization.png)
![organization name modal open](../../images/organization.png)
That's it for the platform quickstart! — We encourage you to continue exploring the documentation to gain a deeper understanding of the extensive features and functionalities that Infisical has to offer.
Next, head back to [Getting Started > Introduction](/documentation/getting-started/overview) to explore ways to fetch secrets from Infisical to your apps and infrastructure.

@ -1,145 +0,0 @@
---
title: "SDKs"
---
From local development to production, Infisical's language-specific SDKs provide the easiest way for your app to fetch back secrets on demand.
Prerequisites:
- Have a project with secrets ready in [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](/documentation/platform/token) scoped to an environment in your project in Infisical.
## Installation
Follow the instructions for your language to install the SDK for it.
<Tabs>
<Tab title="Node">
Run `npm` to add [infisical-node](https://github.com/Infisical/infisical-node) to your project.
```console
$ npm install infisical-node --save
```
## Configuration
Import the SDK and create a client instance with your [Infisical Token](/documentation/platform/token).
<Tabs>
<Tab title="ES6">
```js
import InfisicalClient from "infisical-node";
const client = new InfisicalClient({
token: "your_infisical_token"
});
```
</Tab>
<Tab title="ES5">
```js
const InfisicalClient = require("infisical-node");
const client = new InfisicalClient({
token: "your_infisical_token"
});
````
</Tab>
</Tabs>
## Get a Secret
```js
const secret = await client.getSecret("API_KEY");
const value = secret.secretValue; // get its value
```
## Basic Usage
```js
import express from "express";
import InfisicalClient from "infisical-node";
const app = express();
const PORT = 3000;
const client = new InfisicalClient({
token: "YOUR_INFISICAL_TOKEN"
});
app.get("/", async (req, res) => {
// access value
const name = await client.getSecret("NAME");
res.send(`Hello! My name is: ${name.secretValue}`);
});
app.listen(PORT, async () => {
console.log(`App listening on port ${port}`);
});
```
This example demonstrates how to use the Infisical Node SDK with an Express application. The application retrieves a secret named "NAME" and responds to requests with a greeting that includes the secret value.
</Tab>
<Tab title="Python">
## Installation
Run `pip` to add [infisical-python](https://github.com/Astropilot/infisical-python) to your project
```console
$ pip install infisical
```
Note: You need Python 3.7+.
## Configuration
Import the SDK and create a client instance with your [Infisical Token](/documentation/platform/token).
```py
from infisical import InfisicalClient
client = InfisicalClient(token="your_infisical_token")
```
## Get a Secret
```py
secret = client.get_secret("API_KEY")
value = secret.secret_value # get its value
```
## Basic Usage
```py
from flask import Flask
from infisical import InfisicalClient
app = Flask(__name__)
client = InfisicalClient(token="your_infisical_token")
@app.route("/")
def hello_world():
# access value
name = client.get_secret("NAME")
return f"Hello! My name is: {name.secret_value}"
```
This example demonstrates how to use the Infisical Python SDK with a Flask application. The application retrieves a secret named "NAME" and responds to requests with a greeting that includes the secret value.
</Tab>
<Tab title="Other">
We're currently working on SDKs for other languages. Follow the GitHub issue for your needed language below:
- [Java](https://github.com/Infisical/infisical/issues/434)
- [Ruby](https://github.com/Infisical/infisical/issues/435)
- [Go](https://github.com/Infisical/infisical/issues/436)
- [Rust](https://github.com/Infisical/infisical/issues/437)
- [PHP](https://github.com/Infisical/infisical/issues/531)
Missing a language? [Throw in a request](https://github.com/Infisical/infisical/issues).
</Tab>
</Tabs>
See also:
- Explore the [Node SDK](https://github.com/Infisical/infisical-node)
- Explore the [Python SDK](https://github.com/Infisical/infisical-python)

@ -1,41 +0,0 @@
---
title: "Introduction"
---
Whether you're running a Node application on Heroku, Next.js application with Vercel, or Kubernetes on AWS, Infisical has a secret management strategy from local development to production ready for you.
## Guides by Language
<CardGroup cols={2}>
<Card
title="Node"
href="/documentation/guides/node"
icon="node"
color="#3c8639"
>
Manage secrets across your Node stack
</Card>
<Card
href="/documentation/guides/python"
title="Python"
icon="python"
color="#3775a9"
>
Manage secrets across your Python stack
</Card>
</CardGroup>
## Guides by Stack
<CardGroup cols={2}>
<Card
title="Next.js + Vercel"
href="/documentation/guides/nextjs-vercel"
icon="cloud"
color="#3c8639"
>
Manage secrets for your Next.js + Vercel stack
</Card>
</CardGroup>
Want a guide? [Throw in a request](https://github.com/Infisical/infisical/issues).

@ -1,254 +0,0 @@
---
title: "Next.js + Vercel"
---
This guide demonstrates how to use Infisical to manage secrets for your Next.js + Vercel stack from local development to production. It uses:
- Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets.
## Project Setup
To begin, we need to set up a project in Infisical and add secrets to an environment in it.
### Create a project
1. Create a new project in [Infisical](https://app.infisical.com/).
2. Add a secret to the development environment of this project so we can pull it back for local development. In the **Secrets Overview** page, press **Explore Development** and add a secret with the key `NEXT_PUBLIC_NAME` and value `YOUR_NAME`.
3. Add a secret to the production environment of this project so we can sync it to Vercel. Switch to the **Production** environment and add a secret with the key `NEXT_PUBLIC_NAME` and value `ANOTHER_NAME`.
## Create a Next.js app
Initialize a new Node.js app.
We can use `create-next-app` to initialize an app called `infisical-nextjs`.
<Tabs>
<Tab title="JavaScript">
```console
npx create-next-app@latest --use-npm infisical-nextjs
cd infisical-nextjs
```
</Tab>
<Tab title="TypeScript">
```console
npx create-next-app@latest --ts --use-npm infisical-nextjs
cd infisical-nextjs
```
</Tab>
</Tabs>
Next, inside `pages/_app.js`, lets add a `console.log()` to print out the environment variable in the browser console.
<Tabs>
<Tab title="JavaScript">
```js
import '@/styles/globals.css'
export default function App({ Component, pageProps }) {
console.log('Hello, ', process.env.NEXT_PUBLIC_NAME);
return <Component {...pageProps} />
}
```
</Tab>
<Tab title="TypeScript">
```tsx
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
console.log('Hello, ', process.env.NEXT_PUBLIC_NAME);
return <Component {...pageProps} />
}
```
</Tab>
</Tabs>
## Infisical CLI for local development environment variables
We'll now use the Infisical CLI to fetch secrets from Infisical into your Next.js app for local development.
### CLI Installation
Follow the instructions for your operating system to install the Infisical CLI.
<Tabs>
<Tab title="MacOS">
Use [brew](https://brew.sh/) package manager
```console
$ brew install infisical/get-cli/infisical
```
</Tab>
<Tab title="Windows">
Use [Scoop](https://scoop.sh/) package manager
```console
$ scoop bucket add org https://github.com/Infisical/scoop-infisical.git
```
```console
$ scoop install infisical
```
</Tab>
<Tab title="Alpine">
Install prerequisite
```console
$ apk add --no-cache bash sudo
```
Add Infisical repository
```console
$ curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' \
| bash
```
Then install CLI
```console
$ apk update && sudo apk add infisical
```
</Tab>
<Tab title="RedHat/CentOs/Amazon">
Add Infisical repository
```console
$ curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.rpm.sh' \
| sudo -E bash
```
Then install CLI
```console
$ sudo yum install infisical
```
</Tab>
<Tab title="Debian/Ubuntu">
Add Infisical repository
```console
$ curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' \
| sudo -E bash
```
Then install CLI
```console
$ sudo apt-get update && sudo apt-get install -y infisical
```
</Tab>
<Tab title="Arch Linux">
Use the `yay` package manager to install from the [Arch User Repository](https://aur.archlinux.org/packages/infisical-bin)
```console
$ yay -S infisical-bin
```
</Tab>
</Tabs>
### Login
Authenticate the CLI with the Infisical platform using your email and password.
```console
$ infisical login
```
### Initialization
Run the `init` command at the root of the Next.js app. This step connects your local project to the project on the Infisical platform and creates a `infisical.json` file containing a reference to that latter project.
```console
$ infisical init
```
### Start the Next.js app with secrets injected as environment variables
```console
$ infisical run -- npm run dev
```
If you open your browser console, **Hello, YOUR_NAME** should be printed out.
Here, the CLI fetched the secret from Infisical and injected it into the Next.js app upon starting up. By default,
the CLI fetches secrets from the development environment which has the slug `dev`; you can inject secrets from different
environments by modifying the `env` flag as per the [CLI documentation](/cli/usage).
At this stage, you know how to use the Infisical CLI to inject secrets into your Next.js app for local development.
## Infisical-Vercel integration for production environment variables
We'll now use the Infisical-Vercel integration send secrets from Infisical to Vercel as production environment variables.
### Infisical-Vercel integration
To begin we have to import the Next.js app into Vercel as a project. [Follow these instructions](https://nextjs.org/learn/basics/deploying-nextjs-app/deploy) to deploy the Next.js app to Vercel.
Next, navigate to your project's integrations tab in Infisical and press on the Vercel tile to grant Infisical access to your Vercel account.
![integrations](../../images/integrations.png)
![integrations vercel authorization](../../images/integrations-vercel-auth.png)
<Note>
Opting in for the Infisical-Vercel integration will break end-to-end encryption since Infisical will be able to read
your secrets. This is, however, necessary for Infisical to sync the secrets to Vercel.
Your secrets remain encrypted at rest following our [security guide mechanics](/security/mechanics).
</Note>
Now select **Production** for (the source) **Environment** and sync it to the **Production Environment** of the (target) application in Vercel.
Lastly, press create integration to start syncing secrets to Vercel.
![integrations vercel](../../images/integrations-vercel-create.png)
![integrations vercel](../../images/integrations-vercel.png)
You should now see your secret from Infisical appear as production environment variables in your Vercel project.
At this stage, you know how to use the Infisical-Vercel integration to sync production secrets from Infisical to Vercel.
<Warning>
The following environment variable names are reserved by Vercel and cannot be
synced: `AWS_SECRET_KEY`, `AWS_EXECUTION_ENV`, `AWS_LAMBDA_LOG_GROUP_NAME`,
`AWS_LAMBDA_LOG_STREAM_NAME`, `AWS_LAMBDA_FUNCTION_NAME`,
`AWS_LAMBDA_FUNCTION_MEMORY_SIZE`, `AWS_LAMBDA_FUNCTION_VERSION`,
`NOW_REGION`, `TZ`, `LAMBDA_TASK_ROOT`, `LAMBDA_RUNTIME_DIR`,
`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`,
`AWS_REGION`, and `AWS_DEFAULT_REGION`.
</Warning>
## FAQ
<AccordionGroup>
<Accordion title="Why should I use Infisical if I can centralize all my Next.js + Vercel environment variables across all environments directly in Vercel?">
Vercel does not specialize in secret management which means it lacks many useful features for effectively managing environment variables.
Here are some features that teams benefit from by using Infisical together with Vercel:
- Audit logs: See which team members are creating, reading, updating, and deleting environment variables across all environments.
- Versioning and point in time recovery: Rolling back secrets and an entire project state.
- Overriding secrets that should be unique amongst team members.
And much more.
</Accordion>
<Accordion title="Is opting out of end-to-end encryption for the Infisical-Vercel integration safe?">
Yes. Your secrets are still encrypted at rest. To note, most secret managers actually don't support end-to-end encryption.
Check out the [security guide](/security/overview).
</Accordion>
<Accordion title="Is there way to retain end-to-end encryption for syncing production secrets to Vercel?">
Yes. You can also use the Infisical [Node SDK](https://github.com/Infisical/infisical-node) to fetch secrets back to your Next.js app
in both development and production.
Depending on how you use it, however, it may require certain pages to be server-side rendered.
</Accordion>
</AccordionGroup>
See also:
- [Documentation for the Infisical CLI](/cli/overview)
- [Documentation for the Vercel integration](/integrations/cloud/vercel)

@ -1,121 +0,0 @@
---
title: "Node"
---
This guide demonstrates how to use Infisical to manage secrets for your Node stack from local development to production. It uses:
- Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets.
- The [infisical-node](https://github.com/Infisical/infisical-node) client SDK to fetch secrets back to your Node application on demand.
## Project Setup
To begin, we need to set up a project in Infisical and add secrets to an environment in it.
### Create a project
1. Create a new project in [Infisical](https://app.infisical.com/).
2. Add a secret to the development environment of this project so we can pull it back for local development. In the **Secrets Overview** page, press **Explore Development** and add a secret with the key `NAME` and value `YOUR_NAME`.
### Create an Infisical Token
Now that we've created a project and added a secret to its development environment, we need to provision an Infisical Token that our Node application can use to access the secret.
1. Head to the **Project Settings > Service Tokens** and press **Add New Token**.
2. Call the token anything like **My App Token** and select **Development** under **Environment**.
3. Copy the token and keep it handy.
## Create a Node app
For this demonstration, we use a minimal Express application. However, the same principles will apply for any Node application such as those built on Koa or Fastify.
### Create an Express app
Initialize a new Node.js project with a default `package.json` file.
```console
npm init -y
```
Install `express` and [infisical-node](https://github.com/Infisical/infisical-node), the client Node SDK for Infisical.
```console
npm install express infisical-node
```
Finally, create an index.js file containing the application code.
```js
const express = require("express");
const app = express();
const PORT = 3000;
const client = new InfisicalClient({
token: "YOUR_INFISICAL_TOKEN"
});
app.get("/", async (req, res) => {
const name = (await client.getSecret("NAME")).secretValue;
res.send(`Hello, ${name}!`);
});
app.listen(PORT, () => {
console.log(`Example app listening on port ${PORT}`);
});
```
Here, we initialized a `client` instance of the Infisical Node SDK with the Infisical Token
that we created earlier, giving access to the secrets in the development environment of the
project in Infisical that we created earlier.
Finally, start the app and head to `http://localhost:3000` to see the message **Hello, Your Name**.
```console
node index.js
```
The client fetched the secret with the key `NAME` from Infisical that we returned in the response of the endpoint.
At this stage, you know how to fetch secrets from Infisical back to your Node application. By using Infisical Tokens scoped to different environments, you can easily manage secrets across various stages of your project in Infisical, from local development to production.
## FAQ
<AccordionGroup>
<Accordion title="Are my secrets exposed in transit every time the SDK fetches them?">
No. Infisical uses end-to-end encryption which ensures that secrets are always encrypted in transit
and decrypted on the client side. In fact, not even the server can decrypt your secrets (unless
that permission is explicitly granted from within the platform).
Check out the [security guide](/security/overview).
</Accordion>
<Accordion title="Isn't it inefficient if my app makes a request every time it needs a secret?">
The client SDK caches every secret and implements a 5-minute waiting period before
re-requesting it. The waiting period can be controlled by setting the `cacheTTL` parameter at
the time of initializing the client.
</Accordion>
<Accordion title="What if a request for a secret fails?">
The SDK caches every secret and falls back to the cached value if a request fails. If no cached
value ever-existed, the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="Can I still use process.env with the SDK?">
Yes. If no `token` parameter is passed in at the time of initializing the client or nothing is found when requesting for a secret,
then the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="What's the point if I still have to manage a token for the SDK?">
The token enables the SDK to authenticate with Infisical to fetch back your secrets.
Although the SDK requires you to pass in a token, it enables greater efficiency and security
than if you managed dozens of secrets yourself without it. Here're some benefits:
- You always pull in the right secrets because they're fetched on demand from a centralize source that is Infisical.
- You can use the Infisical which comes with tons of benefits like secret versioning, access controls, audit logs, etc.
- You now risk leaking one token that can be revoked instead of dozens of raw secrets.
And much more.
</Accordion>
</AccordionGroup>
See also:
- Explore the [Node SDK](https://github.com/Infisical/infisical-node)

@ -1,119 +0,0 @@
---
title: "Python"
---
This guide demonstrates how to use Infisical to manage secrets for your Python stack from local development to production. It uses:
- Infisical (you can use [Infisical Cloud](https://app.infisical.com) or a [self-hosted instance of Infisical](https://infisical.com/docs/self-hosting/overview)) to store your secrets.
- The [infisical-python](https://github.com/Infisical/infisical-python) client SDK to fetch secrets back to your Python application on demand.
## Project Setup
To begin, we need to set up a project in Infisical and add secrets to an environment in it.
### Create a project
1. Create a new project in [Infisical](https://app.infisical.com/).
2. Add a secret to the development environment of this project so we can pull it back for local development. In the **Secrets Overview** page, press **Explore Development** and add a secret with the key `NAME` and value `YOUR_NAME`.
### Create an Infisical Token
Now that we've created a project and added a secret to its development environment, we need to provision an Infisical Token that our Node application can use to access the secret.
1. Head to the **Project Settings > Service Tokens** and press **Add New Token**.
2. Call the token anything like **My App Token** and select **Development** under **Environment**.
3. Copy the token and keep it handy.
## Create a Python app
For this demonstration, we use a minimal Flask application. However, the same principles will apply for any Python application such as those built with Django.
### Create a Flask app
First, create a virtual environment and activate it.
```console
python3 -m venv env
source env/bin/activate
```
Install Flask and [infisical-python](https://github.com/Infisical/infisical-python), the client Python SDK for Infisical.
```console
pip install Flask infisical
```
Finally, create an `app.py` file containing the application code.
```python
from flask import Flask
from infisical import InfisicalClient
app = Flask(__name__)
client = InfisicalClient(token="your_infisical_token")
@app.route("/")
def hello_world():
# access value
name = client.get_secret("NAME")
return f"Hello, {name.secret_value}!"
```
Here, we initialized a `client` instance of the Infisical Python SDK with the Infisical Token
that we created earlier, giving access to the secrets in the development environment of the
project in Infisical that we created earlier.
Finally, start the app and head to `http://localhost:5000` to see the message **Hello, Your Name**.
```console
flask run
```
The client fetched the secret with the key `NAME` from Infisical that we returned in the response of the endpoint.
At this stage, you know how to fetch secrets from Infisical back to your Python application. By using Infisical Tokens scoped to different environments, you can easily manage secrets across various stages of your project in Infisical, from local development to production.
## FAQ
<AccordionGroup>
<Accordion title="Are my secrets exposed in transit every time the SDK fetches them?">
No. Infisical uses end-to-end encryption which ensures that secrets are always encrypted in transit
and decrypted on the client side. In fact, not even the server can decrypt your secrets (unless
that permission is explicitly granted from within the platform).
Check out the [security guide](/security/overview).
</Accordion>
<Accordion title="Isn't it inefficient if my app makes a request every time it needs a secret?">
The client SDK caches every secret and implements a 5-minute waiting period before
re-requesting it. The waiting period can be controlled by setting the `cacheTTL` parameter at
the time of initializing the client.
</Accordion>
<Accordion title="What if a request for a secret fails?">
The SDK caches every secret and falls back to the cached value if a request fails. If no cached
value ever-existed, the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="Can I still use process.env with the SDK?">
Yes. If no `token` parameter is passed in at the time of initializing the client or nothing is found when requesting for a secret,
then the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="What's the point if I still have to manage a token for the SDK?">
The token enables the SDK to authenticate with Infisical to fetch back your secrets.
Although the SDK requires you to pass in a token, it enables greater efficiency and security
than if you managed dozens of secrets yourself without it. Here're some benefits:
- You always pull in the right secrets because they're fetched on demand from a centralize source that is Infisical.
- You can use the Infisical which comes with tons of benefits like secret versioning, access controls, audit logs, etc.
- You now risk leaking one token that can be revoked instead of dozens of raw secrets.
And much more.
</Accordion>
</AccordionGroup>
See also:
- Explore the [Python SDK](https://github.com/Infisical/infisical-python)

@ -0,0 +1,30 @@
---
title: "Features"
description: "A non-exhaustive list of features that Infisical has to offer."
---
## Platform
- Provision members access to organizations and projects.
- Manage secrets by adding, deleting, updating them across environments; search, sort, hide/un-hide, export/import them.
- Sync secrets to platforms via integrations to platforms like GitHub, Vercel, and Netlify.
- Rollback secrets to any point in time.
- Rollback each secrets to any version.
- Track actions through audit logs.
## CLI
The [CLI](/cli/overview) is used to inject environment variables into applications and infrastructure.
- Inject environment variables.
- Inject environment variables into containers via service tokens for Docker.
## SDKs
[SDKs](/sdks/overview) enable apps to fetch back secrets using an [Infisical Token](/getting-started/dashboard/token) scoped to a project and environment.
## Roadmap
We're building the future of secret management, one that's comprehensive and accessible to all. Check out our [roadmap](https://www.notion.so/infisical/be2d2585a6694e40889b03aef96ea36b?v=5b19a8127d1a4060b54769567a8785fa).
Interested in contributing? Check out the [guide](/contributing/overview).

@ -0,0 +1,42 @@
---
title: "Introduction"
description: "What is Infisical?"
---
Infisical is an [open-source](https://opensource.com/resources/what-open-source), [end-to-end encrypted](https://en.wikipedia.org/wiki/End-to-end_encryption) secret management platform that enables teams to easily manage and sync their environment variables.
Start syncing environment variables with [Infisical Cloud](https://app.infisical.com) or learn how to [host Infisical](/self-hosting/overview) yourself.
<CardGroup cols={2}>
<Card
title="Quickstart"
href="/getting-started/quickstart"
icon="timer"
color="#ea5a0c"
>
Tour Infisical in a few minutes.
</Card>
<Card href="/cli/overview" title="CLI" icon="square-terminal" color="#16a34a">
Install the CLI to inject secrets into apps and infra.
</Card>
<Card href="/sdks/overview" title="SDKs" icon="puzzle-piece" color="#16a34a">
Install an SDK into your app to fetch secrets.
</Card>
<Card
href="/self-hosting/overview"
title="Self-hosting"
icon="server"
color="#0285c7"
>
Learn how to configure and deploy Infisical.
</Card>
</CardGroup>
<Card
href="/integrations/overview"
title="Integrations"
icon="plug"
color="#dc2626"
>
Explore integrations for Docker, AWS, Heroku, etc.
</Card>

@ -0,0 +1,116 @@
---
title: "Quickstart"
description: "Start managing developer secrets and configs with Infisical in minutes."
---
These examples demonstrate how to store and fetch environment variables from [Infisical Cloud](https://app.infisical.com) into your application.
## Set up Infisical Cloud
1. Login or create an account at `app.infisical.com`.
2. Create a new project.
3. Keep the default environment variables or populate them as in the image below.
![project quickstart](../images/project-quickstart.png)
## Fetch Secrets for Your App
<Tabs>
<Tab title="CLI">
The Infisical CLI is platform-agnostic and enables you to inject environment variables into your app across many tech stacks and frameworks.
### Set up the CLI
1. Follow the instructions to [install our platform-agnostic CLI](/cli/overview).
2. Initialize Infisical for your project.
```bash
# move to your project
cd /path/to/project
# initialize infisical
infisical init
```
### Start your app with environment variables injected
```bash
# inject environment variables into app
infisical run -- [your application start command]
```
Your app should now be running with the environment variables injected.
Check out our [integrations](/integrations/overview) for injecting environment variables into frameworks and platforms like Docker.
</Tab>
<Tab title="SDK">
[Infisical SDKs](/sdks/overview) let your app fetch back secrets using an [Infisical Token](/getting-started/dashboard/token) that is scoped to a project and environment in Infisical. In this example, we demonstrate how to use the [Node SDK](/sdks/languages/node).
Using Python? We have a [Python SDK](/sdks/languages/python) as well.
### Obtain an [Infisical Token](/getting-started/dashboard/token)
Head to your project settings to create a token scoped to the project and environment you wish to fetch secrets from.
![token add](../images/project-token-add.png)
### Install the SDK
```bash
npm install infisical-node --save
```
### Initialize the Infisical client
```js
import InfisicalClient from "infisical-node";
const client = new InfisicalClient({
token: "your_infisical_token",
});
```
### Get a value
```js
const value = await client.getSecret("SOME_KEY");
```
### Example with Express
```js
import InfisicalClient from "infisical-node";
import express from "express";
const app = express();
const PORT = 3000;
const client = InfisicalClient({
token: "st.xxx.xxx",
});
// your application logic
app.get("/", async (req, res) => {
const name = await client.getSecret("NAME");
res.send(`Hello! My name is: ${name.secretValue}`);
});
app.listen(PORT, async () => {
console.log(`App listening on port ${port}`);
});
```
<Warning>
We do not recommend hardcoding your [Infisical
Token](/getting-started/dashboard/token). Setting it as an environment
variable would be best.
</Warning>
Check out our [SDKs](/sdks/overview) for other language SDKs.
</Tab>
</Tabs>

Binary file not shown.

Before

(image error) Size: 502 KiB

Binary file not shown.

Before

(image error) Size: 1.1 MiB

Binary file not shown.

Before

(image error) Size: 1.2 MiB

Binary file not shown.

Before

(image error) Size: 413 KiB

Binary file not shown.

Before

(image error) Size: 78 KiB

Binary file not shown.

Before

(image error) Size: 292 KiB

Binary file not shown.

Before

(image error) Size: 342 KiB

Binary file not shown.

Before

(image error) Size: 104 KiB

@ -37,7 +37,7 @@ Select which Infisical environment secrets you want to sync to which GitLab repo
## Generate service token
Generate an [Infisical Token](/documentation/platform/token) for the specific project and environment in Infisical.
Generate an [Infisical Token](../../getting-started/dashboard/token) for the specific project and environment in Infisical.
## Set the Infisical Token in Gitlab

@ -1,80 +0,0 @@
---
title: "Spring Boot with Maven"
description: "How to use Infisical to inject environment variables into Java Spring Boot"
---
Prerequisites:
- Set up and add your environment variables to [Infisical Cloud](https://app.infisical.com)
- [Install the CLI](/cli/overview)
## Initialize Infisical
In order for Infisical to know which secrets to fetch, you'll need to first initialize Infisical at the root of your project.
```bash
# navigate to the root of your of your project
cd /path/to/project
# then initialize Infisical
infisical init
```
## Start your application with Maven wrapper
To pass in Infisical secrets into your application, we will utilize the Infisical CLI to inject the secrets into the Maven wrapper executable, which is used to launch your application.
The Maven wrapper executable should already be present in the root directory of your project.
```bash
infisical run -- ./mvnw spring-boot:run --quiet
```
#### Accessing injected secrets
```java example.java
...
import org.springframework.core.env.Environment;
@SpringBootApplication
public class DemoApplication {
@Autowired
private Environment env;
@Bean
public void someMethod() {
System.out.println(env.getProperty("SOME_SECRET_NAME"));
};
}
```
## Debugging with secrets
During the process of debugging your code, it may be necessary to have certain environment variables available. To inject these variables for the purpose of debugging, please follow the instructions provided below.
Note that these instructions are currently only available for IntelliJ.
**Step 1:** On the main tool bar, choose Edit Configuration
<img height="200" src="../../images/spring-maven-debug-1.png" />
**Step 2:** Click the plus icon
<img height="200" src="../../images/spring-maven-debug-2.png" />
**Step 3:** Select Shell Script
<img height="200" src="../../images/spring-maven-debug-3.png" />
**Step 4:** Choose Script Text and then paste in the command below.
<img height="200" src="../../images/spring-maven-debug-4.png" />
```
infisical run -- ./mvnw spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
```
**Step 5:** When you need to run a block of code in debug mode, select the Infisical script
<img height="200" src="../../images/spring-maven-debug-5.png" />

@ -5,7 +5,7 @@ description: "How to use Infisical to inject secrets and configs into various 3-
Integrations allow environment variables to be synced from Infisical into your local development workflow, CI/CD pipelines, and production infrastructure.
Missing an integration? [Throw in a request](https://github.com/Infisical/infisical/issues).
Missing an integration? Throw in a [request](https://github.com/Infisical/infisical/issues).
| Integration | Type | Status |
| -------------------------------------------------------------- | --------- | ----------- |

@ -13,7 +13,7 @@ Follow this [guide](./docker) to configure the Infisical CLI for each service th
## Generate Infisical Tokens
Generate a unique [Infisical Token](/documentation/platform/token) for each service.
Generate a unique [Infisical Token](https://infisical.com/docs/getting-started/dashboard/token) for each service.
## Add Infisical Tokens to your Docker Compose file

@ -50,7 +50,7 @@ CMD ["infisical", "run", "--command", "npm run start && ..."]
## Generate an Infisical Token
Head to your project settings in Infisical Cloud to generate an [Infisical Token](/documentation/platform/token).
Head to your project settings in Infisical Cloud to generate an [Infisical Token](https://infisical.com/docs/getting-started/dashboard/token).
## Feed Docker your Infisical Token

@ -79,56 +79,27 @@
{
"group": "Overview",
"pages": [
{
"group": "Getting Started",
"pages": [
"documentation/getting-started/introduction",
"documentation/getting-started/platform",
"documentation/getting-started/sdks",
"documentation/getting-started/cli",
"documentation/getting-started/docker",
"documentation/getting-started/kubernetes"
]
},
{
"group": "Guides",
"pages": [
"documentation/guides/introduction",
"documentation/guides/node",
"documentation/guides/python",
"documentation/guides/nextjs-vercel"
]
}
"getting-started/introduction",
"getting-started/quickstart",
"getting-started/features"
]
},
{
"group": "Platform",
"pages": [
"documentation/platform/organization",
"documentation/platform/project",
"documentation/platform/pit-recovery",
"documentation/platform/secret-versioning",
"documentation/platform/audit-logs",
"documentation/platform/mfa",
"documentation/platform/token"
"getting-started/dashboard/organization",
"getting-started/dashboard/project",
"getting-started/dashboard/pit-recovery",
"getting-started/dashboard/secret-versioning",
"getting-started/dashboard/audit-logs",
"getting-started/dashboard/mfa",
"getting-started/dashboard/token"
]
},
{
"group": "Self-host Infisical",
"group": "Self-hosting",
"pages": [
{
"group": "Deployment options",
"pages": [
"self-hosting/overview",
"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/fly.io",
"self-hosting/deployment-options/render",
"self-hosting/deployment-options/digital-ocean-marketplace"
]
},
"self-hosting/overview",
"self-hosting/configuration/envars",
"self-hosting/configuration/email",
"self-hosting/faq"
@ -188,7 +159,6 @@
"integrations/cicd/gitlab",
"integrations/cicd/circleci",
"integrations/cicd/travisci",
"integrations/frameworks/spring-boot-maven",
"integrations/frameworks/react",
"integrations/frameworks/vue",
"integrations/frameworks/express",
@ -208,6 +178,13 @@
"integrations/platforms/pm2"
]
},
{
"group": "Deployment options",
"pages": [
"self-hosting/deployments/linux",
"self-hosting/deployments/kubernetes"
]
},
{
"group": "Overview",
"pages": [
@ -222,8 +199,7 @@
"sdks/languages/java",
"sdks/languages/ruby",
"sdks/languages/go",
"sdks/languages/rust",
"sdks/languages/php"
"sdks/languages/rust"
]
},
{
@ -312,6 +288,7 @@
]
}
],
"backgroundImage": "/images/background.png",
"integrations": {
"intercom": "hsg644ru"
}

@ -24,15 +24,16 @@ app.get("/", async (req, res) => {
});
app.listen(PORT, async () => {
console.log(`App listening on port ${PORT}`);
// initialize client
console.log(`App listening on port ${port}`);
});
```
This example demonstrates how to use the Infisical Node SDK with an Express application. The application retrieves a secret named "NAME" and responds to requests with a greeting that includes the secret value.
This example demonstrates how to use the Infisical SDK with an Express application. The application retrieves a secret named "NAME" and responds to requests with a greeting that includes the secret value.
<Warning>
We do not recommend hardcoding your [Infisical
Token](/documentation/platform/token). Setting it as an environment
Token](/getting-started/dashboard/token). Setting it as an environment
variable would be best.
</Warning>
@ -46,7 +47,7 @@ $ npm install infisical-node --save
## Configuration
Import the SDK and create a client instance with your [Infisical Token](/documentation/platform/token).
Import the SDK and create a client instance with your Infisical token.
<Tabs>
<Tab title="ES6">
@ -56,6 +57,8 @@ Import the SDK and create a client instance with your [Infisical Token](/documen
const client = new InfisicalClient({
token: "your_infisical_token"
});
// your app logic
```
</Tab>
@ -66,6 +69,8 @@ Import the SDK and create a client instance with your [Infisical Token](/documen
const client = new InfisicalClient({
token: "your_infisical_token"
});
// your app logic
````
</Tab>
@ -76,7 +81,7 @@ Import the SDK and create a client instance with your [Infisical Token](/documen
<ParamField query="options" type="object">
<Expandable title="properties">
<ParamField query="token" type="string" optional>
An [Infisical Token](/documentation/platform/token) scoped to a project
An [Infisical Token](/getting-started/dashboard/token) scoped to a project
and environment
</ParamField>
<ParamField

@ -1,8 +0,0 @@
---
title: "PHP"
icon: "php"
---
Coming soon.
Follow this GitHub [issue](https://github.com/Infisical/infisical/issues/531) to stay updated.

@ -26,7 +26,7 @@ This example demonstrates how to use the Infisical Python SDK with a Flask appli
<Warning>
We do not recommend hardcoding your [Infisical
Token](/documentation/platform/token). Setting it as an environment
Token](/getting-started/dashboard/token). Setting it as an environment
variable would be best.
</Warning>
@ -42,7 +42,7 @@ Note: You need Python 3.7+.
## Configuration
Import the SDK and create a client instance with your [Infisical Token](/documentation/platform/token).
Import the SDK and create a client instance with your Infisical token.
```py
from infisical import InfisicalClient
@ -53,7 +53,7 @@ client = InfisicalClient(token="your_infisical_token")
### Parameters
<ParamField query="token" type="string" optional>
An [Infisical Token](/documentation/platform/token) scoped to a project
An [Infisical Token](/getting-started/dashboard/token) scoped to a project
and environment
</ParamField>
<ParamField

@ -1,105 +1,15 @@
---
title: "Introduction"
title: "Overview"
description: "How to use Infisical SDKs to fetch back secrets for your app"
---
From local development to production, Infisical SDKs provide the easiest way for your app to fetch back secrets from Infisical on demand.
Whether it be for local development or production, Infisical SDKs provide the easiest way for your app to fetch back secrets using an [Infisical Token](/getting-started/dashboard/token).
- Install and initialize a language-specific client SDK into your application
- Provision the client scoped-access to a project and environment in Infisical
- Fetch secrets on demand
We currently have the [Node SDK](/sdks/languages/node) and [Python SDK](/sdks/languages/python) available with more language SDKs coming out soon:
<CardGroup cols={2}>
<Card
title="Node"
href="https://github.com/Infisical/infisical-node"
icon="node"
color="#68a063"
>
Manage secrets for your Node application on demand
</Card>
<Card
href="https://github.com/Infisical/infisical-python"
title="Python"
icon="python"
color="#4c8abe"
>
Manage secrets for your Python application on demand
</Card>
<Card
href="/sdks/languages/java"
title="Java"
icon="java"
color="#e41f23"
>
Manage secrets for your Java application on demand
</Card>
<Card
href="/sdks/languages/ruby"
title="Ruby"
icon="gem"
color="#ac0d01"
>
Manage secrets for your Ruby application on demand
</Card>
<Card
href="/sdks/languages/go"
title="Golang"
icon="golang"
color="#00add8"
>
Manage secrets for your Go application on demand
</Card>
<Card
href="/sdks/languages/rust"
title="Rust"
icon="rust"
color="#cd412b"
>
Manage secrets for your Rust application on demand
</Card>
<Card
href="/sdks/languages/php"
title="PHP"
icon="php"
color="#787cb4"
>
Manage secrets for your PHP application on demand
</Card>
</CardGroup>
## FAQ
<AccordionGroup>
<Accordion title="Are my secrets exposed in transit every time the SDK fetches them?">
No. Infisical uses end-to-end encryption which ensures that secrets are always encrypted in transit
and decrypted on the client side. In fact, not even the server can decrypt your secrets (unless
that permission is explicitly granted from within the platform).
Check out the [security guide](/security/overview).
</Accordion>
<Accordion title="Isn't it inefficient if my app makes a request every time it needs a secret?">
The client SDK caches every secret and implements a 5-minute waiting period before
re-requesting it. The waiting period can be controlled by setting the `cacheTTL` parameter at
the time of initializing the client.
</Accordion>
<Accordion title="What if a request for a secret fails?">
The SDK caches every secret and falls back to the cached value if a request fails. If no cached
value ever-existed, the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="Can I still use process.env with the SDK?">
Yes. If no `token` parameter is passed in at the time of initializing the client or nothing is found when requesting for a secret,
then the SDK falls back to whatever value is on `process.env`.
</Accordion>
<Accordion title="What's the point if I still have to manage a token for the SDK?">
The token enables the SDK to authenticate with Infisical to fetch back your secrets.
Although the SDK requires you to pass in a token, it enables greater efficiency and security
than if you managed dozens of secrets yourself without it. Here're some benefits:
- You always pull in the right secrets because they're fetched on demand from a centralize source that is Infisical.
- You can use the Infisical which comes with tons of benefits like secret versioning, access controls, audit logs, etc.
- You now risk leaking one token that can be revoked instead of dozens of raw secrets.
And much more.
</Accordion>
</AccordionGroup>
- [Node](/sdks/languages/node)
- [Python](/sdks/languages/python)
- [Java](/sdks/languages/java)
- [Ruby](/sdks/languages/ruby)
- [Go](/sdks/languages/go)
- [Rust](/sdks/languages/rust)

@ -23,7 +23,7 @@ By default, you need to configure the following SMTP [environment variables](htt
- `SMTP_FROM_ADDRESS`: Email address to be used for sending emails (e.g. team@infisical.com).
- `SMTP_FROM_NAME`: Name label to be used in `From` field (e.g. Team).
Below you will find details on how to configure common email providers:
Below you will find details on how to configure common email providers (not in any particular order).
<AccordionGroup>
<Accordion title="Twilio SendGrid">
@ -135,34 +135,6 @@ SMTP_FROM_NAME=Infisical
</Info>
</Accordion>
<Accordion title="Gmail">
Create an account and enable "less secure app access" in Gmail Account Settings > Security. This will allow
applications like Infisical to authenticate with Gmail via your username and password.
![Gmail secure app access](../../images/email-gmail-app-access.png)
With your Gmail username and password, you can set your SMTP environment variables:
```
SMTP_HOST=smtp.gmail.com
SMTP_USERNAME=hey@gmail.com # your email
SMTP_PASSWORD=password # your password
SMTP_PORT=587
SMTP_SECURE=true
SMTP_FROM_ADDRESS=hey@gmail.com
SMTP_FROM_NAME=Infisical
```
<Warning>
As per the [notice](https://support.google.com/accounts/answer/6010255?hl=en) by Google, you should note that using Gmail credentials for SMTP configuration
will only work for Google Workspace or Google Cloud Identity customers as of May 30, 2022.
Put differently, the SMTP configuration is only possible with business (not personal) Gmail credentials.
</Warning>
</Accordion>
<Accordion title="Zoho Mail">
1. Create an account and configure [Zoho Mail](https://www.zoho.com/mail/) to send emails.

@ -1,17 +0,0 @@
---
title: "AWS EC2"
description: "Learn to install Infisical on EC2 using Cloud Formation template"
---
<iframe width="560" height="315" src="https://www.youtube.com/embed/jR-gM7vIY2c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
This deployment option will use AWS Cloudformation to auto deploy an instance of Infisical on a single EC2 via Docker Compose.
**Resources that will be provisioned**
- 1 EC2 instance
- 1 DocumentDB cluster
- 1 DocumentDB instance
- Security groups
<a href="https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/review?templateURL=https://ec2-instance-cloudformation.s3.amazonaws.com/infisical-ec2-deployment.template&stackName=infisical">
<img width="200" src="../../images/deploy-aws-button.png" />
</a>

@ -1,6 +0,0 @@
---
title: "Digital Ocean"
description: "Learn to install Infisical on Digital Ocean"
---
Documentation coming soon

@ -1,47 +0,0 @@
---
title: "Docker Compose"
description: "Learn to install Infisical using our Docker Compose template"
---
## Install Docker on your VM
```bash
# Example in ubuntu
apt-get update
apt-get upgrade
apt install docker-compose
```
## Download the required files
```bash
# Download env file template
wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
# Download docker compose template
wget -O docker-compose.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.yml
# Download nginx config
mkdir nginx && wget -O ./nginx/default.conf https://raw.githubusercontent.com/Infisical/infisical/main/nginx/default.dev.conf
```
## Update .env file
Tweak the `.env` according to your preferences. Refer to the available [environment variables](/self-hosting/configuration/envars)
```bash
# update environment variables like mongo login
nano .env
```
<Info>
Infisical assumes that you have configured HTTPS. If you didn't configure HTTPS, set `HTTPS_ENABLED` to `false` in the .env file to avoid frequent logouts.
</Info>
## Get the service up and running.
```bash
# Start up services in detached mode
docker-compose -f docker-compose.yml up -d
```
Your Infisical installation is complete and should be running on [http://localhost:80](http://localhost:80). Please note that the containers are not exposed to the internet and only bind to the localhost. It's up to you to configure a firewall, SSL certificates, and implement any additional security measures.

@ -1,60 +0,0 @@
---
title: "Fly.io"
description: "Learn to install Infisical on Fly.io"
---
**Prerequisites**
- Familiar with Fly.io deployment
- Logged in via fly CLI
#### 1. Make a copy of the deployment config
To begin, you'll to make a copy of the following file on your local machine
```toml fly.toml
# fly.toml app configuration file generated for infisical on 2023-05-05T08:57:03-04:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
app = "infisical"
primary_region = "iad"
[build]
image = "infisical/infisical:latest"
[env]
ENCRYPTION_KEY = <>
JWT_AUTH_SECRET = <>
JWT_REFRESH_SECRET = <>
JWT_SERVICE_SECRET = <>
JWT_SIGNUP_SECRET = <>
MONGO_URL = <>
[http_service]
internal_port = 80
```
#### 2. Add environment variables
Before we can deploy Infisical, we'll need to provide values for the keys under `[env]` config block. For each of the following keys
- `ENCRYPTION_KEY`
- `JWT_AUTH_SECRET`
- `JWT_REFRESH_SECRET`
- `JWT_SERVICE_SECRET`
- `JWT_SIGNUP_SECRET`
you will need to generate a random 16 byte hex string. This can can be generated with `openssl rand -hex 16`.
Lastly, the `MONGO_URL` environment variable requires a document database connection URL.
You can obtain this URL by creating a document database using services such as [MongoDB](https://www.mongodb.com/), [AWS DocumentDB](https://aws.amazon.com/documentdb/), and others.
#### 3. Deploy
Run `fly launch` in the directory where you have the local version of config from step 1 and follow the instructions.
Once done, your very own instance of Infisical should be up and running on Fly.io.
Please note that this version of Infisical requires at least 250MB of memory to operate smoothly.

@ -1,214 +0,0 @@
---
title: "Kubernetes via Helm Chart"
description: "Use our Helm chart to Install Infisical on your Kubernetes cluster"
---
**Prerequisites**
- You have understanding of [Kubernetes](https://kubernetes.io/)
- Installed [Helm package manager](https://helm.sh/) version v3.11.3 or greater
- You have [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) installed and connected to your kubernetes cluster
<iframe width="100%" height="375" src="https://www.youtube.com/embed/ugJZSCcZaV8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
By deploying Infisical on Kubernetes, you can take advantage of its features to ensure that the application is fault-tolerant, highly available, and scalable.
To make the installation process easier and more streamlined, we have created a Helm chart that you can use to install Infisical on Kubernetes.
Helm is a package manager for Kubernetes that simplifies the installation and management of Kubernetes applications.
With our Helm chart, you can easily install Infisical on Kubernetes, configure it to your liking, and scale it up or down as needed.
In the following guide, we'll walk you through the step-by-step process of installing Infisical on Kubernetes using the Helm chart. By the end of this guide, you'll have a fully functional deployment of Infisical running on Kubernetes.
## Install Infisical Helm repository
```bash
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
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).
#### 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).
To determine the appropriate versions to use for the docker images, follow the links bellow
- [frontend Docker image](https://hub.docker.com/r/infisical/frontend/tags)
- [backend Docker image](https://hub.docker.com/r/infisical/backend/tags)
```yaml simple-values-example.yaml
frontend:
name: frontend
replicaCount: 2
image:
repository: infisical/frontend
tag: "v0.1.3"
pullPolicy: Always
backend:
replicaCount: 2
image:
repository: infisical/backend
tag: "v0.1.3"
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).
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`_.
However, when the above environment variables are not defined, our Helm chart
will automatically generate these environment variables for you. The generated environment variables will be saved to a Kubernetes secret and will be preserved between upgrades or uninstalls.
```yaml simple-values-example.yaml
...
backendEnvironmentVariables:
HTTPS_ENABLED: true
INVITE_ONLY_SIGNUP: false
...
```
<Info>
Infisical assumes that you have configured HTTPS. If you didn't configure HTTPS, set `HTTPS_ENABLED` to `false` in the backend environment variable to avoid frequent logouts.
</Info>
#### Routing external traffic
By default, Infisical takes all traffic coming to your external load balancer's IP address and routes them Infisical's services.
Infisical uses Nginx to route external traffic. You can install Nginx along with Infisical by setting `ingress.enabled` to `true` in the Helm values file. View all [properties for ingress](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical).
```yaml simple-values-example.yaml
...
ingress:
nginx:
enabled: false #<-- 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.
When persistence is enabled, the data will be stored a 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
persistence:
enabled: false
```
To increase data redundancy, we recommend that you use a managed MongoDB service such as AWS Document DB, MongoDB or similar services instead.
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.1.3"
pullPolicy: Always
backend:
replicaCount: 2
image:
repository: infisical/backend
tag: "v0.1.3"
pullPolicy: Always
backendEnvironmentVariables:
HTTPS_ENABLED: true
ingress:
nginx:
enabled: false #<-- if you would like to install nginx along with Infisical
```
<Accordion title="Full helm values example">
```yaml values.yaml
ingress:
nginx:
enabled: true
frontend:
enabled: true
name: frontend
podAnnotations: {}
deploymentAnnotations: {}
replicaCount: 4
image:
repository: infisical/frontend
tag: "v0.1.3"
pullPolicy: IfNotPresent
kubeSecretRef: null
service:
annotations: {}
type: ClusterIP
nodePort: ""
backend:
enabled: true
name: backend
podAnnotations: {}
deploymentAnnotations: {}
replicaCount: 4
image:
repository: infisical/backend
tag: "v0.1.3"
pullPolicy: IfNotPresent
kubeSecretRef: null
service:
annotations: {}
type: ClusterIP
nodePort: ""
# View all environment variables https://infisical.com/docs/self-hosting/configuration/envars
backendEnvironmentVariables:
MONGO_URL: <>
HTTPS_ENABLED: <>
## 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
```
</Accordion>
## Install the Helm chart
By default, the helm chart will be installed on your default namespace. If you wish to install the Chart on a different namespace, you may specify
that by adding the `--namespace <namespace-to-install-to>` to your `helm install` command.
```bash
## Installs to default namespace
helm install infisical-helm-charts/infisical --generate-name --values /path/to/values.yaml
```
## Access Infisical
Allow 3-5 minutes for the deployment to complete. Once done, you should now be able to access Infisical on the IP address exposed via Ingress on your load balancer. If you are not sure what the IP address is run `kubectl get ingress` to view the external IP address exposing Infisical.

@ -1,15 +0,0 @@
---
title: "Render.com"
description: "Learn to install Infisical Render.com"
---
**Prerequisites**
- An account at Render.com
- A document DB instance
Deploying on Render is one of the quickest ways to have Infisical running in production.
Before you start deployment, you will need to obtain document db connection string. This will be used for `MONGO_URL` environment variable required during installation.
You can create a document db database using services such as [MongoDB](https://www.mongodb.com/), [AWS DocumentDB](https://aws.amazon.com/documentdb/), and others. Once done, click the link below to start deployment.
### **[Deploy to Render](https://render.com/deploy?repo=https://github.com/Infisical/infisical)**

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