1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-29 22:02:57 +00:00

Compare commits

..

118 Commits

Author SHA1 Message Date
a913cd97a4 fix: upgrade @aws-sdk/client-secrets-manager from 3.312.0 to 3.317.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.312.0 to 3.317.0.

See this package in npm:
https://www.npmjs.com/package/@aws-sdk/client-secrets-manager

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-11 22:13:39 +00:00
781e0b24c8 add docs for spring boot maven 2023-05-10 21:08:38 -04:00
28de8cddd7 Merge pull request from Infisical/snyk-upgrade-9b34558b947330a3bd25eec1f2f2e55c
[Snyk] Upgrade aws-sdk from 2.1358.0 to 2.1360.0
2023-05-10 10:31:42 -04:00
ed3e53f9a3 Merge pull request from Infisical/snyk-upgrade-5eadfb75ba47539ba48a83549a83d185
[Snyk] Upgrade @godaddy/terminus from 4.11.2 to 4.12.0
2023-05-10 10:31:30 -04:00
9cb4d5abb7 improve docker compose and add standalone docs 2023-05-09 22:07:48 -04:00
efdd1e64c4 Merge pull request from Infisical/snyk-upgrade-852b174d6fb41e4afff7bae352b8818d
[Snyk] Upgrade @fortawesome/react-fontawesome from 0.1.19 to 0.2.0
2023-05-09 17:52:12 -07:00
5b3be6063f Merge pull request from akhilmhdh/feat/new-login
refactor(ui): changed frontend to normal i18n without SSR
2023-05-09 17:50:10 -07:00
12c399d4a9 fix typo in k8 docs 2023-05-09 17:34:20 -04:00
ecd17e1d6d refine k8 deploy docs 2023-05-09 17:32:57 -04:00
fb4c811414 update detailed kubernetes helm docs 2023-05-09 16:41:20 -04:00
3561c589b1 refactor(ui): changed frontend to normal i18n without SSR 2023-05-09 23:28:23 +05:30
420d71d923 add membership validate to folder get 2023-05-09 10:23:41 -04:00
3db5c040c3 fix: upgrade @godaddy/terminus from 4.11.2 to 4.12.0
Snyk has created this PR to upgrade @godaddy/terminus from 4.11.2 to 4.12.0.

See this package in npm:
https://www.npmjs.com/package/@godaddy/terminus

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-09 08:42:48 +00:00
b4f336a5bb fix: upgrade aws-sdk from 2.1358.0 to 2.1360.0
Snyk has created this PR to upgrade aws-sdk from 2.1358.0 to 2.1360.0.

See this package in npm:
https://www.npmjs.com/package/aws-sdk

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-09 08:42:43 +00:00
43e61c94f0 get folder by id 2023-05-08 21:01:26 -04:00
69fa4a80c5 update check for CLI update 2023-05-08 16:43:28 -04:00
cf9e8b8a6b patch login bug when override empty 2023-05-08 16:09:57 -04:00
c6d5498a42 add dangling prefix for aur 2023-05-08 10:59:24 -04:00
ad7972e7e1 Merge pull request from Infisical/snyk-upgrade-5957efd1bee99a5df1416be8165fe61a
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.309.0 to 3.312.0
2023-05-07 19:13:46 -04:00
c6d8f24968 Merge pull request from Infisical/snyk-upgrade-2f346d9e1a2c15f0c0929d23f00e36e5
[Snyk] Upgrade @sentry/tracing from 7.47.0 to 7.48.0
2023-05-07 19:13:36 -04:00
d8ff0bef0d add semantic-version gh action 2023-05-07 19:09:24 -04:00
29b96246b9 add back osx cross build 2023-05-07 17:00:12 -04:00
8503c9355b add completions for aurs 2023-05-07 16:55:42 -04:00
ddf0a272f6 back out of dir for archive file 2023-05-07 15:41:23 -04:00
e3980f8666 bring back completions and man page for cli 2023-05-07 15:27:19 -04:00
d52534b185 Dashboard UI update 2023-05-07 12:24:40 -07:00
db07a033e1 Add filter query param to getApps for Netlify integration 2023-05-06 22:19:00 +03:00
3c71bcaa8d fix: upgrade @sentry/tracing from 7.47.0 to 7.48.0
Snyk has created this PR to upgrade @sentry/tracing from 7.47.0 to 7.48.0.

See this package in npm:
https://www.npmjs.com/package/@sentry/tracing

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-06 01:50:17 +00:00
476d0be101 Merge pull request from akhilmhdh/fix/token-cache-change
fix(ui): resolved token missing due to cache invalidation
2023-05-05 14:53:23 -07:00
2eff7b6128 set internal port for render 2023-05-05 16:26:03 -04:00
d8a781af1f remove health check 2023-05-05 16:06:28 -04:00
8b42f4f998 typo in doc 2023-05-05 15:50:19 -04:00
da127a3c0a update step 2 of fly.io 2023-05-05 15:48:03 -04:00
d4aa75a182 update self hosting docs layout 2023-05-05 15:42:41 -04:00
d097003e9b set sync=false for mongo db url render 2023-05-05 14:37:24 -04:00
b615a5084e update render IaC template 2023-05-05 14:31:11 -04:00
379f086828 add render IaC 2023-05-05 14:28:25 -04:00
f11a7d0f87 fix(ui): resolved token missing due to cache invalidation 2023-05-05 21:56:26 +05:30
f5aeb85c62 rename standalone docker image 2023-05-05 08:43:57 -04:00
2966aa6eda Merge pull request from akhilmhdh/feat/dashboard-v2
feat(ui): fixed lagging issues with new dashboard
2023-05-04 15:38:00 -07:00
b1f2515731 fixed minor bugs and updated the design 2023-05-04 15:31:06 -07:00
c5094ec37d patch copy invite link 2023-05-04 18:27:09 -04:00
6c745f617d add org id to complete invite link 2023-05-04 17:50:36 -04:00
82995fbd02 feat(ui): fixed lagging issues with new dashboard 2023-05-04 20:45:26 +05:30
8d09a45454 fix: upgrade @aws-sdk/client-secrets-manager from 3.309.0 to 3.312.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.309.0 to 3.312.0.

