1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-22 18:53:41 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
5f9e1e7a5d fix: backend/package.json & backend/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-FASTXMLPARSER-5668858
2023-06-07 15:27:27 +00:00
201 changed files with 5053 additions and 11507 deletions
.github
Dockerfile.standalone-infisicalREADME.md
backend
cli/packages
docs
frontend
Dockerfilenext.config.js
public
scripts
src
components
context/SubscriptionContext
ee/components
hooks/api
layouts/AppLayout
pages
views
DashboardPage
Settings
OrgSettingsPage
ProjectSettingsPage
ProjectSettingsPage.tsx
components
AutoCapitalizationSection
CopyProjectIDSection
E2EESection
EnvironmentSection
ProjectIndexSecretsSection
ProjectNameChangeSection
SecretTagsSection
ServiceTokenSection
index.tsx

4
.github/values.yaml vendored

@ -6,7 +6,7 @@ frontend:
secrets.infisical.com/auto-reload: "true"
replicaCount: 2
image:
repository: infisical/staging_deployment_frontend
repository: infisical/frontend
tag: "latest"
pullPolicy: Always
kubeSecretRef: managed-secret-frontend
@ -25,7 +25,7 @@ backend:
secrets.infisical.com/auto-reload: "true"
replicaCount: 2
image:
repository: infisical/staging_deployment_backend
repository: infisical/backend
tag: "latest"
pullPolicy: Always
kubeSecretRef: managed-backend-secret

@ -1,118 +0,0 @@
name: Release production images (frontend, backend)
on:
push:
tags:
- "infisical/v*.*.*"
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
run: npm ci --only-production
working-directory: backend
- name: 🧪 Run tests
run: npm run test:ci
working-directory: backend
- 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 }}
load: true
context: backend
tags: infisical/backend:test
- name: ⏻ Spawn backend container and dependencies
run: |
docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
- name: 🧪 Test backend image
run: |
./.github/resources/healthcheck.sh infisical-backend-test
- name: ⏻ Shut down backend container and dependencies
run: |
docker compose -f .github/resources/docker-compose.be-test.yml down
- name: 🏗️ Build backend and push
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true
context: backend
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
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Depot CLI
uses: depot/setup-action@v1
- name: 📦 Build frontend and export to Docker
uses: depot/build-push-action@v1
with:
load: true
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
project: 64mmf0n610
context: frontend
tags: infisical/frontend:test
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
- name: ⏻ Spawn frontend container
run: |
docker run -d --rm --name infisical-frontend-test infisical/frontend:test
- name: 🧪 Test frontend image
run: |
./.github/resources/healthcheck.sh infisical-frontend-test
- name: ⏻ Shut down frontend container
run: |
docker stop infisical-frontend-test
- name: 🏗️ Build frontend and push
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
push: true
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
context: frontend
tags: |
infisical/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,11 +1,17 @@
name: Build, Publish and Deploy to Gamma
on: [workflow_dispatch]
on:
push:
tags:
- "infisical/v*.*.*"
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
@ -51,14 +57,18 @@ jobs:
push: true
context: backend
tags: |
infisical/staging_deployment_backend:${{ steps.commit.outputs.short }}
infisical/staging_deployment_backend:latest
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
@ -80,12 +90,12 @@ jobs:
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
project: 64mmf0n610
context: frontend
tags: infisical/staging_deployment_frontend:test
tags: infisical/frontend:test
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
- name: ⏻ Spawn frontend container
run: |
docker run -d --rm --name infisical-frontend-test infisical/staging_deployment_frontend:test
docker run -d --rm --name infisical-frontend-test infisical/frontend:test
- name: 🧪 Test frontend image
run: |
./.github/resources/healthcheck.sh infisical-frontend-test
@ -100,8 +110,9 @@ jobs:
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
context: frontend
tags: |
infisical/staging_deployment_frontend:${{ steps.commit.outputs.short }}
infisical/staging_deployment_frontend:latest
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 }}
@ -135,7 +146,7 @@ jobs:
- name: Download helm values to file and upgrade gamma deploy
run: |
wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml
helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --wait
helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --recreate-pods
if [[ $(helm status infisical) == *"FAILED"* ]]; then
echo "Helm upgrade failed"
exit 1

@ -25,8 +25,6 @@ ARG POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
# Build
RUN npm run build
@ -44,9 +42,6 @@ VOLUME /app/.next/cache/images
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
COPY --chown=nextjs:nodejs --chmod=555 frontend/scripts ./scripts
COPY --from=frontend-builder /app/public ./public

@ -25,7 +25,7 @@
<img src="https://img.shields.io/github/commit-activity/m/infisical/infisical" alt="git commit activity" />
</a>
<a href="https://cloudsmith.io/~infisical/repos/">
<img src="https://img.shields.io/badge/Downloads-305.8k-orange" alt="Cloudsmith downloads" />
<img src="https://img.shields.io/badge/Downloads-240.2k-orange" alt="Cloudsmith downloads" />
</a>
<a href="https://join.slack.com/t/infisical-users/shared_invite/zt-1wehzfnzn-1aMo5JcGENJiNAC2SD8Jlg">
<img src="https://img.shields.io/badge/chat-on%20Slack-blueviolet" alt="Slack community channel" />
@ -127,7 +127,7 @@ Whether it's big or small, we love contributions. Check out our guide to see how
Not sure where to get started? You can:
- [Book a free, non-pressure pairing session / code walkthrough with one of our teammates](https://cal.com/tony-infisical/30-min-meeting-contributing)!
- [Book a free, non-pressure pairing sessions with one of our teammates](mailto:tony@infisical.com?subject=Pairing%20session&body=I'd%20like%20to%20do%20a%20pairing%20session!)!
- Join our <a href="https://join.slack.com/t/infisical-users/shared_invite/zt-1wehzfnzn-1aMo5JcGENJiNAC2SD8Jlg">Slack</a>, and ask us any questions there.
## Resources

2543
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.319.0",
"@aws-sdk/client-secrets-manager": "^3.347.1",
"@godaddy/terminus": "^4.12.0",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.49.0",
@ -40,6 +40,7 @@
"posthog-node": "^2.6.0",
"query-string": "^7.1.3",
"rate-limit-mongo": "^2.3.2",
"request-ip": "^3.3.0",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",
"swagger-autogen": "^2.22.0",
@ -58,7 +59,7 @@
"start": "node build/index.js",
"dev": "nodemon",
"swagger-autogen": "node ./swagger/index.ts",
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build",
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build",
"lint": "eslint . --ext .ts",
"lint-and-fix": "eslint . --ext .ts --fix",
"lint-staged": "lint-staged",

@ -1,28 +1,18 @@
import { Request, Response } from 'express';
import fs from 'fs';
import path from 'path';
import jwt from 'jsonwebtoken';
import * as bigintConversion from 'bigint-conversion';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const jsrp = require('jsrp');
import {
User,
LoginSRPDetail,
TokenVersion
} from '../../models';
import { User, LoginSRPDetail } from '../../models';
import { createToken, issueAuthTokens, clearTokens } from '../../helpers/auth';
import { checkUserDevice } from '../../helpers/user';
import {
ACTION_LOGIN,
ACTION_LOGOUT,
AUTH_MODE_JWT
ACTION_LOGOUT
} from '../../variables';
import {
BadRequestError,
UnauthorizedRequestError
} from '../../utils/errors';
import { BadRequestError } from '../../utils/errors';
import { EELogService } from '../../ee/services';
import { getChannelFromUserAgent } from '../../utils/posthog';
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
import {
getJwtRefreshSecret,
getJwtAuthLifetime,
@ -33,7 +23,6 @@ import {
declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string;
refreshVersion?: number;
}
}
@ -116,15 +105,11 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.realIP,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
});
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
const tokens = await issueAuthTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
@ -143,7 +128,7 @@ export const login2 = async (req: Request, res: Response) => {
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.realIP
ipAddress: req.ip
});
// return (access) token in response
@ -170,9 +155,9 @@ export const login2 = async (req: Request, res: Response) => {
* @returns
*/
export const logout = async (req: Request, res: Response) => {
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User && req.authData.tokenVersionId) {
await clearTokens(req.authData.tokenVersionId)
}
await clearTokens({
userId: req.user._id.toString()
});
// clear httpOnly cookie
res.cookie('jid', '', {
@ -191,7 +176,7 @@ export const logout = async (req: Request, res: Response) => {
userId: req.user._id,
actions: [logoutAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.realIP
ipAddress: req.ip
});
return res.status(200).send({
@ -199,30 +184,6 @@ export const logout = async (req: Request, res: Response) => {
});
};
export const getCommonPasswords = async (req: Request, res: Response) => {
const commonPasswords = fs.readFileSync(
path.resolve(__dirname, '../../data/' + 'common_passwords.txt'),
'utf8'
).split('\n');
return res.status(200).send(commonPasswords);
}
export const revokeAllSessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany({
user: req.user._id
}, {
$inc: {
refreshVersion: 1,
accessVersion: 1
}
});
return res.status(200).send({
message: 'Successfully revoked all sessions.'
});
}
/**
* Return user is authenticated
* @param req
@ -236,7 +197,7 @@ export const checkAuth = async (req: Request, res: Response) => {
}
/**
* Return new JWT access token by first validating the refresh token
* Return new token by redeeming refresh token
* @param req
* @param res
* @returns
@ -245,7 +206,7 @@ export const getNewToken = async (req: Request, res: Response) => {
const refreshToken = req.cookies.jid;
if (!refreshToken) {
throw new Error('Failed to find refresh token in request cookies');
throw new Error('Failed to find token in request cookies');
}
const decodedToken = <jwt.UserIDJwtPayload>(
@ -254,27 +215,15 @@ export const getNewToken = async (req: Request, res: Response) => {
const user = await User.findOne({
_id: decodedToken.userId
}).select('+publicKey +refreshVersion +accessVersion');
}).select('+publicKey');
if (!user) throw new Error('Failed to authenticate unfound user');
if (!user?.publicKey)
throw new Error('Failed to authenticate not fully set up account');
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
if (!tokenVersion) throw UnauthorizedRequestError({
message: 'Failed to validate refresh token'
});
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({
message: 'Failed to validate refresh token'
});
const token = createToken({
payload: {
userId: decodedToken.userId,
tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.refreshVersion
userId: decodedToken.userId
},
expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret()

@ -86,53 +86,47 @@ export const saveIntegrationAccessToken = async (
// TODO: check if access token is valid for each integration
let integrationAuth;
const {
workspaceId,
accessId,
accessToken,
url,
namespace,
integration
}: {
workspaceId: string;
accessId: string | null;
accessToken: string;
url: string;
namespace: string;
integration: string;
} = req.body;
const {
workspaceId,
accessId,
accessToken,
integration
}: {
workspaceId: string;
accessId: string | null;
accessToken: string;
integration: string;
} = req.body;
const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId),
isActive: true
});
const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId),
isActive: true
});
if (!bot) throw new Error('Bot must be enabled to save integration access token');
if (!bot) throw new Error('Bot must be enabled to save integration access token');
integrationAuth = await IntegrationAuth.findOneAndUpdate({
workspace: new Types.ObjectId(workspaceId),
integration
}, {
workspace: new Types.ObjectId(workspaceId),
integration,
url,
namespace,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8
}, {
new: true,
upsert: true
});
integrationAuth = await IntegrationAuth.findOneAndUpdate({
workspace: new Types.ObjectId(workspaceId),
integration
}, {
workspace: new Types.ObjectId(workspaceId),
integration,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8
}, {
new: true,
upsert: true
});
// encrypt and save integration access details
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessId,
accessToken,
accessExpiresAt: undefined
});
// encrypt and save integration access details
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessId,
accessToken,
accessExpiresAt: undefined
});
if (!integrationAuth) throw new Error('Failed to save integration access token');
if (!integrationAuth) throw new Error('Failed to save integration access token');
return res.status(200).send({
integrationAuth

@ -57,7 +57,6 @@ export const createIntegration = async (req: Request, res: Response) => {
})
});
}
return res.status(200).send({
integration,
});

@ -6,10 +6,8 @@ import { createToken } from '../../helpers/auth';
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
import { sendMail } from '../../helpers/nodemailer';
import { TokenService } from '../../services';
import { EELicenseService } from '../../ee/services';
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED, TOKEN_EMAIL_ORG_INVITATION } from '../../variables';
import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config';
import { validateUserEmail } from '../../validation';
/**
* Delete organization membership with id [membershipOrgId] from organization
@ -98,19 +96,6 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
if (!membershipOrg) {
throw new Error('Failed to validate organization membership');
}
const plan = await EELicenseService.getPlan(organizationId);
if (plan.memberLimit !== null) {
// case: limit imposed on number of members allowed
if (plan.membersUsed >= plan.memberLimit) {
// case: number of members used exceeds the number of members allowed
return res.status(400).send({
message: 'Failed to invite member due to member limit reached. Upgrade plan to invite more members.'
});
}
}
invitee = await User.findOne({
email: inviteeEmail
@ -149,9 +134,6 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
if (!inviteeMembershipOrg) {
// case: invitee has never been invited before
// validate that email is not disposable
validateUserEmail(inviteeEmail);
await new MembershipOrg({
inviteEmail: inviteeEmail,

@ -3,23 +3,12 @@ import { Request, Response } from 'express';
const jsrp = require('jsrp');
import * as bigintConversion from 'bigint-conversion';
import { User, BackupPrivateKey, LoginSRPDetail } from '../../models';
import {
createToken,
sendMail,
clearTokens
} from '../../helpers';
import { createToken } from '../../helpers/auth';
import { sendMail } from '../../helpers/nodemailer';
import { TokenService } from '../../services';
import {
TOKEN_EMAIL_PASSWORD_RESET,
AUTH_MODE_JWT
} from '../../variables';
import { TOKEN_EMAIL_PASSWORD_RESET } from '../../variables';
import { BadRequestError } from '../../utils/errors';
import {
getSiteURL,
getJwtSignupLifetime,
getJwtSignupSecret,
getHttpsEnabled
} from '../../config';
import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret } from '../../config';
/**
* Password reset step 1: Send email verification link to email [email]
@ -29,15 +18,14 @@ import {
* @returns
*/
export const emailPasswordReset = async (req: Request, res: Response) => {
let email: string;
email = req.body.email;
const email = req.body.email;
const user = await User.findOne({ email }).select('+publicKey');
if (!user || !user?.publicKey) {
// case: user has already completed account
return res.status(200).send({
message:"If an account exists with this email, a password reset link has been sent"
return res.status(403).send({
error: 'Failed to send email verification for password reset'
});
}
@ -58,7 +46,7 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
});
return res.status(200).send({
message:"If an account exists with this email, a password reset link has been sent"
message: `Sent an email for account recovery to ${email}`
});
}
@ -110,7 +98,6 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
*/
export const srp1 = async (req: Request, res: Response) => {
// return salt, serverPublicKey as part of first step of SRP protocol
const { clientPublicKey } = req.body;
const user = await User.findOne({
email: req.user.email
@ -140,8 +127,7 @@ export const srp1 = async (req: Request, res: Response) => {
});
}
);
}
};
/**
* Change account SRP authentication information for user
@ -207,19 +193,6 @@ export const changePassword = async (req: Request, res: Response) => {
new: true
}
);
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User && req.authData.tokenVersionId) {
await clearTokens(req.authData.tokenVersionId)
}
// clear httpOnly cookie
res.cookie('jid', '', {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: (await getHttpsEnabled()) as boolean
});
return res.status(200).send({
message: 'Successfully changed password'

@ -100,52 +100,50 @@ export const pushSecrets = async (req: Request, res: Response) => {
* @returns
*/
export const pullSecrets = async (req: Request, res: Response) => {
let secrets;
let key;
const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}
let secrets = await pull({
userId: req.user._id.toString(),
workspaceId,
environment,
channel: channel ? channel : 'cli',
ipAddress: req.ip
});
secrets = await pull({
userId: req.user._id.toString(),
workspaceId,
environment,
channel: channel ? channel : 'cli',
ipAddress: req.realIP
});
const key = await Key.findOne({
workspace: workspaceId,
receiver: req.user._id
})
.sort({ createdAt: -1 })
.populate('sender', '+publicKey');
if (channel !== 'cli') {
// FIX: Fix this any
secrets = reformatPullSecrets({ secrets }) as any;
}
key = await Key.findOne({
workspace: workspaceId,
receiver: req.user._id
})
.sort({ createdAt: -1 })
.populate('sender', '+publicKey');
if (channel !== 'cli') {
secrets = reformatPullSecrets({ secrets });
}
if (postHogClient) {
// capture secrets pushed event in production
postHogClient.capture({
distinctId: req.user.email,
event: 'secrets pulled',
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
}
});
}
if (postHogClient) {
// capture secrets pushed event in production
postHogClient.capture({
distinctId: req.user.email,
event: 'secrets pulled',
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
}
});
}
return res.status(200).send({
secrets,
@ -162,51 +160,48 @@ export const pullSecrets = async (req: Request, res: Response) => {
* @returns
*/
export const pullSecretsServiceToken = async (req: Request, res: Response) => {
let secrets;
let key;
const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}
const secrets = await pull({
userId: req.serviceToken.user._id.toString(),
workspaceId,
environment,
channel: 'cli',
ipAddress: req.ip
});
secrets = await pull({
userId: req.serviceToken.user._id.toString(),
workspaceId,
environment,
channel: 'cli',
ipAddress: req.realIP
});
const key = {
encryptedKey: req.serviceToken.encryptedKey,
nonce: req.serviceToken.nonce,
sender: {
publicKey: req.serviceToken.publicKey
},
receiver: req.serviceToken.user,
workspace: req.serviceToken.workspace
};
key = {
encryptedKey: req.serviceToken.encryptedKey,
nonce: req.serviceToken.nonce,
sender: {
publicKey: req.serviceToken.publicKey
},
receiver: req.serviceToken.user,
workspace: req.serviceToken.workspace
};
if (postHogClient) {
// capture secrets pulled event in production
postHogClient.capture({
distinctId: req.serviceToken.user.email,
event: 'secrets pulled',
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
}
});
}
if (postHogClient) {
// capture secrets pulled event in production
postHogClient.capture({
distinctId: req.serviceToken.user.email,
event: 'secrets pulled',
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
}
});
}
return res.status(200).send({
secrets: reformatPullSecrets({ secrets }),

@ -7,7 +7,6 @@ import {
import { createToken } from '../../helpers/auth';
import { BadRequestError } from '../../utils/errors';
import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config';
import { validateUserEmail } from '../../validation';
/**
* Signup step 1: Initialize account for user under email [email] and send a verification code
@ -17,11 +16,7 @@ import { validateUserEmail } from '../../validation';
* @returns
*/
export const beginEmailSignup = async (req: Request, res: Response) => {
let email: string;
email = req.body.email;
// validate that email is not disposable
validateUserEmail(email);
const email = req.body.email;
const user = await User.findOne({ email }).select('+publicKey');
if (user && user?.publicKey) {

@ -13,7 +13,6 @@ import {
createWorkspace as create,
deleteWorkspace as deleteWork,
} from "../../helpers/workspace";
import { EELicenseService } from '../../ee/services';
import { addMemberships } from "../../helpers/membership";
import { ADMIN } from "../../variables";
@ -116,18 +115,6 @@ export const createWorkspace = async (req: Request, res: Response) => {
throw new Error("Failed to validate organization membership");
}
const plan = await EELicenseService.getPlan(organizationId);
if (plan.workspaceLimit !== null) {
// case: limit imposed on number of workspaces allowed
if (plan.workspacesUsed >= plan.workspaceLimit) {
// case: number of workspaces used exceeds the number of workspaces allowed
return res.status(400).send({
message: 'Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces.'
});
}
}
if (workspaceName.length < 1) {
throw new Error("Workspace names must be at least 1-character long");
}

@ -83,7 +83,7 @@ export const login2 = async (req: Request, res: Response) => {
const { email, clientProof } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices');
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
if (!user) throw new Error('Failed to find user');
@ -105,6 +105,7 @@ export const login2 = async (req: Request, res: Response) => {
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
if (user.isMfaEnabled) {
// case: user has MFA enabled
@ -140,16 +141,12 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.realIP,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
const tokens = await issueAuthTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
@ -159,7 +156,7 @@ export const login2 = async (req: Request, res: Response) => {
secure: await getHttpsEnabled()
});
// case: user does not have MFA enabled
// case: user does not have MFA enablgged
// return (access) token in response
interface ResponseData {
@ -262,7 +259,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
const user = await User.findOne({
email
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices');
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
if (!user) throw new Error('Failed to find user');
@ -270,16 +267,12 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.realIP,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
const tokens = await issueAuthTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
@ -337,7 +330,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.realIP
ipAddress: req.ip
});
return res.status(200).send(resObj);

@ -8,8 +8,7 @@ import {
Membership,
} from '../../models';
import { SecretVersion } from '../../ee/models';
import { EELicenseService } from '../../ee/services';
import { BadRequestError, WorkspaceNotFoundError } from '../../utils/errors';
import { BadRequestError } from '../../utils/errors';
import _ from 'lodash';
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables';
@ -23,26 +22,9 @@ export const createWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const { environmentName, environmentSlug } = req.body;
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) throw WorkspaceNotFoundError();
const plan = await EELicenseService.getPlan(workspace.organization.toString());
if (plan.environmentLimit !== null) {
// case: limit imposed on number of environments allowed
if (workspace.environments.length >= plan.environmentLimit) {
// case: number of environments used exceeds the number of environments allowed
return res.status(400).send({
message: 'Failed to create environment due to environment limit reached. Upgrade plan to create more environments.'
});
}
}
if (
!workspace ||
workspace?.environments.find(
@ -58,8 +40,6 @@ export const createWorkspaceEnvironment = async (
});
await workspace.save();
await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId);
return res.status(200).send({
message: 'Successfully created new environment',
workspace: workspaceId,
@ -206,9 +186,7 @@ export const deleteWorkspaceEnvironment = async (
await Membership.updateMany(
{ workspace: workspaceId },
{ $pull: { deniedPermissions: { environmentSlug: environmentSlug } } }
);
await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId);
)
return res.status(200).send({
message: 'Successfully deleted environment',

@ -1,6 +1,6 @@
import { Types } from "mongoose";
import { Request, Response } from "express";
import { ISecret, Secret, ServiceTokenData } from "../../models";
import { ISecret, Secret } from "../../models";
import { IAction, SecretVersion } from "../../ee/models";
import {
SECRET_PERSONAL,
@ -29,7 +29,6 @@ import { BatchSecretRequest, BatchSecret } from "../../types/secret";
import Folder from "../../models/folder";
import {
getFolderByPath,
getFolderIdFromServiceToken,
searchByFolderId,
} from "../../services/FolderService";
@ -46,15 +45,14 @@ export const batchSecrets = async (req: Request, res: Response) => {
const {
workspaceId,
environment,
folderId,
requests,
secretPath,
}: {
workspaceId: string;
environment: string;
folderId: string;
requests: BatchSecretRequest[];
secretPath: string;
} = req.body;
let folderId = req.body.folderId as string;
const createSecrets: BatchSecret[] = [];
const updateSecrets: BatchSecret[] = [];
@ -72,25 +70,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
if (!folder) throw BadRequestError({ message: "Folder not found" });
}
if (req.authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
// in service token when not giving secretpath folderid must be root
// this is to avoid giving folderid when service tokens are used
if (
(!secretPath && folderId !== "root") ||
(secretPath && secretPath !== serviceTkScopedSecretPath)
) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
if (secretPath) {
folderId = await getFolderIdFromServiceToken(
workspaceId,
environment,
secretPath
);
}
for await (const request of requests) {
// do a validation
@ -173,7 +152,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
numberOfSecrets: createdSecrets.length,
environment,
workspaceId,
folderId,
channel,
userAgent: req.headers?.["user-agent"],
},
@ -240,7 +218,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8,
tags: u.tags,
folder: u.folder,
folder: u.folder
})
);
@ -270,7 +248,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
numberOfSecrets: updateSecrets.length,
environment,
workspaceId,
folderId,
channel,
userAgent: req.headers?.["user-agent"],
},
@ -320,7 +297,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId),
actions,
channel,
ipAddress: req.realIP,
ipAddress: req.ip,
});
}
@ -418,13 +395,8 @@ export const createSecrets = async (req: Request, res: Response) => {
const {
workspaceId,
environment,
secretPath,
}: {
workspaceId: string;
environment: string;
secretPath?: string;
} = req.body;
let folderId = req.body.folderId;
folderId,
}: { workspaceId: string; environment: string; folderId: string } = req.body;
if (req.user) {
const hasAccess = await userHasWorkspaceAccess(
@ -449,24 +421,6 @@ export const createSecrets = async (req: Request, res: Response) => {
// case: create 1 secret
listOfSecretsToCreate = [req.body.secrets];
}
if (req.authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
// in service token when not giving secretpath folderid must be root
// this is to avoid giving folderid when service tokens are used
if (
(!secretPath && folderId !== "root") ||
(secretPath && secretPath !== serviceTkScopedSecretPath)
) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
if (secretPath) {
folderId = await getFolderIdFromServiceToken(
workspaceId,
environment,
secretPath
);
}
// get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({
@ -609,7 +563,7 @@ export const createSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId),
actions: [addAction],
channel,
ipAddress: req.realIP,
ipAddress: req.ip,
}));
// (EE) take a secret snapshot
@ -631,7 +585,6 @@ export const createSecrets = async (req: Request, res: Response) => {
environment,
workspaceId,
channel: channel,
folderId,
userAgent: req.headers?.["user-agent"],
},
});
@ -707,18 +660,6 @@ export const getSecrets = async (req: Request, res: Response) => {
if (!folder) throw BadRequestError({ message: "Folder not found" });
}
if (req.authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
// in service token when not giving secretpath folderid must be root
// this is to avoid giving folderid when service tokens are used
if (
(!secretPath && folderId !== "root") ||
(secretPath && secretPath !== serviceTkScopedSecretPath)
) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
if (folders && secretPath) {
if (!folders) throw BadRequestError({ message: "Folder not found" });
const folder = getFolderByPath(folders.nodes, secretPath as string);
@ -844,7 +785,7 @@ export const getSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(workspaceId as string),
actions: [readAction],
channel,
ipAddress: req.realIP,
ipAddress: req.ip,
}));
const postHogClient = await TelemetryService.getPostHogClient();
@ -859,7 +800,6 @@ export const getSecrets = async (req: Request, res: Response) => {
environment,
workspaceId,
channel,
folderId,
userAgent: req.headers?.["user-agent"],
},
});
@ -970,13 +910,13 @@ export const updateSecrets = async (req: Request, res: Response) => {
keyEncoding: ENCODING_SCHEME_UTF8,
tags,
...(secretCommentCiphertext !== undefined &&
secretCommentIV &&
secretCommentTag
secretCommentIV &&
secretCommentTag
? {
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
}
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
}
: {}),
},
},
@ -1080,7 +1020,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key),
actions: [updateAction],
channel,
ipAddress: req.realIP,
ipAddress: req.ip,
}));
// (EE) take a secret snapshot
@ -1218,7 +1158,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key),
actions: [deleteAction],
channel,
ipAddress: req.realIP,
ipAddress: req.ip,
}));
// (EE) take a secret snapshot