See this package in npm:
https://www.npmjs.com/package/@aws-sdk/client-secrets-manager

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-04 00:03:35 +00:00
38f578c4ae Fixed the issue with favicon 2023-05-03 16:06:50 -07:00
65b12eee5e update standlone gwf 2023-05-03 17:22:32 -04:00
9043db4727 add github workflow to release standalone app 2023-05-03 17:14:24 -04:00
0eceeb6aa9 create standalone infisical docker file 2023-05-03 16:57:19 -04:00
2d2bbbd0ad Update README.md 2023-05-03 15:51:15 -04:00
c9b4e11539 add note to ENCRYPTION_KEY to indicate non prod 2023-05-03 15:48:20 -04:00
fd4ea97e18 remove default smtp since Infisical no longer requires SMTP 2023-05-03 15:45:16 -04:00
49d2ecc460 switch install command to run prod docker compose 2023-05-03 15:41:11 -04:00
ca31a70032 Merge pull request from Infisical/gmail-smtp-support
Add support for Gmail SMTP + docs
2023-05-03 18:34:49 +03:00
3334338eaa Add Gmail SMTP option + docs 2023-05-03 18:28:20 +03:00
6d5e281811 add helm version requirement 2023-05-02 11:11:41 -04:00
87d36ac47a Merge pull request from Infisical/snyk-upgrade-78c720000b2ea0a6b50d66fd8a2a84f9
[Snyk] Upgrade bigint-conversion from 2.3.0 to 2.4.0
2023-05-01 20:44:29 -04:00
b72e1198df Merge pull request from Infisical/snyk-upgrade-965bd6eb4d7e75fef5c7e8cb5d4a3e5a
[Snyk] Upgrade mongoose from 6.10.4 to 6.10.5
2023-05-01 20:44:14 -04:00
837ea2ef40 add sem var to docker image workflow 2023-05-01 20:43:28 -04:00
b462ca3e89 Patch missing function invocation for GitLab envar 2023-05-01 22:38:01 +03:00
f639f682c9 Merge pull request from Spelchure/removing-sentry-logs
Replace Sentry error handling logic
2023-05-01 22:35:11 +03:00
365fcb3044 fix: upgrade mongoose from 6.10.4 to 6.10.5
Snyk has created this PR to upgrade mongoose from 6.10.4 to 6.10.5.

See this package in npm:
https://www.npmjs.com/package/mongoose

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-01 17:58:22 +00:00
01d9695153 fix: upgrade bigint-conversion from 2.3.0 to 2.4.0
Snyk has created this PR to upgrade bigint-conversion from 2.3.0 to 2.4.0.

See this package in npm:
https://www.npmjs.com/package/bigint-conversion

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-01 17:58:17 +00:00
21eb1815c4 feat: remove try-catch blocks for handling errors in middleware 2023-05-01 17:14:39 +03:00
85f3ae95b6 Merge pull request from Infisical/update-docs
Add local machine to deployment options
2023-05-01 16:04:11 +03:00
e888eed1bf Add local machine to deployment options 2023-05-01 16:02:46 +03:00
addac63700 fix broken link for start guide 2023-04-30 15:56:10 -04:00
efd13e6b19 remove completions 2023-04-30 15:49:12 -04:00
4ac74e6e9a add back completion with dir 2023-04-30 15:36:55 -04:00
1d422fa82c Merge pull request from Infisical/docs-guides
Add Preliminary Guides to Docs, Delete README translations
2023-04-30 22:28:06 +03:00
8ba3f8d1f7 Merge branch 'main' into docs-guides 2023-04-30 22:25:22 +03:00
6b83393952 Add initial Node, Python, Nextjs + Vercel guides to docs, delete README translations 2023-04-30 22:21:34 +03:00
da07d71e15 remove completions 2023-04-30 12:42:21 -04:00
82d3971d9e Update README.md 2023-04-30 09:07:25 -07:00
3dd21374e7 update go releaser distribution 2023-04-30 11:40:19 -04:00
c5fe41ae57 Merge pull request from Infisical/multi-tag-repo
Only trigger CLI builds for tags with prefix infisical-cli/v*.*.*
2023-04-30 11:30:36 -04:00
9f0313f50b strip v from existing tags 2023-04-30 11:28:55 -04:00
a6e670e93a update tag fetch method to filetr for cli tags only 2023-04-30 11:22:28 -04:00
ec97e1a930 add mono repo support for goreleaser 2023-04-30 11:09:29 -04:00
55ca6938db update cli github action to only listen to infisical-cli/{version} tags 2023-04-30 11:08:58 -04:00
1401c7f6bc add go releaser pro 2023-04-30 10:32:39 -04:00
bb6d0fd7c6 Patch .secretValue access in INVITE_ONLY_SIGNUP 2023-04-30 14:56:27 +03:00
689a20dca2 Begin adding guides to docs 2023-04-30 14:54:54 +03:00
e4b4126971 Merge pull request from Infisical/snyk-upgrade-291700b772b89271eb89e390de3aca7f
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.306.0 to 3.309.0
2023-04-29 15:14:27 -04:00
04b04cba5c Merge pull request from Infisical/snyk-upgrade-7c8dbe159d6a113e8720970276ee888f
[Snyk] Upgrade sharp from 0.31.3 to 0.32.0
2023-04-29 15:13:58 -04:00
89e5f644a4 Update README.md 2023-04-29 15:13:27 -04:00
c5619d27d7 Merge pull request from Infisical/revise-readme
Updated README
2023-04-29 21:43:17 +03:00
12a1d8e822 Update README 2023-04-29 21:41:33 +03:00
a85a7d1b00 Update README 2023-04-29 21:23:05 +03:00
fc2846534f Update README 2023-04-29 21:06:25 +03:00
2b605856a3 Update README 2023-04-29 20:55:52 +03:00
191582ef26 Merge pull request from Infisical/revise-quickstart
Add quickstarts to documentation
2023-04-29 20:40:34 +03:00
213b5d465b Merge remote-tracking branch 'origin' into revise-quickstart 2023-04-29 20:39:30 +03:00
75f550caf2 Finish documentation quickstarts update 2023-04-29 20:38:58 +03:00
daabf5ab70 add k8 quick start 2023-04-29 12:24:03 -04:00
7b11976a60 Preliminary README change proposal 2023-04-29 18:55:27 +03:00
39be52c6b2 make minor changes to wording for quick start guide 2023-04-29 11:27:04 -04:00
bced5d0151 Complete preliminary new quickstarts 2023-04-29 14:39:22 +03:00
939d7eb433 fix: upgrade @aws-sdk/client-secrets-manager from 3.306.0 to 3.309.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.306.0 to 3.309.0.

See this package in npm:
https://www.npmjs.com/package/@aws-sdk/client-secrets-manager

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-04-29 00:29:10 +00:00
6de25174aa fix: upgrade sharp from 0.31.3 to 0.32.0
Snyk has created this PR to upgrade sharp from 0.31.3 to 0.32.0.

See this package in npm:
https://www.npmjs.com/package/sharp

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-04-28 20:42:03 +00:00
b17a40d83e fix: upgrade @fortawesome/react-fontawesome from 0.1.19 to 0.2.0
Snyk has created this PR to upgrade @fortawesome/react-fontawesome from 0.1.19 to 0.2.0.

See this package in npm:
https://www.npmjs.com/package/@fortawesome/react-fontawesome

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-04-28 20:41:56 +00:00
2aa79d4ad6 Merge pull request from seonggwonyoon/main
Add namespace option for using helm
2023-04-28 14:18:38 -04:00
44b4de754a remove test check in workflow 2023-04-28 13:21:38 -04:00
db0f0d0d9c disable secrets integ tests temp 2023-04-28 13:18:25 -04:00
3471e387ae Merge branch 'main' of https://github.com/Infisical/infisical 2023-04-28 10:11:36 -07:00
aadd964409 Fix the deployment issue 2023-04-28 10:11:25 -07:00
102e45891c Update getAppsGitHub to include pagination 2023-04-28 20:10:29 +03:00
b9ae224aef Patch organization invitation emails expiring for existing users and billing logic affected by missing organization populate call 2023-04-28 17:57:50 +03:00
e5cb0cbca3 Add preliminary platform, sdks, and cli quickstarts 2023-04-28 14:30:13 +03:00
330968c7af added gradient to the menu 2023-04-27 19:46:01 -07:00
68e8e727cd Merge branch 'main' of https://github.com/Infisical/infisical 2023-04-27 18:44:46 -07:00
3b94ee42e9 Animated menu icons 2023-04-27 18:44:23 -07:00
09286b4421 Merge pull request from PylotLight/update-k8s-doc
Update k8s doc to add backend service info
2023-04-27 11:24:31 -04:00
04a9604ba9 add advanced use cases for hostAPI 2023-04-27 11:14:47 -04:00
d86f88db92 Merge pull request from Infisical/snyk-upgrade-9829915033f54fef09ffef896e2c5908
[Snyk] Upgrade @sentry/tracing from 7.46.0 to 7.47.0
2023-04-27 09:57:55 -04:00
c3a47597b6 fix formatting 2023-04-27 23:31:33 +10:00
a696a99232 add backend service inof to doc 2023-04-27 23:28:19 +10:00
a5c5ec1f4d Print helm with namespace 2023-04-25 10:55:27 +09:00
344 changed files with 20989 additions and 25148 deletions
.env.example
.github/workflows
.goreleaser.yamlDockerfile.standalone-infisicalREADME.mdSECURITY.md
backend
cli/packages
docs
ecosystem.config.js
frontend
next-i18next.config.jsnext.config.jspackage-lock.jsonpackage.json
public
locales
en
es
fr
ko
pt-BR
tr
lotties
src
components
ee/components
hooks/api/secrets
i18n.ts
layouts/AppLayout
AppLayout.tsx
components/NavBar
pages
reactQuery.ts
views
DashboardPage
Settings
OrgSettingsPage
OrgSettingsPage.tsx
components
OrgMembersTable
OrgNameChangeSection
ProjectSettingsPage
ProjectSettingsPage.tsx
components
AutoCapitalizationSection
CopyProjectIDSection
ProjectNameChangeSection
ServiceTokenSection
helm-charts/infisical/templates
i18n
nginx
render.yaml

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

@ -1,12 +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
@ -15,9 +20,6 @@ jobs:
- name: 🧪 Run tests
run: npm run test:ci
working-directory: backend
- name: Check if Jest tests failed
if: ${{ always() }} && ${{ steps.backend-test.outcome }} != 'success'
run: exit 1
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
@ -54,15 +56,19 @@ jobs:
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true
context: backend
tags: infisical/backend:${{ steps.commit.outputs.short }},
tags: |
infisical/backend:${{ steps.commit.outputs.short }}
infisical/backend:latest
infisical/backend:${{ steps.extract_version.outputs.version }}
platforms: linux/amd64,linux/arm64
frontend-image:
name: Build frontend image
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: extract_version
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical/}"
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: Save commit hashes for tag
@ -103,8 +109,10 @@ jobs:
push: true
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
context: frontend
tags: infisical/frontend:${{ steps.commit.outputs.short }},
tags: |
infisical/frontend:${{ steps.commit.outputs.short }}
infisical/frontend:latest
infisical/frontend:${{ steps.extract_version.outputs.version }}
platforms: linux/amd64,linux/arm64
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}

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

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

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

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

356
README.md

File diff suppressed because one or more lines are too long

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

1793
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

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

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

@ -1,3 +1,4 @@
import { Types } from 'mongoose';
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { MembershipOrg, Organization, User } from '../../models';
@ -139,7 +140,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
inviteEmail: inviteeEmail,
organization: organizationId,
role: MEMBER,
status: invitee?.publicKey ? ACCEPTED : INVITED
status: INVITED
}).save();
}
} else {
@ -164,6 +165,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
const organization = await Organization.findOne({ _id: organizationId });
if (organization) {
const token = await TokenService.createToken({
type: TOKEN_EMAIL_ORG_INVITATION,
email: inviteeEmail,
@ -179,13 +181,14 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
inviterEmail: req.user.email,
organizationName: organization.name,
email: inviteeEmail,
organizationId: organization._id.toString(),
token,
callback_url: (await getSiteURL()) + '/signupinvite'
}
});
if (!(await getSmtpConfigured())) {
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}`
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}&organization_id=${organization._id}`
}
}
@ -214,13 +217,18 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
export const verifyUserToOrganization = async (req: Request, res: Response) => {
let user, token;
try {
const { email, code } = req.body;
const {
email,
organizationId,
code
} = req.body;
user = await User.findOne({ email }).select('+publicKey');
const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email,
status: INVITED
status: INVITED,
organization: new Types.ObjectId(organizationId)
});
if (!membershipOrg)