@ -1,27 +1,29 @@
import { Request, Response } from "express";
import crypto from "crypto";
import bcrypt from "bcrypt";
import { User, ServiceAccount, ServiceTokenData } from "../../models";
import { userHasWorkspaceAccess } from "../../ee/helpers/checkMembershipPermissions";
import { Request, Response } from 'express';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import {
PERMISSION_READ_SECRETS,
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
} from "../../variables";
import { getSaltRounds } from "../../config";
import { BadRequestError } from "../../utils/errors";
import Folder from "../../models/folder";
import { getFolderByPath } from "../../services/FolderService";
User,
ServiceAccount,
ServiceTokenData
} from '../../models';
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
import {
PERMISSION_READ_SECRETS,
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN
} from '../../variables';
import { getSaltRounds } from '../../config';
import { BadRequestError } from '../../utils/errors';
/**
* Return service token data associated with service token on request
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getServiceTokenData = async (req: Request, res: Response) => {
/*
/*
#swagger.summary = 'Return Infisical Token data'
#swagger.description = 'Return Infisical Token data'
@ -34,135 +36,111 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
"application/json": {
"schema": {
"type": "object",
"properties": {
"properties": {
"serviceTokenData": {
"type": "object",
$ref: "#/components/schemas/ServiceTokenData",
"description": "Details of service token"
}
}
}
}
}
}
}
*/
if (!(req.authData.authPayload instanceof ServiceTokenData))
throw BadRequestError({
message: "Failed accepted client validation for service token data",
if (!(req.authData.authPayload instanceof ServiceTokenData)) throw BadRequestError({
message: 'Failed accepted client validation for service token data'
});
const serviceTokenData = await ServiceTokenData.findById(
req.authData.authPayload._id
)
.select("+encryptedKey +iv +tag")
.populate("user");
const serviceTokenData = await ServiceTokenData
.findById(req.authData.authPayload._id)
.select('+encryptedKey +iv +tag')
.populate('user');
return res.status(200).json(serviceTokenData);
};
return res.status(200).json(serviceTokenData);
}
/**
* Create new service token data for workspace with id [workspaceId] and
* environment [environment].
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const createServiceTokenData = async (req: Request, res: Response) => {
let serviceTokenData;
let serviceTokenData;
const {
name,
workspaceId,
environment,
encryptedKey,
iv,
tag,
expiresIn,
secretPath,
permissions,
} = req.body;
const {
name,
workspaceId,
environment,
encryptedKey,
iv,
tag,
expiresIn,
permissions
} = req.body;
const folders = await Folder.findOne({
workspace: workspaceId,
environment,
});
const secret = crypto.randomBytes(16).toString('hex');
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
if (folders) {
const folder = getFolderByPath(folders.nodes, secretPath);
if (folder == undefined) {
throw BadRequestError({ message: "Path for service token does not exist" })
let expiresAt;
if (expiresIn) {
expiresAt = new Date()
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
}
}
const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
let user, serviceAccount;
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
user = req.authData.authPayload._id;
}
let expiresAt;
if (expiresIn) {
expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
}
if (req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && req.authData.authPayload instanceof ServiceAccount) {
serviceAccount = req.authData.authPayload._id;
}
serviceTokenData = await new ServiceTokenData({
name,
workspace: workspaceId,
environment,
user,
serviceAccount,
lastUsed: new Date(),
expiresAt,
secretHash,
encryptedKey,
iv,
tag,
permissions
}).save();
let user, serviceAccount;
// return service token data without sensitive data
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
if (
req.authData.authMode === AUTH_MODE_JWT &&
req.authData.authPayload instanceof User
) {
user = req.authData.authPayload._id;
}
if (!serviceTokenData) throw new Error('Failed to find service token data');
if (
req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
req.authData.authPayload instanceof ServiceAccount
) {
serviceAccount = req.authData.authPayload._id;
}
const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
serviceTokenData = await new ServiceTokenData({
name,
workspace: workspaceId,
environment,
user,
serviceAccount,
lastUsed: new Date(),
expiresAt,
secretHash,
encryptedKey,
iv,
tag,
secretPath,
permissions,
}).save();
// return service token data without sensitive data
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
if (!serviceTokenData) throw new Error("Failed to find service token data");
const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
return res.status(200).send({
serviceToken,
serviceTokenData,
});
};
return res.status(200).send({
serviceToken,
serviceTokenData
});
}
/**
* Delete service token data with id [serviceTokenDataId].
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const deleteServiceTokenData = async (req: Request, res: Response) => {
const { serviceTokenDataId } = req.params;
const { serviceTokenDataId } = req.params;
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(
serviceTokenDataId
);
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
return res.status(200).send({
serviceTokenData,
});
};
return res.status(200).send({
serviceTokenData
});
}

@ -114,9 +114,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
userId: user._id.toString()
});
token = tokens.token;
@ -239,9 +237,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
userId: user._id.toString()
});
token = tokens.token;

@ -68,7 +68,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
environment,
secrets,
channel: channel ? channel : 'cli',
ipAddress: req.realIP
ipAddress: req.ip
});
await pushKeys({
@ -111,7 +111,6 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
* @returns
*/
export const pullSecrets = async (req: Request, res: Response) => {
let secrets;
const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
@ -129,16 +128,17 @@ export const pullSecrets = async (req: Request, res: Response) => {
throw new Error('Failed to validate environment');
}
secrets = await pull({
let secrets = await pull({
userId,
workspaceId,
environment,
channel: channel ? channel : 'cli',
ipAddress: req.realIP
ipAddress: req.ip
});
if (channel !== 'cli') {
secrets = reformatPullSecrets({ secrets });
// FIX: Fix this any
secrets = reformatPullSecrets({ secrets }) as any;
}
if (postHogClient) {

@ -113,7 +113,7 @@ export const login2 = async (req: Request, res: Response) => {
const user = await User.findOne({
email,
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices');
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
if (!user) throw new Error('Failed to find user');
@ -179,16 +179,12 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.realIP,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
});
const tokens = await issueAuthTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
@ -243,7 +239,7 @@ export const login2 = async (req: Request, res: Response) => {
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.realIP
ipAddress: req.ip
});
return res.status(200).send(response);

@ -1,420 +1,183 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { SecretService, EventService } from "../../services";
import { eventPushSecrets } from "../../events";
import { BotService } from "../../services";
import { repackageSecretToRaw } from "../../helpers/secrets";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import {
SecretService,
TelemetryService,
EventService
} from '../../services';
import { eventPushSecrets } from '../../events';
import { getAuthDataPayloadIdObj } from '../../utils/auth';
import { BadRequestError } from '../../utils/errors';
/**
* Return secrets for workspace with id [workspaceId] and environment
* [environment] in plaintext
* @param req
* @param res
*/
export const getSecretsRaw = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
authData: req.authData,
});
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
});
return res.status(200).send({
secrets: secrets.map((secret) => {
const rep = repackageSecretToRaw({
secret,
key
});
return rep;
})
});
};
/**
* Return secret with name [secretName] in plaintext
* @param req
* @param res
*/
export const getSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const type = req.query.type as "shared" | "personal" | undefined;
const secret = await SecretService.getSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
secretPath,
authData: req.authData,
});
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
});
return res.status(200).send({
secret: repackageSecretToRaw({
secret,
key
})
});
};
/**
* Create secret with name [secretName] in plaintext
* @param req
* Get secrets for workspace with id [workspaceId] and environment
* [environment]
* @param req
* @param res
*/
export const createSecretRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretValue,
secretComment,
secretPath = "/"
} = req.body;
export const getSecrets = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
});
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
authData: req.authData
});
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretName,
key
});
return res.status(200).send({
secrets
});
}
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretValue,
key
});
/**
* Get secret with name [secretName]
* @param req
* @param res
*/
export const getSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const type = req.query.type as 'shared' | 'personal' | undefined;
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretComment,
key
});
const secret = await SecretService.getSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData
});
return res.status(200).send({
secret
});
}
const secret = await SecretService.createSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData,
secretKeyCiphertext: secretKeyEncrypted.ciphertext,
secretKeyIV: secretKeyEncrypted.iv,
secretKeyTag: secretKeyEncrypted.tag,
secretValueCiphertext: secretValueEncrypted.ciphertext,
secretValueIV: secretValueEncrypted.iv,
secretValueTag: secretValueEncrypted.tag,
secretPath,
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
secretCommentIV: secretCommentEncrypted.iv,
secretCommentTag: secretCommentEncrypted.tag
});
/**
* Create secret with name [secretName]
* @param req
* @param res
*/
export const createSecret = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
} = req.body;
const secret = await SecretService.createSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
...((secretCommentCiphertext && secretCommentIV && secretCommentTag) ? {
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
} : {})
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
}),
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment
})
});
const secretWithoutBlindIndex = secret.toObject();
delete secretWithoutBlindIndex.secretBlindIndex;
return res.status(200).send({
secret: repackageSecretToRaw({
secret: secretWithoutBlindIndex,
key
})
});
const secretWithoutBlindIndex = secret.toObject();
delete secretWithoutBlindIndex.secretBlindIndex;
return res.status(200).send({
secret: secretWithoutBlindIndex
});
}
/**
* Update secret with name [secretName]
* @param req
* @param res
*/
export const updateSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretValue,
secretPath = "/",
} = req.body;
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
});
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretValue,
key
});
const secret = await SecretService.updateSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData,
secretValueCiphertext: secretValueEncrypted.ciphertext,
secretValueIV: secretValueEncrypted.iv,
secretValueTag: secretValueEncrypted.tag,
secretPath,
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
}),
});
return res.status(200).send({
secret: repackageSecretToRaw({
secret,
key
})
});
};
/**
* Delete secret with name [secretName]
* @param req
* @param res
*/
export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretPath = "/"
} = req.body;
const { secret } = await SecretService.deleteSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData,
secretPath,
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
}),
});
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
});
return res.status(200).send({
secret: repackageSecretToRaw({
secret,
key
})
});
};
/**
* Get secrets for workspace with id [workspaceId] and environment
* [environment]
* @param req
* @param res
*/
export const getSecrets = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
authData: req.authData,
});
return res.status(200).send({
secrets,
});
};
/**
* Return secret with name [secretName]
* @param req
* @param res
*/
export const getSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const type = req.query.type as "shared" | "personal" | undefined;
const secret = await SecretService.getSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
secretPath,
authData: req.authData,
});
return res.status(200).send({
secret,
});
};
/**
* Create secret with name [secretName]
* @param req
* @param res
*/
export const createSecret = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
secretPath = "/",
} = req.body;
const secret = await SecretService.createSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretPath,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
}),
});
const secretWithoutBlindIndex = secret.toObject();
delete secretWithoutBlindIndex.secretBlindIndex;
return res.status(200).send({
secret: secretWithoutBlindIndex,
});
};
/**
* Update secret with name [secretName]
* @param req
* @param res
* @param res
*/
export const updateSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretPath = "/",
} = req.body;
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretValueCiphertext,
secretValueIV,
secretValueTag
} = req.body;
const secret = await SecretService.updateSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretPath,
});
const secret = await SecretService.updateSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData,
secretValueCiphertext,
secretValueIV,
secretValueTag
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment
})
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
}),
});
return res.status(200).send({
secret,
});
};
return res.status(200).send({
secret
});
}
/**
* Delete secret with name [secretName]
* @param req
* @param res
* @param req
* @param res
*/
export const deleteSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretPath = "/"
} = req.body;
const { secretName } = req.params;
const {
workspaceId,
environment,
type
} = req.body;
const { secret, secrets } = await SecretService.deleteSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData
});
const { secret } = await SecretService.deleteSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData,
secretPath,
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment
})
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
}),
});
return res.status(200).send({
secret,
});
};
return res.status(200).send({
secret
});
}