@ -85,7 +85,7 @@ export const createOrganization = async (req: Request, res: Response) => {
export const getOrganization = async (req: Request, res: Response) => {
let organization;
try {
organization = req.membershipOrg.organization;
organization = req.organization
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
@ -323,14 +323,14 @@ export const createOrganizationPortalSession = async (
// check if there is a payment method on file
const paymentMethods = await stripe.paymentMethods.list({
customer: req.membershipOrg.organization.customerId,
customer: req.organization.customerId,
type: 'card'
});
if (paymentMethods.data.length < 1) {
// case: no payment method on file
session = await stripe.checkout.sessions.create({
customer: req.membershipOrg.organization.customerId,
customer: req.organization.customerId,
mode: 'setup',
payment_method_types: ['card'],
success_url: (await getSiteURL()) + '/dashboard',
@ -338,7 +338,7 @@ export const createOrganizationPortalSession = async (
});
} else {
session = await stripe.billingPortal.sessions.create({
customer: req.membershipOrg.organization.customerId,
customer: req.organization.customerId,
return_url: (await getSiteURL()) + '/dashboard'
});
}
@ -370,7 +370,7 @@ export const getOrganizationSubscriptions = async (
});
subscriptions = await stripe.subscriptions.list({
customer: req.membershipOrg.organization.customerId
customer: req.organization.customerId
});
} catch (err) {
Sentry.setUser({ email: req.user.email });

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

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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

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

@ -19,6 +19,7 @@ router.post(
router.post(
'/verify',
body('email').exists().trim().notEmpty(),
body('organizationId').exists().trim().notEmpty(),
body('code').exists().trim().notEmpty(),
validateRequest,
membershipOrgController.verifyUserToOrganization

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

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

@ -9,7 +9,7 @@
<body>
<h2>Join your organization on Infisical</h2>
<p>{{inviterFirstName}} ({{inviterEmail}}) has invited you to their Infisical organization — {{organizationName}}</p>
<a href="{{callback_url}}?token={{token}}&to={{email}}">Join now</a>
<a href="{{callback_url}}?token={{token}}&to={{email}}&organization_id={{organizationId}}">Join now</a>
<h3>What is Infisical?</h3>
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
</body>

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

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

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

@ -1,408 +1,408 @@
import request from 'supertest'
import main from '../../../../src/index'
import { testWorkspaceId } from '../../../../src/utils/addDevelopmentUser';
import { deleteAllSecrets, getAllSecrets, getJWTFromTestUser, getServiceTokenFromTestUser } from '../../../helper/helper';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const batchSecretRequestWithNoOverride = require('../../../data/batch-secrets-no-override.json');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const batchSecretRequestWithOverrides = require('../../../data/batch-secrets-with-overrides.json');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const batchSecretRequestWithBadRequest = require('../../../data/batch-create-secrets-with-some-missing-params.json');
let server: any;
beforeAll(async () => {
server = await main;
});
afterAll(async () => {
server.close();
});
describe("GET /api/v2/secrets", () => {
describe("Get secrets via JTW", () => {
test("should create secrets and read secrets via jwt", async () => {
try {
// get login details
const loginResponse = await getJWTFromTestUser()
// create creates
const createSecretsResponse = await request(server)
.post("/api/v2/secrets/batch")
.set('Authorization', `Bearer ${loginResponse.token}`)
.send({
workspaceId: testWorkspaceId,
environment: "dev",
requests: batchSecretRequestWithNoOverride
})
expect(createSecretsResponse.statusCode).toBe(200)
const getSecrets = await request(server)
.get("/api/v2/secrets")
.set('Authorization', `Bearer ${loginResponse.token}`)
.query({
workspaceId: testWorkspaceId,
environment: "dev"
})
expect(getSecrets.statusCode).toBe(200)
expect(getSecrets.body).toHaveProperty("secrets")
expect(getSecrets.body.secrets).toHaveLength(3)
expect(getSecrets.body.secrets).toBeInstanceOf(Array);
getSecrets.body.secrets.forEach((secret: any) => {
expect(secret).toHaveProperty('_id');
expect(secret._id).toBeTruthy();
expect(secret).toHaveProperty('version');
expect(secret.version).toBeTruthy();
expect(secret).toHaveProperty('workspace');
expect(secret.workspace).toBeTruthy();
expect(secret).toHaveProperty('type');
expect(secret.type).toBeTruthy();
expect(secret).toHaveProperty('tags');
expect(secret.tags).toHaveLength(0);
expect(secret).toHaveProperty('environment');
expect(secret.environment).toEqual("dev");
expect(secret).toHaveProperty('secretKeyCiphertext');
expect(secret.secretKeyCiphertext).toBeTruthy();
expect(secret).toHaveProperty('secretKeyIV');
expect(secret.secretKeyIV).toBeTruthy();
// import request from 'supertest'
// import main from '../../../../src/index'
// import { testWorkspaceId } from '../../../../src/utils/addDevelopmentUser';
// import { deleteAllSecrets, getAllSecrets, getJWTFromTestUser, getServiceTokenFromTestUser } from '../../../helper/helper';
// // eslint-disable-next-line @typescript-eslint/no-var-requires
// const batchSecretRequestWithNoOverride = require('../../../data/batch-secrets-no-override.json');
// // eslint-disable-next-line @typescript-eslint/no-var-requires
// const batchSecretRequestWithOverrides = require('../../../data/batch-secrets-with-overrides.json');
// // eslint-disable-next-line @typescript-eslint/no-var-requires
// const batchSecretRequestWithBadRequest = require('../../../data/batch-create-secrets-with-some-missing-params.json');
// let server: any;
// beforeAll(async () => {
// server = await main;
// });
// afterAll(async () => {
// server.close();
// });
// describe("GET /api/v2/secrets", () => {
// describe("Get secrets via JTW", () => {
// test("should create secrets and read secrets via jwt", async () => {
// try {
// // get login details
// const loginResponse = await getJWTFromTestUser()
// // create creates
// const createSecretsResponse = await request(server)
// .post("/api/v2/secrets/batch")
// .set('Authorization', `Bearer ${loginResponse.token}`)
// .send({
// workspaceId: testWorkspaceId,
// environment: "dev",
// requests: batchSecretRequestWithNoOverride
// })
// expect(createSecretsResponse.statusCode).toBe(200)
// const getSecrets = await request(server)
// .get("/api/v2/secrets")
// .set('Authorization', `Bearer ${loginResponse.token}`)
// .query({
// workspaceId: testWorkspaceId,
// environment: "dev"
// })
// expect(getSecrets.statusCode).toBe(200)
// expect(getSecrets.body).toHaveProperty("secrets")
// expect(getSecrets.body.secrets).toHaveLength(3)
// expect(getSecrets.body.secrets).toBeInstanceOf(Array);
// getSecrets.body.secrets.forEach((secret: any) => {
// expect(secret).toHaveProperty('_id');
// expect(secret._id).toBeTruthy();
// expect(secret).toHaveProperty('version');
// expect(secret.version).toBeTruthy();
// expect(secret).toHaveProperty('workspace');
// expect(secret.workspace).toBeTruthy();
// expect(secret).toHaveProperty('type');
// expect(secret.type).toBeTruthy();
// expect(secret).toHaveProperty('tags');
// expect(secret.tags).toHaveLength(0);
// expect(secret).toHaveProperty('environment');
// expect(secret.environment).toEqual("dev");
// expect(secret).toHaveProperty('secretKeyCiphertext');
// expect(secret.secretKeyCiphertext).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyIV');
// expect(secret.secretKeyIV).toBeTruthy();
expect(secret).toHaveProperty('secretKeyTag');
expect(secret.secretKeyTag).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyTag');
// expect(secret.secretKeyTag).toBeTruthy();
expect(secret).toHaveProperty('secretValueCiphertext');
expect(secret.secretValueCiphertext).toBeTruthy();
// expect(secret).toHaveProperty('secretValueCiphertext');
// expect(secret.secretValueCiphertext).toBeTruthy();
expect(secret).toHaveProperty('secretValueIV');
expect(secret.secretValueIV).toBeTruthy();
// expect(secret).toHaveProperty('secretValueIV');
// expect(secret.secretValueIV).toBeTruthy();
expect(secret).toHaveProperty('secretValueTag');
expect(secret.secretValueTag).toBeTruthy();
// expect(secret).toHaveProperty('secretValueTag');
// expect(secret.secretValueTag).toBeTruthy();
expect(secret).toHaveProperty('secretCommentCiphertext');
expect(secret.secretCommentCiphertext).toBeFalsy();
// expect(secret).toHaveProperty('secretCommentCiphertext');
// expect(secret.secretCommentCiphertext).toBeFalsy();
expect(secret).toHaveProperty('secretCommentIV');
expect(secret.secretCommentIV).toBeTruthy();
expect(secret).toHaveProperty('secretCommentTag');
expect(secret.secretCommentTag).toBeTruthy();
expect(secret).toHaveProperty('createdAt');
expect(secret.createdAt).toBeTruthy();
expect(secret).toHaveProperty('updatedAt');
expect(secret.updatedAt).toBeTruthy();
});
} finally {
// clean up
await deleteAllSecrets()
}
})
test("Get secrets via jwt when personal overrides exist", async () => {
try {
// get login details
const loginResponse = await getJWTFromTestUser()
// create creates
const createSecretsResponse = await request(server)
.post("/api/v2/secrets/batch")
.set('Authorization', `Bearer ${loginResponse.token}`)
.send({
workspaceId: testWorkspaceId,
environment: "dev",
requests: batchSecretRequestWithOverrides
})
expect(createSecretsResponse.statusCode).toBe(200)
const getSecrets = await request(server)
.get("/api/v2/secrets")
.set('Authorization', `Bearer ${loginResponse.token}`)
.query({
workspaceId: testWorkspaceId,
environment: "dev"
})
// expect(secret).toHaveProperty('secretCommentIV');
// expect(secret.secretCommentIV).toBeTruthy();
// expect(secret).toHaveProperty('secretCommentTag');
// expect(secret.secretCommentTag).toBeTruthy();
// expect(secret).toHaveProperty('createdAt');
// expect(secret.createdAt).toBeTruthy();
// expect(secret).toHaveProperty('updatedAt');
// expect(secret.updatedAt).toBeTruthy();
// });
// } finally {
// // clean up
// await deleteAllSecrets()
// }
// })
// test("Get secrets via jwt when personal overrides exist", async () => {
// try {
// // get login details
// const loginResponse = await getJWTFromTestUser()
// // create creates
// const createSecretsResponse = await request(server)
// .post("/api/v2/secrets/batch")
// .set('Authorization', `Bearer ${loginResponse.token}`)
// .send({
// workspaceId: testWorkspaceId,
// environment: "dev",
// requests: batchSecretRequestWithOverrides
// })
// expect(createSecretsResponse.statusCode).toBe(200)
// const getSecrets = await request(server)
// .get("/api/v2/secrets")
// .set('Authorization', `Bearer ${loginResponse.token}`)
// .query({
// workspaceId: testWorkspaceId,
// environment: "dev"
// })
expect(getSecrets.statusCode).toBe(200)
expect(getSecrets.body).toHaveProperty("secrets")
expect(getSecrets.body.secrets).toHaveLength(2)
expect(getSecrets.body.secrets).toBeInstanceOf(Array);
getSecrets.body.secrets.forEach((secret: any) => {
expect(secret).toHaveProperty('_id');
expect(secret._id).toBeTruthy();
// expect(getSecrets.statusCode).toBe(200)
// expect(getSecrets.body).toHaveProperty("secrets")
// expect(getSecrets.body.secrets).toHaveLength(2)
// expect(getSecrets.body.secrets).toBeInstanceOf(Array);
// getSecrets.body.secrets.forEach((secret: any) => {
// expect(secret).toHaveProperty('_id');
// expect(secret._id).toBeTruthy();
expect(secret).toHaveProperty('version');
expect(secret.version).toBeTruthy();
// expect(secret).toHaveProperty('version');
// expect(secret.version).toBeTruthy();
expect(secret).toHaveProperty('workspace');
expect(secret.workspace).toBeTruthy();
// expect(secret).toHaveProperty('workspace');
// expect(secret.workspace).toBeTruthy();
expect(secret).toHaveProperty('type');
expect(secret.type).toBeTruthy();
// expect(secret).toHaveProperty('type');
// expect(secret.type).toBeTruthy();
expect(secret).toHaveProperty('tags');
expect(secret.tags).toHaveLength(0);
// expect(secret).toHaveProperty('tags');
// expect(secret.tags).toHaveLength(0);
expect(secret).toHaveProperty('environment');
expect(secret.environment).toEqual("dev");
// expect(secret).toHaveProperty('environment');
// expect(secret.environment).toEqual("dev");
expect(secret).toHaveProperty('secretKeyCiphertext');
expect(secret.secretKeyCiphertext).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyCiphertext');
// expect(secret.secretKeyCiphertext).toBeTruthy();
expect(secret).toHaveProperty('secretKeyIV');
expect(secret.secretKeyIV).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyIV');
// expect(secret.secretKeyIV).toBeTruthy();
expect(secret).toHaveProperty('secretKeyTag');
expect(secret.secretKeyTag).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyTag');
// expect(secret.secretKeyTag).toBeTruthy();
expect(secret).toHaveProperty('secretValueCiphertext');
expect(secret.secretValueCiphertext).toBeTruthy();
// expect(secret).toHaveProperty('secretValueCiphertext');
// expect(secret.secretValueCiphertext).toBeTruthy();
expect(secret).toHaveProperty('secretValueIV');
expect(secret.secretValueIV).toBeTruthy();
// expect(secret).toHaveProperty('secretValueIV');
// expect(secret.secretValueIV).toBeTruthy();
expect(secret).toHaveProperty('secretValueTag');
expect(secret.secretValueTag).toBeTruthy();
// expect(secret).toHaveProperty('secretValueTag');
// expect(secret.secretValueTag).toBeTruthy();
expect(secret).toHaveProperty('secretCommentCiphertext');
expect(secret.secretCommentCiphertext).toBeFalsy();
// expect(secret).toHaveProperty('secretCommentCiphertext');
// expect(secret.secretCommentCiphertext).toBeFalsy();
expect(secret).toHaveProperty('secretCommentIV');
expect(secret.secretCommentIV).toBeTruthy();
// expect(secret).toHaveProperty('secretCommentIV');
// expect(secret.secretCommentIV).toBeTruthy();
expect(secret).toHaveProperty('secretCommentTag');
expect(secret.secretCommentTag).toBeTruthy();
// expect(secret).toHaveProperty('secretCommentTag');
// expect(secret.secretCommentTag).toBeTruthy();
expect(secret).toHaveProperty('createdAt');
expect(secret.createdAt).toBeTruthy();
// expect(secret).toHaveProperty('createdAt');
// expect(secret.createdAt).toBeTruthy();
expect(secret).toHaveProperty('updatedAt');
expect(secret.updatedAt).toBeTruthy();
});
} finally {
// clean up
await deleteAllSecrets()
}
})
})
describe("fetch secrets via service token", () => {
test("Get secrets via jwt when personal overrides exist", async () => {
try {
// get login details
const loginResponse = await getJWTFromTestUser()
// create creates
const createSecretsResponse = await request(server)
.post("/api/v2/secrets/batch")
.set('Authorization', `Bearer ${loginResponse.token}`)
.send({
workspaceId: testWorkspaceId,
environment: "dev",
requests: batchSecretRequestWithOverrides
})
// expect(secret).toHaveProperty('updatedAt');
// expect(secret.updatedAt).toBeTruthy();
// });
// } finally {
// // clean up
// await deleteAllSecrets()
// }
// })
// })
// describe("fetch secrets via service token", () => {
// test("Get secrets via jwt when personal overrides exist", async () => {
// try {
// // get login details
// const loginResponse = await getJWTFromTestUser()
// // create creates
// const createSecretsResponse = await request(server)
// .post("/api/v2/secrets/batch")
// .set('Authorization', `Bearer ${loginResponse.token}`)
// .send({
// workspaceId: testWorkspaceId,
// environment: "dev",
// requests: batchSecretRequestWithOverrides
// })
expect(createSecretsResponse.statusCode).toBe(200)
// now use the service token to fetch secrets
const serviceToken = await getServiceTokenFromTestUser()
// expect(createSecretsResponse.statusCode).toBe(200)
// // now use the service token to fetch secrets
// const serviceToken = await getServiceTokenFromTestUser()
const getSecrets = await request(server)
.get("/api/v2/secrets")
.set('Authorization', `Bearer ${serviceToken}`)
.query({
workspaceId: testWorkspaceId,
environment: "dev"
})
expect(getSecrets.statusCode).toBe(200)
expect(getSecrets.body).toHaveProperty("secrets")
expect(getSecrets.body.secrets).toHaveLength(2)
expect(getSecrets.body.secrets).toBeInstanceOf(Array);
// const getSecrets = await request(server)
// .get("/api/v2/secrets")
// .set('Authorization', `Bearer ${serviceToken}`)
// .query({
// workspaceId: testWorkspaceId,
// environment: "dev"
// })
// expect(getSecrets.statusCode).toBe(200)
// expect(getSecrets.body).toHaveProperty("secrets")
// expect(getSecrets.body.secrets).toHaveLength(2)
// expect(getSecrets.body.secrets).toBeInstanceOf(Array);
getSecrets.body.secrets.forEach((secret: any) => {
expect(secret).toHaveProperty('_id');
expect(secret._id).toBeTruthy();
// getSecrets.body.secrets.forEach((secret: any) => {
// expect(secret).toHaveProperty('_id');
// expect(secret._id).toBeTruthy();
expect(secret).toHaveProperty('version');
expect(secret.version).toBeTruthy();
// expect(secret).toHaveProperty('version');
// expect(secret.version).toBeTruthy();
expect(secret).toHaveProperty('workspace');
expect(secret.workspace).toBeTruthy();
// expect(secret).toHaveProperty('workspace');
// expect(secret.workspace).toBeTruthy();
expect(secret).toHaveProperty('type');
expect(secret.type).toBeTruthy();
// expect(secret).toHaveProperty('type');
// expect(secret.type).toBeTruthy();
expect(secret).toHaveProperty('tags');
expect(secret.tags).toHaveLength(0);
// expect(secret).toHaveProperty('tags');
// expect(secret.tags).toHaveLength(0);
expect(secret).toHaveProperty('environment');
expect(secret.environment).toEqual("dev");
// expect(secret).toHaveProperty('environment');
// expect(secret.environment).toEqual("dev");
expect(secret).toHaveProperty('secretKeyCiphertext');
expect(secret.secretKeyCiphertext).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyCiphertext');
// expect(secret.secretKeyCiphertext).toBeTruthy();
expect(secret).toHaveProperty('secretKeyIV');
expect(secret.secretKeyIV).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyIV');
// expect(secret.secretKeyIV).toBeTruthy();
expect(secret).toHaveProperty('secretKeyTag');
expect(secret.secretKeyTag).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyTag');
// expect(secret.secretKeyTag).toBeTruthy();
expect(secret).toHaveProperty('secretValueCiphertext');
expect(secret.secretValueCiphertext).toBeTruthy();
// expect(secret).toHaveProperty('secretValueCiphertext');
// expect(secret.secretValueCiphertext).toBeTruthy();
expect(secret).toHaveProperty('secretValueIV');
expect(secret.secretValueIV).toBeTruthy();
// expect(secret).toHaveProperty('secretValueIV');
// expect(secret.secretValueIV).toBeTruthy();
expect(secret).toHaveProperty('secretValueTag');
expect(secret.secretValueTag).toBeTruthy();
// expect(secret).toHaveProperty('secretValueTag');
// expect(secret.secretValueTag).toBeTruthy();
expect(secret).toHaveProperty('secretCommentCiphertext');
expect(secret.secretCommentCiphertext).toBeFalsy();
// expect(secret).toHaveProperty('secretCommentCiphertext');
// expect(secret.secretCommentCiphertext).toBeFalsy();
expect(secret).toHaveProperty('secretCommentIV');
expect(secret.secretCommentIV).toBeTruthy();
// expect(secret).toHaveProperty('secretCommentIV');
// expect(secret.secretCommentIV).toBeTruthy();
expect(secret).toHaveProperty('secretCommentTag');
expect(secret.secretCommentTag).toBeTruthy();
// expect(secret).toHaveProperty('secretCommentTag');
// expect(secret.secretCommentTag).toBeTruthy();
expect(secret).toHaveProperty('createdAt');
expect(secret.createdAt).toBeTruthy();
// expect(secret).toHaveProperty('createdAt');
// expect(secret.createdAt).toBeTruthy();
expect(secret).toHaveProperty('updatedAt');
expect(secret.updatedAt).toBeTruthy();
});
} finally {
// clean up
await deleteAllSecrets()
}
})
test("should create secrets and read secrets via service token when no overrides", async () => {
try {
// get login details
const loginResponse = await getJWTFromTestUser()
// create secrets
const createSecretsResponse = await request(server)
.post("/api/v2/secrets/batch")
.set('Authorization', `Bearer ${loginResponse.token}`)
.send({
workspaceId: testWorkspaceId,
environment: "dev",
requests: batchSecretRequestWithNoOverride
})
expect(createSecretsResponse.statusCode).toBe(200)
// expect(secret).toHaveProperty('updatedAt');
// expect(secret.updatedAt).toBeTruthy();
// });
// } finally {
// // clean up
// await deleteAllSecrets()
// }
// })
// test("should create secrets and read secrets via service token when no overrides", async () => {
// try {
// // get login details
// const loginResponse = await getJWTFromTestUser()
// // create secrets
// const createSecretsResponse = await request(server)
// .post("/api/v2/secrets/batch")
// .set('Authorization', `Bearer ${loginResponse.token}`)
// .send({
// workspaceId: testWorkspaceId,
// environment: "dev",
// requests: batchSecretRequestWithNoOverride
// })
// expect(createSecretsResponse.statusCode).toBe(200)
// now use the service token to fetch secrets
const serviceToken = await getServiceTokenFromTestUser()
// // now use the service token to fetch secrets
// const serviceToken = await getServiceTokenFromTestUser()
const getSecrets = await request(server)
.get("/api/v2/secrets")
.set('Authorization', `Bearer ${serviceToken}`)
.query({
workspaceId: testWorkspaceId,
environment: "dev"
})
// const getSecrets = await request(server)
// .get("/api/v2/secrets")
// .set('Authorization', `Bearer ${serviceToken}`)
// .query({
// workspaceId: testWorkspaceId,
// environment: "dev"
// })
expect(getSecrets.statusCode).toBe(200)
expect(getSecrets.body).toHaveProperty("secrets")
expect(getSecrets.body.secrets).toHaveLength(3)
expect(getSecrets.body.secrets).toBeInstanceOf(Array);
// expect(getSecrets.statusCode).toBe(200)
// expect(getSecrets.body).toHaveProperty("secrets")
// expect(getSecrets.body.secrets).toHaveLength(3)
// expect(getSecrets.body.secrets).toBeInstanceOf(Array);
getSecrets.body.secrets.forEach((secret: any) => {
expect(secret).toHaveProperty('_id');
expect(secret._id).toBeTruthy();
// getSecrets.body.secrets.forEach((secret: any) => {
// expect(secret).toHaveProperty('_id');
// expect(secret._id).toBeTruthy();
expect(secret).toHaveProperty('version');
expect(secret.version).toBeTruthy();
// expect(secret).toHaveProperty('version');
// expect(secret.version).toBeTruthy();
expect(secret).toHaveProperty('workspace');
expect(secret.workspace).toBeTruthy();
// expect(secret).toHaveProperty('workspace');
// expect(secret.workspace).toBeTruthy();
expect(secret).toHaveProperty('type');
expect(secret.type).toBeTruthy();
// expect(secret).toHaveProperty('type');
// expect(secret.type).toBeTruthy();
expect(secret).toHaveProperty('tags');
expect(secret.tags).toHaveLength(0);
// expect(secret).toHaveProperty('tags');
// expect(secret.tags).toHaveLength(0);
expect(secret).toHaveProperty('environment');
expect(secret.environment).toEqual("dev");
// expect(secret).toHaveProperty('environment');
// expect(secret.environment).toEqual("dev");
expect(secret).toHaveProperty('secretKeyCiphertext');
expect(secret.secretKeyCiphertext).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyCiphertext');
// expect(secret.secretKeyCiphertext).toBeTruthy();
expect(secret).toHaveProperty('secretKeyIV');
expect(secret.secretKeyIV).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyIV');
// expect(secret.secretKeyIV).toBeTruthy();
expect(secret).toHaveProperty('secretKeyTag');
expect(secret.secretKeyTag).toBeTruthy();
// expect(secret).toHaveProperty('secretKeyTag');
// expect(secret.secretKeyTag).toBeTruthy();
expect(secret).toHaveProperty('secretValueCiphertext');
expect(secret.secretValueCiphertext).toBeTruthy();
// expect(secret).toHaveProperty('secretValueCiphertext');
// expect(secret.secretValueCiphertext).toBeTruthy();
expect(secret).toHaveProperty('secretValueIV');
expect(secret.secretValueIV).toBeTruthy();
expect(secret).toHaveProperty('secretValueTag');
expect(secret.secretValueTag).toBeTruthy();
expect(secret).toHaveProperty('secretCommentCiphertext');
expect(secret.secretCommentCiphertext).toBeFalsy();
expect(secret).toHaveProperty('secretCommentIV');
expect(secret.secretCommentIV).toBeTruthy();
expect(secret).toHaveProperty('secretCommentTag');
expect(secret.secretCommentTag).toBeTruthy();
expect(secret).toHaveProperty('createdAt');
expect(secret.createdAt).toBeTruthy();
expect(secret).toHaveProperty('updatedAt');
expect(secret.updatedAt).toBeTruthy();
});
} finally {
// clean up
await deleteAllSecrets()
}
})
})
describe("create secrets via JWT", () => {
test("Create secrets via jwt when some requests have missing required parameters", async () => {
// get login details
const loginResponse = await getJWTFromTestUser()
// create creates
const createSecretsResponse = await request(server)
.post("/api/v2/secrets/batch")
.set('Authorization', `Bearer ${loginResponse.token}`)
.send({
workspaceId: testWorkspaceId,
environment: "dev",
requests: batchSecretRequestWithBadRequest
})
const allSecretsInDB = await getAllSecrets()
// expect(secret).toHaveProperty('secretValueIV');
// expect(secret.secretValueIV).toBeTruthy();
// expect(secret).toHaveProperty('secretValueTag');
// expect(secret.secretValueTag).toBeTruthy();
// expect(secret).toHaveProperty('secretCommentCiphertext');
// expect(secret.secretCommentCiphertext).toBeFalsy();
// expect(secret).toHaveProperty('secretCommentIV');
// expect(secret.secretCommentIV).toBeTruthy();
// expect(secret).toHaveProperty('secretCommentTag');
// expect(secret.secretCommentTag).toBeTruthy();
// expect(secret).toHaveProperty('createdAt');
// expect(secret.createdAt).toBeTruthy();
// expect(secret).toHaveProperty('updatedAt');
// expect(secret.updatedAt).toBeTruthy();
// });
// } finally {
// // clean up
// await deleteAllSecrets()
// }
// })
// })
// describe("create secrets via JWT", () => {
// test("Create secrets via jwt when some requests have missing required parameters", async () => {
// // get login details
// const loginResponse = await getJWTFromTestUser()
// // create creates
// const createSecretsResponse = await request(server)
// .post("/api/v2/secrets/batch")
// .set('Authorization', `Bearer ${loginResponse.token}`)
// .send({
// workspaceId: testWorkspaceId,
// environment: "dev",
// requests: batchSecretRequestWithBadRequest
// })
// const allSecretsInDB = await getAllSecrets()
expect(createSecretsResponse.statusCode).toBe(500) // TODO should be set to 400
expect(allSecretsInDB).toHaveLength(0)
})
})
})
// expect(createSecretsResponse.statusCode).toBe(500) // TODO should be set to 400
// expect(allSecretsInDB).toHaveLength(0)
// })
// })
// })

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

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

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

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

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

@ -6,7 +6,7 @@ description: "How to add a secret using an Infisical Token scoped to a project a
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment with write access enabled.
- 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).

@ -6,7 +6,7 @@ description: "How to delete a secret using an Infisical Token scoped to a projec
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create either an [API Key](/api-reference/overview/authentication) or [Infisical Token](../../../getting-started/dashboard/token) for your project and environment with write access enabled.
- 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).

@ -6,7 +6,7 @@ description: "How to get a secret using an Infisical Token scoped to a project a
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
- 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).

@ -6,7 +6,7 @@ description: "How to get all secrets using an Infisical Token scoped to a projec
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
- 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).

@ -6,7 +6,7 @@ description: "How to update a secret using an Infisical Token scoped to a projec
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment with write access enabled.
- 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).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Binary file not shown.

After

(image error) Size: 502 KiB

Binary file not shown.

After

(image error) Size: 1.1 MiB

Binary file not shown.

After

(image error) Size: 1.2 MiB

Binary file not shown.

After

(image error) Size: 413 KiB

Binary file not shown.

After

(image error) Size: 78 KiB

Binary file not shown.

After

(image error) Size: 292 KiB

Binary file not shown.

After

(image error) Size: 342 KiB

Binary file not shown.

After

(image error) Size: 104 KiB

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

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

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

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

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

@ -72,6 +72,17 @@ spec:
` https://your-self-hosted-instace.com/api`
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
<Accordion title="Advanced use case">
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
To achieve this, use the following address for the hostAPI field:
``` bash
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
```
Make sure to replace `<backend-svc-name>` and `<namespace>` with the appropriate values for your backend service and namespace.
</Accordion>
</Accordion>
<Accordion title="authentication">

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

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

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

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

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

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

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

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