@ -137,9 +137,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers['user-agent'] ?? ''
userId: user._id.toString()
});
token = tokens.token;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -8,9 +8,8 @@ import { EELicenseService } from '../../services';
*/
export const getOrganizationPlan = async (req: Request, res: Response) => {
const { organizationId } = req.params;
const workspaceId = req.query.workspaceId as string;
const plan = await EELicenseService.getPlan(organizationId, workspaceId);
const plan = await EELicenseService.getOrganizationPlan(organizationId);
return res.status(200).send({
plan,

@ -5,7 +5,7 @@ import {
requireOrganizationAuth,
validateRequest
} from '../../../middleware';
import { param, body, query } from 'express-validator';
import { param, body } from 'express-validator';
import { organizationsController } from '../../controllers/v1';
import {
OWNER, ADMIN, MEMBER, ACCEPTED
@ -21,7 +21,6 @@ router.get(
acceptedStatuses: [ACCEPTED]
}),
param('organizationId').exists().trim(),
query('workspaceId').optional().isString(),
validateRequest,
organizationsController.getOrganizationPlan
);

@ -1,4 +1,3 @@
import * as Sentry from '@sentry/node';
import NodeCache from 'node-cache';
import {
getLicenseKey,
@ -22,8 +21,6 @@ interface FeatureSet {
workspacesUsed: number;
memberLimit: number | null;
membersUsed: number;
environmentLimit: number | null;
environmentsUsed: number;
secretVersioning: boolean;
pitRecovery: boolean;
rbac: boolean;
@ -52,8 +49,6 @@ class EELicenseService {
workspacesUsed: 0,
memberLimit: null,
membersUsed: 0,
environmentLimit: null,
environmentsUsed: 0,
secretVersioning: true,
pitRecovery: true,
rbac: true,
@ -71,10 +66,10 @@ class EELicenseService {
});
}
public async getPlan(organizationId: string, workspaceId?: string): Promise<FeatureSet> {
public async getOrganizationPlan(organizationId: string): Promise<FeatureSet> {
try {
if (this.instanceType === 'cloud') {
const cachedPlan = this.localFeatureSet.get<FeatureSet>(`${organizationId}-${workspaceId ?? ''}`);
const cachedPlan = this.localFeatureSet.get<FeatureSet>(organizationId);
if (cachedPlan) {
return cachedPlan;
}
@ -82,16 +77,12 @@ class EELicenseService {
const organization = await Organization.findById(organizationId);
if (!organization) throw OrganizationNotFoundError();
let url = `${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`;
if (workspaceId) {
url += `?workspaceId=${workspaceId}`;
}
const { data: { currentPlan } } = await licenseServerKeyRequest.get(url);
const { data: { currentPlan } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`
);
// cache fetched plan for organization
this.localFeatureSet.set(`${organizationId}-${workspaceId ?? ''}`, currentPlan);
this.localFeatureSet.set(organizationId, currentPlan);
return currentPlan;
}
@ -101,47 +92,34 @@ class EELicenseService {
return this.globalFeatureSet;
}
public async refreshPlan(organizationId: string, workspaceId?: string) {
if (this.instanceType === 'cloud') {
this.localFeatureSet.del(`${organizationId}-${workspaceId ?? ''}`);
await this.getPlan(organizationId, workspaceId);
}
}
public async initGlobalFeatureSet() {
const licenseServerKey = await getLicenseServerKey();
const licenseKey = await getLicenseKey();
try {
if (licenseServerKey) {
// license server key is present -> validate it
const token = await refreshLicenseServerKeyToken()
if (token) {
this.instanceType = 'cloud';
}
if (licenseServerKey) {
// license server key is present -> validate it
const token = await refreshLicenseServerKeyToken()
return;
if (token) {
this.instanceType = 'cloud';
}
if (licenseKey) {
// license key is present -> validate it
const token = await refreshLicenseKeyToken();
if (token) {
const { data: { currentPlan } } = await licenseKeyRequest.get(
`${await getLicenseServerUrl()}/api/license/v1/plan`
);
this.globalFeatureSet = currentPlan;
this.instanceType = 'enterprise-self-hosted';
}
return;
}
if (licenseKey) {
// license key is present -> validate it
const token = await refreshLicenseKeyToken();
if (token) {
const { data: { currentPlan } } = await licenseKeyRequest.get(
`${await getLicenseServerUrl()}/api/license/v1/plan`
);
this.globalFeatureSet = currentPlan;
this.instanceType = 'enterprise-self-hosted';
}
} catch (err) {
// case: self-hosted free
Sentry.setUser(null);
Sentry.captureException(err);
}
}

@ -6,9 +6,7 @@ import {
User,
ServiceTokenData,
ServiceAccount,
APIKeyData,
TokenVersion,
ITokenVersion
APIKeyData
} from '../models';
import {
AccountNotFoundError,
@ -37,7 +35,7 @@ import {
* @param {Object} obj
* @param {Object} obj.headers - HTTP request headers object
*/
export const validateAuthMode = ({
const validateAuthMode = ({
headers,
acceptedAuthModes
}: {
@ -99,7 +97,7 @@ export const validateAuthMode = ({
* @param {String} obj.authTokenValue - JWT token value
* @returns {User} user - user corresponding to JWT token
*/
export const getAuthUserPayload = async ({
const getAuthUserPayload = async ({
authTokenValue
}: {
authTokenValue: string;
@ -109,32 +107,14 @@ export const getAuthUserPayload = async ({
);
const user = await User.findOne({
_id: new Types.ObjectId(decodedToken.userId)
}).select('+publicKey +accessVersion');
_id: decodedToken.userId
}).select('+publicKey');
if (!user) throw AccountNotFoundError({ message: 'Failed to find user' });
if (!user) throw AccountNotFoundError({ message: 'Failed to find User' });
if (!user?.publicKey) throw UnauthorizedRequestError({ message: 'Failed to authenticate user with partially set up account' });
if (!user?.publicKey) throw UnauthorizedRequestError({ message: 'Failed to authenticate User with partially set up account' });
const tokenVersion = await TokenVersion.findOneAndUpdate({
_id: new Types.ObjectId(decodedToken.tokenVersionId),
user: user._id
}, {
lastUsed: new Date()
});
if (!tokenVersion) throw UnauthorizedRequestError({
message: 'Failed to validate access token'
});
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({
message: 'Failed to validate access token'
});
return ({
user,
tokenVersionId: tokenVersion._id
});
return user;
}
/**
@ -143,7 +123,7 @@ export const getAuthUserPayload = async ({
* @param {String} obj.authTokenValue - service token value
* @returns {ServiceTokenData} serviceTokenData - service token data
*/
export const getAuthSTDPayload = async ({
const getAuthSTDPayload = async ({
authTokenValue
}: {
authTokenValue: string;
@ -189,7 +169,7 @@ export const getAuthSTDPayload = async ({
* @param {String} obj.authTokenValue - service account access token value
* @returns {ServiceAccount} serviceAccount
*/
export const getAuthSAAKPayload = async ({
const getAuthSAAKPayload = async ({
authTokenValue
}: {
authTokenValue: string;
@ -218,7 +198,7 @@ export const getAuthSAAKPayload = async ({
* @param {String} obj.authTokenValue - API key value
* @returns {APIKeyData} apiKeyData - API key data
*/
export const getAuthAPIKeyPayload = async ({
const getAuthAPIKeyPayload = async ({
authTokenValue
}: {
authTokenValue: string;
@ -275,43 +255,12 @@ export const getAuthAPIKeyPayload = async ({
* @return {String} obj.token - issued JWT token
* @return {String} obj.refreshToken - issued refresh token
*/
export const issueAuthTokens = async ({
userId,
ip,
userAgent
}: {
userId: Types.ObjectId;
ip: string;
userAgent: string;
}) => {
let tokenVersion: ITokenVersion | null;
// continue with (session) token version matching existing ip and user agent
tokenVersion = await TokenVersion.findOne({
user: userId,
ip,
userAgent
});
if (!tokenVersion) {
// case: no existing ip and user agent exists
// -> create new (session) token version for ip and user agent
tokenVersion = await new TokenVersion({
user: userId,
refreshVersion: 0,
accessVersion: 0,
ip,
userAgent,
lastUsed: new Date()
}).save();
}
const issueAuthTokens = async ({ userId }: { userId: string }) => {
// issue tokens
const token = createToken({
payload: {
userId,
tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.accessVersion
userId
},
expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret()
@ -319,9 +268,7 @@ export const issueAuthTokens = async ({
const refreshToken = createToken({
payload: {
userId,
tokenVersionId: tokenVersion._id.toString(),
refreshVersion: tokenVersion.refreshVersion
userId
},
expiresIn: await getJwtRefreshLifetime(),
secret: await getJwtRefreshSecret()
@ -338,15 +285,13 @@ export const issueAuthTokens = async ({
* @param {Object} obj
* @param {String} obj.userId - id of user whose tokens are cleared.
*/
export const clearTokens = async (tokenVersionId: Types.ObjectId): Promise<void> => {
const clearTokens = async ({ userId }: { userId: string }): Promise<void> => {
// increment refreshVersion on user by 1
await TokenVersion.findOneAndUpdate({
_id: tokenVersionId
User.findOneAndUpdate({
_id: userId
}, {
$inc: {
refreshVersion: 1,
accessVersion: 1
refreshVersion: 1
}
});
};
@ -359,7 +304,7 @@ export const clearTokens = async (tokenVersionId: Types.ObjectId): Promise<void>
* @param {String} obj.secret - (JWT) secret such as [JWT_AUTH_SECRET]
* @param {String} obj.expiresIn - string describing time span such as '10h' or '7d'
*/
export const createToken = ({
const createToken = ({
payload,
expiresIn,
secret
@ -373,7 +318,7 @@ export const createToken = ({
});
};
export const validateProviderAuthToken = async ({
const validateProviderAuthToken = async ({
email,
user,
providerAuthToken,
@ -396,4 +341,16 @@ export const validateProviderAuthToken = async ({
) {
throw new Error('Invalid authentication credentials.')
}
}
}
export {
validateAuthMode,
validateProviderAuthToken,
getAuthUserPayload,
getAuthSTDPayload,
getAuthSAAKPayload,
getAuthAPIKeyPayload,
createToken,
issueAuthTokens,
clearTokens
};

@ -31,7 +31,7 @@ import { InternalServerError } from "../utils/errors";
* @param {String} obj.name - name of bot
* @param {String} obj.workspaceId - id of workspace that bot belongs to
*/
export const createBot = async ({
const createBot = async ({
name,
workspaceId,
}: {
@ -86,18 +86,6 @@ export const createBot = async ({
});
};
/**
* Return whether or not workspace with id [workspaceId] is end-to-end encrypted
* @param {Types.ObjectId} workspaceId - id of workspace to check
*/
export const getIsWorkspaceE2EEHelper = async (workspaceId: Types.ObjectId) => {
const botKey = await BotKey.exists({
workspace: workspaceId
});
return botKey ? false : true;
}
/**
* Return decrypted secrets for workspace with id [workspaceId]
* and [environment] using bot
@ -105,7 +93,7 @@ export const getIsWorkspaceE2EEHelper = async (workspaceId: Types.ObjectId) => {
* @param {String} obj.workspaceId - id of workspace
* @param {String} obj.environment - environment
*/
export const getSecretsBotHelper = async ({
const getSecretsHelper = async ({
workspaceId,
environment,
}: {
@ -113,7 +101,7 @@ export const getSecretsBotHelper = async ({
environment: string;
}) => {
const content = {} as any;
const key = await getKey({ workspaceId: workspaceId });
const key = await getKey({ workspaceId: workspaceId.toString() });
const secrets = await Secret.find({
workspace: workspaceId,
environment,
@ -148,7 +136,7 @@ export const getSecretsBotHelper = async ({
* @param {String} obj.workspaceId - id of workspace
* @returns {String} key - decrypted workspace key
*/
export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) => {
const getKey = async ({ workspaceId }: { workspaceId: string }) => {
const encryptionKey = await getEncryptionKey();
const rootEncryptionKey = await getRootEncryptionKey();
@ -206,14 +194,14 @@ export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) =
* @param {String} obj1.workspaceId - id of workspace
* @param {String} obj1.plaintext - plaintext to encrypt
*/
export const encryptSymmetricHelper = async ({
const encryptSymmetricHelper = async ({
workspaceId,
plaintext,
}: {
workspaceId: Types.ObjectId;
plaintext: string;
}) => {
const key = await getKey({ workspaceId: workspaceId });
const key = await getKey({ workspaceId: workspaceId.toString() });
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
plaintext,
key,
@ -234,7 +222,7 @@ export const encryptSymmetricHelper = async ({
* @param {String} obj.iv - iv
* @param {String} obj.tag - tag
*/
export const decryptSymmetricHelper = async ({
const decryptSymmetricHelper = async ({
workspaceId,
ciphertext,
iv,
@ -245,7 +233,7 @@ export const decryptSymmetricHelper = async ({
iv: string;
tag: string;
}) => {
const key = await getKey({ workspaceId: workspaceId });
const key = await getKey({ workspaceId: workspaceId.toString() });
const plaintext = decryptSymmetric128BitHexKeyUTF8({
ciphertext,
iv,
@ -254,4 +242,11 @@ export const decryptSymmetricHelper = async ({
});
return plaintext;
};
};
export {
createBot,
getSecretsHelper,
encryptSymmetricHelper,
decryptSymmetricHelper
};

@ -7,7 +7,7 @@ import { getLogger } from '../utils/logger';
* @param {String} obj.mongoURL - mongo connection string
* @returns
*/
export const initDatabaseHelper = async ({
const initDatabaseHelper = async ({
mongoURL
}: {
mongoURL: string;
@ -30,7 +30,7 @@ export const initDatabaseHelper = async ({
/**
* Close database conection
*/
export const closeDatabaseHelper = async () => {
const closeDatabaseHelper = async () => {
return Promise.all([
new Promise((resolve) => {
if (mongoose.connection && mongoose.connection.readyState == 1) {
@ -41,4 +41,9 @@ export const closeDatabaseHelper = async () => {
}
})
]);
}
export {
initDatabaseHelper,
closeDatabaseHelper
}

@ -18,7 +18,7 @@ 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)
*/
export const handleEventHelper = async ({ event }: { event: Event }) => {
const handleEventHelper = async ({ event }: { event: Event }) => {
const { workspaceId, environment } = event;
// TODO: moduralize bot check into separate function
@ -37,4 +37,6 @@ export const handleEventHelper = async ({ event }: { event: Event }) => {
});
break;
}
};
};
export { handleEventHelper };

@ -1,17 +0,0 @@
export * from './auth';
export * from './bot';
export * from './database';
export * from './event';
export * from './integration';
export * from './key';
export * from './membership';
export * from './membershipOrg';
export * from './nodemailer';
export * from './organization';
export * from './rateLimiter';
export * from './secret';
export * from './secrets';
export * from './signup';
export * from './token';
export * from './user';
export * from './workspace';

@ -15,6 +15,7 @@ import {
import {
UnauthorizedRequestError,
} from '../utils/errors';
import RequestError from '../utils/requestError';
interface Update {
workspace: string;
@ -35,7 +36,7 @@ interface Update {
* @param {String} obj.code - code
* @returns {IntegrationAuth} integrationAuth - integration auth after OAuth2 code-token exchange
*/
export const handleOAuthExchangeHelper = async ({
const handleOAuthExchangeHelper = async ({
workspaceId,
integration,
code,
@ -109,7 +110,7 @@ export const handleOAuthExchangeHelper = async ({
* @param {Object} obj
* @param {Object} obj.workspaceId - id of workspace
*/
export const syncIntegrationsHelper = async ({
const syncIntegrationsHelper = async ({
workspaceId,
environment
}: {
@ -161,7 +162,7 @@ export const syncIntegrationsHelper = async ({
* @param {String} obj.integrationAuthId - id of integration auth
* @param {String} refreshToken - decrypted refresh token
*/
export const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
const integrationAuth = await IntegrationAuth
.findById(integrationAuthId)
.select('+refreshCiphertext +refreshIV +refreshTag');
@ -186,7 +187,7 @@ export const getIntegrationAuthRefreshHelper = async ({ integrationAuthId }: { i
* @param {String} obj.integrationAuthId - id of integration auth
* @returns {String} accessToken - decrypted access token
*/
export const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: Types.ObjectId }) => {
let accessId;
let accessToken;
const integrationAuth = await IntegrationAuth
@ -239,7 +240,7 @@ export const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { in
* @param {String} obj.integrationAuthId - id of integration auth
* @param {String} obj.refreshToken - refresh token
*/
export const setIntegrationAuthRefreshHelper = async ({
const setIntegrationAuthRefreshHelper = async ({
integrationAuthId,
refreshToken
}: {
@ -281,7 +282,7 @@ export const setIntegrationAuthRefreshHelper = async ({
* @param {String} obj.accessToken - access token
* @param {Date} obj.accessExpiresAt - expiration date of access token
*/
export const setIntegrationAuthAccessHelper = async ({
const setIntegrationAuthAccessHelper = async ({
integrationAuthId,
accessId,
accessToken,
@ -326,4 +327,13 @@ export const setIntegrationAuthAccessHelper = async ({
});
return integrationAuth;
}
}
export {
handleOAuthExchangeHelper,
syncIntegrationsHelper,
getIntegrationAuthRefreshHelper,
getIntegrationAuthAccessHelper,
setIntegrationAuthRefreshHelper,
setIntegrationAuthAccessHelper
}

@ -17,7 +17,7 @@ interface Key {
* @param {String} obj.keys.nonce - nonce for encryption
* @param {String} obj.keys.userId - id of receiver user
*/
export const pushKeys = async ({
const pushKeys = async ({
userId,
workspaceId,
keys
@ -50,4 +50,6 @@ export const pushKeys = async ({
workspace: workspaceId
}))
);
};
};
export { pushKeys };

@ -10,10 +10,10 @@ import { MembershipNotFoundError, BadRequestError } from "../utils/errors";
* @param {String} obj.workspaceId - id of workspace
* @returns {Membership} membership - membership of user with id [userId] for workspace with id [workspaceId]
*/
export const validateMembership = async ({
userId,
workspaceId,
acceptedRoles,
const validateMembership = async ({
userId,
workspaceId,
acceptedRoles,
}: {
userId: Types.ObjectId | string;
workspaceId: Types.ObjectId | string;
@ -46,8 +46,8 @@ export const validateMembership = async ({
* @param {Object} queryObj - query object
* @return {Object} membership - membership
*/
export const findMembership = async (queryObj: any) => {
const membership = await Membership.findOne(queryObj);
const findMembership = async (queryObj: any) => {
const membership = await Membership.findOne(queryObj);
return membership;
};
@ -59,10 +59,10 @@ export const findMembership = async (queryObj: any) => {
* @param {String} obj.workspaceId - id of workspace.
* @param {String[]} obj.roles - roles of users.
*/
export const addMemberships = async ({
userIds,
workspaceId,
roles
const addMemberships = async ({
userIds,
workspaceId,
roles,
}: {
userIds: string[];
workspaceId: string;
@ -93,9 +93,9 @@ export const addMemberships = async ({
* @param {Object} obj
* @param {String} obj.membershipId - id of membership to delete
*/
export const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
const deletedMembership = await Membership.findOneAndDelete({
_id: membershipId
const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
const deletedMembership = await Membership.findOneAndDelete({
_id: membershipId,
});
// delete keys associated with the membership
@ -107,5 +107,7 @@ export const deleteMembership = async ({ membershipId }: { membershipId: string
});
}
return deletedMembership;
return deletedMembership;
};
export { validateMembership, addMemberships, findMembership, deleteMembership };

@ -18,7 +18,7 @@ import {
* @param {Types.ObjectId} obj.organizationId
* @param {String[]} obj.acceptedRoles
*/
export const validateMembershipOrg = async ({
const validateMembershipOrg = async ({
userId,
organizationId,
acceptedRoles,
@ -59,7 +59,7 @@ export const validateMembershipOrg = async ({
* @param {Object} queryObj - query object
* @return {Object} membershipOrg - membership
*/
export const findMembershipOrg = (queryObj: any) => {
const findMembershipOrg = (queryObj: any) => {
const membershipOrg = MembershipOrg.findOne(queryObj);
return membershipOrg;
};
@ -72,7 +72,7 @@ export const findMembershipOrg = (queryObj: any) => {
* @param {String} obj.organizationId - id of organization.
* @param {String[]} obj.roles - roles of users.
*/
export const addMembershipsOrg = async ({
const addMembershipsOrg = async ({
userIds,
organizationId,
roles,
@ -111,7 +111,7 @@ export const addMembershipsOrg = async ({
* @param {Object} obj
* @param {String} obj.membershipOrgId - id of organization membership to delete
*/
export const deleteMembershipOrg = async ({
const deleteMembershipOrg = async ({
membershipOrgId
}: {
membershipOrgId: string;
@ -148,4 +148,11 @@ export const deleteMembershipOrg = async ({
}
return deletedMembershipOrg;
};
};
export {
validateMembershipOrg,
findMembershipOrg,
addMembershipsOrg,
deleteMembershipOrg
};

@ -13,7 +13,7 @@ let smtpTransporter: nodemailer.Transporter;
* @param {String[]} obj.recipients - email addresses of people to send email to
* @param {Object} obj.substitutions - object containing template substitutions
*/
export const sendMail = async ({
const sendMail = async ({
template,
subjectLine,
recipients,
@ -41,6 +41,8 @@ export const sendMail = async ({
}
};
export const setTransporter = (transporter: nodemailer.Transporter) => {
const setTransporter = (transporter: nodemailer.Transporter) => {
smtpTransporter = transporter;
};
};
export { sendMail, setTransporter };

@ -28,7 +28,7 @@ import {
* @param {String} obj.email - POC email that will receive invoice info
* @param {Object} organization - new organization
*/
export const createOrganization = async ({
const createOrganization = async ({
name,
email,
}: {
@ -70,7 +70,7 @@ export const createOrganization = async ({
* @return {Object} obj.stripeSubscription - new stripe subscription
* @return {Subscription} obj.subscription - new subscription
*/
export const initSubscriptionOrg = async ({
const initSubscriptionOrg = async ({
organizationId,
}: {
organizationId: Types.ObjectId;
@ -125,7 +125,7 @@ export const initSubscriptionOrg = async ({
* @param {Object} obj
* @param {Number} obj.organizationId - id of subscription's organization
*/
export const updateSubscriptionOrgQuantity = async ({
const updateSubscriptionOrgQuantity = async ({
organizationId,
}: {
organizationId: string;
@ -153,24 +153,28 @@ export const updateSubscriptionOrgQuantity = async ({
EELicenseService.localFeatureSet.del(organizationId);
}
}
if (EELicenseService.instanceType === 'enterprise-self-hosted') {
// instance of Infisical is an enterprise self-hosted instance
const usedSeats = await MembershipOrg.countDocuments({
status: ACCEPTED
});
if (EELicenseService.instanceType === 'enterprise-self-hosted') {
// instance of Infisical is an enterprise self-hosted instance
const usedSeats = await MembershipOrg.countDocuments({
status: ACCEPTED
});
await licenseKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license/v1/license`,
{
usedSeats
}
);
await licenseKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license/v1/license`,
{
usedSeats
}
);
}
}
await EELicenseService.refreshPlan(organizationId);
return stripeSubscription;
};
};
export {
createOrganization,
initSubscriptionOrg,
updateSubscriptionOrgQuantity
};

@ -1,64 +1,70 @@
import rateLimit from 'express-rate-limit';
// const MongoStore = require('rate-limit-mongo');
const MongoStore = require('rate-limit-mongo');
// 200 per minute
export const apiLimiter = rateLimit({
// store: new MongoStore({
// uri: process.env.MONGO_URL,
// expireTimeMs: 1000 * 60,
// collectionName: "expressRateRecords-apiLimiter",
// errorHandler: console.error.bind(null, 'rate-limit-mongo')
// }),
windowMs: 60 * 1000,
max: 240,
const apiLimiter = rateLimit({
store: new MongoStore({
uri: process.env.MONGO_URL,
expireTimeMs: 1000 * 60,
collectionName: "expressRateRecords-apiLimiter",
errorHandler: console.error.bind(null, 'rate-limit-mongo')
}),
windowMs: 1000 * 60,
max: 200,
standardHeaders: true,
legacyHeaders: false,
skip: (request) => {
return request.path === '/healthcheck' || request.path === '/api/status'
},
keyGenerator: (req, res) => {
return req.realIP
return req.clientIp
}
});
// 50 requests per 1 hours
const authLimit = rateLimit({
// store: new MongoStore({
// uri: process.env.MONGO_URL,
// expireTimeMs: 1000 * 60 * 60,
// errorHandler: console.error.bind(null, 'rate-limit-mongo'),
// collectionName: "expressRateRecords-authLimit",
// }),
windowMs: 60 * 1000,
max: 10,
store: new MongoStore({
uri: process.env.MONGO_URL,
expireTimeMs: 1000 * 60 * 60,
errorHandler: console.error.bind(null, 'rate-limit-mongo'),
collectionName: "expressRateRecords-authLimit",
}),
windowMs: 1000 * 60 * 60,
max: 50,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req, res) => {
return req.realIP
return req.clientIp
}
});
// 5 requests per 1 hour
export const passwordLimiter = rateLimit({
// store: new MongoStore({
// uri: process.env.MONGO_URL,
// expireTimeMs: 1000 * 60 * 60,
// errorHandler: console.error.bind(null, 'rate-limit-mongo'),
// collectionName: "expressRateRecords-passwordLimiter",
// }),
windowMs: 60 * 60 * 1000,
max: 10,
// 50 requests per 1 hour
const passwordLimiter = rateLimit({
store: new MongoStore({
uri: process.env.MONGO_URL,
expireTimeMs: 1000 * 60 * 60,
errorHandler: console.error.bind(null, 'rate-limit-mongo'),
collectionName: "expressRateRecords-passwordLimiter",
}),
windowMs: 1000 * 60 * 60,
max: 50,
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req, res) => {
return req.realIP
return req.clientIp
}
});
export const authLimiter = (req: any, res: any, next: any) => {
const authLimiter = (req: any, res: any, next: any) => {
if (process.env.NODE_ENV === 'production') {
authLimit(req, res, next);
} else {
next();
}
};
};
export {
apiLimiter,
authLimiter,
passwordLimiter
};

@ -60,7 +60,7 @@ interface Update {
* @param {String} obj.environment - environment for secrets
* @param {Object[]} obj.secrets - secrets to push
*/
export const v1PushSecrets = async ({
const v1PushSecrets = async ({
userId,
workspaceId,
environment,
@ -304,7 +304,7 @@ export const v1PushSecrets = async ({
* @param {String} obj.channel - channel (web/cli/auto)
* @param {String} obj.ipAddress - ip address of request to push secrets
*/
export const v2PushSecrets = async ({
const v2PushSecrets = async ({
userId,
workspaceId,
environment,
@ -530,7 +530,7 @@ export const v2PushSecrets = async ({
* @param {String} obj.workspaceId - id of workspace to pull from
* @param {String} obj.environment - environment for secrets
*/
export const getSecrets = async ({
const getSecrets = async ({
userId,
workspaceId,
environment,
@ -570,7 +570,7 @@ export const getSecrets = async ({
* @param {String} obj.channel - channel (web/cli/auto)
* @param {String} obj.ipAddress - ip address of request to push secrets
*/
export const pullSecrets = async ({
const pullSecrets = async ({
userId,
workspaceId,
environment,
@ -614,7 +614,7 @@ export const pullSecrets = async ({
* @param {Object} obj
* @param {Object} obj.secrets
*/
export const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
const reformatedSecrets = secrets.map((s) => ({
_id: s._id,
workspace: s.workspace,
@ -644,4 +644,6 @@ export const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
}));
return reformatedSecrets;
};
};
export { v1PushSecrets, v2PushSecrets, pullSecrets, reformatPullSecrets };

@ -1,4 +1,4 @@
import { Types } from "mongoose";
import { Types } from 'mongoose';
import {
CreateSecretParams,
GetSecretsParams,
@ -6,20 +6,14 @@ import {
UpdateSecretParams,
DeleteSecretParams,
} from '../interfaces/services/SecretService';
import {
Secret,
ISecret,
SecretBlindIndexData,
ServiceTokenData,
} from "../models";
import { SecretVersion } from "../ee/models";
import { Secret, ISecret, SecretBlindIndexData } from '../models';
import { SecretVersion } from '../ee/models';
import {
BadRequestError,
SecretNotFoundError,
SecretBlindIndexDataNotFoundError,
InternalServerError,
UnauthorizedRequestError,
} from "../utils/errors";
} from '../utils/errors';
import {
SECRET_PERSONAL,
SECRET_SHARED,
@ -30,75 +24,20 @@ import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64,
} from "../variables";
import crypto from "crypto";
import * as argon2 from "argon2";
} from '../variables';
import crypto from 'crypto';
import * as argon2 from 'argon2';
import {
encryptSymmetric128BitHexKeyUTF8,
decryptSymmetric128BitHexKeyUTF8,
} from '../utils/crypto';
import { getEncryptionKey, client, getRootEncryptionKey } from '../config';
import { TelemetryService } from '../services';
import { getEncryptionKey, client, getRootEncryptionKey } from "../config";
import { EESecretService, EELogService } from "../ee/services";
import { EESecretService, EELogService } from '../ee/services';
import {
getAuthDataPayloadIdObj,
getAuthDataPayloadUserObj,
} from "../utils/auth";
import { getFolderIdFromServiceToken } from "../services/FolderService";
/**
* Returns an object containing secret [secret] but with its value, key, comment decrypted.
*
* Precondition: the workspace for secret [secret] must have E2EE disabled
* @param {ISecret} secret - secret to repackage to raw
* @param {String} key - symmetric key to use to decrypt secret
* @returns
*/
export const repackageSecretToRaw = ({
secret,
key
}: {
secret: ISecret;
key: string;
}) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
key
});
const secretValue = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
key
});
let secretComment: string = '';
if (secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
secretComment = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secret.secretCommentCiphertext,
iv: secret.secretCommentIV,
tag: secret.secretCommentTag,
key
});
}
return ({
_id: secret._id,
version: secret.version,
workspace: secret.workspace,
type: secret.type,
environment: secret.environment,
user: secret.user,
secretKey,
secretValue,
secretComment
});
}
} from '../utils/auth';
/**
* Create secret blind index data containing encrypted blind index [salt]
@ -106,13 +45,13 @@ export const repackageSecretToRaw = ({
* @param {Object} obj
* @param {Types.ObjectId} obj.workspaceId
*/
export const createSecretBlindIndexDataHelper = async ({
const createSecretBlindIndexDataHelper = async ({
workspaceId,
}: {
workspaceId: Types.ObjectId;
}) => {
// initialize random blind index salt for workspace
const salt = crypto.randomBytes(16).toString("base64");
const salt = crypto.randomBytes(16).toString('base64');
const encryptionKey = await getEncryptionKey();
const rootEncryptionKey = await getRootEncryptionKey();
@ -159,7 +98,7 @@ export const createSecretBlindIndexDataHelper = async ({
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get salt for
* @returns
*/
export const getSecretBlindIndexSaltHelper = async ({
const getSecretBlindIndexSaltHelper = async ({
workspaceId,
}: {
workspaceId: Types.ObjectId;
@ -169,7 +108,7 @@ export const getSecretBlindIndexSaltHelper = async ({
const secretBlindIndexData = await SecretBlindIndexData.findOne({
workspace: workspaceId,
}).select("+algorithm +keyEncoding");
}).select('+algorithm +keyEncoding');
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
@ -197,7 +136,7 @@ export const getSecretBlindIndexSaltHelper = async ({
}
throw InternalServerError({
message: "Failed to obtain workspace salt needed for secret blind indexing",
message: 'Failed to obtain workspace salt needed for secret blind indexing',
});
};
@ -208,7 +147,7 @@ export const getSecretBlindIndexSaltHelper = async ({
* @param {String} obj.secretName - name of secret to generate blind index for
* @param {String} obj.salt - base64-salt
*/
export const generateSecretBlindIndexWithSaltHelper = async ({
const generateSecretBlindIndexWithSaltHelper = async ({
secretName,
salt,
}: {
@ -219,14 +158,14 @@ export const generateSecretBlindIndexWithSaltHelper = async ({
const secretBlindIndex = (
await argon2.hash(secretName, {
type: argon2.argon2id,
salt: Buffer.from(salt, "base64"),
salt: Buffer.from(salt, 'base64'),
saltLength: 16, // default 16 bytes
memoryCost: 65536, // default pool of 64 MiB per thread.
hashLength: 32,
parallelism: 1,
raw: true,
})
).toString("base64");
).toString('base64');
return secretBlindIndex;
};
@ -238,7 +177,7 @@ export const generateSecretBlindIndexWithSaltHelper = async ({
* @param {Stringj} obj.secretName - name of secret to generate blind index for
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
*/
export const generateSecretBlindIndexHelper = async ({
const generateSecretBlindIndexHelper = async ({
secretName,
workspaceId,
}: {
@ -251,7 +190,7 @@ export const generateSecretBlindIndexHelper = async ({
const secretBlindIndexData = await SecretBlindIndexData.findOne({
workspace: workspaceId,
}).select("+algorithm +keyEncoding");
}).select('+algorithm +keyEncoding');
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
@ -292,9 +231,9 @@ export const generateSecretBlindIndexHelper = async ({
return secretBlindIndex;
}
throw InternalServerError({
message: "Failed to generate secret blind index",
message: 'Failed to generate secret blind index'
});
};
@ -308,7 +247,7 @@ export const generateSecretBlindIndexHelper = async ({
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
export const createSecretHelper = async ({
const createSecretHelper = async ({
secretName,
workspaceId,
environment,
@ -323,38 +262,23 @@ export const createSecretHelper = async ({
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
secretPath = "/",
folderId,
}: CreateSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
});
// if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
if (secretPath !== serviceTkScopedSecretPath) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
const folderId = await getFolderIdFromServiceToken(
workspaceId,
environment,
secretPath
);
const exists = await Secret.exists({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
folder: folderId,
type,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
});
if (exists)
throw BadRequestError({
message: "Failed to create secret that already exists",
message: 'Failed to create secret that already exists',
});
if (type === SECRET_PERSONAL) {
@ -363,7 +287,6 @@ export const createSecretHelper = async ({
const exists = await Secret.exists({
secretBlindIndex,
folder: folderId,
workspace: new Types.ObjectId(workspaceId),
type: SECRET_SHARED,
});
@ -371,7 +294,7 @@ export const createSecretHelper = async ({
if (!exists)
throw BadRequestError({
message:
"Failed to create personal secret override for no corresponding shared secret",
'Failed to create personal secret override for no corresponding shared secret',
});
}
@ -402,7 +325,6 @@ export const createSecretHelper = async ({
version: secret.version,
workspace: secret.workspace,
type,
folder: folderId,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
environment: secret.environment,
isDeleted: false,
@ -450,7 +372,7 @@ export const createSecretHelper = async ({
if (postHogClient) {
postHogClient.capture({
event: "secrets added",
event: 'secrets added',
distinctId: await TelemetryService.getDistinctId({
authData,
}),
@ -458,7 +380,6 @@ export const createSecretHelper = async ({
numberOfSecrets: 1,
environment,
workspaceId,
folderId,
channel: authData.authChannel,
userAgent: authData.authUserAgent,
},
@ -476,46 +397,31 @@ export const createSecretHelper = async ({
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
export const getSecretsHelper = async ({
const getSecretsHelper = async ({
workspaceId,
environment,
authData,
secretPath = "/",
}: GetSecretsParams) => {
let secrets: ISecret[] = [];
// if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
if (secretPath !== serviceTkScopedSecretPath) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
const folderId = await getFolderIdFromServiceToken(
workspaceId,
environment,
secretPath
);
// get personal secrets first
secrets = await Secret.find({
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: SECRET_PERSONAL,
...getAuthDataPayloadUserObj(authData),
}).populate("tags").lean();
});
// concat with shared secrets
secrets = secrets.concat(
await Secret.find({
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: SECRET_SHARED,
secretBlindIndex: {
$nin: secrets.map((secret) => secret.secretBlindIndex),
},
}).populate("tags").lean()
})
);
// (EE) create (audit) log
@ -539,7 +445,7 @@ export const getSecretsHelper = async ({
if (postHogClient) {
postHogClient.capture({
event: "secrets pulled",
event: 'secrets pulled',
distinctId: await TelemetryService.getDistinctId({
authData,
}),
@ -547,7 +453,6 @@ export const getSecretsHelper = async ({
numberOfSecrets: secrets.length,
environment,
workspaceId,
folderId,
channel: authData.authChannel,
userAgent: authData.authUserAgent,
},
@ -567,41 +472,27 @@ export const getSecretsHelper = async ({
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
export const getSecretHelper = async ({
const getSecretHelper = async ({
secretName,
workspaceId,
environment,
type,
authData,
secretPath = "/",
}: GetSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
});
let secret: ISecret | null = null;
// if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
if (secretPath !== serviceTkScopedSecretPath) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
const folderId = await getFolderIdFromServiceToken(
workspaceId,
environment,
secretPath
);
// try getting personal secret first (if exists)
secret = await Secret.findOne({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: type ?? SECRET_PERSONAL,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
}).lean();
});
if (!secret) {
// case: failed to find personal secret matching criteria
@ -610,9 +501,8 @@ export const getSecretHelper = async ({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type: SECRET_SHARED,
}).lean();
});
}
if (!secret) throw SecretNotFoundError();
@ -638,7 +528,7 @@ export const getSecretHelper = async ({
if (postHogClient) {
postHogClient.capture({
event: "secrets pull",
event: 'secrets pull',
distinctId: await TelemetryService.getDistinctId({
authData,
}),
@ -646,7 +536,6 @@ export const getSecretHelper = async ({
numberOfSecrets: 1,
environment,
workspaceId,
folderId,
channel: authData.authChannel,
userAgent: authData.authUserAgent,
},
@ -669,8 +558,7 @@ export const getSecretHelper = async ({
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
export const updateSecretHelper = async ({
const updateSecretHelper = async ({
secretName,
workspaceId,
environment,
@ -679,7 +567,6 @@ export const updateSecretHelper = async ({
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretPath,
}: UpdateSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
@ -687,18 +574,6 @@ export const updateSecretHelper = async ({
});
let secret: ISecret | null = null;
// if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
if (secretPath !== serviceTkScopedSecretPath) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
const folderId = await getFolderIdFromServiceToken(
workspaceId,
environment,
secretPath
);
if (type === SECRET_SHARED) {
// case: update shared secret
@ -707,7 +582,6 @@ export const updateSecretHelper = async ({
secretBlindIndex,
workspace: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
type,
},
{
@ -729,7 +603,6 @@ export const updateSecretHelper = async ({
workspace: new Types.ObjectId(workspaceId),
environment,
type,
folder: folderId,
...getAuthDataPayloadUserObj(authData),
},
{
@ -750,7 +623,6 @@ export const updateSecretHelper = async ({
secret: secret._id,
version: secret.version,
workspace: secret.workspace,
folder: folderId,
type,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
environment: secret.environment,
@ -799,7 +671,7 @@ export const updateSecretHelper = async ({
if (postHogClient) {
postHogClient.capture({
event: "secrets modified",
event: 'secrets modified',
distinctId: await TelemetryService.getDistinctId({
authData,
}),
@ -807,7 +679,6 @@ export const updateSecretHelper = async ({
numberOfSecrets: 1,
environment,
workspaceId,
folderId,
channel: authData.authChannel,
userAgent: authData.authUserAgent,
},
@ -827,33 +698,18 @@ export const updateSecretHelper = async ({
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
export const deleteSecretHelper = async ({
const deleteSecretHelper = async ({
secretName,
workspaceId,
environment,
type,
authData,
secretPath = "/",
}: DeleteSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
});
// if using service token filter towards the folderId by secretpath
if (authData.authPayload instanceof ServiceTokenData) {
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
if (secretPath !== serviceTkScopedSecretPath) {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
}
}
const folderId = await getFolderIdFromServiceToken(
workspaceId,
environment,
secretPath
);
let secrets: ISecret[] = [];
let secret: ISecret | null = null;
@ -862,32 +718,28 @@ export const deleteSecretHelper = async ({
secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
}).lean();
});
secret = await Secret.findOneAndDelete({
secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
folder: folderId,
}).lean();
});
await Secret.deleteMany({
secretBlindIndex,
workspaceId: new Types.ObjectId(workspaceId),
environment,
folder: folderId,
});
} else {
secret = await Secret.findOneAndDelete({
secretBlindIndex,
folder: folderId,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
...getAuthDataPayloadUserObj(authData),
}).lean();
});
if (secret) {
secrets = [secret];
@ -908,13 +760,15 @@ export const deleteSecretHelper = async ({
secretIds: secrets.map((secret) => secret._id),
});
action && (await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.authChannel,
ipAddress: authData.authIP,
}));
// (EE) take a secret snapshot
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.authChannel,
ipAddress: authData.authIP,
}));
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
@ -927,7 +781,7 @@ export const deleteSecretHelper = async ({
if (postHogClient) {
postHogClient.capture({
event: "secrets deleted",
event: 'secrets deleted',
distinctId: await TelemetryService.getDistinctId({
authData,
}),
@ -935,15 +789,26 @@ export const deleteSecretHelper = async ({
numberOfSecrets: secrets.length,
environment,
workspaceId,
folderId,
channel: authData.authChannel,
userAgent: authData.authUserAgent,
},
});
}
return ({
return {
secrets,
secret
});
secret,
};
};
export {
createSecretBlindIndexDataHelper,
getSecretBlindIndexSaltHelper,
generateSecretBlindIndexWithSaltHelper,
generateSecretBlindIndexHelper,
createSecretHelper,
getSecretsHelper,
getSecretHelper,
updateSecretHelper,
deleteSecretHelper,
};

@ -13,11 +13,11 @@ import { TOKEN_EMAIL_CONFIRMATION } from '../variables';
* @param {String} obj.email - email
* @returns {Boolean} success - whether or not operation was successful
*/
export const sendEmailVerification = async ({ email }: { email: string }) => {
const token = await TokenService.createToken({
type: TOKEN_EMAIL_CONFIRMATION,
email
});
const sendEmailVerification = async ({ email }: { email: string }) => {
const token = await TokenService.createToken({
type: TOKEN_EMAIL_CONFIRMATION,
email
});
// send mail
await sendMail({
@ -36,7 +36,7 @@ export const sendEmailVerification = async ({ email }: { email: string }) => {
* @param {String} obj.email - emai
* @param {String} obj.code - code that was sent to [email]
*/
export const checkEmailVerification = async ({
const checkEmailVerification = async ({
email,
code
}: {
@ -57,7 +57,7 @@ export const checkEmailVerification = async ({
* @param {String} obj.organizationName - name of organization to initialize
* @param {IUser} obj.user - user who we are initializing for
*/
export const initializeDefaultOrg = async ({
const initializeDefaultOrg = async ({
organizationName,
user
}: {
@ -81,4 +81,6 @@ export const initializeDefaultOrg = async ({
} catch (err) {
throw new Error(`Failed to initialize default organization and workspace [err=${err}]`);
}
};
};
export { sendEmailVerification, checkEmailVerification, initializeDefaultOrg };

@ -20,7 +20,7 @@ import { getSaltRounds } from "../config";
* @param {Types.ObjectId} obj.organizationId
* @returns {String} token - the created token
*/
export const createTokenHelper = async ({
const createTokenHelper = async ({
type,
email,
phoneNumber,
@ -121,7 +121,7 @@ export const createTokenHelper = async ({
* @param {String} obj.email - email associated with the token
* @param {String} obj.token - value of the token
*/
export const validateTokenHelper = async ({
const validateTokenHelper = async ({
type,
email,
phoneNumber,
@ -212,4 +212,6 @@ export const validateTokenHelper = async ({
// case: token is valid
await TokenData.findByIdAndDelete(tokenData._id);
};
};
export { createTokenHelper, validateTokenHelper };

@ -6,7 +6,6 @@ import {
Secret
} from '../models';
import { createBot } from '../helpers/bot';
import { EELicenseService } from '../ee/services';
import { SecretService } from '../services';
/**
@ -16,32 +15,31 @@ import { SecretService } from '../services';
* @param {String} organizationId - id of organization to create workspace in
* @param {Object} workspace - new workspace
*/
export const createWorkspace = async ({
const createWorkspace = async ({
name,
organizationId
}: {
name: string;
organizationId: string;
}) => {
// create workspace
const workspace = await new Workspace({
name,
organization: organizationId,
autoCapitalization: true
}).save();
// create workspace
const workspace = await new Workspace({
name,
organization: organizationId,
autoCapitalization: true
}).save();
// initialize bot for workspace
await createBot({
name: 'Infisical Bot',
workspaceId: workspace._id
});
// initialize bot for workspace
await createBot({
name: 'Infisical Bot',
workspaceId: workspace._id
});
// initialize blind index salt for workspace
await SecretService.createSecretBlindIndexData({
workspaceId: workspace._id
});
// initialize blind index salt for workspace
await SecretService.createSecretBlindIndexData({
workspaceId: workspace._id
});
await EELicenseService.refreshPlan(organizationId);
return workspace;
};
@ -52,18 +50,23 @@ export const createWorkspace = async ({
* @param {Object} obj
* @param {String} obj.id - id of workspace to delete
*/
export const deleteWorkspace = async ({ id }: { id: string }) => {
await Workspace.deleteOne({ _id: id });
await Bot.deleteOne({
workspace: id
});
await Membership.deleteMany({
workspace: id
});
await Secret.deleteMany({
workspace: id
});
await Key.deleteMany({
workspace: id
});
const deleteWorkspace = async ({ id }: { id: string }) => {
await Workspace.deleteOne({ _id: id });
await Bot.deleteOne({
workspace: id
});
await Membership.deleteMany({
workspace: id
});
await Secret.deleteMany({
workspace: id
});
await Key.deleteMany({
workspace: id
});
};
export {
createWorkspace,
deleteWorkspace
};

@ -13,6 +13,7 @@ import swaggerUi = require("swagger-ui-express");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const swaggerFile = require("../spec.json");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const requestIp = require("request-ip");
import { apiLimiter } from "./helpers/rateLimiter";
import {
workspace as eeWorkspaceRouter,
@ -85,6 +86,8 @@ const main = async () => {
})
);
app.use(requestIp.mw());
if ((await getNodeEnv()) === "production") {
// enable app-wide rate-limiting + helmet security
// in production
@ -93,13 +96,6 @@ const main = async () => {
app.use(helmet());
}
app.use((req, res, next) => {
// default to IP address provided by Cloudflare
const cfIp = req.headers['cf-connecting-ip'];
req.realIP = Array.isArray(cfIp) ? cfIp[0] : (cfIp as string) || req.ip;
next();
});
// (EE) routes
app.use("/api/v1/secret", eeSecretRouter);
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);

@ -16,7 +16,6 @@ import {
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_GITLAB_API_URL,
INTEGRATION_VERCEL_API_URL,
@ -27,7 +26,6 @@ import {
INTEGRATION_CIRCLECI_API_URL,
INTEGRATION_TRAVISCI_API_URL,
INTEGRATION_SUPABASE_API_URL,
INTEGRATION_CHECKLY_API_URL
} from "../variables";
interface App {
@ -122,11 +120,6 @@ const getApps = async ({
accessToken,
});
break;
case INTEGRATION_CHECKLY:
apps = await getAppsCheckly({
accessToken,
});
break;
}
return apps;
@ -608,32 +601,4 @@ const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => {
return apps;
};
/**
* Return list of projects for the Checkly integration
* @param {Object} obj
* @param {String} obj.accessToken - api key for the Checkly API
* @returns {Object[]} apps - Сheckly accounts
* @returns {String} apps.name - name of Checkly account
*/
const getAppsCheckly = async ({ accessToken }: { accessToken: string }) => {
const { data } = await standardRequest.get(
`${INTEGRATION_CHECKLY_API_URL}/v1/accounts`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept": "application/json",
},
}
);
const apps = data.map((a: any) => {
return {
name: a.name,
appId: a.id,
};
});
return apps;
};
export { getApps };

@ -34,10 +34,7 @@ import {
INTEGRATION_FLYIO_API_URL,
INTEGRATION_CIRCLECI_API_URL,
INTEGRATION_TRAVISCI_API_URL,
INTEGRATION_SUPABASE_API_URL,
INTEGRATION_CHECKLY,
INTEGRATION_CHECKLY_API_URL,
INTEGRATION_HASHICORP_VAULT
INTEGRATION_SUPABASE_API_URL
} from "../variables";
import { standardRequest} from '../config/request';
@ -164,53 +161,9 @@ const syncSecrets = async ({
integration,
secrets,
accessToken
});
break;
case INTEGRATION_FLYIO:
await syncSecretsFlyio({
integration,
secrets,
accessToken,
});
break;
case INTEGRATION_CIRCLECI:
await syncSecretsCircleCI({
integration,
secrets,
accessToken,
});
break;
case INTEGRATION_TRAVISCI:
await syncSecretsTravisCI({
integration,
secrets,
accessToken,
});
break;
case INTEGRATION_SUPABASE:
await syncSecretsSupabase({
integration,
secrets,
accessToken
});
break;
case INTEGRATION_CHECKLY:
await syncSecretsCheckly({
integration,
secrets,
accessToken,
});
break;
case INTEGRATION_HASHICORP_VAULT:
await syncSecretsHashiCorpVault({
integration,
integrationAuth,
secrets,
accessId,
accessToken
});
break;
}
});
break;
}
};
/**
@ -1676,161 +1629,4 @@ const syncSecretsSupabase = async ({
};
/**
* Sync/push [secrets] to Checkly app
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for Checkly integration
*/
const syncSecretsCheckly = async ({
integration,
secrets,
accessToken,
}: {
integration: IIntegration;
secrets: any;
accessToken: string;
}) => {
// get secrets from travis-ci
const getSecretsRes = (
await standardRequest.get(
`${INTEGRATION_CHECKLY_API_URL}/v1/variables`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
"X-Checkly-Account": integration.appId
},
}
)
)
.data
.reduce((obj: any, secret: any) => ({
...obj,
[secret.key]: secret.value
}), {});
// add secrets
for await (const key of Object.keys(secrets)) {
if (!(key in getSecretsRes)) {
// case: secret does not exist in checkly
// -> add secret
await standardRequest.post(
`${INTEGRATION_CHECKLY_API_URL}/v1/variables`,
{
key,
value: secrets[key]
},
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept": "application/json",
"Content-Type": "application/json",
"X-Checkly-Account": integration.appId
},
}
);
} else {
// case: secret exists in checkly
// -> update/set secret
if (secrets[key] !== getSecretsRes[key]) {
await standardRequest.put(
`${INTEGRATION_CHECKLY_API_URL}/v1/variables/${key}`,
{
value: secrets[key]
},
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Accept": "application/json",
"X-Checkly-Account": integration.appId
},
}
);
}
}
}
for await (const key of Object.keys(getSecretsRes)) {
if (!(key in secrets)){
// delete secret
await standardRequest.delete(
`${INTEGRATION_CHECKLY_API_URL}/v1/variables/${key}`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept": "application/json",
"X-Checkly-Account": integration.appId
},
}
);
}
}
};
/**
* Sync/push [secrets] to HashiCorp Vault path
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for HashiCorp Vault integration
*/
const syncSecretsHashiCorpVault = async ({
integration,
integrationAuth,
secrets,
accessId,
accessToken,
}: {
integration: IIntegration;
integrationAuth: IIntegrationAuth;
secrets: any;
accessId: string | null;
accessToken: string;
}) => {
if (!accessId) return;
interface LoginAppRoleRes {
auth: {
client_token: string;
}
}
// get Vault client token (could be optimized)
const { data }: { data: LoginAppRoleRes } = await standardRequest.post(
`${integrationAuth.url}/v1/auth/approle/login`,
{
"role_id": accessId,
"secret_id": accessToken
},
{
headers: {
"X-Vault-Namespace": integrationAuth.namespace
}
}
);
const clientToken = data.auth.client_token;
await standardRequest.post(
`${integrationAuth.url}/v1/${integration.app}/data/${integration.path}`,
{
data: secrets
},
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept": "application/json",
"Content-Type": "application/json",
"X-Vault-Token": clientToken,
"X-Vault-Namespace": integrationAuth.namespace
},
}
);
};
export { syncSecrets };

@ -1,4 +1,3 @@
import { Types } from 'mongoose';
import {
IUser,
IServiceAccount,
@ -11,5 +10,4 @@ export interface AuthData {
authChannel: string;
authIP: string;
authUserAgent: string;
tokenVersionId?: Types.ObjectId;
}

@ -5,6 +5,7 @@ export interface CreateSecretParams {
secretName: string;
workspaceId: Types.ObjectId;
environment: string;
folderId?: string;
type: "shared" | "personal";
authData: AuthData;
secretKeyCiphertext: string;
@ -16,20 +17,17 @@ export interface CreateSecretParams {
secretCommentCiphertext?: string;
secretCommentIV?: string;
secretCommentTag?: string;
secretPath: string;
}
export interface GetSecretsParams {
workspaceId: Types.ObjectId;
environment: string;
secretPath: string;
authData: AuthData;
}
export interface GetSecretParams {
secretName: string;
workspaceId: Types.ObjectId;
secretPath: string;
environment: string;
type?: "shared" | "personal";
authData: AuthData;
@ -44,7 +42,7 @@ export interface UpdateSecretParams {
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
secretPath: string;
folderId?: string;
}
export interface DeleteSecretParams {
@ -53,5 +51,4 @@ export interface DeleteSecretParams {
environment: string;
type: "shared" | "personal";
authData: AuthData;
secretPath: string;
}

@ -3,7 +3,6 @@ import { ErrorRequestHandler } from 'express';
import { InternalServerError } from '../utils/errors';
import { getLogger } from '../utils/logger';
import RequestError, { LogLevel } from '../utils/requestError';
import { getNodeEnv } from '../config';
export const requestErrorHandler: ErrorRequestHandler = async (
error: RequestError | Error,
@ -13,11 +12,6 @@ export const requestErrorHandler: ErrorRequestHandler = async (
) => {
if (res.headersSent) return next();
if (await getNodeEnv() !== "production") {
/* eslint-disable no-console */
console.error(error);
}
//TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError
if (!(error instanceof RequestError)) {
error = InternalServerError({

@ -71,12 +71,10 @@ const requireAuth = ({
req.user = authPayload;
break;
default:
const { user, tokenVersionId } = await getAuthUserPayload({
authPayload = await getAuthUserPayload({
authTokenValue
});
authPayload = user;
req.user = user;
req.tokenVersionId = tokenVersionId;
req.user = authPayload;
break;
}
@ -90,9 +88,8 @@ const requireAuth = ({
authMode,
authPayload, // User, ServiceAccount, ServiceTokenData
authChannel: getChannelFromUserAgent(req.headers['user-agent']),
authIP: req.realIP,
authUserAgent: req.headers['user-agent'] ?? 'other',
tokenVersionId: req.tokenVersionId
authIP: req.ip,
authUserAgent: req.headers['user-agent'] ?? 'other'
}
return next();

@ -16,15 +16,13 @@ const requireWorkspaceAuth = ({
locationWorkspaceId,
locationEnvironment = undefined,
requiredPermissions = [],
requireBlindIndicesEnabled = false,
requireE2EEOff = false
requireBlindIndicesEnabled = false
}: {
acceptedRoles: Array<'admin' | 'member'>;
locationWorkspaceId: req;
locationEnvironment?: req | undefined;
requiredPermissions?: string[];
requireBlindIndicesEnabled?: boolean;
requireE2EEOff?: boolean;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const workspaceId = req[locationWorkspaceId]?.workspaceId;
@ -37,8 +35,7 @@ const requireWorkspaceAuth = ({
environment,
acceptedRoles,
requiredPermissions,
requireBlindIndicesEnabled,
requireE2EEOff
requireBlindIndicesEnabled
});
if (membership) {

@ -22,7 +22,6 @@ import Workspace, { IWorkspace } from './workspace';
import ServiceTokenData, { IServiceTokenData } from './serviceTokenData';
import APIKeyData, { IAPIKeyData } from './apiKeyData';
import LoginSRPDetail, { ILoginSRPDetail } from './loginSRPDetail';
import TokenVersion, { ITokenVersion } from './tokenVersion';
export {
AuthProvider,
@ -73,7 +72,5 @@ export {
APIKeyData,
IAPIKeyData,
LoginSRPDetail,
ILoginSRPDetail,
TokenVersion,
ITokenVersion
ILoginSRPDetail
};

@ -13,9 +13,7 @@ import {
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY,
INTEGRATION_HASHICORP_VAULT
INTEGRATION_SUPABASE
} from "../variables";
export interface IIntegration {
@ -47,9 +45,7 @@ export interface IIntegration {
| 'flyio'
| 'circleci'
| 'travisci'
| 'supabase'
| 'checkly'
| 'hashicorp-vault';
| 'supabase';
integrationAuth: Types.ObjectId;
}
@ -134,9 +130,7 @@ const integrationSchema = new Schema<IIntegration>(
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY,
INTEGRATION_HASHICORP_VAULT
INTEGRATION_SUPABASE
],
required: true,
},

@ -14,7 +14,6 @@ import {
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_HASHICORP_VAULT,
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64
@ -23,11 +22,9 @@ import {
export interface IIntegrationAuth extends Document {
_id: Types.ObjectId;
workspace: Types.ObjectId;
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager' | 'checkly';
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager';
teamId: string;
accountId: string;
url: string;
namespace: string;
refreshCiphertext?: string;
refreshIV?: string;
refreshTag?: string;
@ -65,8 +62,7 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_HASHICORP_VAULT
INTEGRATION_SUPABASE
],
required: true,
},
@ -74,14 +70,6 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
// vercel-specific integration param
type: String,
},
url: {
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
type: String
},
namespace: {
// hashicorp-vault-specific integration param
type: String
},
accountId: {
// netlify-specific integration param
type: String,

@ -1,88 +1,79 @@
import { Schema, model, Types, Document } from "mongoose";
import { Schema, model, Types, Document } from 'mongoose';
export interface IServiceTokenData extends Document {
_id: Types.ObjectId;
name: string;
workspace: Types.ObjectId;
environment: string;
user: Types.ObjectId;
serviceAccount: Types.ObjectId;
lastUsed: Date;
expiresAt: Date;
secretHash: string;
encryptedKey: string;
iv: string;
tag: string;
secretPath: string;
permissions: string[];
_id: Types.ObjectId;
name: string;
workspace: Types.ObjectId;
environment: string;
user: Types.ObjectId;
serviceAccount: Types.ObjectId;
lastUsed: Date;
expiresAt: Date;
secretHash: string;
encryptedKey: string;
iv: string;
tag: string;
permissions: string[];
}
const serviceTokenDataSchema = new Schema<IServiceTokenData>(
{
name: {
type: String,
required: true,
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
environment: {
type: String,
required: true,
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount",
},
lastUsed: {
type: Date,
},
expiresAt: {
type: Date,
},
secretHash: {
type: String,
required: true,
select: false,
},
encryptedKey: {
type: String,
select: false,
},
iv: {
type: String,
select: false,
},
tag: {
type: String,
select: false,
},
permissions: {
type: [String],
enum: ["read", "write"],
default: ["read"],
},
secretPath: {
type: String,
default: "/",
required: true,
},
},
{
timestamps: true,
}
);
const ServiceTokenData = model<IServiceTokenData>(
"ServiceTokenData",
serviceTokenDataSchema
{
name: {
type: String,
required: true
},
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true
},
environment: {
type: String,
required: true
},
user: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: 'ServiceAccount'
},
lastUsed: {
type: Date
},
expiresAt: {
type: Date
},
secretHash: {
type: String,
required: true,
select: false
},
encryptedKey: {
type: String,
select: false
},
iv: {
type: String,
select: false
},
tag: {
type: String,
select: false
},
permissions: {
type: [String],
enum: ['read', 'write'],
default: ['read']
}
},
{
timestamps: true
}
);
const ServiceTokenData = model<IServiceTokenData>('ServiceTokenData', serviceTokenDataSchema);
export default ServiceTokenData;

@ -1,47 +0,0 @@
import { Schema, model, Types, Document } from 'mongoose';
export interface ITokenVersion extends Document {
user: Types.ObjectId;
ip: string;
userAgent: string;
refreshVersion: number;
accessVersion: number;
lastUsed: Date;
}
const tokenVersionSchema = new Schema<ITokenVersion>(
{
user: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
ip: {
type: String,
required: true
},
userAgent: {
type: String,
required: true
},
refreshVersion: {
type: Number,
required: true
},
accessVersion: {
type: Number,
required: true
},
lastUsed: {
type: Date,
required: true
}
},
{
timestamps: true
}
);
const TokenVersion = model<ITokenVersion>('TokenVersion', tokenVersionSchema);
export default TokenVersion;

@ -21,6 +21,7 @@ export interface IUser extends Document {
tag?: string;
salt?: string;
verifier?: string;
refreshVersion?: number;
isMfaEnabled: boolean;
mfaMethods: boolean;
devices: {
@ -90,6 +91,11 @@ const userSchema = new Schema<IUser>(
type: String,
select: false
},
refreshVersion: {
type: Number,
default: 0,
select: false
},
isMfaEnabled: {
type: Boolean,
default: false
@ -102,8 +108,7 @@ const userSchema = new Schema<IUser>(
ip: String,
userAgent: String
}],
default: [],
select: false
default: []
}
},
{

@ -37,6 +37,10 @@ const workspaceSchema = new Schema<IWorkspace>({
name: "Development",
slug: "dev"
},
{
name: "Test",
slug: "test"
},
{
name: "Staging",
slug: "staging"

@ -44,6 +44,8 @@ router.post(
authController.checkAuth
);
router.get(
'/redirect/google',
authLimiter,
@ -51,27 +53,12 @@ router.get(
scope: ['profile', 'email'],
session: false,
}),
);
)
router.get(
'/callback/google',
passport.authenticate('google', { failureRedirect: '/login/provider/error', session: false }),
authController.handleAuthProviderCallback,
);
router.get(
'/common-passwords',
authLimiter,
authController.getCommonPasswords
);
router.delete(
'/sessions',
authLimiter,
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT]
}),
authController.revokeAllSessions
);
)
export default router;

@ -15,7 +15,7 @@ import {
import { body, param } from 'express-validator';
import { integrationController } from '../../controllers/v1';
router.post(
router.post( // new: add new integration for integration auth
'/',
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY]

@ -57,8 +57,6 @@ router.post(
body('workspaceId').exists().trim().notEmpty(),
body('accessId').trim(),
body('accessToken').exists().trim().notEmpty(),
body('url').trim(),
body('namespace').trim(),
body('integration').exists().trim().notEmpty(),
validateRequest,
requireAuth({

@ -26,7 +26,7 @@ router.post(
router.post(
'/mfa/send',
authLimiter,
body('email').isString().trim().notEmpty().isEmail(),
body('email').isString().trim().notEmpty(),
validateRequest,
authController.sendMfaToken
);

@ -1,15 +1,15 @@
import express from "express";
import express from 'express';
const router = express.Router();
import { Types } from "mongoose";
import { Types } from 'mongoose';
import {
requireAuth,
requireWorkspaceAuth,
requireSecretsAuth,
validateRequest,
} from "../../middleware";
import { validateClientForSecrets } from "../../validation";
import { query, body } from "express-validator";
import { secretsController } from "../../controllers/v2";
} from '../../middleware';
import { validateClientForSecrets } from '../../validation';
import { query, body } from 'express-validator';
import { secretsController } from '../../controllers/v2';
import {
ADMIN,
MEMBER,
@ -21,11 +21,11 @@ import {
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY,
} from "../../variables";
import { BatchSecretRequest } from "../../types/secret";
} from '../../variables';
import { BatchSecretRequest } from '../../types/secret';
router.post(
"/batch",
'/batch',
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
@ -35,13 +35,12 @@ router.post(
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationWorkspaceId: 'body',
}),
body("workspaceId").exists().isString().trim(),
body("folderId").default("root").isString().trim(),
body("environment").exists().isString().trim(),
body("secretPath").optional().isString().trim(),
body("requests")
body('workspaceId').exists().isString().trim(),
body('folderId').default('root').isString().trim(),
body('environment').exists().isString().trim(),
body('requests')
.exists()
.custom(async (requests: BatchSecretRequest[], { req }) => {
if (Array.isArray(requests)) {
@ -66,18 +65,17 @@ router.post(
);
router.post(
"/",
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("folderId").default("root").isString().trim(),
body("secretPath").optional().isString().trim(),
body("secrets")
'/',
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(),
body('folderId').default('root').isString().trim(),
body('secrets')
.exists()
.custom((value) => {
if (Array.isArray(value)) {
// case: create multiple secrets
if (value.length === 0)
throw new Error("secrets cannot be an empty array");
throw new Error('secrets cannot be an empty array');
for (const secret of value) {
if (
!secret.type ||
@ -87,16 +85,16 @@ router.post(
!secret.secretKeyCiphertext ||
!secret.secretKeyIV ||
!secret.secretKeyTag ||
typeof secret.secretValueCiphertext !== "string" ||
typeof secret.secretValueCiphertext !== 'string' ||
!secret.secretValueIV ||
!secret.secretValueTag
) {
throw new Error(
"secrets array must contain objects that have required secret properties"
'secrets array must contain objects that have required secret properties'
);
}
}
} else if (typeof value === "object") {
} else if (typeof value === 'object') {
// case: update 1 secret
if (
!value.type ||
@ -109,11 +107,11 @@ router.post(
!value.secretValueTag
) {
throw new Error(
"secrets object is missing required secret properties"
'secrets object is missing required secret properties'
);
}
} else {
throw new Error("secrets must be an object or an array of objects");
throw new Error('secrets must be an object or an array of objects');
}
return true;
@ -128,20 +126,19 @@ router.post(
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
locationWorkspaceId: 'body',
locationEnvironment: 'body',
requiredPermissions: [PERMISSION_WRITE_SECRETS],
}),
secretsController.createSecrets
);
router.get(
"/",
query("workspaceId").exists().trim(),
query("environment").exists().trim(),
query("tagSlugs"),
query("folderId").default("root").isString().trim(),
query("secretPath").optional().isString().trim(),
'/',
query('workspaceId').exists().trim(),
query('environment').exists().trim(),
query('tagSlugs'),
query('folderId').default('root').isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
@ -153,34 +150,34 @@ router.get(
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
locationWorkspaceId: 'query',
locationEnvironment: 'query',
requiredPermissions: [PERMISSION_READ_SECRETS],
}),
secretsController.getSecrets
);
router.patch(
"/",
body("secrets")
'/',
body('secrets')
.exists()
.custom((value) => {
if (Array.isArray(value)) {
// case: update multiple secrets
if (value.length === 0)
throw new Error("secrets cannot be an empty array");
throw new Error('secrets cannot be an empty array');
for (const secret of value) {
if (!secret.id) {
throw new Error("Each secret must contain a ID property");
throw new Error('Each secret must contain a ID property');
}
}
} else if (typeof value === "object") {
} else if (typeof value === 'object') {
// case: update 1 secret
if (!value.id) {
throw new Error("secret must contain a ID property");
throw new Error('secret must contain a ID property');
}
} else {
throw new Error("secrets must be an object or an array of objects");
throw new Error('secrets must be an object or an array of objects');
}
return true;
@ -201,21 +198,21 @@ router.patch(
);
router.delete(
"/",
body("secretIds")
'/',
body('secretIds')
.exists()
.custom((value) => {
// case: delete 1 secret
if (typeof value === "string") return true;
if (typeof value === 'string') return true;
if (Array.isArray(value)) {
// case: delete multiple secrets
if (value.length === 0)
throw new Error("secrets cannot be an empty array");
return value.every((id: string) => typeof id === "string");
throw new Error('secrets cannot be an empty array');
return value.every((id: string) => typeof id === 'string');
}
throw new Error("secretIds must be a string or an array of strings");
throw new Error('secretIds must be a string or an array of strings');
})
.not()
.isEmpty(),

@ -1,79 +1,72 @@
import express from "express";
import express from 'express';
const router = express.Router();
import {
requireAuth,
requireWorkspaceAuth,
requireServiceTokenDataAuth,
validateRequest,
} from "../../middleware";
import { param, body } from "express-validator";
requireAuth,
requireWorkspaceAuth,
requireServiceTokenDataAuth,
validateRequest
} from '../../middleware';
import { param, body } from 'express-validator';
import {
ADMIN,
MEMBER,
PERMISSION_WRITE_SECRETS,
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN,
} from "../../variables";
import { serviceTokenDataController } from "../../controllers/v2";
ADMIN,
MEMBER,
PERMISSION_WRITE_SECRETS,
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT,
AUTH_MODE_SERVICE_TOKEN
} from '../../variables';
import { serviceTokenDataController } from '../../controllers/v2';
router.get(
"/",
requireAuth({
acceptedAuthModes: [AUTH_MODE_SERVICE_TOKEN],
}),
serviceTokenDataController.getServiceTokenData
'/',
requireAuth({
acceptedAuthModes: [AUTH_MODE_SERVICE_TOKEN]
}),
serviceTokenDataController.getServiceTokenData
);
router.post(
"/",
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
}),
body("name").exists().isString().trim(),
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("encryptedKey").exists().isString().trim(),
body("iv").exists().isString().trim(),
body("secretPath").isString().default("/").trim(),
body("tag").exists().isString().trim(),
body("expiresIn").exists().isNumeric(), // measured in ms
body("permissions")
.isArray({ min: 1 })
.custom((value: string[]) => {
const allowedPermissions = ["read", "write"];
const invalidValues = value.filter(
(v) => !allowedPermissions.includes(v)
);
if (invalidValues.length > 0) {
throw new Error(
`permissions contains invalid values: ${invalidValues.join(", ")}`
);
}
return true;
'/',
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'body',
locationEnvironment: 'body',
requiredPermissions: [PERMISSION_WRITE_SECRETS]
}),
validateRequest,
serviceTokenDataController.createServiceTokenData
body('name').exists().isString().trim(),
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(),
body('encryptedKey').exists().isString().trim(),
body('iv').exists().isString().trim(),
body('tag').exists().isString().trim(),
body('expiresIn').exists().isNumeric(), // measured in ms
body('permissions').isArray({ min: 1 }).custom((value: string[]) => {
const allowedPermissions = ['read', 'write'];
const invalidValues = value.filter((v) => !allowedPermissions.includes(v));
if (invalidValues.length > 0) {
throw new Error(`permissions contains invalid values: ${invalidValues.join(', ')}`);
}
return true
}),
validateRequest,
serviceTokenDataController.createServiceTokenData
);
router.delete(
"/:serviceTokenDataId",
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT],
}),
requireServiceTokenDataAuth({
acceptedRoles: [ADMIN, MEMBER],
}),
param("serviceTokenDataId").exists().trim(),
validateRequest,
serviceTokenDataController.deleteServiceTokenData
'/:serviceTokenDataId',
requireAuth({
acceptedAuthModes: [AUTH_MODE_JWT]
}),
requireServiceTokenDataAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
param('serviceTokenDataId').exists().trim(),
validateRequest,
serviceTokenDataController.deleteServiceTokenData
);
export default router;
export default router;

@ -1,301 +1,157 @@
import express from "express";
import express from 'express';
const router = express.Router();
import {
requireAuth,
requireWorkspaceAuth,
validateRequest,
} from "../../middleware";
import { body, param, query } from "express-validator";
import { secretsController } from "../../controllers/v3";
requireAuth,
requireWorkspaceAuth,
validateRequest
} from '../../middleware';
import { body, param, query } from 'express-validator';
import { secretsController } from '../../controllers/v3';
import {
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
ADMIN,
MEMBER,
PERMISSION_WRITE_SECRETS,
SECRET_SHARED,
SECRET_PERSONAL,
PERMISSION_READ_SECRETS,
} from "../../variables";
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
ADMIN,
MEMBER,
PERMISSION_WRITE_SECRETS,
SECRET_SHARED,
SECRET_PERSONAL,
PERMISSION_READ_SECRETS
} from '../../variables';
router.get(
"/raw",
query("workspaceId").exists().isString().trim(),
query("environment").exists().isString().trim(),
query("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true
}),
secretsController.getSecretsRaw
);
router.get(
"/raw/:secretName",
param("secretName").exists().isString().trim(),
query("workspaceId").exists().isString().trim(),
query("environment").exists().isString().trim(),
query("secretPath").default("/").isString().trim(),
query("type").optional().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true
}),
secretsController.getSecretByNameRaw
'/',
query('workspaceId').exists().isString().trim(),
query('environment').exists().isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT
]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'query',
locationEnvironment: 'query',
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.getSecrets
);
router.post(
"/raw/:secretName",
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body("secretValue").exists().isString().trim(),
body("secretComment").default("").isString().trim(),
body("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true
}),
secretsController.createSecretRaw
);
router.patch(
"/raw/:secretName",
param("secretName").exists().isString().trim(),
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body("secretValue").exists().isString().trim(),
body("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true
}),
secretsController.updateSecretByNameRaw
);
router.delete(
"/raw/:secretName",
param("secretName").exists().isString().trim(),
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("secretPath").default("/").isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true
}),
secretsController.deleteSecretByNameRaw
'/:secretName',
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(),
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body('secretKeyCiphertext').exists().isString().trim(),
body('secretKeyIV').exists().isString().trim(),
body('secretKeyTag').exists().isString().trim(),
body('secretValueCiphertext').exists().isString().trim(),
body('secretValueIV').exists().isString().trim(),
body('secretValueTag').exists().isString().trim(),
body('secretCommentCiphertext').optional().isString().trim(),
body('secretCommentIV').optional().isString().trim(),
body('secretCommentTag').optional().isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT
]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'body',
locationEnvironment: 'body',
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.createSecret
);
router.get(
"/",
query("workspaceId").exists().isString().trim(),
query("environment").exists().isString().trim(),
query("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false
}),
secretsController.getSecrets
);
router.post(
"/:secretName",
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body("secretKeyCiphertext").exists().isString().trim(),
body("secretKeyIV").exists().isString().trim(),
body("secretKeyTag").exists().isString().trim(),
body("secretValueCiphertext").exists().isString().trim(),
body("secretValueIV").exists().isString().trim(),
body("secretValueTag").exists().isString().trim(),
body("secretCommentCiphertext").optional().isString().trim(),
body("secretCommentIV").optional().isString().trim(),
body("secretCommentTag").optional().isString().trim(),
body("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false
}),
secretsController.createSecret
);
router.get(
"/:secretName",
param("secretName").exists().isString().trim(),
query("workspaceId").exists().isString().trim(),
query("environment").exists().isString().trim(),
query("secretPath").default("/").isString().trim(),
query("type").optional().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.getSecretByName
'/:secretName',
param('secretName').exists().isString().trim(),
query('workspaceId').exists().isString().trim(),
query('environment').exists().isString().trim(),
query('type').optional().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT
]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'query',
locationEnvironment: 'query',
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.getSecretByName
);
router.patch(
"/:secretName",
param("secretName").exists().isString().trim(),
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body("secretValueCiphertext").exists().isString().trim(),
body("secretValueIV").exists().isString().trim(),
body("secretValueTag").exists().isString().trim(),
body("secretPath").default("/").isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false
}),
secretsController.updateSecretByName
'/:secretName',
param('secretName').exists().isString().trim(),
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(),
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body('secretValueCiphertext').exists().isString().trim(),
body('secretValueIV').exists().isString().trim(),
body('secretValueTag').exists().isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT
]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'body',
locationEnvironment: 'body',
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.updateSecretByName
);
router.delete(
"/:secretName",
param("secretName").exists().isString().trim(),
body("workspaceId").exists().isString().trim(),
body("environment").exists().isString().trim(),
body("secretPath").default("/").isString().trim(),
body("type").exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT,
],
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "body",
locationEnvironment: "body",
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: false
}),
secretsController.deleteSecretByName
'/:secretName',
param('secretName').exists().isString().trim(),
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(),
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
validateRequest,
requireAuth({
acceptedAuthModes: [
AUTH_MODE_JWT,
AUTH_MODE_API_KEY,
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_SERVICE_ACCOUNT
]
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: 'body',
locationEnvironment: 'body',
requiredPermissions: [PERMISSION_WRITE_SECRETS],
requireBlindIndicesEnabled: true,
}),
secretsController.deleteSecretByName
);
export default router;
export default router;

@ -8,9 +8,10 @@ import {
import { workspacesController } from '../../controllers/v3';
import {
AUTH_MODE_JWT,
ADMIN
ADMIN,
PERMISSION_READ_SECRETS
} from '../../variables';
import { param, body } from 'express-validator';
import { param, body, validationResult } from 'express-validator';
// -- migration to blind indices endpoints

@ -1,10 +1,8 @@
import { Types } from 'mongoose';
import {
getSecretsBotHelper,
getSecretsHelper,
encryptSymmetricHelper,
decryptSymmetricHelper,
getKey,
getIsWorkspaceE2EEHelper
decryptSymmetricHelper
} from '../helpers/bot';
/**
@ -12,31 +10,6 @@ import {
*/
class BotService {
/**
* Return whether or not workspace with id [workspaceId] is end-to-end encrypted
* @param workspaceId - id of workspace
* @returns {Boolean}
*/
static async getIsWorkspaceE2EE(workspaceId: Types.ObjectId) {
return await getIsWorkspaceE2EEHelper(workspaceId);
}
/**
* Get workspace key for workspace with id [workspaceId] shared to bot.
* @param {Object} obj
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get workspace key for
* @returns
*/
static async getWorkspaceKeyWithBot({
workspaceId
}: {
workspaceId: Types.ObjectId;
}) {
return await getKey({
workspaceId
});
}
/**
* Return decrypted secrets for workspace with id [workspaceId] and
* environment [environmen] shared to bot.
@ -52,7 +25,7 @@ class BotService {
workspaceId: Types.ObjectId;
environment: string;
}) {
return await getSecretsBotHelper({
return await getSecretsHelper({
workspaceId,
environment
});

@ -1,6 +1,5 @@
import { nanoid } from "nanoid";
import { Types } from "mongoose";
import Folder, { TFolderSchema } from "../models/folder";
import { TFolderSchema } from "../models/folder";
type TAppendFolderDTO = {
folderName: string;
@ -175,11 +174,6 @@ export const searchByFolderIdWithDir = (
// to get folder of a path given
// Like /frontend/folder#1
export const getFolderByPath = (folders: TFolderSchema, searchPath: string) => {
// corner case when its just / return root
if (searchPath === "/") {
return folders.id === "root" ? folders : undefined;
}
const path = searchPath.split("/").filter(Boolean);
const queue = [folders];
let segment: TFolderSchema | undefined;
@ -193,25 +187,3 @@ export const getFolderByPath = (folders: TFolderSchema, searchPath: string) => {
}
return segment;
};
export const getFolderIdFromServiceToken = async (
workspaceId: Types.ObjectId | string,
environment: string,
secretPath: string
) => {
const folders = await Folder.findOne({
workspace: workspaceId,
environment,
});
if (!folders) {
if (secretPath !== "/") throw new Error("Invalid path. Folders not found");
} else {
const folder = getFolderByPath(folders.nodes, secretPath);
if (!folder) {
throw new Error("Folder not found");
}
return folder.id;
}
return "root";
};

@ -1,4 +1,7 @@
import { Types } from "mongoose";
import { Types } from 'mongoose';
import {
ISecret
} from '../models';
import {
CreateSecretParams,
GetSecretsParams,
@ -19,150 +22,150 @@ import {
} from '../helpers/secrets';
class SecretService {
/**
* Create secret blind index data containing encrypted blind index salt
* for workspace with id [workspaceId]
* @param {Object} obj
* @param {Buffer} obj.salt - 16-byte random salt
* @param {Types.ObjectId} obj.workspaceId
*/
static async createSecretBlindIndexData({
workspaceId,
}: {
workspaceId: Types.ObjectId;
}) {
return await createSecretBlindIndexDataHelper({
workspaceId,
});
}
/**
* Get secret blind index salt for workspace with id [workspaceId]
* @param {Object} obj
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get salt for
* @returns
*/
static async getSecretBlindIndexSalt({
workspaceId,
}: {
workspaceId: Types.ObjectId;
}) {
return await getSecretBlindIndexSaltHelper({
workspaceId,
});
}
/**
* Create secret blind index data containing encrypted blind index salt
* for workspace with id [workspaceId]
* @param {Object} obj
* @param {Buffer} obj.salt - 16-byte random salt
* @param {Types.ObjectId} obj.workspaceId
*/
static async createSecretBlindIndexData({
workspaceId,
}: {
workspaceId: Types.ObjectId;
}) {
return await createSecretBlindIndexDataHelper({
workspaceId
});
}
/**
* Generate blind index for secret with name [secretName]
* and salt [salt]
* @param {Object} obj
* @param {Object} obj.secretName - name of secret to generate blind index for
* @param {String} obj.salt - base64-salt
*/
static async generateSecretBlindIndexWithSalt({
secretName,
salt,
}: {
secretName: string;
salt: string;
}) {
return await generateSecretBlindIndexWithSaltHelper({
secretName,
salt,
});
}
/**
* Get secret blind index salt for workspace with id [workspaceId]
* @param {Object} obj
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get salt for
* @returns
*/
static async getSecretBlindIndexSalt({
workspaceId
}: {
workspaceId: Types.ObjectId;
}) {
return await getSecretBlindIndexSaltHelper({
workspaceId
});
}
/**
* Create and return blind index for secret with
* name [secretName] part of workspace with id [workspaceId]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to generate blind index for
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
*/
static async generateSecretBlindIndex({
secretName,
workspaceId,
}: {
secretName: string;
workspaceId: Types.ObjectId;
}) {
return await generateSecretBlindIndexHelper({
secretName,
workspaceId,
});
}
/**
* Generate blind index for secret with name [secretName]
* and salt [salt]
* @param {Object} obj
* @param {Object} obj.secretName - name of secret to generate blind index for
* @param {String} obj.salt - base64-salt
*/
static async generateSecretBlindIndexWithSalt({
secretName,
salt
}: {
secretName: string;
salt: string;
}) {
return await generateSecretBlindIndexWithSaltHelper({
secretName,
salt
});
}
/**
* Create secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to create
* @param {Types.ObjectId} obj.workspaceId - id of workspace to create secret for
* @param {String} obj.environment - environment in workspace to create secret for
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async createSecret(createSecretParams: CreateSecretParams) {
return await createSecretHelper(createSecretParams);
}
/**
* Create and return blind index for secret with
* name [secretName] part of workspace with id [workspaceId]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to generate blind index for
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
*/
static async generateSecretBlindIndex({
secretName,
workspaceId,
}: {
secretName: string;
workspaceId: Types.ObjectId;
}) {
return await generateSecretBlindIndexHelper({
secretName,
workspaceId
});
}
/**
* Create secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to create
* @param {Types.ObjectId} obj.workspaceId - id of workspace to create secret for
* @param {String} obj.environment - environment in workspace to create secret for
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async createSecret(createSecretParams: CreateSecretParams) {
return await createSecretHelper(createSecretParams);
}
/**
* Get secrets for workspace with id [workspaceId] and environment [environment]
* @param {Object} obj
* @param {Types.ObjectId} obj.workspaceId - id of workspace
* @param {String} obj.environment - environment in workspace
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async getSecrets(getSecretsParams: GetSecretsParams) {
return await getSecretsHelper(getSecretsParams);
}
/**
* Get secrets for workspace with id [workspaceId] and environment [environment]
* @param {Object} obj
* @param {Types.ObjectId} obj.workspaceId - id of workspace
* @param {String} obj.environment - environment in workspace
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async getSecrets(getSecretsParams: GetSecretsParams) {
return await getSecretsHelper(getSecretsParams);
}
/**
* Get secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to get
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async getSecret(getSecretParams: GetSecretParams) {
return await getSecretHelper(getSecretParams);
}
/**
* Update secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to update
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {String} obj.secretValueCiphertext - ciphertext of secret value
* @param {String} obj.secretValueIV - IV of secret value
* @param {String} obj.secretValueTag - tag of secret value
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async updateSecret(updateSecretParams: UpdateSecretParams) {
return await updateSecretHelper(updateSecretParams);
}
/**
* Get secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to get
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async getSecret(getSecretParams: GetSecretParams) {
// TODO(akhilmhdh) The one above is diff. Change this to some other name
return await getSecretHelper(getSecretParams);
}
/**
* Update secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to update
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {String} obj.secretValueCiphertext - ciphertext of secret value
* @param {String} obj.secretValueIV - IV of secret value
* @param {String} obj.secretValueTag - tag of secret value
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async updateSecret(updateSecretParams: UpdateSecretParams) {
return await updateSecretHelper(updateSecretParams);
}
/**
* Delete secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to delete
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async deleteSecret(deleteSecretParams: DeleteSecretParams) {
return await deleteSecretHelper(deleteSecretParams);
}
/**
* Delete secret with name [secretName]
* @param {Object} obj
* @param {String} obj.secretName - name of secret to delete
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
* @param {String} obj.environment - environment in workspace that secret belongs to
* @param {'shared' | 'personal'} obj.type - type of secret
* @param {AuthData} obj.authData - authentication data on request
* @returns
*/
static async deleteSecret(deleteSecretParams: DeleteSecretParams) {
return await deleteSecretHelper(deleteSecretParams);
}
}
export default SecretService;
export default SecretService;

@ -1,5 +1,4 @@
import * as express from 'express';
import { Types } from 'mongoose';
import {
IUser,
IServiceAccount,
@ -40,9 +39,7 @@ declare global {
serviceTokenData: any;
apiKeyData: any;
query?: any;
tokenVersionId?: Types.ObjectId;
authData: AuthData;
realIP: string;
requestData: {
[key: string]: string
};

@ -1,4 +1,3 @@
/* eslint-disable no-console */
import crypto from "crypto";
import { Types } from "mongoose";
import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto";
@ -12,7 +11,6 @@ import {
Bot,
BackupPrivateKey,
IntegrationAuth,
ServiceTokenData,
} from "../../models";
import { generateKeyPair } from "../../utils/crypto";
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
@ -66,7 +64,7 @@ export const backfillSecretVersions = async () => {
),
});
}
console.log("Migration: Secret version migration v1 complete");
console.log("Migration: Secret version migration v1 complete")
};
/**
@ -382,15 +380,13 @@ export const backfillSecretFolders = async () => {
});
const newSnapshots = Object.keys(groupSnapByEnv).map((snapEnv) => {
const secretIdsOfEnvGroup = groupSnapByEnv[snapEnv]
? groupSnapByEnv[snapEnv].map((secretVersion) => secretVersion._id)
: [];
const secretIdsOfEnvGroup = groupSnapByEnv[snapEnv] ? groupSnapByEnv[snapEnv].map(secretVersion => secretVersion._id) : []
return {
...secSnapshot.toObject({ virtuals: false }),
_id: new Types.ObjectId(),
environment: snapEnv,
secretVersions: secretIdsOfEnvGroup,
};
}
});
await SecretSnapshot.insertMany(newSnapshots);
@ -406,21 +402,5 @@ export const backfillSecretFolders = async () => {
.limit(50);
}
console.log("Migration: Folder migration v1 complete");
};
export const backfillServiceToken = async () => {
await ServiceTokenData.updateMany(
{
secretPath: {
$exists: false,
},
},
{
$set: {
secretPath: "/",
},
}
);
console.log("Migration: Service token migration v1 complete");
console.log("Migration: Folder migration v1 complete")
};

@ -1,31 +1,30 @@
import * as Sentry from "@sentry/node";
import { DatabaseService, TelemetryService } from "../../services";
import { setTransporter } from "../../helpers/nodemailer";
import { EELicenseService } from "../../ee/services";
import { initSmtp } from "../../services/smtp";
import { createTestUserForDevelopment } from "../addDevelopmentUser";
import * as Sentry from '@sentry/node';
import { DatabaseService, TelemetryService } from '../../services';
import { setTransporter } from '../../helpers/nodemailer';
import { EELicenseService } from '../../ee/services';
import { initSmtp } from '../../services/smtp';
import { createTestUserForDevelopment } from '../addDevelopmentUser';
// eslint-disable-next-line @typescript-eslint/no-var-requires
import { validateEncryptionKeysConfig } from "./validateConfig";
import { validateEncryptionKeysConfig } from './validateConfig';
import {
backfillSecretVersions,
backfillBots,
backfillSecretBlindIndexData,
backfillEncryptionMetadata,
backfillSecretFolders,
backfillServiceToken,
} from "./backfillData";
} from './backfillData';
import {
reencryptBotPrivateKeys,
reencryptSecretBlindIndexDataSalts,
} from "./reencryptData";
} from './reencryptData';
import {
getNodeEnv,
getMongoURL,
getSentryDSN,
getClientSecretGoogle,
getClientIdGoogle,
} from "../../config";
import { initializePassport } from "../auth";
} from '../../config';
import { initializePassport } from '../auth';
/**
* Prepare Infisical upon startup. This includes tasks like:
@ -76,7 +75,6 @@ export const setup = async () => {
await backfillSecretBlindIndexData();
await backfillEncryptionMetadata();
await backfillSecretFolders();
await backfillServiceToken();
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
// to base64 256-bit ROOT_ENCRYPTION_KEY
@ -87,7 +85,7 @@ export const setup = async () => {
Sentry.init({
dsn: await getSentryDSN(),
tracesSampleRate: 1.0,
debug: (await getNodeEnv()) === "production" ? false : true,
debug: (await getNodeEnv()) === 'production' ? false : true,
environment: await getNodeEnv(),
});

@ -1,4 +1,3 @@
export * from './user';
export * from './workspace';
export * from './bot';
export * from './integration';

@ -1,5 +1,3 @@
import fs from 'fs';
import path from 'path';
import { Types } from 'mongoose';
import {
IUser,
@ -10,7 +8,7 @@ import {
} from '../models';
import { validateMembership } from '../helpers/membership';
import _ from 'lodash';
import { BadRequestError, UnauthorizedRequestError, ValidationError } from '../utils/errors';
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
import {
validateMembershipOrg
} from '../helpers/membershipOrg';
@ -19,22 +17,6 @@ import {
PERMISSION_WRITE_SECRETS
} from '../variables';
/**
* Validate that email [email] is not disposable
* @param email - email to validate
*/
export const validateUserEmail = (email: string) => {
const emailDomain = email.split('@')[1];
const disposableEmails = fs.readFileSync(
path.resolve(__dirname, '../data/' + 'disposable_emails.txt'),
'utf8'
).split('\n');
if (disposableEmails.includes(emailDomain)) throw ValidationError({
message: 'Failed to validate email as non-disposable'
});
}
/**
* Validate that user (client) can access workspace
* with id [workspaceId] and its environment [environment] with required permissions

@ -13,7 +13,6 @@ import { validateServiceAccountClientForWorkspace } from './serviceAccount';
import { validateUserClientForWorkspace } from './user';
import { validateServiceTokenDataClientForWorkspace } from './serviceTokenData';
import {
BadRequestError,
UnauthorizedRequestError,
WorkspaceNotFoundError
} from '../utils/errors';
@ -23,7 +22,6 @@ import {
AUTH_MODE_SERVICE_TOKEN,
AUTH_MODE_API_KEY
} from '../variables';
import { BotService } from '../services';
/**
* Validate authenticated clients for workspace with id [workspaceId] based
@ -41,8 +39,7 @@ export const validateClientForWorkspace = async ({
environment,
acceptedRoles,
requiredPermissions,
requireBlindIndicesEnabled,
requireE2EEOff
requireBlindIndicesEnabled
}: {
authData: {
authMode: string;
@ -53,7 +50,6 @@ export const validateClientForWorkspace = async ({
acceptedRoles: Array<'admin' | 'member'>;
requiredPermissions?: string[];
requireBlindIndicesEnabled: boolean;
requireE2EEOff: boolean;
}) => {
const workspace = await Workspace.findById(workspaceId);
@ -74,14 +70,6 @@ export const validateClientForWorkspace = async ({
message: 'Failed workspace authorization due to blind indices not being enabled'
});
}
if (requireE2EEOff) {
const isWorkspaceE2EE = await BotService.getIsWorkspaceE2EE(workspaceId);
if (isWorkspaceE2EE) throw BadRequestError({
message: 'Failed workspace authorization due to end-to-end encryption not being disabled'
});
}
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
const membership = await validateUserClientForWorkspace({

@ -22,8 +22,6 @@ export const INTEGRATION_FLYIO = "flyio";
export const INTEGRATION_CIRCLECI = "circleci";
export const INTEGRATION_TRAVISCI = "travisci";
export const INTEGRATION_SUPABASE = 'supabase';
export const INTEGRATION_CHECKLY = 'checkly';
export const INTEGRATION_HASHICORP_VAULT = 'hashicorp-vault';
export const INTEGRATION_SET = new Set([
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU,
@ -35,9 +33,7 @@ export const INTEGRATION_SET = new Set([
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY,
INTEGRATION_HASHICORP_VAULT
INTEGRATION_SUPABASE
]);
// integration types
@ -64,7 +60,6 @@ export const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql";
export const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
export const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
export const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com';
export const INTEGRATION_CHECKLY_API_URL = 'https://api.checklyhq.com';
export const getIntegrationOptions = async () => {
const INTEGRATION_OPTIONS = [
@ -195,24 +190,6 @@ export const getIntegrationOptions = async () => {
clientId: '',
docsLink: ''
},
{
name: 'Checkly',
slug: 'checkly',
image: 'Checkly.png',
isAvailable: true,
type: 'pat',
clientId: '',
docsLink: ''
},
{
name: 'HashiCorp Vault',
slug: 'hashicorp-vault',
image: 'Vault.png',
isAvailable: true,
type: 'pat',
clientId: '',
docsLink: ''
},
{
name: 'Google Cloud Platform',
slug: 'gcp',

@ -2,7 +2,6 @@ package api
import (
"fmt"
"net/http"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/go-resty/resty/v2"
@ -11,6 +10,63 @@ import (
const USER_AGENT = "cli"
func CallBatchModifySecretsByWorkspaceAndEnv(httpClient *resty.Client, request BatchModifySecretsByWorkspaceAndEnvRequest) error {
endpoint := fmt.Sprintf("%v/v2/secrets", config.INFISICAL_URL)
response, err := httpClient.
R().
SetBody(request).
SetHeader("User-Agent", USER_AGENT).
Patch(endpoint)
if err != nil {
return fmt.Errorf("CallBatchModifySecretsByWorkspaceAndEnv: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return fmt.Errorf("CallBatchModifySecretsByWorkspaceAndEnv: Unsuccessful response: [response=%s]", response)
}
return nil
}
func CallBatchCreateSecretsByWorkspaceAndEnv(httpClient *resty.Client, request BatchCreateSecretsByWorkspaceAndEnvRequest) error {
endpoint := fmt.Sprintf("%v/v2/secrets/", config.INFISICAL_URL)
response, err := httpClient.
R().
SetBody(request).
SetHeader("User-Agent", USER_AGENT).
Post(endpoint)
if err != nil {
return fmt.Errorf("CallBatchCreateSecretsByWorkspaceAndEnv: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return fmt.Errorf("CallBatchCreateSecretsByWorkspaceAndEnv: Unsuccessful response: [response=%s]", response)
}
return nil
}
func CallBatchDeleteSecretsByWorkspaceAndEnv(httpClient *resty.Client, request BatchDeleteSecretsBySecretIdsRequest) error {
endpoint := fmt.Sprintf("%v/v2/secrets", config.INFISICAL_URL)
response, err := httpClient.
R().
SetBody(request).
SetHeader("User-Agent", USER_AGENT).
Delete(endpoint)
if err != nil {
return fmt.Errorf("CallBatchDeleteSecretsByWorkspaceAndEnv: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return fmt.Errorf("CallBatchDeleteSecretsByWorkspaceAndEnv: Unsuccessful response: [response=%s]", response)
}
return nil
}
func CallGetEncryptedWorkspaceKey(httpClient *resty.Client, request GetEncryptedWorkspaceKeyRequest) (GetEncryptedWorkspaceKeyResponse, error) {
endpoint := fmt.Sprintf("%v/v2/workspace/%v/encrypted-key", config.INFISICAL_URL, request.WorkspaceId)
var result GetEncryptedWorkspaceKeyResponse
@ -50,6 +106,28 @@ func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDeta
return tokenDetailsResponse, nil
}
func CallGetSecretsV2(httpClient *resty.Client, request GetEncryptedSecretsV2Request) (GetEncryptedSecretsV2Response, error) {
var secretsResponse GetEncryptedSecretsV2Response
response, err := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetQueryParam("environment", request.Environment).
SetQueryParam("workspaceId", request.WorkspaceId).
SetQueryParam("tagSlugs", request.TagSlugs).
Get(fmt.Sprintf("%v/v2/secrets", config.INFISICAL_URL))
if err != nil {
return GetEncryptedSecretsV2Response{}, fmt.Errorf("CallGetSecretsV2: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return GetEncryptedSecretsV2Response{}, fmt.Errorf("CallGetSecretsV2: Unsuccessful response: [response=%s]", response)
}
return secretsResponse, nil
}
func CallLogin1V2(httpClient *resty.Client, request GetLoginOneV2Request) (GetLoginOneV2Response, error) {
var loginOneV2Response GetLoginOneV2Response
response, err := httpClient.
@ -81,22 +159,6 @@ func CallVerifyMfaToken(httpClient *resty.Client, request VerifyMfaTokenRequest)
SetBody(request).
Post(fmt.Sprintf("%v/v2/auth/mfa/verify", config.INFISICAL_URL))
cookies := response.Cookies()
// Find a cookie by name
cookieName := "jid"
var refreshToken *http.Cookie
for _, cookie := range cookies {
if cookie.Name == cookieName {
refreshToken = cookie
break
}
}
// When MFA is enabled
if refreshToken != nil {
verifyMfaTokenResponse.RefreshToken = refreshToken.Value
}
if err != nil {
return nil, nil, fmt.Errorf("CallVerifyMfaToken: Unable to complete api request [err=%s]", err)
}
@ -117,22 +179,6 @@ func CallLogin2V2(httpClient *resty.Client, request GetLoginTwoV2Request) (GetLo
SetBody(request).
Post(fmt.Sprintf("%v/v2/auth/login2", config.INFISICAL_URL))
cookies := response.Cookies()
// Find a cookie by name
cookieName := "jid"
var refreshToken *http.Cookie
for _, cookie := range cookies {
if cookie.Name == cookieName {
refreshToken = cookie
break
}
}
// When MFA is enabled
if refreshToken != nil {
loginTwoV2Response.RefreshToken = refreshToken.Value
}
if err != nil {
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V2: Unable to complete api request [err=%s]", err)
}
@ -201,133 +247,3 @@ func CallGetAccessibleEnvironments(httpClient *resty.Client, request GetAccessib
return accessibleEnvironmentsResponse, nil
}
func CallGetNewAccessTokenWithRefreshToken(httpClient *resty.Client, refreshToken string) (GetNewAccessTokenWithRefreshTokenResponse, error) {
var newAccessToken GetNewAccessTokenWithRefreshTokenResponse
response, err := httpClient.
R().
SetResult(&newAccessToken).
SetHeader("User-Agent", USER_AGENT).
SetCookie(&http.Cookie{
Name: "jid",
Value: refreshToken,
}).
Post(fmt.Sprintf("%v/v1/auth/token", config.INFISICAL_URL))
if err != nil {
return GetNewAccessTokenWithRefreshTokenResponse{}, err
}
if response.IsError() {
return GetNewAccessTokenWithRefreshTokenResponse{}, fmt.Errorf("CallGetNewAccessTokenWithRefreshToken: Unsuccessful response: [response=%v]", response)
}
return newAccessToken, nil
}
func CallGetSecretsV3(httpClient *resty.Client, request GetEncryptedSecretsV3Request) (GetEncryptedSecretsV3Response, error) {
var secretsResponse GetEncryptedSecretsV3Response
httpRequest := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetQueryParam("environment", request.Environment).
SetQueryParam("workspaceId", request.WorkspaceId)
if request.SecretPath != "" {
httpRequest.SetQueryParam("secretPath", request.SecretPath)
}
response, err := httpRequest.Get(fmt.Sprintf("%v/v3/secrets", config.INFISICAL_URL))
if err != nil {
return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return GetEncryptedSecretsV3Response{}, fmt.Errorf("CallGetSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}
return secretsResponse, nil
}
func CallCreateSecretsV3(httpClient *resty.Client, request CreateSecretV3Request) error {
var secretsResponse GetEncryptedSecretsV3Response
response, err := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Post(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName))
if err != nil {
return fmt.Errorf("CallCreateSecretsV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return fmt.Errorf("CallCreateSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}
return nil
}
func CallDeleteSecretsV3(httpClient *resty.Client, request DeleteSecretV3Request) error {
var secretsResponse GetEncryptedSecretsV3Response
response, err := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Delete(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName))
if err != nil {
return fmt.Errorf("CallDeleteSecretsV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return fmt.Errorf("CallDeleteSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}
return nil
}
func CallUpdateSecretsV3(httpClient *resty.Client, request UpdateSecretByNameV3Request) error {
var secretsResponse GetEncryptedSecretsV3Response
response, err := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Patch(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName))
if err != nil {
return fmt.Errorf("CallUpdateSecretsV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return fmt.Errorf("CallUpdateSecretsV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}
return nil
}
func CallGetSingleSecretByNameV3(httpClient *resty.Client, request CreateSecretV3Request) error {
var secretsResponse GetEncryptedSecretsV3Response
response, err := httpClient.
R().
SetResult(&secretsResponse).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Post(fmt.Sprintf("%v/v3/secrets/%s", config.INFISICAL_URL, request.SecretName))
if err != nil {
return fmt.Errorf("CallGetSingleSecretByNameV3: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return fmt.Errorf("CallGetSingleSecretByNameV3: Unsuccessful response. Please make sure your secret path, workspace and environment name are all correct [response=%s]", response)
}
return nil
}

@ -143,7 +143,24 @@ type Secret struct {
SecretCommentHash string `json:"secretCommentHash,omitempty"`
Type string `json:"type,omitempty"`
ID string `json:"id,omitempty"`
PlainTextKey string `json:"plainTextKey"`
}
type BatchCreateSecretsByWorkspaceAndEnvRequest struct {
Environment string `json:"environment"`
WorkspaceId string `json:"workspaceId"`
Secrets []Secret `json:"secrets"`
}
type BatchModifySecretsByWorkspaceAndEnvRequest struct {
Environment string `json:"environment"`
WorkspaceId string `json:"workspaceId"`
Secrets []Secret `json:"secrets"`
}
type BatchDeleteSecretsBySecretIdsRequest struct {
EnvironmentName string `json:"environmentName"`
WorkspaceId string `json:"workspaceId"`
SecretIds []string `json:"secretIds"`
}
type GetEncryptedWorkspaceKeyRequest struct {
@ -177,6 +194,41 @@ type GetSecretsByWorkspaceIdAndEnvironmentRequest struct {
WorkspaceId string `json:"workspaceId"`
}
type GetEncryptedSecretsV2Request struct {
Environment string `json:"environment"`
WorkspaceId string `json:"workspaceId"`
TagSlugs string `json:"tagSlugs"`
}
type GetEncryptedSecretsV2Response struct {
Secrets []struct {
ID string `json:"_id"`
Version int `json:"version"`
Workspace string `json:"workspace"`
Type string `json:"type"`
Environment string `json:"environment"`
SecretKeyCiphertext string `json:"secretKeyCiphertext"`
SecretKeyIV string `json:"secretKeyIV"`
SecretKeyTag string `json:"secretKeyTag"`
SecretValueCiphertext string `json:"secretValueCiphertext"`
SecretValueIV string `json:"secretValueIV"`
SecretValueTag string `json:"secretValueTag"`
SecretCommentCiphertext string `json:"secretCommentCiphertext"`
SecretCommentIV string `json:"secretCommentIV"`
SecretCommentTag string `json:"secretCommentTag"`
V int `json:"__v"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
User string `json:"user,omitempty"`
Tags []struct {
ID string `json:"_id"`
Name string `json:"name"`
Slug string `json:"slug"`
Workspace string `json:"workspace"`
} `json:"tags"`
} `json:"secrets"`
}
type GetServiceTokenDetailsResponse struct {
ID string `json:"_id"`
Name string `json:"name"`
@ -229,7 +281,6 @@ type GetLoginTwoV2Response struct {
ProtectedKey string `json:"protectedKey"`
ProtectedKeyIV string `json:"protectedKeyIV"`
ProtectedKeyTag string `json:"protectedKeyTag"`
RefreshToken string `json:"RefreshToken"`
}
type VerifyMfaTokenRequest struct {
@ -247,7 +298,6 @@ type VerifyMfaTokenResponse struct {
ProtectedKey string `json:"protectedKey"`
ProtectedKeyIV string `json:"protectedKeyIV"`
ProtectedKeyTag string `json:"protectedKeyTag"`
RefreshToken string `json:"refreshToken"`
}
type VerifyMfaTokenErrorResponse struct {
@ -264,113 +314,3 @@ type VerifyMfaTokenErrorResponse struct {
Application string `json:"application"`
Extra []interface{} `json:"extra"`
}
type GetNewAccessTokenWithRefreshTokenResponse struct {
Token string `json:"token"`
}
type GetEncryptedSecretsV3Request struct {
Environment string `json:"environment"`
WorkspaceId string `json:"workspaceId"`
SecretPath string `json:"secretPath"`
}
type GetEncryptedSecretsV3Response struct {
Secrets []struct {
ID string `json:"_id"`
Version int `json:"version"`
Workspace string `json:"workspace"`
Type string `json:"type"`
Tags []struct {
ID string `json:"_id"`
Name string `json:"name"`
Slug string `json:"slug"`
Workspace string `json:"workspace"`
} `json:"tags"`
Environment string `json:"environment"`
SecretKeyCiphertext string `json:"secretKeyCiphertext"`
SecretKeyIV string `json:"secretKeyIV"`
SecretKeyTag string `json:"secretKeyTag"`
SecretValueCiphertext string `json:"secretValueCiphertext"`
SecretValueIV string `json:"secretValueIV"`
SecretValueTag string `json:"secretValueTag"`
SecretCommentCiphertext string `json:"secretCommentCiphertext"`
SecretCommentIV string `json:"secretCommentIV"`
SecretCommentTag string `json:"secretCommentTag"`
Algorithm string `json:"algorithm"`
KeyEncoding string `json:"keyEncoding"`
Folder string `json:"folder"`
V int `json:"__v"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
} `json:"secrets"`
}
type CreateSecretV3Request struct {
SecretName string `json:"secretName"`
WorkspaceID string `json:"workspaceId"`
Type string `json:"type"`
Environment string `json:"environment"`
SecretKeyCiphertext string `json:"secretKeyCiphertext"`
SecretKeyIV string `json:"secretKeyIV"`
SecretKeyTag string `json:"secretKeyTag"`
SecretValueCiphertext string `json:"secretValueCiphertext"`
SecretValueIV string `json:"secretValueIV"`
SecretValueTag string `json:"secretValueTag"`
SecretCommentCiphertext string `json:"secretCommentCiphertext"`
SecretCommentIV string `json:"secretCommentIV"`
SecretCommentTag string `json:"secretCommentTag"`
SecretPath string `json:"secretPath"`
}
type DeleteSecretV3Request struct {
SecretName string `json:"secretName"`
WorkspaceId string `json:"workspaceId"`
Environment string `json:"environment"`
Type string `json:"type"`
SecretPath string `json:"secretPath"`
}
type UpdateSecretByNameV3Request struct {
SecretName string `json:"secretName"`
WorkspaceID string `json:"workspaceId"`
Environment string `json:"environment"`
Type string `json:"type"`
SecretPath string `json:"secretPath"`
SecretValueCiphertext string `json:"secretValueCiphertext"`
SecretValueIV string `json:"secretValueIV"`
SecretValueTag string `json:"secretValueTag"`
}
type GetSingleSecretByNameV3Request struct {
SecretName string `json:"secretName"`
WorkspaceId string `json:"workspaceId"`
Environment string `json:"environment"`
Type string `json:"type"`
SecretPath string `json:"secretPath"`
}
type GetSingleSecretByNameSecretResponse struct {
Secrets []struct {
ID string `json:"_id"`
Version int `json:"version"`
Workspace string `json:"workspace"`
Type string `json:"type"`
Environment string `json:"environment"`
SecretKeyCiphertext string `json:"secretKeyCiphertext"`
SecretKeyIV string `json:"secretKeyIV"`
SecretKeyTag string `json:"secretKeyTag"`
SecretValueCiphertext string `json:"secretValueCiphertext"`
SecretValueIV string `json:"secretValueIV"`
SecretValueTag string `json:"secretValueTag"`
SecretCommentCiphertext string `json:"secretCommentCiphertext"`
SecretCommentIV string `json:"secretCommentIV"`
SecretCommentTag string `json:"secretCommentTag"`
Algorithm string `json:"algorithm"`
KeyEncoding string `json:"keyEncoding"`
Folder string `json:"folder"`
V int `json:"__v"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
} `json:"secrets"`
}

@ -97,7 +97,7 @@ var loginCmd = &cobra.Command{
loginOneResponse, loginTwoResponse, err := getFreshUserCredentials(email, password)
if err != nil {
log.Warn().Msg("Unable to authenticate with the provided credentials, please ensure your email and password are correct")
fmt.Println("Unable to authenticate with the provided credentials, please try again")
log.Debug().Err(err)
return
}
@ -143,7 +143,7 @@ var loginCmd = &cobra.Command{
loginTwoResponse.Tag = verifyMFAresponse.Tag
loginTwoResponse.Token = verifyMFAresponse.Token
loginTwoResponse.EncryptionVersion = verifyMFAresponse.EncryptionVersion
loginTwoResponse.RefreshToken = verifyMFAresponse.RefreshToken
break
}
}
@ -244,10 +244,9 @@ var loginCmd = &cobra.Command{
}
userCredentialsToBeStored := &models.UserCredentials{
Email: email,
PrivateKey: string(decryptedPrivateKey),
JTWToken: loginTwoResponse.Token,
RefreshToken: loginTwoResponse.RefreshToken,
Email: email,
PrivateKey: string(decryptedPrivateKey),
JTWToken: loginTwoResponse.Token,
}
err = util.StoreUserCredsInKeyRing(userCredentialsToBeStored)
@ -415,7 +414,7 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R
})
if err != nil {
return nil, nil, err
util.HandleError(err)
}
// **** Login 2

@ -82,12 +82,7 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secretsPath, err := cmd.Flags().GetString("path")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
if err != nil {
util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid")
@ -189,7 +184,6 @@ func init() {
runCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
runCmd.Flags().StringP("command", "c", "", "chained commands to execute (e.g. \"npm install && npm run dev; echo ...\")")
runCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs ")
runCmd.Flags().String("path", "/", "get secrets within a folder path")
}
// Will execute a single command and pass in the given secrets into the process

@ -44,11 +44,6 @@ var secretsCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secretsPath, err := cmd.Flags().GetString("path")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
shouldExpandSecrets, err := cmd.Flags().GetBool("expand")
if err != nil {
util.HandleError(err)
@ -59,7 +54,7 @@ var secretsCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, SecretsPath: secretsPath})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
if err != nil {
util.HandleError(err)
}
@ -108,11 +103,6 @@ var secretsSetCmd = &cobra.Command{
}
}
secretsPath, err := cmd.Flags().GetString("path")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
workspaceFile, err := util.GetWorkSpaceFromFile()
if err != nil {
util.HandleError(err, "Unable to get your local config details")
@ -150,7 +140,7 @@ var secretsSetCmd = &cobra.Command{
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
// pull current secrets
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName})
if err != nil {
util.HandleError(err, "unable to retrieve secrets")
}
@ -201,8 +191,6 @@ var secretsSetCmd = &cobra.Command{
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
SecretValueHash: hashedValue,
PlainTextKey: key,
Type: existingSecret.Type,
}
// Only add to modifications if the value is different
@ -234,7 +222,6 @@ var secretsSetCmd = &cobra.Command{
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
SecretValueHash: hashedValue,
Type: util.SECRET_TYPE_SHARED,
PlainTextKey: key,
}
secretsToCreate = append(secretsToCreate, encryptedSecretDetails)
secretOperations = append(secretOperations, SecretSetOperation{
@ -245,43 +232,30 @@ var secretsSetCmd = &cobra.Command{
}
}
for _, secret := range secretsToCreate {
createSecretRequest := api.CreateSecretV3Request{
WorkspaceID: workspaceFile.WorkspaceId,
Environment: environmentName,
SecretName: secret.PlainTextKey,
SecretKeyCiphertext: secret.SecretKeyCiphertext,
SecretKeyIV: secret.SecretKeyIV,
SecretKeyTag: secret.SecretKeyTag,
SecretValueCiphertext: secret.SecretValueCiphertext,
SecretValueIV: secret.SecretValueIV,
SecretValueTag: secret.SecretValueTag,
Type: secret.Type,
SecretPath: secretsPath,
if len(secretsToCreate) > 0 {
batchCreateRequest := api.BatchCreateSecretsByWorkspaceAndEnvRequest{
WorkspaceId: workspaceFile.WorkspaceId,
Environment: environmentName,
Secrets: secretsToCreate,
}
err = api.CallCreateSecretsV3(httpClient, createSecretRequest)
err = api.CallBatchCreateSecretsByWorkspaceAndEnv(httpClient, batchCreateRequest)
if err != nil {
util.HandleError(err, "Unable to process new secret creations")
return
}
}
for _, secret := range secretsToModify {
updateSecretRequest := api.UpdateSecretByNameV3Request{
WorkspaceID: workspaceFile.WorkspaceId,
Environment: environmentName,
SecretName: secret.PlainTextKey,
SecretValueCiphertext: secret.SecretValueCiphertext,
SecretValueIV: secret.SecretValueIV,
SecretValueTag: secret.SecretValueTag,
Type: secret.Type,
SecretPath: secretsPath,
if len(secretsToModify) > 0 {
batchModifyRequest := api.BatchModifySecretsByWorkspaceAndEnvRequest{
WorkspaceId: workspaceFile.WorkspaceId,
Environment: environmentName,
Secrets: secretsToModify,
}
err = api.CallUpdateSecretsV3(httpClient, updateSecretRequest)
err = api.CallBatchModifySecretsByWorkspaceAndEnv(httpClient, batchModifyRequest)
if err != nil {
util.HandleError(err, "Unable to process secret update request")
util.HandleError(err, "Unable to process the modifications to your secrets")
return
}
}
@ -314,16 +288,6 @@ var secretsDeleteCmd = &cobra.Command{
}
}
secretsPath, err := cmd.Flags().GetString("path")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
secretType, err := cmd.Flags().GetString("type")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
if err != nil {
util.HandleError(err, "Unable to authenticate")
@ -334,28 +298,46 @@ var secretsDeleteCmd = &cobra.Command{
util.HandleError(err, "Unable to get local project details")
}
for _, secretName := range args {
request := api.DeleteSecretV3Request{
WorkspaceId: workspaceFile.WorkspaceId,
Environment: environmentName,
SecretName: secretName,
Type: secretType,
SecretPath: secretsPath,
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName})
if err != nil {
util.HandleError(err, "Unable to fetch secrets")
}
httpClient := resty.New().
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
SetHeader("Accept", "application/json")
secretByKey := getSecretsByKeys(secrets)
validSecretIdsToDelete := []string{}
invalidSecretNamesThatDoNotExist := []string{}
err = api.CallDeleteSecretsV3(httpClient, request)
if err != nil {
util.HandleError(err, "Unable to complete your delete request")
for _, secretKeyFromArg := range args {
if value, ok := secretByKey[strings.ToUpper(secretKeyFromArg)]; ok {
validSecretIdsToDelete = append(validSecretIdsToDelete, value.ID)
} else {
invalidSecretNamesThatDoNotExist = append(invalidSecretNamesThatDoNotExist, secretKeyFromArg)
}
}
if len(invalidSecretNamesThatDoNotExist) != 0 {
message := fmt.Sprintf("secret name(s) [%v] does not exist in your project. To see which secrets exist run [infisical secrets]", strings.Join(invalidSecretNamesThatDoNotExist, ", "))
util.PrintErrorMessageAndExit(message)
}
request := api.BatchDeleteSecretsBySecretIdsRequest{
WorkspaceId: workspaceFile.WorkspaceId,
EnvironmentName: environmentName,
SecretIds: validSecretIdsToDelete,
}
httpClient := resty.New().
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
SetHeader("Accept", "application/json")
err = api.CallBatchDeleteSecretsByWorkspaceAndEnv(httpClient, request)
if err != nil {
util.HandleError(err, "Unable to complete your batch delete request")
}
fmt.Printf("secret name(s) [%v] have been deleted from your project \n", strings.Join(args, ", "))
Telemetry.CaptureEvent("cli-command:secrets delete", posthog.NewProperties().Set("secretCount", len(args)).Set("version", util.CLI_VERSION))
Telemetry.CaptureEvent("cli-command:secrets delete", posthog.NewProperties().Set("secretCount", len(secrets)).Set("version", util.CLI_VERSION))
},
}
@ -629,15 +611,11 @@ func init() {
secretsCmd.AddCommand(secretsGetCmd)
secretsCmd.AddCommand(secretsSetCmd)
secretsSetCmd.Flags().String("path", "/", "get secrets within a folder path")
secretsSetCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
util.RequireLogin()
util.RequireLocalWorkspaceFile()
}
secretsDeleteCmd.Flags().String("type", "personal", "the type of secret to delete: personal or shared (default: personal)")
secretsDeleteCmd.Flags().String("path", "/", "get secrets within a folder path")
secretsCmd.AddCommand(secretsDeleteCmd)
secretsDeleteCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
util.RequireLogin()
@ -648,6 +626,5 @@ func init() {
secretsCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on")
secretsCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
secretsCmd.PersistentFlags().StringP("tags", "t", "", "filter secrets by tag slugs")
secretsCmd.Flags().String("path", "/", "get secrets within a folder path")
rootCmd.AddCommand(secretsCmd)
}

@ -5,10 +5,9 @@ import (
)
type UserCredentials struct {
Email string `json:"email"`
PrivateKey string `json:"privateKey"`
JTWToken string `json:"JTWToken"`
RefreshToken string `json:"RefreshToken"`
Email string `json:"email"`
PrivateKey string `json:"privateKey"`
JTWToken string `json:"JTWToken"`
}
// The file struct for Infisical config file
@ -64,5 +63,4 @@ type GetAllSecretsParameters struct {
InfisicalToken string
TagSlugs string
WorkspaceId string
SecretsPath string
}

@ -9,7 +9,6 @@ import (
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/go-resty/resty/v2"
"github.com/rs/zerolog/log"
)
type LoggedInUserDetails struct {
@ -97,20 +96,6 @@ func GetCurrentLoggedInUserDetails() (LoggedInUserDetails, error) {
}
isAuthenticated := api.CallIsAuthenticated(httpClient)
if !isAuthenticated {
accessTokenResponse, _ := api.CallGetNewAccessTokenWithRefreshToken(httpClient, userCreds.RefreshToken)
if accessTokenResponse.Token != "" {
isAuthenticated = true
userCreds.JTWToken = accessTokenResponse.Token
}
}
err = StoreUserCredsInKeyRing(&userCreds)
if err != nil {
log.Debug().Msg("unable to store your user credentials with new access token")
}
if !isAuthenticated {
return LoggedInUserDetails{
IsUserLoggedIn: true, // was logged in

@ -74,12 +74,23 @@ func ConfigContainsEmail(users []models.LoggedInUser, email string) bool {
}
func RequireLogin() {
// get the config file that stores the current logged in user email
configFile, _ := GetConfigFile()
currentUserDetails, err := GetCurrentLoggedInUserDetails()
if configFile.LoggedInUserEmail == "" {
if err != nil {
HandleError(err, "unable to retrieve your login details")
}
if !currentUserDetails.IsUserLoggedIn {
PrintErrorMessageAndExit("You must be logged in to run this command. To login, run [infisical login]")
}
if currentUserDetails.LoginExpired {
PrintErrorMessageAndExit("Your login expired, please login in again. To login, run [infisical login]")
}
if currentUserDetails.UserCredentials.Email == "" && currentUserDetails.UserCredentials.JTWToken == "" && currentUserDetails.UserCredentials.PrivateKey == "" {
PrintErrorMessageAndExit("One or more of your login details is empty. Please try logging in again via by running [infisical login]")
}
}
func RequireServiceToken() {

@ -34,7 +34,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.Singl
return nil, api.GetServiceTokenDetailsResponse{}, fmt.Errorf("unable to get service token details. [err=%v]", err)
}
encryptedSecrets, err := api.CallGetSecretsV3(httpClient, api.GetEncryptedSecretsV3Request{
encryptedSecrets, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
WorkspaceId: serviceTokenDetails.Workspace,
Environment: serviceTokenDetails.Environment,
})
@ -61,7 +61,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string) ([]models.Singl
return plainTextSecrets, serviceTokenDetails, nil
}
func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, workspaceId string, environmentName string, tagSlugs string, secretsPath string) ([]models.SingleEnvironmentVariable, error) {
func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, workspaceId string, environmentName string, tagSlugs string) ([]models.SingleEnvironmentVariable, error) {
httpClient := resty.New()
httpClient.SetAuthToken(JTWToken).
SetHeader("Accept", "application/json")
@ -102,17 +102,11 @@ func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, work
plainTextWorkspaceKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
getSecretsRequest := api.GetEncryptedSecretsV3Request{
encryptedSecrets, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
WorkspaceId: workspaceId,
Environment: environmentName,
// TagSlugs: tagSlugs,
}
if secretsPath != "" {
getSecretsRequest.SecretPath = secretsPath
}
encryptedSecrets, err := api.CallGetSecretsV3(httpClient, getSecretsRequest)
TagSlugs: tagSlugs,
})
if err != nil {
return nil, err
@ -168,7 +162,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
return nil, fmt.Errorf("unable to validate environment name because [err=%s]", err)
}
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, params.Environment, params.TagSlugs, params.SecretsPath)
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, params.Environment, params.TagSlugs)
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn)
backupSecretsEncryptionKey := []byte(loggedInUserDetails.UserCredentials.PrivateKey)[0:32]
@ -339,7 +333,7 @@ func OverrideSecrets(secrets []models.SingleEnvironmentVariable, secretType stri
return secretsToReturn
}
func GetPlainTextSecrets(key []byte, encryptedSecrets api.GetEncryptedSecretsV3Response) ([]models.SingleEnvironmentVariable, error) {
func GetPlainTextSecrets(key []byte, encryptedSecrets api.GetEncryptedSecretsV2Response) ([]models.SingleEnvironmentVariable, error) {
plainTextSecrets := []models.SingleEnvironmentVariable{}
for _, secret := range encryptedSecrets.Secrets {
// Decrypt key

@ -3,29 +3,49 @@ title: "Authentication"
description: "How to authenticate with the Infisical Public API"
---
The Public API accepts multiple modes of authentication being via [Infisical Token](/documentation/platform/token) or API Key.
## Essentials
The Public API accepts multiple modes of authentication being via API Key, Service Account credentials, or [Infisical Token](/documentation/platform/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.
- API Key: Provides full access to all endpoints representing the user without ability to encrypt/decrypt secrets for **E2EE** endpoints.
<Tabs>
<Tab title="Infisical Token">
The Infisical Token mode uses an Infisical Token to authenticate with the API.
<AccordionGroup>
<Accordion title="API Key">
The API key mode uses an API key to authenticate with the API.
To authenticate requests with Infisical using the Infisical Token, you must include your Infisical Token in the `Authorization` header of HTTP requests made to the platform with the value `Bearer <infisical_token>`.
To authenticate requests with Infisical using the API Key, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform.
You can obtain an Infisical Token in Project Settings > Service Tokens.
You can obtain an API key in User Settings > API Keys
![token add](../../images/project-token-add.png)
</Tab>
<Tab title="API Key">
The API key mode uses an API key to authenticate with the API.
![API key dashboard](../../images/api-key-dashboard.png)
![API key in personal settings](../../images/api-key-settings.png)
</Accordion>
<Accordion title="Service Account">
The Service Account mode uses an Access Key to authenticate with the API and a Public Key and Private Key to perform any cryptographic operations.
To authenticate requests with Infisical using the API Key, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform.
To authenticate requests with Infisical using the Access Key, you must include it in the `Authorization` header of HTTP requests made to the platform with the value `Bearer <access_key>`.
You can obtain an API key in User Settings > API Keys
You can create a Service Account in Organization Settings > Service Accounts
![API key dashboard](../../images/api-key-dashboard.png)
![API key in personal settings](../../images/api-key-settings.png)
</Tab>
</Tabs>
</Accordion>
<Accordion title="Infisical Token">
The Infisical Token mode uses an Infisical Token to authenticate with the API.
To authenticate requests with Infisical using the Infisical Token, you must include your Infisical Token in the `Authorization` header of HTTP requests made to the platform with the value `Bearer <infisical_token>`.
You can obtain an Infisical Token in Project Settings > Service Tokens.
![token add](../../images/project-token-add.png)
</Accordion>
</AccordionGroup>
## Use Cases
Depending on your use case, it may make sense to use one or another authentication mode:
- API Key (not recommended): Use if you need full access to the Public API without needing to access any secrets endpoints (because API keys can't encrypt/decrypt secrets).
- Service Account (recommeded): Use if you need access to multiple projects and environments in an organization; service accounts can generate short-lived access tokens, making them useful for some complex setups.
- Service Token (recommeded): Use if you need short-lived, scoped CRUD access to the secrets of a specific project and environment.

@ -0,0 +1,233 @@
---
title: "Create secret"
description: "How to add a secret using an Infisical Token scoped to a project and environment"
---
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.
- 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).
## Flow
1. [Get your Infisical Token data](/api-reference/endpoints/service-tokens/get) including a (encrypted) project key.
2. Decrypt the (encrypted) project key with the key from your Infisical Token.
3. Encrypt your secret with the project key
4. [Send (encrypted) secret to Infisical](/api-reference/endpoints/secrets/create)
## Example
<Tabs>
<Tab title="Javascript">
```js
const crypto = require('crypto');
const axios = require('axios');
const nacl = require('tweetnacl');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const BLOCK_SIZE_BYTES = 16;
const encrypt = ({ text, secret }) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
ciphertext,
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64')
};
}
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, 'base64')
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
return cleartext;
}
const createSecrets = async () => {
const serviceToken = '';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
const secretType = 'shared'; // 'shared' or 'personal'
const secretKey = 'some_key';
const secretValue = 'some_value';
const secretComment = 'some_comment';
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 3. Encrypt your secret with the project key
const {
ciphertext: secretKeyCiphertext,
iv: secretKeyIV,
tag: secretKeyTag
} = encrypt({
text: secretKey,
secret: projectKey
});
const {
ciphertext: secretValueCiphertext,
iv: secretValueIV,
tag: secretValueTag
} = encrypt({
text: secretValue,
secret: projectKey
});
const {
ciphertext: secretCommentCiphertext,
iv: secretCommentIV,
tag: secretCommentTag
} = encrypt({
text: secretComment,
secret: projectKey
});
// 4. Send (encrypted) secret to Infisical
await axios.post(
`${BASE_URL}/api/v3/secrets/${secretKey}`,
{
workspaceId: serviceTokenData.workspace,
environment: serviceTokenData.environment,
type: secretType,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
},
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
}
createSecrets();
```
</Tab>
<Tab title="Python">
```Python
import base64
import requests
from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes
BASE_URL = "https://app.infisical.com"
BLOCK_SIZE_BYTES = 16
def encrypt(text, secret):
iv = get_random_bytes(BLOCK_SIZE_BYTES)
secret = bytes(secret, "utf-8")
cipher = AES.new(secret, AES.MODE_GCM, iv)
ciphertext, tag = cipher.encrypt_and_digest(text.encode("utf-8"))
return {
"ciphertext": base64.standard_b64encode(ciphertext).decode("utf-8"),
"tag": base64.standard_b64encode(tag).decode("utf-8"),
"iv": base64.standard_b64encode(iv).decode("utf-8"),
}
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def create_secrets():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
secret_type = "shared" # "shared or "personal"
secret_key = "some_key"
secret_value = "some_value"
secret_comment = "some_comment"
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 3. Encrypt your secret with the project key
encrypted_key_data = encrypt(text=secret_key, secret=project_key)
encrypted_value_data = encrypt(text=secret_value, secret=project_key)
encrypted_comment_data = encrypt(text=secret_comment, secret=project_key)
# 4. Send (encrypted) secret to Infisical
requests.post(
f"{BASE_URL}/api/v3/secrets/{secret_key}",
json={
"workspaceId": service_token_data["workspace"],
"environment": service_token_data["environment"],
"type": secret_type,
"secretKeyCiphertext": encrypted_key_data["ciphertext"],
"secretKeyIV": encrypted_key_data["iv"],
"secretKeyTag": encrypted_key_data["tag"],
"secretValueCiphertext": encrypted_value_data["ciphertext"],
"secretValueIV": encrypted_value_data["iv"],
"secretValueTag": encrypted_value_data["tag"],
"secretCommentCiphertext": encrypted_comment_data["ciphertext"],
"secretCommentIV": encrypted_comment_data["iv"],
"secretCommentTag": encrypted_comment_data["tag"]
},
headers={"Authorization": f"Bearer {service_token}"},
)
create_secrets()
```
</Tab>
</Tabs>

@ -0,0 +1,94 @@
---
title: "Delete secret"
description: "How to delete a secret using an Infisical Token scoped to a project and environment"
---
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.
- 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).
## Example
<Tabs>
<Tab title="Javascript">
```js
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const deleteSecrets = async () => {
const serviceToken = 'your_service_token';
const secretType = 'shared' // 'shared' or 'personal'
const secretKey = 'some_key'
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. Delete secret from Infisical
await axios.delete(
`${BASE_URL}/api/v3/secrets/${secretKey}`,
{
workspaceId: serviceTokenData.workspace,
environment: serviceTokenData.environment,
type: secretType
},
{
headers: {
Authorization: `Bearer ${serviceToken}`
},
}
);
};
deleteSecrets();
```
</Tab>
<Tab title="Python">
```Python
import requests
BASE_URL = "https://app.infisical.com"
def delete_secrets():
service_token = "<your_service_token>"
secret_type = "shared" # "shared" or "personal"
secret_key = "some_key"
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Delete secret from Infisical
requests.delete(
f"{BASE_URL}/api/v2/secrets/{secret_key}",
json={
"workspaceId": service_token_data["workspace"],
"environment": service_token_data["environment"],
"type": secret_type
},
headers={"Authorization": f"Bearer {service_token}"},
)
delete_secrets()
```
</Tab>
</Tabs>
<Info>
If using an `API_KEY` to authenticate with the Infisical API, then you should include it in the `X_API_KEY` header.
</Info>

@ -1,176 +0,0 @@
---
title: "E2EE Disabled"
---
Using Infisical's API to read/write secrets with E2EE disabled allows you to create, update, and retrieve secrets
in plaintext. Effectively, this means each such secret operation only requires 1 HTTP call.
<AccordionGroup>
<Accordion title="Retrieve secrets">
Retrieve all secrets for an Infisical project and environment.
<Tabs>
<Tab title="cURL">
```bash
curl --location --request GET 'https://app.infisical.com/api/v3/secrets/raw?environment=environment&workspaceId=workspaceId' \
--header 'Authorization: Bearer serviceToken'
```
</Tab>
</Tabs>
<ParamField query="workspaceId" type="string" required>
The ID of the workspace
</ParamField>
<ParamField query="environment" type="string" required>
The environment slug
</ParamField>
<ParamField query="secretPath" type="string" default="/" optional>
Path to secrets in workspace
</ParamField>
</Accordion>
<Accordion title="Create secret">
Create a secret in Infisical.
<Tabs>
<Tab title="cURL">
```bash
curl --location --request POST 'https://app.infisical.com/api/v3/secrets/raw/secretName' \
--header 'Authorization: Bearer serviceToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"workspaceId": "workspaceId",
"environment": "environment",
"type": "shared",
"secretValue": "secretValue",
"secretPath": "/"
}'
```
</Tab>
</Tabs>
<ParamField path="secretName" type="string" required>
Name of secret to create
</ParamField>
<ParamField body="workspaceId" type="string" required>
The ID of the workspace
</ParamField>
<ParamField body="environment" type="string" required>
The environment slug
</ParamField>
<ParamField body="secretValue" type="string" required>
Value of secret
</ParamField>
<ParamField body="secretComment" type="string" optional>
Comment of secret
</ParamField>
<ParamField body="secretPath" type="string" default="/" optional>
Path to secret in workspace
</ParamField>
<ParamField query="type" type="string" optional default="shared">
The type of the secret. Valid options are “shared” or “personal”
</ParamField>
</Accordion>
<Accordion title="Retrieve secret">
Retrieve a secret from Infisical.
<Tabs>
<Tab title="cURL">
```bash
curl --location --request GET 'https://app.infisical.com/api/v3/secrets/raw/secretName?workspaceId=workspaceId&environment=environment' \
--header 'Authorization: Bearer serviceToken'
```
</Tab>
</Tabs>
<ParamField path="secretName" type="string" required>
Name of secret to retrieve
</ParamField>
<ParamField query="workspaceId" type="string" required>
The ID of the workspace
</ParamField>
<ParamField query="environment" type="string" required>
The environment slug
</ParamField>
<ParamField query="secretPath" type="string" default="/" optional>
Path to secrets in workspace
</ParamField>
<ParamField query="type" type="string" optional default="personal">
The type of the secret. Valid options are “shared” or “personal”
</ParamField>
</Accordion>
<Accordion title="Update secret">
Update an existing secret in Infisical.
<Tabs>
<Tab title="cURL">
```bash
curl --location --request PATCH 'https://app.infisical.com/api/v3/secrets/raw/secretName' \
--header 'Authorization: Bearer serviceToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"workspaceId": "workspaceId",
"environment": "environment",
"type": "shared",
"secretValue": "secretValue",
"secretPath": "/"
}'
```
</Tab>
</Tabs>
<ParamField path="secretName" type="string" required>
Name of secret to update
</ParamField>
<ParamField body="workspaceId" type="string" required>
The ID of the workspace
</ParamField>
<ParamField body="environment" type="string" required>
The environment slug
</ParamField>
<ParamField body="secretValue" type="string" required>
Value of secret
</ParamField>
<ParamField body="secretPath" type="string" default="/" optional>
Path to secret in workspace.
</ParamField>
<ParamField query="type" type="string" optional default="shared">
The type of the secret. Valid options are “shared” or “personal”
</ParamField>
</Accordion>
<Accordion title="Delete secret">
Delete a secret in Infisical.
<Tabs>
<Tab title="cURL">
```bash
curl --location --request DELETE 'https://app.infisical.com/api/v3/secrets/raw/secretName' \
--header 'Authorization: Bearer serviceToken' \
--header 'Content-Type: application/json' \
--data-raw '{
"workspaceId": "workspaceId",
"environment": "environment",
"type": "shared",
"secretPath": "/"
}'
```
</Tab>
</Tabs>
<ParamField path="secretName" type="string" required>
Name of secret to update
</ParamField>
<ParamField body="workspaceId" type="string" required>
The ID of the workspace
</ParamField>
<ParamField body="environment" type="string" required>
The environment slug
</ParamField>
<ParamField body="secretPath" type="string" default="/" optional>
Path to secret in workspace.
</ParamField>
<ParamField query="type" type="string" optional default="personal">
The type of the secret. Valid options are “shared” or “personal”
</ParamField>
</Accordion>
</AccordionGroup>

@ -1,858 +0,0 @@
---
title: "E2EE Enabled"
---
Using Infisical's API to read/write secrets with E2EE enabled allows you to create, update, and retrieve secrets
but requires you to perform client-side encryption/decryption operations. For this reason, we recommend using one of the available
SDKs instead.
<AccordionGroup>
<Accordion title="Retrieve secrets">
<Tabs>
<Tab title="Javascript">
Retrieve all secrets for an Infisical project and environment.
```js
const crypto = require('crypto');
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, 'base64')
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
return cleartext;
}
const getSecrets = async () => {
const serviceToken = 'your_service_token';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. Get secrets for your project and environment
const { data } = await axios.get(
`${BASE_URL}/api/v3/secrets?${new URLSearchParams({
environment: serviceTokenData.environment,
workspaceId: serviceTokenData.workspace
})}`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
const encryptedSecrets = data.secrets;
// 3. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 4. Decrypt the (encrypted) secrets
const secrets = encryptedSecrets.map((secret) => {
const secretKey = decrypt({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
secret: projectKey
});
const secretValue = decrypt({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
secret: projectKey
});
return ({
secretKey,
secretValue
});
});
console.log('secrets: ', secrets);
}
getSecrets();
```
</Tab>
<Tab title="Python">
```Python
import requests
import base64
from Cryptodome.Cipher import AES
BASE_URL = "http://app.infisical.com"
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def get_secrets():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Get secrets for your project and environment
data = requests.get(
f"{BASE_URL}/api/v3/secrets",
params={
"environment": service_token_data["environment"],
"workspaceId": service_token_data["workspace"],
},
headers={"Authorization": f"Bearer {service_token}"},
).json()
encrypted_secrets = data["secrets"]
# 3. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 4. Decrypt the (encrypted) secrets
secrets = []
for secret in encrypted_secrets:
secret_key = decrypt(
ciphertext=secret["secretKeyCiphertext"],
iv=secret["secretKeyIV"],
tag=secret["secretKeyTag"],
secret=project_key,
)
secret_value = decrypt(
ciphertext=secret["secretValueCiphertext"],
iv=secret["secretValueIV"],
tag=secret["secretValueTag"],
secret=project_key,
)
secrets.append(
{
"secret_key": secret_key,
"secret_value": secret_value,
}
)
print("secrets:", secrets)
get_secrets()
```
</Tab>
</Tabs>
</Accordion>
<Accordion title="Create secret">
<Tabs>
<Tab title="Javascript">
Create a secret in Infisical.
```js
const crypto = require('crypto');
const axios = require('axios');
const nacl = require('tweetnacl');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const BLOCK_SIZE_BYTES = 16;
const encrypt = ({ text, secret }) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
ciphertext,
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64')
};
}
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, 'base64')
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
return cleartext;
}
const createSecrets = async () => {
const serviceToken = '';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
const secretType = 'shared'; // 'shared' or 'personal'
const secretKey = 'some_key';
const secretValue = 'some_value';
const secretComment = 'some_comment';
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 3. Encrypt your secret with the project key
const {
ciphertext: secretKeyCiphertext,
iv: secretKeyIV,
tag: secretKeyTag
} = encrypt({
text: secretKey,
secret: projectKey
});
const {
ciphertext: secretValueCiphertext,
iv: secretValueIV,
tag: secretValueTag
} = encrypt({
text: secretValue,
secret: projectKey
});
const {
ciphertext: secretCommentCiphertext,
iv: secretCommentIV,
tag: secretCommentTag
} = encrypt({
text: secretComment,
secret: projectKey
});
// 4. Send (encrypted) secret to Infisical
await axios.post(
`${BASE_URL}/api/v3/secrets/${secretKey}`,
{
workspaceId: serviceTokenData.workspace,
environment: serviceTokenData.environment,
type: secretType,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
},
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
}
createSecrets();
```
</Tab>
<Tab title="Python">
```Python
import base64
import requests
from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes
BASE_URL = "https://app.infisical.com"
BLOCK_SIZE_BYTES = 16
def encrypt(text, secret):
iv = get_random_bytes(BLOCK_SIZE_BYTES)
secret = bytes(secret, "utf-8")
cipher = AES.new(secret, AES.MODE_GCM, iv)
ciphertext, tag = cipher.encrypt_and_digest(text.encode("utf-8"))
return {
"ciphertext": base64.standard_b64encode(ciphertext).decode("utf-8"),
"tag": base64.standard_b64encode(tag).decode("utf-8"),
"iv": base64.standard_b64encode(iv).decode("utf-8"),
}
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def create_secrets():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
secret_type = "shared" # "shared or "personal"
secret_key = "some_key"
secret_value = "some_value"
secret_comment = "some_comment"
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 3. Encrypt your secret with the project key
encrypted_key_data = encrypt(text=secret_key, secret=project_key)
encrypted_value_data = encrypt(text=secret_value, secret=project_key)
encrypted_comment_data = encrypt(text=secret_comment, secret=project_key)
# 4. Send (encrypted) secret to Infisical
requests.post(
f"{BASE_URL}/api/v3/secrets/{secret_key}",
json={
"workspaceId": service_token_data["workspace"],
"environment": service_token_data["environment"],
"type": secret_type,
"secretKeyCiphertext": encrypted_key_data["ciphertext"],
"secretKeyIV": encrypted_key_data["iv"],
"secretKeyTag": encrypted_key_data["tag"],
"secretValueCiphertext": encrypted_value_data["ciphertext"],
"secretValueIV": encrypted_value_data["iv"],
"secretValueTag": encrypted_value_data["tag"],
"secretCommentCiphertext": encrypted_comment_data["ciphertext"],
"secretCommentIV": encrypted_comment_data["iv"],
"secretCommentTag": encrypted_comment_data["tag"]
},
headers={"Authorization": f"Bearer {service_token}"},
)
create_secrets()
```
</Tab>
</Tabs>
</Accordion>
<Accordion title="Retrieve secret">
<Tabs>
<Tab title="Javascript">
Retrieve a secret from Infisical.
```js
const crypto = require('crypto');
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, 'base64')
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
return cleartext;
}
const getSecret = async () => {
const serviceToken = 'your_service_token';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
const secretType = 'shared' // 'shared' or 'personal'
const secretKey = 'some_key';
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. Get the secret from your project and environment
const { data } = await axios.get(
`${BASE_URL}/api/v3/secrets/${secretKey}?${new URLSearchParams({
environment: serviceTokenData.environment,
workspaceId: serviceTokenData.workspace,
type: secretType // optional, defaults to 'shared'
})}`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
const encryptedSecret = data.secret;
// 3. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 4. Decrypt the (encrypted) secret value
const secretValue = decrypt({
ciphertext: encryptedSecret.secretValueCiphertext,
iv: encryptedSecret.secretValueIV,
tag: encryptedSecret.secretValueTag,
secret: projectKey
});
console.log('secret: ', ({
secretKey,
secretValue
}));
}
getSecret();
```
</Tab>
<Tab title="Python">
```Python
import requests
import base64
from Cryptodome.Cipher import AES
BASE_URL = "http://app.infisical.com"
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def get_secret():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
secret_type = "shared" # "shared" or "personal"
secret_key = "some_key"
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Get secret from your project and environment
data = requests.get(
f"{BASE_URL}/api/v3/secrets/{secret_key}",
params={
"environment": service_token_data["environment"],
"workspaceId": service_token_data["workspace"],
"type": secret_type # optional, defaults to "shared"
},
headers={"Authorization": f"Bearer {service_token}"},
).json()
encrypted_secret = data["secret"]
# 3. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 4. Decrypt the (encrypted) secret value
secret_value = decrypt(
ciphertext=encrypted_secret["secretValueCiphertext"],
iv=encrypted_secret["secretValueIV"],
tag=encrypted_secret["secretValueTag"],
secret=project_key,
)
print("secret: ", {
"secret_key": secret_key,
"secret_value": secret_value
})
get_secret()
```
</Tab>
</Tabs>
</Accordion>
<Accordion title="Update secret">
<Tabs>
<Tab title="Javascript">
Update an existing secret in Infisical.
```js
const crypto = require('crypto');
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const BLOCK_SIZE_BYTES = 16;
const encrypt = ({ text, secret }) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
ciphertext,
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64')
};
}
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, 'base64')
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
return cleartext;
}
const updateSecrets = async () => {
const serviceToken = 'your_service_token';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
const secretType = 'shared' // 'shared' or 'personal'
const secretKey = 'some_key';
const secretValue = 'updated_value';
const secretComment = 'updated_comment';
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 3. Encrypt your updated secret with the project key
const {
ciphertext: secretKeyCiphertext,
iv: secretKeyIV,
tag: secretKeyTag
} = encrypt({
text: secretKey,
secret: projectKey
});
const {
ciphertext: secretValueCiphertext,
iv: secretValueIV,
tag: secretValueTag
} = encrypt({
text: secretValue,
secret: projectKey
});
const {
ciphertext: secretCommentCiphertext,
iv: secretCommentIV,
tag: secretCommentTag
} = encrypt({
text: secretComment,
secret: projectKey
});
// 4. Send (encrypted) updated secret to Infisical
await axios.patch(
`${BASE_URL}/api/v3/secrets/${secretKey}`,
{
workspaceId: serviceTokenData.workspace,
environment: serviceTokenData.environment,
type: secretType,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
},
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
}
updateSecrets();
```
</Tab>
<Tab title="Python">
```Python
import base64
import requests
from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes
BASE_URL = "https://app.infisical.com"
BLOCK_SIZE_BYTES = 16
def encrypt(text, secret):
iv = get_random_bytes(BLOCK_SIZE_BYTES)
secret = bytes(secret, "utf-8")
cipher = AES.new(secret, AES.MODE_GCM, iv)
ciphertext, tag = cipher.encrypt_and_digest(text.encode("utf-8"))
return {
"ciphertext": base64.standard_b64encode(ciphertext).decode("utf-8"),
"tag": base64.standard_b64encode(tag).decode("utf-8"),
"iv": base64.standard_b64encode(iv).decode("utf-8"),
}
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def update_secret():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
secret_type = "shared" # "shared" or "personal"
secret_key = "some_key"
secret_value = "updated_value"
secret_comment = "updated_comment"
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 3. Encrypt your updated secret with the project key
encrypted_key_data = encrypt(text=secret_key, secret=project_key)
encrypted_value_data = encrypt(text=secret_value, secret=project_key)
encrypted_comment_data = encrypt(text=secret_comment, secret=project_key)
# 4. Send (encrypted) updated secret to Infisical
requests.patch(
f"{BASE_URL}/api/v3/secrets/{secret_key}",
json={
"workspaceId": service_token_data["workspace"],
"environment": service_token_data["environment"],
"type": secret_type,
"secretKeyCiphertext": encrypted_key_data["ciphertext"],
"secretKeyIV": encrypted_key_data["iv"],
"secretKeyTag": encrypted_key_data["tag"],
"secretValueCiphertext": encrypted_value_data["ciphertext"],
"secretValueIV": encrypted_value_data["iv"],
"secretValueTag": encrypted_value_data["tag"],
"secretCommentCiphertext": encrypted_comment_data["ciphertext"],
"secretCommentIV": encrypted_comment_data["iv"],
"secretCommentTag": encrypted_comment_data["tag"]
},
headers={"Authorization": f"Bearer {service_token}"},
)
update_secret()
```
</Tab>
</Tabs>
</Accordion>
<Accordion title="Delete secret">
<Tabs>
<Tab title="Javascript">
Delete a secret in Infisical.
```js
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const deleteSecrets = async () => {
const serviceToken = 'your_service_token';
const secretType = 'shared' // 'shared' or 'personal'
const secretKey = 'some_key'
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. Delete secret from Infisical
await axios.delete(
`${BASE_URL}/api/v3/secrets/${secretKey}`,
{
workspaceId: serviceTokenData.workspace,
environment: serviceTokenData.environment,
type: secretType
},
{
headers: {
Authorization: `Bearer ${serviceToken}`
},
}
);
};
deleteSecrets();
```
</Tab>
<Tab title="Python">
```Python
import requests
BASE_URL = "https://app.infisical.com"
def delete_secrets():
service_token = "<your_service_token>"
secret_type = "shared" # "shared" or "personal"
secret_key = "some_key"
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Delete secret from Infisical
requests.delete(
f"{BASE_URL}/api/v2/secrets/{secret_key}",
json={
"workspaceId": service_token_data["workspace"],
"environment": service_token_data["environment"],
"type": secret_type
},
headers={"Authorization": f"Bearer {service_token}"},
)
delete_secrets()
```
</Tab>
</Tabs>
<Info>
If using an `API_KEY` to authenticate with the Infisical API, then you should include it in the `X_API_KEY` header.
</Info>
</Accordion>
</AccordionGroup>

@ -1,54 +0,0 @@
---
title: "Note on E2EE"
---
Each project in Infisical can have **End-to-End Encryption (E2EE)** enabled or disabled.
By default, all projects have **E2EE** enabled which means the server is not able to decrypt any values because all secret encryption/decryption operations occur on the client-side; this can be (optionally) disabled. However, this has limitations around functionality and ease-of-use:
- You cannot make HTTP calls to Infisical to read/write secrets in plaintext.
- You cannot leverage non-E2EE features like native integrations and in-platform automations like dynamic secrets and secret rotation.
<CardGroup cols={2}>
<Card
title="E2EE Disabled"
href="/api-reference/overview/examples/e2ee-disabled"
icon="shield-halved"
color="#3c8639"
>
Example read/write secrets without client-side encryption/decryption
</Card>
<Card
href="/api-reference/overview/examples/e2ee-enabled"
title="E2EE Enabled"
icon="shield"
color="#3775a9"
>
Example read/write secrets with client-side encryption/decryption
</Card>
</CardGroup>
## FAQ
<AccordionGroup>
<Accordion title="Should I have E2EE enabled or disabled?">
We recommend starting with having **E2EE** enabled and disabling it if:
- You're self-hosting Infisical, so having your instance of Infisical be able to read your secrets isn't an issue.
- You want an easier way to read/write secrets with Infisical.
- You need more power out of non-E2EE features such as secret rotation, dynamic secrets, etc.
</Accordion>
<Accordion title="How can I enable/disable E2EE?">
You can enable/disable E2EE for your project in Infisical in the Project Settings.
</Accordion>
<Accordion title="Is disabling E2EE secure?">
It is secure and in fact how most vendors in our industry are able to offer features like secret rotation. In this mode, secrets are encrypted at rest by
a series of keys, secured ultimately by a top-level `ROOT_ENCRYPTION_KEY` located on the server.
If you're concerned about Infisical Cloud's ability to read your secrets, then you may wish to
use it with **E2EE** enabled or self-host Infisical on your own infrastructure and disable E2EE there.
As an organization, we do not read any customer secrets without explicit permission; access to the `ROOT_ENCRYPTION_KEY` is restricted to one individual in the organization.
</Accordion>
</AccordionGroup>

@ -0,0 +1,180 @@
---
title: "Retrieve secret"
description: "How to get a secret using an Infisical Token scoped to a project and environment"
---
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.
- 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).
## Flow
1. [Get your Infisical Token data](/api-reference/endpoints/service-tokens/get) including a (encrypted) project key.
2. [Get the secret from your project and environment](/api-reference/endpoints/secrets/read-one).
3. Decrypt the (encrypted) project key with the key from your Infisical Token.
4. Decrypt the (encrypted) secret
## Example
<Tabs>
<Tab title="Javascript">
```js
const crypto = require('crypto');
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, 'base64')
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
return cleartext;
}
const getSecret = async () => {
const serviceToken = 'your_service_token';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
const secretType = 'shared' // 'shared' or 'personal'
const secretKey = 'some_key';
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. Get the secret from your project and environment
const { data } = await axios.get(
`${BASE_URL}/api/v3/secrets/${secretKey}?${new URLSearchParams({
environment: serviceTokenData.environment,
workspaceId: serviceTokenData.workspace,
type: secretType // optional, defaults to 'shared'
})}`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
const encryptedSecret = data.secret;
// 3. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 4. Decrypt the (encrypted) secret value
const secretValue = decrypt({
ciphertext: encryptedSecret.secretValueCiphertext,
iv: encryptedSecret.secretValueIV,
tag: encryptedSecret.secretValueTag,
secret: projectKey
});
console.log('secret: ', ({
secretKey,
secretValue
}));
}
getSecret();
```
</Tab>
<Tab title="Python">
```Python
import requests
import base64
from Cryptodome.Cipher import AES
BASE_URL = "http://app.infisical.com"
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def get_secret():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
secret_type = "shared" # "shared" or "personal"
secret_key = "some_key"
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Get secret from your project and environment
data = requests.get(
f"{BASE_URL}/api/v3/secrets/{secret_key}",
params={
"environment": service_token_data["environment"],
"workspaceId": service_token_data["workspace"],
"type": secret_type # optional, defaults to "shared"
},
headers={"Authorization": f"Bearer {service_token}"},
).json()
encrypted_secret = data["secret"]
# 3. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 4. Decrypt the (encrypted) secret value
secret_value = decrypt(
ciphertext=encrypted_secret["secretValueCiphertext"],
iv=encrypted_secret["secretValueIV"],
tag=encrypted_secret["secretValueTag"],
secret=project_key,
)
print("secret: ", {
"secret_key": secret_key,
"secret_value": secret_value
})
get_secret()
```
</Tab>
</Tabs>

@ -0,0 +1,195 @@
---
title: "Retrieve secrets"
description: "How to get all secrets using an Infisical Token scoped to a project and environment"
---
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.
- 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).
## Flow
1. [Get your Infisical Token data](/api-reference/endpoints/service-tokens/get) including a (encrypted) project key.
2. [Get secrets for your project and environment](/api-reference/endpoints/secrets/read).
3. Decrypt the (encrypted) project key with the key from your Infisical Token.
4. Decrypt the (encrypted) secrets
## Example
<Tabs>
<Tab title="Javascript">
```js
const crypto = require('crypto');
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, 'base64')
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
return cleartext;
}
const getSecrets = async () => {
const serviceToken = 'your_service_token';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. Get secrets for your project and environment
const { data } = await axios.get(
`${BASE_URL}/api/v3/secrets?${new URLSearchParams({
environment: serviceTokenData.environment,
workspaceId: serviceTokenData.workspace
})}`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
const encryptedSecrets = data.secrets;
// 3. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 4. Decrypt the (encrypted) secrets
const secrets = encryptedSecrets.map((secret) => {
const secretKey = decrypt({
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
secret: projectKey
});
const secretValue = decrypt({
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
secret: projectKey
});
return ({
secretKey,
secretValue
});
});
console.log('secrets: ', secrets);
}
getSecrets();
```
</Tab>
<Tab title="Python">
```Python
import requests
import base64
from Cryptodome.Cipher import AES
BASE_URL = "http://app.infisical.com"
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def get_secrets():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Get secrets for your project and environment
data = requests.get(
f"{BASE_URL}/api/v3/secrets",
params={
"environment": service_token_data["environment"],
"workspaceId": service_token_data["workspace"],
},
headers={"Authorization": f"Bearer {service_token}"},
).json()
encrypted_secrets = data["secrets"]
# 3. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 4. Decrypt the (encrypted) secrets
secrets = []
for secret in encrypted_secrets:
secret_key = decrypt(
ciphertext=secret["secretKeyCiphertext"],
iv=secret["secretKeyIV"],
tag=secret["secretKeyTag"],
secret=project_key,
)
secret_value = decrypt(
ciphertext=secret["secretValueCiphertext"],
iv=secret["secretValueIV"],
tag=secret["secretValueTag"],
secret=project_key,
)
secrets.append(
{
"secret_key": secret_key,
"secret_value": secret_value,
}
)
print("secrets:", secrets)
get_secrets()
```
</Tab>
</Tabs>

@ -0,0 +1,229 @@
---
title: "Update secret"
description: "How to update a secret using an Infisical Token scoped to a project and environment"
---
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.
- 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).
## Flow
1. [Get your Infisical Token data](/api-reference/endpoints/service-tokens/get) including a (encrypted) project key.
2. Decrypt the (encrypted) project key with the key from your Infisical Token.
3. Encrypt your updated secret with the project key
4. [Send (encrypted) updated secret to Infical](/api-reference/endpoints/secrets/update)
## Example
<Tabs>
<Tab title="Javascript">
```js
const crypto = require('crypto');
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const BLOCK_SIZE_BYTES = 16;
const encrypt = ({ text, secret }) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
ciphertext,
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64')
};
}
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
Buffer.from(iv, 'base64')
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
return cleartext;
}
const updateSecrets = async () => {
const serviceToken = 'your_service_token';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
const secretType = 'shared' // 'shared' or 'personal'
const secretKey = 'some_key';
const secretValue = 'updated_value';
const secretComment = 'updated_comment';
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 3. Encrypt your updated secret with the project key
const {
ciphertext: secretKeyCiphertext,
iv: secretKeyIV,
tag: secretKeyTag
} = encrypt({
text: secretKey,
secret: projectKey
});
const {
ciphertext: secretValueCiphertext,
iv: secretValueIV,
tag: secretValueTag
} = encrypt({
text: secretValue,
secret: projectKey
});
const {
ciphertext: secretCommentCiphertext,
iv: secretCommentIV,
tag: secretCommentTag
} = encrypt({
text: secretComment,
secret: projectKey
});
// 4. Send (encrypted) updated secret to Infisical
await axios.patch(
`${BASE_URL}/api/v3/secrets/${secretKey}`,
{
workspaceId: serviceTokenData.workspace,
environment: serviceTokenData.environment,
type: secretType,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
},
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
}
updateSecrets();
```
</Tab>
<Tab title="Python">
```Python
import base64
import requests
from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes
BASE_URL = "https://app.infisical.com"
BLOCK_SIZE_BYTES = 16
def encrypt(text, secret):
iv = get_random_bytes(BLOCK_SIZE_BYTES)
secret = bytes(secret, "utf-8")
cipher = AES.new(secret, AES.MODE_GCM, iv)
ciphertext, tag = cipher.encrypt_and_digest(text.encode("utf-8"))
return {
"ciphertext": base64.standard_b64encode(ciphertext).decode("utf-8"),
"tag": base64.standard_b64encode(tag).decode("utf-8"),
"iv": base64.standard_b64encode(iv).decode("utf-8"),
}
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def update_secret():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
secret_type = "shared" # "shared" or "personal"
secret_key = "some_key"
secret_value = "updated_value"
secret_comment = "updated_comment"
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 3. Encrypt your updated secret with the project key
encrypted_key_data = encrypt(text=secret_key, secret=project_key)
encrypted_value_data = encrypt(text=secret_value, secret=project_key)
encrypted_comment_data = encrypt(text=secret_comment, secret=project_key)
# 4. Send (encrypted) updated secret to Infisical
requests.patch(
f"{BASE_URL}/api/v3/secrets/{secret_key}",
json={
"workspaceId": service_token_data["workspace"],
"environment": service_token_data["environment"],
"type": secret_type,
"secretKeyCiphertext": encrypted_key_data["ciphertext"],
"secretKeyIV": encrypted_key_data["iv"],
"secretKeyTag": encrypted_key_data["tag"],
"secretValueCiphertext": encrypted_value_data["ciphertext"],
"secretValueIV": encrypted_value_data["iv"],
"secretValueTag": encrypted_value_data["tag"],
"secretCommentCiphertext": encrypted_comment_data["ciphertext"],
"secretCommentIV": encrypted_comment_data["iv"],
"secretCommentTag": encrypted_comment_data["tag"]
},
headers={"Authorization": f"Bearer {service_token}"},
)
update_secret()
```
</Tab>
</Tabs>

@ -8,6 +8,31 @@ rotating credentials, or for integrating secret management into a larger system.
With the Public API, users can create, read, update, and delete secrets, as well as manage access control, query audit logs, and more.
<Warning>
We highly recommend using one of the available SDKs when working with the Infisical API.
If you decide to make your own requests using the API reference instead, be prepared for a steeper learning curve and more manual work.
</Warning>
<Warning>
In April 2023, we added the capability for users to query for secrets by name to improve the user experience of Infisical. If your project was created prior to April 2023, please read and follow the section on [blind indices](./blind-indices) and how to enable them for better usage of Infisical.
</Warning>
</Warning>
## Concepts
Using Infisical's API to manage secrets requires a basic understanding of the system and its underlying cryptography detailed [here](/security/overview). A few key points:
- Each user has a public/private key pair that is stored with the platform; private keys are encrypted locally by protected keys that are encrypted by keys derived from Argon2id applied to the user's password before being sent off to the server during the account signup process.
- Each (encrypted) secret belongs to a project and environment.
- Each project has an (encrypted) project key used to encrypt the secrets within that project; Infisical stores copies of the project key, for each member of that project, encrypted under each member's public key.
- Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing.
- Infisical Tokens contain a symmetric key that can be used to decrypt a copy of a project key from the [call to get the Infisical Token data](/api-reference/endpoints/service-tokens/get).
- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations.
<Info>
Infisical's system requires that secrets be encrypted/decrypted on the
client-side to maintain E2EE. We strongly recommend you read up on the system
prior to using the Infisical API. The (opt-in) ability to retrieve secrets
back in decrypted format if you choose to share secrets with Infisical is on
our roadmap.
</Info>

@ -1,59 +0,0 @@
---
title: "REST API"
---
Infisical's Public (REST) API is the most flexible, platform-agnostic way to read/write secrets for your application.
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.
To keep it simple, we're going to fetch secrets from the API with **End-to-End Encryption (E2EE)** disabled.
<Note>
It's possible to use the API with **E2EE** enabled but this means learning about how encryption works with Infisical and performing client-side encryption/decryption operations yourself.
yourself.
If **E2EE** is a must for your team, we recommend either using one of the [Infisical SDKs](/documentation/getting-started/sdks) or checking out the [examples for E2EE](/api-reference/overview/examples/e2ee-disabled).
</Note>
## Configuration
Head to your Project Settings, where you created your service token, and un-check the **E2EE** setting.
## Retrieve Secret
Retrieve a secret from the project and environment in Infisical scoped to your service token by making a HTTP request with the following format/details:
```bash
curl --location --request GET 'https://app.infisical.com/api/v3/secrets/raw/secretName?workspaceId=workspaceId&environment=environment' \
--header 'Authorization: Bearer serviceToken'
```
<ParamField path="secretName" type="string" required>
Name of secret to retrieve
</ParamField>
<ParamField query="workspaceId" type="string" required>
The ID of the workspace
</ParamField>
<ParamField query="environment" type="string" required>
The environment slug
</ParamField>
<ParamField query="secretPath" type="string" default="/" optional>
Path to secrets in workspace
</ParamField>
<ParamField query="type" type="string" optional default="personal">
The type of the secret. Valid options are “shared” or “personal”
</ParamField>
Depending on your application requirements, you may wish to use Infisical's API in different ways such as by retaining **E2EE**
or fetching multiple secrets at once instead of one at a time.
Whatever the case, we recommend glossing over the [API Examples](/api-reference/overview/examples/note)
to gain a deeper understanding of how you to best leverage the Infisical API for your use-case.
See also:
- Explore the [API Examples](/api-reference/overview/examples/note)
- [API Reference](/api-reference/overview/introduction)

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