1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-20 22:32:28 +00:00

Compare commits

..

150 Commits

Author SHA1 Message Date
c086579260 Merge pull request from jon4hz/main
fix homebrew
2023-02-01 21:44:22 -08:00
3d14bc9a00 remove env name check 2023-02-01 20:31:25 -08:00
f2175b948c Merge pull request from nirga/main
chore: fix typo in quick start guide
2023-02-01 13:17:54 -08:00
6f3d102ecb chore: fix typo in quick start guide 2023-02-01 23:15:39 +02:00
54fa39f347 Fixed issues with breadcrumbs and redirects of forgot password 2023-02-01 12:22:41 -08:00
c99b207e9e ci: maybe fix brew 2023-02-01 14:28:12 +01:00
4886537a56 Revert "Revert "Merge pull request from jon4hz/main""
This reverts commit 1878bed10a1e06a8340d8b02385a8d0081394d61.
2023-02-01 14:19:49 +01:00
71cf54c28b add auto cli version to all-other-builds 2023-01-31 20:19:42 -08:00
1878bed10a Revert "Merge pull request from jon4hz/main"
This reverts commit 87fd5e33f11a354a622990fb58d185d8094f29c6, reversing
changes made to 2c4e066f6421c461e28129fedc14fb6fb6b2b1b9.
2023-01-31 20:17:15 -08:00
87fd5e33f1 Merge pull request from jon4hz/main
CI Improvements
2023-01-31 19:39:46 -08:00
ffda30bd65 ci: mark goreleaser snapshots as such 2023-02-01 03:54:22 +01:00
716795532e ci: bump goreleaser action 2023-02-01 03:38:18 +01:00
f9ff99748b ci: remove obsolete var 2023-02-01 03:37:07 +01:00
723fa153be ci: completion and manpages for homebrew 2023-02-01 03:36:26 +01:00
1871d1a842 fix: improve goreleaser 2023-02-01 03:35:54 +01:00
2c4e066f64 bring back auto cli version in CI 2023-01-31 17:34:50 -08:00
b371dad506 Increase cli version 2023-01-31 17:22:44 -08:00
a6d4431940 Auto add cli version from tag 2023-01-31 17:03:19 -08:00
871d80aad5 when login expired, do not ask to override login 2023-01-31 16:37:56 -08:00
6711979445 Disallow service token creation based on permission 2023-01-31 09:24:55 -08:00
cb080b356c increase cli version 2023-01-30 22:17:02 -08:00
9950c5e02d empty commit 2023-01-30 22:15:44 -08:00
22a11be4e0 Update host rules for permissioning 2023-01-30 21:38:09 -08:00
6e01c80282 Merge branch 'main' of https://github.com/Infisical/infisical 2023-01-30 21:14:41 -08:00
4e14f84df9 Allow editing personal permissions 2023-01-30 21:14:22 -08:00
55522404b4 Merge pull request from Infisical/dependabot/npm_and_yarn/backend/cookiejar-2.1.4
Bump cookiejar from 2.1.3 to 2.1.4 in /backend
2023-01-30 20:37:44 -08:00
4ef8c273f7 Wired access controls for environemnts to frontend 2023-01-30 20:36:04 -08:00
61c17ccc5e update getAllAccessibleEnvironmentsOfWorkspace controller 2023-01-30 19:39:45 -08:00
2832476c2b Add write permission status 2023-01-30 19:38:40 -08:00
c0fc74b62a Add write permission status 2023-01-30 19:22:52 -08:00
54caaffe3a Bump cookiejar from 2.1.3 to 2.1.4 in /backend
Bumps [cookiejar](https://github.com/bmeck/node-cookiejar) from 2.1.3 to 2.1.4.
- [Release notes](https://github.com/bmeck/node-cookiejar/releases)
- [Commits](https://github.com/bmeck/node-cookiejar/commits)

---
updated-dependencies:
- dependency-name: cookiejar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 12:41:45 +00:00
55f0a491cb Release fly.io integrartion 2023-01-29 22:38:20 -08:00
a940fa210a Add deny api/get envs api 2023-01-29 21:12:41 -08:00
5162ba9b91 add basic auth model for Organization 2023-01-29 21:12:41 -08:00
3b6022de64 Merge branch 'main' of https://github.com/Infisical/infisical 2023-01-29 15:55:22 -08:00
bf743f5f72 Make the loading animation smaller 2023-01-29 15:55:01 -08:00
3e177539d5 Remove state from password controllers 2023-01-29 15:48:42 -08:00
5743dd3a8c Merge pull request from Neeraj138/subscription-check
Add check for subscriptions call before setting the current plan
2023-01-29 09:25:04 -08:00
9f8ad95a59 Revert "correct tags in docker image workflow"
This reverts commit 3ef2ac8a77b50c1fbac1fa2173acccbf1736a011.
2023-01-29 09:17:22 -08:00
3c05a4cebd Add check for subscriptions call before setting the current plan 2023-01-29 14:16:05 +05:30
bc955a9afd increase cli version 2023-01-28 22:32:23 -08:00
ec8d86e662 Merge pull request from akhilmhdh/feat/react-query
feat(ui): added new auth guard with react-query and axios
2023-01-29 12:21:08 +07:00
bc70bedb78 Fixed the bug with empty variables 2023-01-28 20:41:54 -08:00
7a4b77ce59 Update README.md 2023-01-28 14:31:29 -08:00
8600cee54c Merge pull request from sanyamjain04/tailwind-plugin
added prettier-plugin-tailwindcss
2023-01-28 14:14:51 -08:00
fe9573ea3c Merge pull request from asheliahut/patch-1
Include Id on project
2023-01-28 14:11:32 -08:00
61db6c54c2 Merge pull request from kimcore/main
Skip update check if github returns non-200
2023-01-28 14:08:35 -08:00
65093c73c5 Merge pull request from mocherfaoui/inf-nsc-pt
New secrets are now added to the top in the dashboard UI
2023-01-28 13:54:24 -08:00
9986521e41 Merge pull request from kimcore/readme-ko
Translate README.md to korean
2023-01-28 13:35:13 -08:00
655f015109 Merge branch 'main' of https://github.com/Infisical/infisical 2023-01-28 12:53:51 -08:00
3cea59ce5d Improved docs SEO 2023-01-28 12:53:44 -08:00
a184192452 Inform k8 self host about latest tags 2023-01-28 12:34:04 -08:00
2dbcab32d5 update gamma pull image policy 2023-01-28 12:03:53 -08:00
13aeeb4731 console.log in posthog 2023-01-28 11:22:16 -08:00
233a468127 Revert "add console.log for post"
This reverts commit dd960aa5f045f62a556e67f81bf172372401a465.
2023-01-28 11:22:16 -08:00
8a9e05b08f Revert "add test comment for docker build issue"
This reverts commit fdac590a023433113ae21295dbe1abf165fb5500.
2023-01-28 11:22:16 -08:00
3ef2ac8a77 correct tags in docker image workflow 2023-01-28 11:14:00 -08:00
fdac590a02 add test comment for docker build issue 2023-01-28 10:25:42 -08:00
dd960aa5f0 add console.log for post 2023-01-28 10:04:34 -08:00
0bd9a848c4 add back depot 2023-01-28 09:53:06 -08:00
1b86c58f91 remove depot from docker build 2023-01-28 09:24:58 -08:00
d5166d343d Remove depot docker 2023-01-28 09:17:54 -08:00
b315cf6022 Translate README.md to korean 2023-01-29 00:20:10 +09:00
37de32ec90 return proper error 2023-01-28 23:13:36 +09:00
6eb81802c3 Skip update check if github returns non-200 2023-01-28 23:06:37 +09:00
e6068a6f7f Merge pull request from samsbg/main
Adding Spanish translation to the README 🌎ES
2023-01-27 18:39:35 -08:00
c059c088d1 update k8 selfhost docs values.yaml file 2023-01-27 12:42:47 -08:00
b530847edc increase chart version 2023-01-27 12:34:46 -08:00
c87c2dadd7 add readinessProbe check for pods 2023-01-27 12:31:46 -08:00
7b1ff04436 add deployment annotations 2023-01-27 10:45:42 -08:00
83aa440b62 Remove mongo url from envs 2023-01-27 10:43:23 -08:00
a555ef836b remove default sensitive keys 2023-01-27 09:33:49 -08:00
528601e442 Merge pull request from Infisical/patch-empty-values
Allow empty values for secrets
2023-01-27 22:16:24 +07:00
13acb19e9f Allow empty values for secrets 2023-01-27 22:07:56 +07:00
079063157f added prettier-plugin-tailwindcss 2023-01-27 12:16:23 +05:30
e38933c0b3 Include Id on project
The project should have its id exposed.
2023-01-26 19:32:44 -08:00
d09b406c4e Merge pull request from kmlgkcy/turkish-translation
translation: Turkish
2023-01-26 16:46:00 -08:00
a5eba8e722 Updated the billing engine for Cloud 2023-01-26 16:32:51 -08:00
7acb4cc22a fix helm deploymentAnnotations 2023-01-26 14:43:53 -08:00
b95ab6c6a1 added deploymentAnnotations to helm chart 2023-01-26 14:17:51 -08:00
038445e13e change from cal.com to calendly 2023-01-26 11:30:36 -08:00
07e9dd5a39 add managed secrets to deployment in gamma 2023-01-26 00:39:08 -08:00
6ec520d358 update helm values for k8 self host 2023-01-26 00:30:54 -08:00
06bfd2429b Update gemma helm chart with auto reload 2023-01-26 00:17:07 -08:00
099c4836e6 update helm charts to be more flexible 2023-01-26 00:14:07 -08:00
ddf8ceb45d translation: Turkish 2023-01-26 10:46:47 +03:00
8a49e0817a add error to failed org creation 2023-01-25 21:58:53 -08:00
88908297f5 add error object to log 2023-01-25 21:30:32 -08:00
cf0e111c09 increase replica count for gamma 2023-01-25 20:12:41 -08:00
ae0ee727fa Make backend login stateless 2023-01-25 20:09:57 -08:00
be2945c445 Merge pull request from Infisical/stripe-adjustment
Update backend envars types and add STRIPE_PRODUCT_TEAM envar
2023-01-26 10:22:54 +07:00
237a10da1e Update backend envars types and add STRIPE_PRODUCT_TEAM envar 2023-01-26 10:20:42 +07:00
1baf14084d new secrets are added to the top 2023-01-25 19:55:48 +01:00
a6387e7552 feat(ui): added new auth guard with react-query and axios 2023-01-26 00:14:01 +05:30
a6f480d3f8 increase CLI 2023-01-24 19:59:45 -08:00
0413059fbe patch executeMultipleCommandWithEnvs when no /bin/zsh 2023-01-24 19:59:45 -08:00
65f049f6ac Merge pull request from franky47/patch-1
docs: Fix typo in encryption overview
2023-01-24 19:51:10 -08:00
62f886a3b3 docs: Fix typo in encryption overview 2023-01-25 04:31:04 +01:00
271ca148e3 Make support link clickable 2023-01-24 11:01:49 -08:00
8aa294309f remove icon from support link 2023-01-24 10:53:46 -08:00
ca3233110b add support link for 1 on 1 in docs 2023-01-24 10:52:09 -08:00
1e4f6a4b9d increase CLI 2023-01-24 00:10:42 -08:00
a73fc6de19 add cli version check before every command 2023-01-24 00:08:42 -08:00
0bb750488b fix typo in docs 2023-01-23 23:00:24 -08:00
32f98f83c5 update nav name for development instructions 2023-01-23 22:56:41 -08:00
6943785ce5 remove stay alive 2023-01-23 22:55:14 -08:00
86558a8221 simplify contribution docs 2023-01-23 22:55:14 -08:00
f2c35a302d Merge pull request from akhilmhdh/feat/component-update-1
Infisical component library foundations
2023-01-23 21:47:28 -08:00
0794b6132a auto create user for dev mode 2023-01-23 20:57:59 -08:00
062c287e75 feat(ui): changed to interfonts 2023-01-23 22:10:51 +05:30
e67d68a7b9 feat(ui): implemented basic table component 2023-01-23 22:10:51 +05:30
054acc689a feat(ui): implemented dropdown component 2023-01-23 22:10:51 +05:30
9b95d18b85 feat(ui): implemented switch component 2023-01-23 22:10:51 +05:30
7f9bc77253 feat(ui): added checkbox component 2023-01-23 22:10:51 +05:30
b92907aca6 feat(ui): added textarea component 2023-01-23 22:10:51 +05:30
c4ee03c73b featIui): added menu component 2023-01-23 22:10:51 +05:30
89ba80740b feat: added card and modal component 2023-01-23 22:10:50 +05:30
606a5e5317 feat(ui): added card component 2023-01-23 22:10:50 +05:30
f859bf528e feat(ui): added icon button component, updated secondary button style and added select to barrel export 2023-01-23 22:10:50 +05:30
ad504fa84e feat(ui) added select, spinner components 2023-01-23 22:10:50 +05:30
e7ac74c5a0 feat(ui): implemented form control components 2023-01-23 22:10:50 +05:30
b80504ae00 feat(ui): implemented new input component 2023-01-23 22:10:50 +05:30
68f1887d66 feat(ui): implemented button component 2023-01-23 22:10:50 +05:30
201c8352e3 fix typo in k8 self host 2023-01-22 16:50:45 -08:00
a0f0ffe566 improve i-dev command 2023-01-22 14:03:09 -08:00
4b4e8e2bfc Update docker integration 2023-01-22 12:50:06 -08:00
4db4c172c1 Fix the start guide redirect issue 2023-01-22 00:35:36 -08:00
08c54a910f Adding Spanish translation to the README 2023-01-22 01:42:25 -06:00
00fee63ff3 Merge pull request from Infisical/more-integrations
Adjust integration bot authorization sequence
2023-01-22 11:17:15 +07:00
6b80cd6590 Modify case where integration bot was authorized but user didn't finish inputting their PAT -> should result in not sharing keys with bot 2023-01-22 11:12:47 +07:00
840efbdc2f Update API_URL to INFISICAL_API_URL 2023-01-21 13:14:39 -08:00
b91dc9e43e increase cli version 2023-01-21 13:04:14 -08:00
7470cd7af5 Merge pull request from asheliahut/add-domain-env
Allow INFISICAL_URL to use domain for self-hosted
2023-01-21 12:56:24 -08:00
d3a6977938 update docs for change cli api 2023-01-21 12:53:18 -08:00
7cc341ea40 update INFISICAL_DEFAULT_API_URL constant name 2023-01-21 12:26:26 -08:00
5297133c07 Set INFISICAL_URL to nothing 2023-01-21 12:25:56 -08:00
7a6230f2f8 Change INFISICAL_URL to API_URL 2023-01-21 12:24:24 -08:00
ffe66a3b8e Merge pull request from caioluis/main
docs(README): fix typo - Portueguese should be Portuguese
2023-01-21 12:10:23 -08:00
936cd51f29 Update README.md 2023-01-21 11:13:38 -08:00
0c24671d8b Merge pull request from Infisical/more-integrations
Render & Fly.io integrations, reduce page reloads for integrations page.
2023-01-22 00:20:09 +07:00
6969593b38 Fix Mintlify mint.json issue and list Render, Fly.io integrations as availalbe 2023-01-22 00:18:00 +07:00
0c351c0925 Add Render, Fly.io integrations and reduce integrations page reloads 2023-01-22 00:09:37 +07:00
656c408034 docs(README): fix typo - Portueguese should be Portuguese 2023-01-21 15:43:38 +00:00
74fb64bbb9 fix edge case of input same as default clobber env 2023-01-21 01:05:25 -08:00
3af85f9fba fix typo of company name 2023-01-21 00:26:52 -08:00
3c282460b2 Allow INFISCAL_URL to use domain for self-hosted 2023-01-21 00:18:43 -08:00
68b7e6e5ab Merge pull request from alexdanilowicz/patch-1
docs(README): nit typo - Frence should say French
2023-01-20 18:10:42 -08:00
9594157f3e docs(README): nit typo - Frence to French
Update the translations section to say French instead of Frence.
2023-01-20 17:49:43 -08:00
b6ed6ad61e increase cli version 2023-01-19 17:10:03 -08:00
06803519e6 Reduce page reloads for integrations page 2023-01-18 17:38:52 +07:00
251 changed files with 7819 additions and 1795 deletions
.env.example.eslintignore
.github
.goreleaser.yamlMakefileREADME.md
backend
cli/packages
docker-compose.dev.ymldocker-compose.yml
docs
frontend
.storybook
package-lock.jsonpackage.json
public
src
components
config
const.ts
context/AuthContext
ee/api/memberships
hooks
pages
reactQuery.ts
styles
tailwind.config.js
helm-charts/infisical
i18n

@ -64,7 +64,7 @@ POSTHOG_PROJECT_API_KEY=
STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRODUCT_CARD_AUTH=
STRIPE_PRODUCT_PRO=
STRIPE_PRODUCT_STARTER=
STRIPE_PRODUCT_TEAM=
STRIPE_PRODUCT_PRO=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=

@ -1,3 +1,4 @@
node_modules
built
healthcheck.js
tailwind.config.js

83
.github/values.yaml vendored

@ -1,36 +1,93 @@
#####
# INFISICAL K8 DEFAULT VALUES FILE
# PLEASE REPLACE VALUES/EDIT AS REQUIRED
#####
nameOverride: ""
frontend:
replicaCount: 1
name: frontend
podAnnotations: {}
deploymentAnnotations:
secrets.infisical.com/auto-reload: "true"
replicaCount: 2
image:
repository:
pullPolicy: Always
repository: infisical/frontend
pullPolicy: Always
tag: "latest"
kubeSecretRef: managed-secret-frontend
service:
# type of the frontend service
type: ClusterIP
# define the nodePort if service type is NodePort
# nodePort:
annotations: {}
backend:
replicaCount: 1
name: backend
podAnnotations: {}
deploymentAnnotations:
secrets.infisical.com/auto-reload: "true"
replicaCount: 2
image:
repository:
repository: infisical/backend
pullPolicy: Always
tag: "latest"
kubeSecretRef: managed-backend-secret
service:
annotations: {}
mongodb:
name: mongodb
podAnnotations: {}
image:
repository: mongo
pullPolicy: IfNotPresent
tag: "latest"
service:
annotations: {}
# By default the backend will be connected to a Mongo instance in the cluster.
# However, it is recommended to add a managed document DB connection string because the DB instance in the cluster does not have persistence yet ( data will be deleted on next deploy).
# Learn about connection string type here https://www.mongodb.com/docs/manual/reference/connection-string/
mongodbConnection: {}
# externalMongoDBConnectionString: <>
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
hostName: gamma.infisical.com
hostName: gamma.infisical.com # replace with your domain
frontend:
path: /
pathType: Prefix
backend:
path: /api
pathType: Prefix
tls:
- secretName: echo-tls
hosts:
- gamma.infisical.com
tls: []
backendEnvironmentVariables:
frontendEnvironmentVariables:
## Complete Ingress example
# ingress:
# enabled: true
# annotations:
# kubernetes.io/ingress.class: "nginx"
# cert-manager.io/issuer: letsencrypt-nginx
# hostName: k8.infisical.com
# frontend:
# path: /
# pathType: Prefix
# backend:
# path: /api
# pathType: Prefix
# tls:
# - secretName: letsencrypt-nginx
# hosts:
# - k8.infisical.com
###
### YOU MUST FILL IN ALL SECRETS BELOW
###
backendEnvironmentVariables: {}
frontendEnvironmentVariables: {}

@ -19,6 +19,7 @@ jobs:
with:
fetch-depth: 0
- run: git fetch --force --tags
- run: echo "Ref name ${{github.ref_name}}"
- uses: actions/setup-go@v3
with:
go-version: '>=1.19.3'
@ -33,11 +34,11 @@ jobs:
run: |
mkdir ../../osxcross
git clone https://github.com/plentico/osxcross-target.git ../../osxcross/target
- uses: goreleaser/goreleaser-action@v2
- uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
version: latest
args: release --rm-dist
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}

@ -14,6 +14,9 @@ before:
builds:
- id: darwin-build
binary: infisical
ldflags: -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
flags:
- -trimpath
env:
- CGO_ENABLED=1
- CC=/home/runner/work/osxcross/target/bin/o64-clang
@ -24,10 +27,14 @@ builds:
- goos: darwin
goarch: "386"
dir: ./cli
- id: all-other-builds
env:
- CGO_ENABLED=0
binary: infisical
ldflags: -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
flags:
- -trimpath
goos:
- freebsd
- linux
@ -65,8 +72,10 @@ release:
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}"
name_template: "{{ incpatch .Version }}-devel"
changelog:
sort: asc
filters:
@ -80,6 +89,7 @@ changelog:
# - infisical
# dir: "{{ dir .ArtifactPath }}"
# cmd: curl -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/infisical/
brews:
- name: infisical
tap:
@ -91,6 +101,13 @@ brews:
folder: Formula
homepage: "https://infisical.com"
description: "The official Infisical CLI"
install: |-
bin.install "infisical"
bash_completion.install "completions/infisical.bash" => "infisical"
zsh_completion.install "completions/infisical.zsh" => "_infisical"
fish_completion.install "completions/infisical.fish"
man1.install "manpages/infisical.1.gz"
nfpms:
- id: infisical
package_name: infisical
@ -116,6 +133,7 @@ nfpms:
dst: /usr/share/zsh/site-functions/_infisical
- src: ./manpages/infisical.1.gz
dst: /usr/share/man/man1/infisical.1.gz
scoop:
bucket:
owner: Infisical
@ -126,6 +144,7 @@ scoop:
homepage: "https://infisical.com"
description: "The official Infisical CLI"
license: MIT
aurs:
-
name: infisical-bin

@ -8,7 +8,7 @@ up-dev:
docker-compose -f docker-compose.dev.yml up --build
i-dev:
infisical export && infisical export > .env && docker-compose -f docker-compose.dev.yml up --build
infisical run -- docker-compose -f docker-compose.dev.yml up --build
up-prod:
docker-compose -f docker-compose.yml up --build

@ -24,6 +24,9 @@
<a href="https://github.com/Infisical/infisical/issues">
<img src="https://img.shields.io/github/commit-activity/m/infisical/infisical" alt="git commit activity" />
</a>
<a href="https://cloudsmith.io/~infisical/repos/">
<img src="https://img.shields.io/badge/Downloads-19.9k-orange" alt="Cloudsmith downloads" />
</a>
<a href="https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g">
<img src="https://img.shields.io/badge/chat-on%20Slack-blueviolet" alt="Slack community channel" />
</a>
@ -34,6 +37,12 @@
<img src="/img/infisical_github_repo.png" width="100%" alt="Dashboard" />
**Read this in other languages**: <kbd>[<img title="English" alt="English language" src="https://cdn.staticaly.com/gh/hjnilsson/country-flags/master/svg/us.svg" width="22">](i18n/README.en.md)</kbd>
<kbd>[<img title="Spanish" alt="Spanish language" src="https://cdn.staticaly.com/gh/hjnilsson/country-flags/master/svg/es.svg" width="22">](i18n/README.es.md)</kbd>
<kbd>[<img title="Korean" alt="Korean language" src="https://cdn.staticaly.com/gh/hjnilsson/country-flags/master/svg/kr.svg" width="22">](i18n/README.ko.md)</kbd>
<kbd>[<img title="Turkish" alt="Turkish language" src="https://cdn.staticaly.com/gh/hjnilsson/country-flags/master/svg/tr.svg" width="22">](i18n/README.tr.md)</kbd>
**[Infisical](https://infisical.com)** is an open source, E2EE tool to help teams manage and sync secrets and configs across their development workflow and infrastructure. It's designed to be simple and take minutes to get going.
- **[User-Friendly Dashboard](https://infisical.com/docs/getting-started/dashboard/project)** to manage your team's secrets and configs within projects
@ -141,7 +150,9 @@ We're currently setting the foundation and building [integrations](https://infis
</a>
</td>
<td align="left" valign="middle">
🔜 Fly.io
<a href="https://infisical.com/docs/integrations/cloud/flyio">
✔️ Fly.io
</a>
</td>
</tr>
<tr>
@ -154,7 +165,7 @@ We're currently setting the foundation and building [integrations](https://infis
</a>
</td>
<td align="left" valign="middle">
🔜 Railway
🔜 Railway (https://github.com/Infisical/infisical/issues/271)
</td>
</tr>
<tr>
@ -189,7 +200,7 @@ We're currently setting the foundation and building [integrations](https://infis
</a>
</td>
<td align="left" valign="middle">
🔜 Railway
🔜 Forge
</td>
</tr>
<tr>
@ -200,7 +211,9 @@ We're currently setting the foundation and building [integrations](https://infis
🔜 Supabase
</td>
<td align="left" valign="middle">
🔜 Render (https://github.com/Infisical/infisical/issues/132)
<a href="https://infisical.com/docs/integrations/cloud/render">
✔️ Render
</a>
</td>
</tr>
</tbody>
@ -334,10 +347,10 @@ Infisical officially launched as v.1.0 on November 21st, 2022. There are a lot o
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/JoaoVictor6"><img src="https://avatars.githubusercontent.com/u/68869379?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mocherfaoui"><img src="https://avatars.githubusercontent.com/u/37941426?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jon4hz"><img src="https://avatars.githubusercontent.com/u/26183582?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Grraahaam"><img src="https://avatars.githubusercontent.com/u/72856427?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Gabriellopes232"><img src="https://avatars.githubusercontent.com/u/74881862?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/cerrussell"><img src="https://avatars.githubusercontent.com/u/80227828?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/imakecodes"><img src="https://avatars.githubusercontent.com/u/35536648?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/JoaoVictor6"><img src="https://avatars.githubusercontent.com/u/68869379?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mocherfaoui"><img src="https://avatars.githubusercontent.com/u/37941426?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/cerrussell"><img src="https://avatars.githubusercontent.com/u/80227828?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jon4hz"><img src="https://avatars.githubusercontent.com/u/26183582?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Grraahaam"><img src="https://avatars.githubusercontent.com/u/72856427?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Gabriellopes232"><img src="https://avatars.githubusercontent.com/u/74881862?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/kmlgkcy"><img src="https://avatars.githubusercontent.com/u/102428035?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/samsbg"><img src="https://avatars.githubusercontent.com/u/70488844?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/imakecodes"><img src="https://avatars.githubusercontent.com/u/35536648?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/caioluis"><img src="https://avatars.githubusercontent.com/u/30005368?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/franky47"><img src="https://avatars.githubusercontent.com/u/1174092?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/alexdanilowicz"><img src="https://avatars.githubusercontent.com/u/29822597?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
## 🌎 Translations
Infisical is currently available in English, Korean, Frence, and Portueguese (Brazil). Help us translate Infisical to your language!
Infisical is currently available in English, Korean, French, and Portuguese (Brazil). Help us translate Infisical to your language!
You can find all the info in [this issue](https://github.com/Infisical/infisical/issues/181).

@ -3,8 +3,10 @@ export {};
declare global {
namespace NodeJS {
interface ProcessEnv {
PORT: string;
EMAIL_TOKEN_LIFETIME: string;
ENCRYPTION_KEY: string;
SALT_ROUNDS: string;
JWT_AUTH_LIFETIME: string;
JWT_AUTH_SECRET: string;
JWT_REFRESH_LIFETIME: string;
@ -19,23 +21,31 @@ declare global {
CLIENT_ID_HEROKU: string;
CLIENT_ID_VERCEL: string;
CLIENT_ID_NETLIFY: string;
CLIENT_ID_GITHUB: string;
CLIENT_SECRET_HEROKU: string;
CLIENT_SECRET_VERCEL: string;
CLIENT_SECRET_NETLIFY: string;
CLIENT_SECRET_GITHUB: string;
CLIENT_SLUG_VERCEL: string;
POSTHOG_HOST: string;
POSTHOG_PROJECT_API_KEY: string;
SENTRY_DSN: string;
SITE_URL: string;
SMTP_HOST: string;
SMTP_NAME: string;
SMTP_PASSWORD: string;
SMTP_SECURE: string;
SMTP_PORT: string;
SMTP_USERNAME: string;
STRIPE_PRODUCT_CARD_AUTH: string;
STRIPE_PRODUCT_PRO: string;
SMTP_PASSWORD: string;
SMTP_FROM_ADDRESS: string;
SMTP_FROM_NAME: string;
STRIPE_PRODUCT_STARTER: string;
STRIPE_PRODUCT_TEAM: string;
STRIPE_PRODUCT_PRO: string;
STRIPE_PUBLISHABLE_KEY: string;
STRIPE_SECRET_KEY: string;
STRIPE_WEBHOOK_SECRET: string;
TELEMETRY_ENABLED: string;
LICENSE_KEY: string;
}
}
}

@ -19,6 +19,7 @@
"axios": "^1.1.3",
"bcrypt": "^5.1.0",
"bigint-conversion": "^2.2.2",
"builder-pattern": "^2.2.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
@ -32,6 +33,7 @@
"jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21",
"mongoose": "^6.7.2",
"nodemailer": "^6.8.0",
"posthog-node": "^2.2.2",
@ -58,6 +60,7 @@
"@types/express": "^4.17.14",
"@types/jest": "^29.2.4",
"@types/jsonwebtoken": "^8.5.9",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.3",
"@types/nodemailer": "^6.4.6",
"@types/supertest": "^2.0.12",
@ -3221,6 +3224,12 @@
"resolved": "https://registry.npmjs.org/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz",
"integrity": "sha512-BqI9B92u+cM3ccp8mpHf+HzJ8fBlRwdmyd6+fz3p99m3V6ifT5O3zmOMi612PGkpeFeG/G6loxUnzlDNhfjPSA=="
},
"node_modules/@types/lodash": {
"version": "4.14.191",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
"dev": true
},
"node_modules/@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
@ -4085,6 +4094,11 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"node_modules/builder-pattern": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/builder-pattern/-/builder-pattern-2.2.0.tgz",
"integrity": "sha512-cES3qdeBzA4QyJi7rV/l/kAhIFX6AKo3vK66ZPXLNpjcQWCS8sjLKscly8imlfW2YPTo/hquMRMnaWpZ80Kj+g=="
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -4434,9 +4448,9 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/cookiejar": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz",
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
"dev": true
},
"node_modules/core-util-is": {
@ -14641,6 +14655,12 @@
"resolved": "https://registry.npmjs.org/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz",
"integrity": "sha512-BqI9B92u+cM3ccp8mpHf+HzJ8fBlRwdmyd6+fz3p99m3V6ifT5O3zmOMi612PGkpeFeG/G6loxUnzlDNhfjPSA=="
},
"@types/lodash": {
"version": "4.14.191",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
"dev": true
},
"@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
@ -15269,6 +15289,11 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true
},
"builder-pattern": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/builder-pattern/-/builder-pattern-2.2.0.tgz",
"integrity": "sha512-cES3qdeBzA4QyJi7rV/l/kAhIFX6AKo3vK66ZPXLNpjcQWCS8sjLKscly8imlfW2YPTo/hquMRMnaWpZ80Kj+g=="
},
"bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -15536,9 +15561,9 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"cookiejar": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz",
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
"dev": true
},
"core-util-is": {

@ -36,6 +36,7 @@
"@types/express": "^4.17.14",
"@types/jest": "^29.2.4",
"@types/jsonwebtoken": "^8.5.9",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.3",
"@types/nodemailer": "^6.4.6",
"@types/supertest": "^2.0.12",
@ -85,6 +86,7 @@
"axios": "^1.1.3",
"bcrypt": "^5.1.0",
"bigint-conversion": "^2.2.2",
"builder-pattern": "^2.2.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
@ -98,6 +100,7 @@
"jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21",
"mongoose": "^6.7.2",
"nodemailer": "^6.8.0",
"posthog-node": "^2.2.2",

@ -13,11 +13,11 @@ const MONGO_URL = process.env.MONGO_URL!;
const NODE_ENV = process.env.NODE_ENV! || 'production';
const VERBOSE_ERROR_OUTPUT = process.env.VERBOSE_ERROR_OUTPUT! === 'true' && true;
const LOKI_HOST = process.env.LOKI_HOST || undefined;
const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!;
const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!;
const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!;
const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!;
const CLIENT_ID_GITHUB = process.env.CLIENT_ID_GITHUB!;
const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!;
const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!;
const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!;
const CLIENT_SECRET_GITHUB = process.env.CLIENT_SECRET_GITHUB!;
@ -35,9 +35,9 @@ const SMTP_USERNAME = process.env.SMTP_USERNAME!;
const SMTP_PASSWORD = process.env.SMTP_PASSWORD!;
const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS!;
const SMTP_FROM_NAME = process.env.SMTP_FROM_NAME! || 'Infisical';
const STRIPE_PRODUCT_CARD_AUTH = process.env.STRIPE_PRODUCT_CARD_AUTH!;
const STRIPE_PRODUCT_PRO = process.env.STRIPE_PRODUCT_PRO!;
const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!;
const STRIPE_PRODUCT_PRO = process.env.STRIPE_PRODUCT_PRO!;
const STRIPE_PRODUCT_TEAM = process.env.STRIPE_PRODUCT_TEAM!;
const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY!;
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!;
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;
@ -80,9 +80,9 @@ export {
SMTP_PASSWORD,
SMTP_FROM_ADDRESS,
SMTP_FROM_NAME,
STRIPE_PRODUCT_CARD_AUTH,
STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER,
STRIPE_PRODUCT_TEAM,
STRIPE_PRODUCT_PRO,
STRIPE_PUBLISHABLE_KEY,
STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET,

@ -12,6 +12,8 @@ import {
JWT_AUTH_SECRET,
JWT_REFRESH_SECRET
} from '../../config';
import LoginSRPDetail from '../../models/LoginSRPDetail';
import { BadRequestError } from '../../utils/errors';
declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload {
@ -19,8 +21,6 @@ declare module 'jsonwebtoken' {
}
}
const clientPublicKeys: any = {};
/**
* Log in user step 1: Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol
* @param req
@ -46,13 +46,15 @@ export const login1 = async (req: Request, res: Response) => {
salt: user.salt,
verifier: user.verifier
},
() => {
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
clientPublicKeys[email] = {
clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
};
await LoginSRPDetail.findOneAndReplace({ email: email }, {
email: email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false })
return res.status(200).send({
serverPublicKey,
@ -85,15 +87,21 @@ export const login2 = async (req: Request, res: Response) => {
if (!user) throw new Error('Failed to find user');
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email })
if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
}
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: clientPublicKeys[email].serverBInt
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(clientPublicKeys[email].clientPublicKey);
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {

@ -1,8 +1,11 @@
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import axios from 'axios';
import { readFileSync } from 'fs';
import { IntegrationAuth, Integration } from '../../models';
import {
Integration,
IntegrationAuth,
Bot
} from '../../models';
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
import { IntegrationService } from '../../services';
import { getApps, revokeAccess } from '../../integrations';
@ -44,7 +47,7 @@ export const oAuthExchange = async (
environment: environments[0].slug,
});
} catch (err) {
Sentry.setUser(null);
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get OAuth2 code-token exchange'
@ -56,6 +59,67 @@ export const oAuthExchange = async (
});
};
/**
* Save integration access token as part of integration [integration] for workspace with id [workspaceId]
* @param req
* @param res
*/
export const saveIntegrationAccessToken = async (
req: Request,
res: Response
) => {
// TODO: refactor
let integrationAuth;
try {
const {
workspaceId,
accessToken,
integration
}: {
workspaceId: string;
accessToken: string;
integration: string;
} = req.body;
integrationAuth = await IntegrationAuth.findOneAndUpdate({
workspace: new Types.ObjectId(workspaceId),
integration
}, {
workspace: new Types.ObjectId(workspaceId),
integration
}, {
new: true,
upsert: true
});
const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId),
isActive: true
});
if (!bot) throw new Error('Bot must be enabled to save integration access token');
// encrypt and save integration access token
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessToken,
accessExpiresAt: undefined
});
if (!integrationAuth) throw new Error('Failed to save integration access token');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to save access token for integration'
});
}
return res.status(200).send({
integrationAuth
});
}
/**
* Return list of applications allowed for integration with integration authorization id [integrationAuthId]
* @param req
@ -70,7 +134,7 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
accessToken: req.accessToken
});
} catch (err) {
Sentry.setUser(null);
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get integration authorization applications'
@ -89,15 +153,14 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
* @returns
*/
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
let integrationAuth;
try {
const { integrationAuthId } = req.params;
await revokeAccess({
integrationAuth = await revokeAccess({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken
});
} catch (err) {
Sentry.setUser(null);
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete integration authorization'
@ -105,6 +168,6 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => {
}
return res.status(200).send({
message: 'Successfully deleted integration authorization'
integrationAuth
});
}

@ -1,25 +1,43 @@
import { Request, Response } from 'express';
import { readFileSync } from 'fs';
import * as Sentry from '@sentry/node';
import { Integration, Bot, BotKey } from '../../models';
import {
Integration,
Workspace,
Bot,
BotKey
} from '../../models';
import { EventService } from '../../services';
import { eventPushSecrets } from '../../events';
interface Key {
encryptedKey: string;
nonce: string;
}
/**
* Create/initialize an (empty) integration for integration authorization
* @param req
* @param res
* @returns
*/
export const createIntegration = async (req: Request, res: Response) => {
let integration;
try {
// initialize new integration after saving integration access token
integration = await new Integration({
workspace: req.integrationAuth.workspace._id,
isActive: false,
app: null,
environment: req.integrationAuth.workspace?.environments[0].slug,
integration: req.integrationAuth.integration,
integrationAuth: req.integrationAuth._id
}).save();
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to create integration'
});
}
interface PushSecret {
ciphertextKey: string;
ivKey: string;
tagKey: string;
hashKey: string;
ciphertextValue: string;
ivValue: string;
tagValue: string;
hashValue: string;
type: 'shared' | 'personal';
return res.status(200).send({
integration
});
}
/**
@ -36,13 +54,12 @@ export const updateIntegration = async (req: Request, res: Response) => {
try {
const {
app,
environment,
isActive,
target, // vercel-specific integration param
context, // netlify-specific integration param
siteId, // netlify-specific integration param
owner // github-specific integration param
app,
appId,
targetEnvironment,
owner, // github-specific integration param
} = req.body;
integration = await Integration.findOneAndUpdate(
@ -53,9 +70,8 @@ export const updateIntegration = async (req: Request, res: Response) => {
environment,
isActive,
app,
target,
context,
siteId,
appId,
targetEnvironment,
owner
},
{
@ -92,36 +108,15 @@ export const updateIntegration = async (req: Request, res: Response) => {
* @returns
*/
export const deleteIntegration = async (req: Request, res: Response) => {
let deletedIntegration;
let integration;
try {
const { integrationId } = req.params;
deletedIntegration = await Integration.findOneAndDelete({
integration = await Integration.findOneAndDelete({
_id: integrationId
});
if (!deletedIntegration) throw new Error('Failed to find integration');
const integrations = await Integration.find({
workspace: deletedIntegration.workspace
});
if (integrations.length === 0) {
// case: no integrations left, deactivate bot
const bot = await Bot.findOneAndUpdate({
workspace: deletedIntegration.workspace
}, {
isActive: false
}, {
new: true
});
if (bot) {
await BotKey.deleteOne({
bot: bot._id
});
}
}
if (!integration) throw new Error('Failed to find integration');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
@ -129,8 +124,8 @@ export const deleteIntegration = async (req: Request, res: Response) => {
message: 'Failed to delete integration'
});
}
return res.status(200).send({
deletedIntegration
integration
});
};

@ -1,6 +1,6 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Membership, MembershipOrg, User, Key } from '../../models';
import { Membership, MembershipOrg, User, Key, IMembership, Workspace } from '../../models';
import {
findMembership,
deleteMembership as deleteMember
@ -230,4 +230,4 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
invitee,
latestKey
});
};
};

@ -2,10 +2,7 @@ import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import {
SITE_URL,
STRIPE_SECRET_KEY,
STRIPE_PRODUCT_STARTER,
STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_CARD_AUTH
STRIPE_SECRET_KEY
} from '../../config';
import Stripe from 'stripe';
@ -23,12 +20,6 @@ import { createOrganization as create } from '../../helpers/organization';
import { addMembershipsOrg } from '../../helpers/membershipOrg';
import { OWNER, ACCEPTED } from '../../variables';
const productToPriceMap = {
starter: STRIPE_PRODUCT_STARTER,
pro: STRIPE_PRODUCT_PRO,
cardAuth: STRIPE_PRODUCT_CARD_AUTH
};
export const getOrganizations = async (req: Request, res: Response) => {
let organizations;
try {
@ -340,7 +331,6 @@ export const createOrganizationPortalSession = async (
if (paymentMethods.data.length < 1) {
// case: no payment method on file
productToPriceMap['cardAuth'];
session = await stripe.checkout.sessions.create({
customer: req.membershipOrg.organization.customerId,
mode: 'setup',

@ -9,8 +9,8 @@ import { checkEmailVerification } from '../../helpers/signup';
import { createToken } from '../../helpers/auth';
import { sendMail } from '../../helpers/nodemailer';
import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../../config';
const clientPublicKeys: any = {};
import LoginSRPDetail from '../../models/LoginSRPDetail';
import { BadRequestError } from '../../utils/errors';
/**
* Password reset step 1: Send email verification link to email [email]
@ -32,7 +32,7 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
error: 'Failed to send email verification for password reset'
});
}
const token = crypto.randomBytes(16).toString('hex');
await Token.findOneAndUpdate(
@ -44,7 +44,7 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
},
{ upsert: true, new: true }
);
await sendMail({
template: 'passwordReset.handlebars',
subjectLine: 'Infisical password reset',
@ -55,15 +55,15 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
callback_url: SITE_URL + '/password-reset'
}
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to send email for account recovery'
});
});
}
return res.status(200).send({
message: `Sent an email for account recovery to ${email}`
});
@ -79,7 +79,7 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
let user, token;
try {
const { email, code } = req.body;
user = await User.findOne({ email }).select('+publicKey');
if (!user || !user?.publicKey) {
// case: user doesn't exist with email [email] or
@ -93,7 +93,7 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
email,
code
});
// generate temporary password-reset token
token = createToken({
payload: {
@ -107,7 +107,7 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed email verification for password reset'
});
});
}
return res.status(200).send({
@ -130,7 +130,7 @@ export const srp1 = async (req: Request, res: Response) => {
const user = await User.findOne({
email: req.user.email
}).select('+salt +verifier');
if (!user) throw new Error('Failed to find user');
const server = new jsrp.server();
@ -139,13 +139,15 @@ export const srp1 = async (req: Request, res: Response) => {
salt: user.salt,
verifier: user.verifier
},
() => {
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
clientPublicKeys[req.user.email] = {
clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
};
await LoginSRPDetail.findOneAndReplace({ email: req.user.email }, {
email: req.user.email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false })
return res.status(200).send({
serverPublicKey,
@ -180,17 +182,21 @@ export const changePassword = async (req: Request, res: Response) => {
if (!user) throw new Error('Failed to find user');
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email })
if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
}
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: clientPublicKeys[req.user.email].serverBInt
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(
clientPublicKeys[req.user.email].clientPublicKey
);
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
@ -249,16 +255,22 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
if (!user) throw new Error('Failed to find user');
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email })
if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
}
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: clientPublicKeys[req.user.email].serverBInt
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(
clientPublicKeys[req.user.email].clientPublicKey
loginSRPDetailFromDB.clientPublicKey
);
// compare server and client shared keys
@ -311,16 +323,16 @@ export const getBackupPrivateKey = async (req: Request, res: Response) => {
backupPrivateKey = await BackupPrivateKey.findOne({
user: req.user._id
}).select('+encryptedPrivateKey +iv +tag');
if (!backupPrivateKey) throw new Error('Failed to find backup private key');
} catch (err) {
Sentry.setUser({ email: req.user.email});
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get backup private key'
});
}
return res.status(200).send({
backupPrivateKey
});
@ -348,15 +360,15 @@ export const resetPassword = async (req: Request, res: Response) => {
{
new: true
}
);
);
} catch (err) {
Sentry.setUser({ email: req.user.email});
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get backup private key'
});
});
}
return res.status(200).send({
message: 'Successfully reset password'
});

@ -6,8 +6,12 @@ import {
Workspace,
Integration,
ServiceTokenData,
Membership,
} from '../../models';
import { SecretVersion } from '../../ee/models';
import { BadRequestError } from '../../utils/errors';
import _ from 'lodash';
import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
/**
* Create new workspace environment named [environmentName] under workspace with id
@ -120,6 +124,15 @@ export const renameWorkspaceEnvironment = async (
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await Membership.updateMany(
{
workspace: workspaceId,
"deniedPermissions.environmentSlug": oldEnvironmentSlug
},
{ $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } },
{ arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] }
)
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
@ -188,6 +201,11 @@ export const deleteWorkspaceEnvironment = async (
workspace: workspaceId,
environment: environmentSlug,
});
await Membership.updateMany(
{ workspace: workspaceId },
{ $pull: { deniedPermissions: { environmentSlug: environmentSlug } } }
)
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
@ -202,3 +220,42 @@ export const deleteWorkspaceEnvironment = async (
environment: environmentSlug,
});
};
export const getAllAccessibleEnvironmentsOfWorkspace = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const workspacesUserIsMemberOf = await Membership.findOne({
workspace: workspaceId,
user: req.user
})
if (!workspacesUserIsMemberOf) {
throw BadRequestError()
}
const accessibleEnvironments: any = []
const deniedPermission = workspacesUserIsMemberOf.deniedPermissions
const relatedWorkspace = await Workspace.findById(workspaceId)
if (!relatedWorkspace) {
throw BadRequestError()
}
relatedWorkspace.environments.forEach(environment => {
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: ABILITY_READ })
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: ABILITY_WRITE })
if (isReadBlocked) {
return
} else {
accessibleEnvironments.push({
name: environment.name,
slug: environment.slug,
isWriteDenied: isWriteBlocked
})
}
})
res.json({ accessibleEnvironments })
};

@ -1,7 +1,7 @@
import to from 'await-to-js';
import { Types } from 'mongoose';
import { Request, Response } from 'express';
import { ISecret, Secret } from '../../models';
import { ISecret, Membership, Secret, Workspace } from '../../models';
import {
SECRET_PERSONAL,
SECRET_SHARED,
@ -10,13 +10,14 @@ import {
ACTION_UPDATE_SECRETS,
ACTION_DELETE_SECRETS
} from '../../variables';
import { ValidationError } from '../../utils/errors';
import { UnauthorizedRequestError, ValidationError } from '../../utils/errors';
import { EventService } from '../../services';
import { eventPushSecrets } from '../../events';
import { EESecretService, EELogService } from '../../ee/services';
import { postHogClient } from '../../services';
import { BadRequestError } from '../../utils/errors';
import { getChannelFromUserAgent } from '../../utils/posthog';
import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
/**
* Create secret(s) for workspace with id [workspaceId] and environment [environment]
@ -76,9 +77,15 @@ export const createSecrets = async (req: Request, res: Response) => {
}
}
*/
const channel = getChannelFromUserAgent(req.headers['user-agent'])
const { workspaceId, environment } = req.body;
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_WRITE)
if (!hasAccess) {
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
}
let toAdd;
if (Array.isArray(req.body.secrets)) {
// case: create multiple secrets
@ -105,19 +112,21 @@ export const createSecrets = async (req: Request, res: Response) => {
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
}) => ({
version: 1,
workspace: new Types.ObjectId(workspaceId),
type,
user: type === SECRET_PERSONAL ? req.user : undefined,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
}))
}) => {
return ({
version: 1,
workspace: new Types.ObjectId(workspaceId),
type,
user: type === SECRET_PERSONAL ? req.user : undefined,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
});
})
);
setTimeout(async () => {
@ -267,6 +276,14 @@ export const getSecrets = async (req: Request, res: Response) => {
userEmail = req.serviceTokenData.user.email;
}
// none service token case as service tokens are already scoped
if (!req.serviceTokenData) {
const hasAccess = await userHasWorkspaceAccess(userId, workspaceId, environment, ABILITY_READ)
if (!hasAccess) {
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
}
}
const [err, secrets] = await to(Secret.find(
{
workspace: workspaceId,
@ -369,7 +386,6 @@ export const updateSecrets = async (req: Request, res: Response) => {
*/
const channel = req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli';
// TODO: move type
interface PatchSecret {
id: string;

@ -8,6 +8,8 @@ import {
import {
SALT_ROUNDS
} from '../../config';
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
import { ABILITY_READ } from '../../variables/organization';
/**
* Return service token data associated with service token on request
@ -37,6 +39,11 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
expiresIn
} = req.body;
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_READ)
if (!hasAccess) {
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
}
const secret = crypto.randomBytes(16).toString('hex');
const secretHash = await bcrypt.hash(secret, SALT_ROUNDS);
@ -100,4 +107,8 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
return res.status(200).send({
serviceTokenData
});
}
}
function UnauthorizedRequestError(arg0: { message: string; }) {
throw new Error('Function not implemented.');
}

@ -3,11 +3,13 @@ import * as secretController from './secretController';
import * as secretSnapshotController from './secretSnapshotController';
import * as workspaceController from './workspaceController';
import * as actionController from './actionController';
import * as membershipController from './membershipController';
export {
stripeController,
secretController,
secretSnapshotController,
workspaceController,
actionController
actionController,
membershipController
}

@ -0,0 +1,63 @@
import { Request, Response } from "express";
import { Membership, Workspace } from "../../../models";
import { IMembershipPermission } from "../../../models/membership";
import { BadRequestError, UnauthorizedRequestError } from "../../../utils/errors";
import { ABILITY_READ, ABILITY_WRITE, ADMIN, MEMBER } from "../../../variables/organization";
import { Builder } from "builder-pattern"
import _ from "lodash";
export const denyMembershipPermissions = async (req: Request, res: Response) => {
const { membershipId } = req.params;
const { permissions } = req.body;
const sanitizedMembershipPermissions: IMembershipPermission[] = permissions.map((permission: IMembershipPermission) => {
if (!permission.ability || !permission.environmentSlug || ![ABILITY_READ, ABILITY_WRITE].includes(permission.ability)) {
throw BadRequestError({ message: "One or more required fields are missing from the request or have incorrect type" })
}
return Builder<IMembershipPermission>()
.environmentSlug(permission.environmentSlug)
.ability(permission.ability)
.build();
})
const sanitizedMembershipPermissionsUnique = _.uniqWith(sanitizedMembershipPermissions, _.isEqual)
const membershipToModify = await Membership.findById(membershipId)
if (!membershipToModify) {
throw BadRequestError({ message: "Unable to locate resource" })
}
// check if the user making the request is a admin of this project
if (![ADMIN, MEMBER].includes(membershipToModify.role)) {
throw UnauthorizedRequestError()
}
// check if the requested slugs are indeed a part of this related workspace
const relatedWorkspace = await Workspace.findById(membershipToModify.workspace)
if (!relatedWorkspace) {
throw BadRequestError({ message: "Something went wrong when locating the related workspace" })
}
const uniqueEnvironmentSlugs = new Set(_.uniq(_.map(relatedWorkspace.environments, 'slug')));
sanitizedMembershipPermissionsUnique.forEach(permission => {
if (!uniqueEnvironmentSlugs.has(permission.environmentSlug)) {
throw BadRequestError({ message: "Unknown environment slug reference" })
}
})
// update the permissions
const updatedMembershipWithPermissions = await Membership.findByIdAndUpdate(
{ _id: membershipToModify._id },
{ $set: { deniedPermissions: sanitizedMembershipPermissionsUnique } },
{ new: true }
)
if (!updatedMembershipWithPermissions) {
throw BadRequestError({ message: "The resource has been removed before it can be modified" })
}
res.send({
permissionsDenied: updatedMembershipWithPermissions.deniedPermissions
})
}

@ -0,0 +1,18 @@
import _ from "lodash";
import { Membership } from "../../models";
export const userHasWorkspaceAccess = async (userId: any, workspaceId: any, environment: any, action: any) => {
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
if (!membershipForWorkspace) {
return false
}
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: action });
if (isDisallowed) {
return false
}
return true
}

@ -1,5 +1,4 @@
import mongoose from 'mongoose';
import { ISecret, Secret } from '../models';
import { EESecretService } from '../ee/services';
import { getLogger } from '../utils/logger';
@ -16,6 +15,10 @@ const initDatabaseHelper = async ({
}) => {
try {
await mongoose.connect(mongoURL);
// allow empty strings to pass the required validator
mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string');
getLogger("database").info("Database connection established");
await EESecretService.initSecretVersioning();

@ -127,7 +127,6 @@ const syncIntegrationsHelper = async ({
}) => {
let integrations;
try {
integrations = await Integration.find({
workspace: workspaceId,
isActive: true,
@ -142,7 +141,7 @@ const syncIntegrationsHelper = async ({
workspaceId: integration.workspace.toString(),
environment: integration.environment
});
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
if (!integrationAuth) throw new Error('Failed to find integration auth');
@ -316,7 +315,7 @@ const setIntegrationAuthAccessHelper = async ({
}: {
integrationAuthId: string;
accessToken: string;
accessExpiresAt: Date;
accessExpiresAt: Date | undefined;
}) => {
let integrationAuth;
try {

@ -3,6 +3,7 @@ import Stripe from 'stripe';
import {
STRIPE_SECRET_KEY,
STRIPE_PRODUCT_STARTER,
STRIPE_PRODUCT_TEAM,
STRIPE_PRODUCT_PRO
} from '../config';
const stripe = new Stripe(STRIPE_SECRET_KEY, {
@ -14,6 +15,7 @@ import { Organization, MembershipOrg } from '../models';
const productToPriceMap = {
starter: STRIPE_PRODUCT_STARTER,
team: STRIPE_PRODUCT_TEAM,
pro: STRIPE_PRODUCT_PRO
};
@ -55,7 +57,7 @@ const createOrganization = async ({
} catch (err) {
Sentry.setUser({ email });
Sentry.captureException(err);
throw new Error('Failed to create organization');
throw new Error(`Failed to create organization [err=${err}]`);
}
return organization;

@ -12,14 +12,17 @@ import {
import {
IAction
} from '../ee/models';
import {
SECRET_SHARED,
import {
SECRET_SHARED,
SECRET_PERSONAL,
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS
} from '../variables';
import _ from 'lodash';
import { ABILITY_WRITE } from '../variables/organization';
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
/**
* Validate that user with id [userId] can modify secrets with ids [secretIds]
@ -34,7 +37,7 @@ const validateSecrets = async ({
}: {
userId: string;
secretIds: string[];
}) =>{
}) => {
let secrets;
try {
secrets = await Secret.find({
@ -42,20 +45,31 @@ const validateSecrets = async ({
$in: secretIds.map((secretId: string) => new Types.ObjectId(secretId))
}
});
const workspaceIdsSet = new Set((await Membership.find({
user: userId
}, 'workspace'))
.map((m) => m.workspace.toString()));
if (secrets.length != secretIds.length) {
throw BadRequestError({ message: 'Unable to validate some secrets' })
}
const userMemberships = await Membership.find({ user: userId })
const userMembershipById = _.keyBy(userMemberships, 'workspace');
const workspaceIdsSet = new Set(userMemberships.map((m) => m.workspace.toString()));
// for each secret check if the secret belongs to a workspace the user is a member of
secrets.forEach((secret: ISecret) => {
if (!workspaceIdsSet.has(secret.workspace.toString())) {
throw new Error('Failed to validate secret');
if (workspaceIdsSet.has(secret.workspace.toString())) {
const deniedMembershipPermissions = userMembershipById[secret.workspace.toString()].deniedPermissions;
const isDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: secret.environment, ability: ABILITY_WRITE });
if (isDisallowed) {
throw UnauthorizedRequestError({ message: 'You do not have the required permissions to perform this action' });
}
} else {
throw BadRequestError({ message: 'You cannot edit secrets of a workspace you are not a member of' });
}
});
} catch (err) {
throw new Error('Failed to validate secrets');
throw BadRequestError({ message: 'Unable to validate secrets' })
}
return secrets;
@ -127,13 +141,13 @@ const v1PushSecrets = async ({
workspaceId,
environment
});
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) =>
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) =>
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s })
, {});
const newSecretsObj: any = secrets.reduce((accumulator, s) =>
, {});
const newSecretsObj: any = secrets.reduce((accumulator, s) =>
({ ...accumulator, [`${s.type}-${s.hashKey}`]: s })
, {});
, {});
// handle deleting secrets
const toDelete = oldSecrets
@ -150,12 +164,12 @@ const v1PushSecrets = async ({
secretIds: toDelete
});
}
const toUpdate = oldSecrets
.filter((s) => {
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashComment) {
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashComment) {
// case: filter secrets where value or comment changed
return true;
}
@ -165,7 +179,7 @@ const v1PushSecrets = async ({
return true;
}
}
return false;
});
@ -217,7 +231,7 @@ const v1PushSecrets = async ({
};
});
await Secret.bulkWrite(operations as any);
// (EE) add secret versions for updated secrets
await EESecretService.addSecretVersions({
secretVersions: toUpdate.map(({
@ -245,7 +259,7 @@ const v1PushSecrets = async ({
secretValueTag: newSecret.tagValue,
secretValueHash: newSecret.hashValue
})
})
})
});
// handle adding new secrets
@ -319,7 +333,7 @@ const v1PushSecrets = async ({
}))
});
}
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
@ -344,7 +358,7 @@ const v1PushSecrets = async ({
* @param {String} obj.channel - channel (web/cli/auto)
* @param {String} obj.ipAddress - ip address of request to push secrets
*/
const v2PushSecrets = async ({
const v2PushSecrets = async ({
userId,
workspaceId,
environment,
@ -362,20 +376,20 @@ const v1PushSecrets = async ({
// TODO: clean up function and fix up types
try {
const actions: IAction[] = [];
// construct useful data structures
const oldSecrets = await getSecrets({
userId,
workspaceId,
environment
});
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) =>
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) =>
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s })
, {});
const newSecretsObj: any = secrets.reduce((accumulator, s) =>
, {});
const newSecretsObj: any = secrets.reduce((accumulator, s) =>
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s })
, {});
, {});
// handle deleting secrets
const toDelete = oldSecrets
@ -391,7 +405,7 @@ const v1PushSecrets = async ({
await EESecretService.markDeletedSecretVersions({
secretIds: toDelete
});
const deleteAction = await EELogService.createActionSecret({
name: ACTION_DELETE_SECRETS,
userId,
@ -401,12 +415,12 @@ const v1PushSecrets = async ({
deleteAction && actions.push(deleteAction);
}
const toUpdate = oldSecrets
.filter((s) => {
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretValueHash
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretCommentHash) {
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretValueHash
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretCommentHash) {
// case: filter secrets where value or comment changed
return true;
}
@ -416,7 +430,7 @@ const v1PushSecrets = async ({
return true;
}
}
return false;
});
@ -469,7 +483,7 @@ const v1PushSecrets = async ({
};
});
await Secret.bulkWrite(operations as any);
// (EE) add secret versions for updated secrets
await EESecretService.addSecretVersions({
secretVersions: toUpdate.map((s) => {
@ -482,7 +496,7 @@ const v1PushSecrets = async ({
environment: s.environment,
isDeleted: false
})
})
})
});
const updateAction = await EELogService.createActionSecret({
@ -507,18 +521,19 @@ const v1PushSecrets = async ({
workspace: workspaceId,
type: toAdd[idx].type,
environment,
...( toAdd[idx].type === 'personal' ? { user: userId } : {})
...(toAdd[idx].type === 'personal' ? { user: userId } : {})
}))
);
// (EE) add secret versions for new secrets
EESecretService.addSecretVersions({
secretVersions: newSecrets.map((secretDocument) => {
secretVersions: newSecrets.map((secretDocument) => {
return {
...secretDocument.toObject(),
secret: secretDocument._id,
isDeleted: false
}})
}
})
});
const addAction = await EELogService.createActionSecret({
@ -529,7 +544,7 @@ const v1PushSecrets = async ({
});
addAction && actions.push(addAction);
}
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
@ -560,7 +575,7 @@ const v1PushSecrets = async ({
* @param {String} obj.workspaceId - id of workspace to pull from
* @param {String} obj.environment - environment for secrets
*/
const getSecrets = async ({
const getSecrets = async ({
userId,
workspaceId,
environment
@ -570,7 +585,7 @@ const v1PushSecrets = async ({
environment: string;
}): Promise<ISecret[]> => {
let secrets: any; // TODO: FIX any
try {
// get shared workspace secrets
const sharedSecrets = await Secret.find({
@ -622,7 +637,7 @@ const pullSecrets = async ({
ipAddress: string;
}): Promise<ISecret[]> => {
let secrets: any;
try {
secrets = await getSecrets({
userId,

@ -66,7 +66,7 @@ const checkEmailVerification = async ({
email,
token: code
});
if (!token) throw new Error('Failed to find email verification token');
} catch (err) {
Sentry.setUser(null);
@ -116,7 +116,7 @@ const initializeDefaultOrg = async ({
roles: [ADMIN]
});
} catch (err) {
throw new Error('Failed to initialize default organization and workspace');
throw new Error(`Failed to initialize default organization and workspace [err=${err}]`);
}
};

@ -8,6 +8,7 @@ import { DatabaseService } from './services';
import { setUpHealthEndpoint } from './services/health';
import { initSmtp } from './services/smtp';
import { setTransporter } from './helpers/nodemailer';
import { createTestUserForDevelopment } from './utils/addDevelopmentUser';
DatabaseService.initDatabase(MONGO_URL);
@ -23,3 +24,5 @@ if (NODE_ENV !== 'test') {
environment: NODE_ENV
});
}
createTestUserForDevelopment()

@ -7,9 +7,13 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL,
INTEGRATION_FLYIO_API_URL
} from '../variables';
/**
@ -29,10 +33,11 @@ const getApps = async ({
}) => {
interface App {
name: string;
siteId?: string;
appId?: string;
owner?: string;
}
let apps: App[]; // TODO: add type and define payloads for apps
let apps: App[];
try {
switch (integrationAuth.integration) {
case INTEGRATION_HEROKU:
@ -48,13 +53,21 @@ const getApps = async ({
break;
case INTEGRATION_NETLIFY:
apps = await getAppsNetlify({
integrationAuth,
accessToken
});
break;
case INTEGRATION_GITHUB:
apps = await getAppsGithub({
integrationAuth,
accessToken
});
break;
case INTEGRATION_RENDER:
apps = await getAppsRender({
accessToken
});
break;
case INTEGRATION_FLYIO:
apps = await getAppsFlyio({
accessToken
});
break;
@ -69,7 +82,7 @@ const getApps = async ({
};
/**
* Return list of names of apps for Heroku integration
* Return list of apps for Heroku integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for Heroku API
* @returns {Object[]} apps - names of Heroku apps
@ -141,17 +154,15 @@ const getAppsVercel = async ({
};
/**
* Return list of names of sites for Netlify integration
* Return list of sites for Netlify integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for Netlify API
* @returns {Object[]} apps - names of Netlify sites
* @returns {String} apps.name - name of Netlify site
*/
const getAppsNetlify = async ({
integrationAuth,
accessToken
}: {
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
let apps;
@ -166,7 +177,7 @@ const getAppsNetlify = async ({
apps = res.map((a: any) => ({
name: a.name,
siteId: a.site_id
appId: a.site_id
}));
} catch (err) {
Sentry.setUser(null);
@ -178,17 +189,15 @@ const getAppsNetlify = async ({
};
/**
* Return list of names of repositories for Github integration
* Return list of repositories for Github integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for Netlify API
* @returns {Object[]} apps - names of Netlify sites
* @returns {String} apps.name - name of Netlify site
*/
const getAppsGithub = async ({
integrationAuth,
accessToken
}: {
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
let apps;
@ -220,4 +229,94 @@ const getAppsGithub = async ({
return apps;
};
/**
* Return list of services for Render integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for Render API
* @returns {Object[]} apps - names and ids of Render services
* @returns {String} apps.name - name of Render service
* @returns {String} apps.appId - id of Render service
*/
const getAppsRender = async ({
accessToken
}: {
accessToken: string;
}) => {
let apps: any;
try {
const res = (
await axios.get(`${INTEGRATION_RENDER_API_URL}/v1/services`, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})
).data;
apps = res
.map((a: any) => ({
name: a.service.name,
appId: a.service.id
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get Render services');
}
return apps;
}
/**
* Return list of apps for Fly.io integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for Fly.io API
* @returns {Object[]} apps - names and ids of Fly.io apps
* @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 res = (await axios({
url: INTEGRATION_FLYIO_API_URL,
method: 'post',
headers: {
'Authorization': 'Bearer ' + accessToken
},
data: {
query,
variables: {
role: null
}
}
})).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');
}
return apps;
}
export { getApps };

@ -1,6 +1,11 @@
import axios from 'axios';
import * as Sentry from '@sentry/node';
import { IIntegrationAuth, IntegrationAuth, Integration } from '../models';
import {
IIntegrationAuth,
IntegrationAuth,
Integration,
Bot,
BotKey
} from '../models';
import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
@ -15,6 +20,7 @@ const revokeAccess = async ({
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
let deletedIntegrationAuth;
try {
// add any integration-specific revocation logic
switch (integrationAuth.integration) {
@ -28,7 +34,7 @@ const revokeAccess = async ({
break;
}
const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
_id: integrationAuth._id
});
@ -42,6 +48,8 @@ const revokeAccess = async ({
Sentry.captureException(err);
throw new Error('Failed to delete integration authorization');
}
return deletedIntegrationAuth;
};
export { revokeAccess };

@ -1,4 +1,4 @@
import axios, { AxiosError } from 'axios';
import axios from 'axios';
import * as Sentry from '@sentry/node';
import { Octokit } from '@octokit/rest';
// import * as sodium from 'libsodium-wrappers';
@ -10,9 +10,13 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL,
INTEGRATION_FLYIO_API_URL
} from '../variables';
import { access, appendFile } from 'fs';
@ -21,8 +25,6 @@ import { access, appendFile } from 'fs';
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {IIntegrationAuth} obj.integrationAuth - integration auth details
* @param {Object} obj.app - app in integration
* @param {Object} obj.target - (optional) target (environment) in integration
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for integration
*/
@ -69,6 +71,20 @@ const syncSecrets = async ({
accessToken
});
break;
case INTEGRATION_RENDER:
await syncSecretsRender({
integration,
secrets,
accessToken
});
break;
case INTEGRATION_FLYIO:
await syncSecretsFlyio({
integration,
secrets,
accessToken
});
break;
}
} catch (err) {
Sentry.setUser(null);
@ -78,10 +94,11 @@ const syncSecrets = async ({
};
/**
* Sync/push [secrets] to Heroku [app]
* Sync/push [secrets] to Heroku app named [integration.app]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for Heroku integration
*/
const syncSecretsHeroku = async ({
integration,
@ -129,7 +146,7 @@ const syncSecretsHeroku = async ({
};
/**
* Sync/push [secrets] to Heroku [app]
* Sync/push [secrets] to Vercel project named [integration.app]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
@ -174,7 +191,7 @@ const syncSecretsVercel = async ({
))
.data
.envs
.filter((secret: VercelSecret) => secret.target.includes(integration.target))
.filter((secret: VercelSecret) => secret.target.includes(integration.targetEnvironment))
.map(async (secret: VercelSecret) => (await axios.get(
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`,
{
@ -201,7 +218,7 @@ const syncSecretsVercel = async ({
key: key,
value: secrets[key],
type: 'encrypted',
target: [integration.target]
target: [integration.targetEnvironment]
});
}
});
@ -216,7 +233,7 @@ const syncSecretsVercel = async ({
key: key,
value: secrets[key],
type: 'encrypted',
target: [integration.target]
target: [integration.targetEnvironment]
});
}
} else {
@ -226,7 +243,7 @@ const syncSecretsVercel = async ({
key: key,
value: res[key].value,
type: 'encrypted',
target: [integration.target],
target: [integration.targetEnvironment],
});
}
});
@ -287,11 +304,12 @@ const syncSecretsVercel = async ({
}
/**
* Sync/push [secrets] to Netlify site [app]
* Sync/push [secrets] to Netlify site with id [integration.appId]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {IIntegrationAuth} obj.integrationAuth - integration auth details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {Object} obj.accessToken - access token for Netlify integration
*/
const syncSecretsNetlify = async ({
integration,
@ -323,7 +341,7 @@ const syncSecretsNetlify = async ({
const getParams = new URLSearchParams({
context_name: 'all', // integration.context or all
site_id: integration.siteId
site_id: integration.appId
});
const res = (await axios.get(
@ -354,7 +372,7 @@ const syncSecretsNetlify = async ({
key,
values: [{
value: secrets[key],
context: integration.context
context: integration.targetEnvironment
}]
});
} else {
@ -365,15 +383,15 @@ const syncSecretsNetlify = async ({
[value.context]: value
}), {});
if (integration.context in contexts) {
if (integration.targetEnvironment in contexts) {
// case: Netlify secret value exists in integration context
if (secrets[key] !== contexts[integration.context].value) {
if (secrets[key] !== contexts[integration.targetEnvironment].value) {
// case: Infisical and Netlify secret values are different
// -> update Netlify secret context and value
updateSecrets.push({
key,
values: [{
context: integration.context,
context: integration.targetEnvironment,
value: secrets[key]
}]
});
@ -384,7 +402,7 @@ const syncSecretsNetlify = async ({
updateSecrets.push({
key,
values: [{
context: integration.context,
context: integration.targetEnvironment,
value: secrets[key]
}]
});
@ -402,7 +420,7 @@ const syncSecretsNetlify = async ({
const numberOfValues = res[key].values.length;
res[key].values.forEach((value: NetlifyValue) => {
if (value.context === integration.context) {
if (value.context === integration.targetEnvironment) {
if (numberOfValues <= 1) {
// case: Netlify secret value has less than 1 context -> delete secret
deleteSecrets.push(key);
@ -412,7 +430,7 @@ const syncSecretsNetlify = async ({
key,
values: [{
id: value.id,
context: integration.context,
context: integration.targetEnvironment,
value: value.value
}]
});
@ -423,7 +441,7 @@ const syncSecretsNetlify = async ({
});
const syncParams = new URLSearchParams({
site_id: integration.siteId
site_id: integration.appId
});
if (newSecrets.length > 0) {
@ -492,11 +510,12 @@ const syncSecretsNetlify = async ({
}
/**
* Sync/push [secrets] to GitHub [repo]
* Sync/push [secrets] to GitHub repo with name [integration.app]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {IIntegrationAuth} obj.integrationAuth - integration auth details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for GitHub integration
*/
const syncSecretsGitHub = async ({
integration,
@ -605,4 +624,175 @@ const syncSecretsGitHub = async ({
}
};
/**
* Sync/push [secrets] to Render service with id [integration.appId]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for Render integration
*/
const syncSecretsRender = async ({
integration,
secrets,
accessToken
}: {
integration: IIntegration;
secrets: any;
accessToken: string;
}) => {
try {
await axios.put(
`${INTEGRATION_RENDER_API_URL}/v1/services/${integration.appId}/env-vars`,
Object.keys(secrets).map((key) => ({
key,
value: secrets[key]
})),
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to sync secrets to Render');
}
}
/**
* Sync/push [secrets] to Fly.io app
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for Render integration
*/
const syncSecretsFlyio = async ({
integration,
secrets,
accessToken
}: {
integration: IIntegration;
secrets: any;
accessToken: string;
}) => {
try {
// set secrets
const SetSecrets = `
mutation($input: SetSecretsInput!) {
setSecrets(input: $input) {
release {
id
version
reason
description
user {
id
email
name
}
evaluationId
createdAt
}
}
}
`;
await axios({
url: INTEGRATION_FLYIO_API_URL,
method: 'post',
headers: {
'Authorization': 'Bearer ' + accessToken
},
data: {
query: SetSecrets,
variables: {
input: {
appId: integration.app,
secrets: Object.entries(secrets).map(([key, value]) => ({ key, value }))
}
}
}
});
// get secrets
interface FlyioSecret {
name: string;
digest: string;
createdAt: string;
}
const GetSecrets = `query ($appName: String!) {
app(name: $appName) {
secrets {
name
digest
createdAt
}
}
}`;
const getSecretsRes = (await axios({
method: 'post',
url: INTEGRATION_FLYIO_API_URL,
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
data: {
query: GetSecrets,
variables: {
appName: integration.app
}
}
})).data.data.app.secrets;
const deleteSecretsKeys = getSecretsRes
.filter((secret: FlyioSecret) => !(secret.name in secrets))
.map((secret: FlyioSecret) => secret.name);
// unset (delete) secrets
const DeleteSecrets = `mutation($input: UnsetSecretsInput!) {
unsetSecrets(input: $input) {
release {
id
version
reason
description
user {
id
email
name
}
evaluationId
createdAt
}
}
}`;
await axios({
method: 'post',
url: INTEGRATION_FLYIO_API_URL,
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
data: {
query: DeleteSecrets,
variables: {
input: {
appId: integration.app,
keys: deleteSecretsKeys
}
}
}
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to sync secrets to Fly.io');
}
}
export { syncSecrets };

@ -1,10 +1,12 @@
import * as Sentry from '@sentry/node';
import { Request, Response, NextFunction } from 'express';
import { IntegrationAuth } from '../models';
import { IntegrationAuth, IWorkspace } from '../models';
import { IntegrationService } from '../services';
import { validateMembership } from '../helpers/membership';
import { UnauthorizedRequestError } from '../utils/errors';
type req = 'params' | 'body' | 'query';
/**
* Validate if user on request is a member of workspace with proper roles associated
* with the integration authorization on request params.
@ -14,17 +16,20 @@ import { UnauthorizedRequestError } from '../utils/errors';
*/
const requireIntegrationAuthorizationAuth = ({
acceptedRoles,
attachAccessToken = true
attachAccessToken = true,
location = 'params'
}: {
acceptedRoles: string[];
attachAccessToken?: boolean;
location?: req;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const { integrationAuthId } = req.params;
const { integrationAuthId } = req[location];
const integrationAuth = await IntegrationAuth.findOne({
_id: integrationAuthId
}).select(
})
.populate<{ workspace: IWorkspace }>('workspace')
.select(
'+refreshCiphertext +refreshIV +refreshTag +accessCiphertext +accessIV +accessTag +accessExpiresAt'
);
@ -34,7 +39,7 @@ const requireIntegrationAuthorizationAuth = ({
await validateMembership({
userId: req.user._id.toString(),
workspaceId: integrationAuth.workspace.toString(),
workspaceId: integrationAuth.workspace._id.toString(),
acceptedRoles
});

@ -0,0 +1,23 @@
import mongoose, { Schema, model } from 'mongoose';
const LoginSRPDetailSchema = new Schema(
{
clientPublicKey: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
serverBInt: { type: mongoose.Schema.Types.Buffer },
expireAt: { type: Date }
}
);
const LoginSRPDetail = model('LoginSRPDetail', LoginSRPDetailSchema);
// LoginSRPDetailSchema.index({ "expireAt": 1 }, { expireAfterSeconds: 0 });
export default LoginSRPDetail;

@ -3,7 +3,9 @@ import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
INTEGRATION_GITHUB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO
} from '../variables';
export interface IIntegration {
@ -12,20 +14,19 @@ export interface IIntegration {
environment: string;
isActive: boolean;
app: string;
target: string;
context: string;
siteId: string;
owner: string;
integration: 'heroku' | 'vercel' | 'netlify' | 'github';
targetEnvironment: string;
appId: string;
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'render' | 'flyio';
integrationAuth: Types.ObjectId;
}
const integrationSchema = new Schema<IIntegration>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true
},
environment: {
type: String,
@ -40,18 +41,13 @@ const integrationSchema = new Schema<IIntegration>(
type: String,
default: null
},
target: {
// vercel-specific target (environment)
appId: { // (new)
// id of app in provider
type: String,
default: null
},
context: {
// netlify-specific context (deploy)
type: String,
default: null
},
siteId: {
// netlify-specific site (app) id
targetEnvironment: { // (new)
// target environment
type: String,
default: null
},
@ -66,7 +62,9 @@ const integrationSchema = new Schema<IIntegration>(
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
INTEGRATION_GITHUB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO
],
required: true
},

@ -9,7 +9,7 @@ import {
export interface IIntegrationAuth {
_id: Types.ObjectId;
workspace: Types.ObjectId;
integration: 'heroku' | 'vercel' | 'netlify' | 'github';
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'render' | 'flyio';
teamId: string;
accountId: string;
refreshCiphertext?: string;
@ -24,8 +24,9 @@ export interface IIntegrationAuth {
const integrationAuthSchema = new Schema<IIntegrationAuth>(
{
workspace: {
type: Schema.Types.ObjectId,
required: true
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true
},
integration: {
type: String,

@ -1,15 +1,21 @@
import { Schema, model, Types } from 'mongoose';
import { ADMIN, MEMBER } from '../variables';
export interface IMembershipPermission {
environmentSlug: string,
ability: string
}
export interface IMembership {
_id: Types.ObjectId;
user: Types.ObjectId;
inviteEmail?: string;
workspace: Types.ObjectId;
role: 'admin' | 'member';
deniedPermissions: IMembershipPermission[]
}
const membershipSchema = new Schema(
const membershipSchema = new Schema<IMembership>(
{
user: {
type: Schema.Types.ObjectId,
@ -23,6 +29,18 @@ const membershipSchema = new Schema(
ref: 'Workspace',
required: true
},
deniedPermissions: {
type: [
{
environmentSlug: String,
ability: {
type: String,
enum: ['read', 'write']
},
},
],
default: []
},
role: {
type: String,
enum: [ADMIN, MEMBER],

@ -3,12 +3,27 @@ const router = express.Router();
import {
requireAuth,
requireIntegrationAuth,
requireIntegrationAuthorizationAuth,
validateRequest
} from '../../middleware';
import { ADMIN, MEMBER } from '../../variables';
import { body, param } from 'express-validator';
import { integrationController } from '../../controllers/v1';
router.post( // new: add new integration
'/',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER],
location: 'body'
}),
body('integrationAuthId').exists().trim(),
validateRequest,
integrationController.createIntegration
);
router.patch(
'/:integrationId',
requireAuth({
@ -18,12 +33,11 @@ router.patch(
acceptedRoles: [ADMIN, MEMBER]
}),
param('integrationId').exists().trim(),
body('isActive').exists().isBoolean(),
body('app').exists().trim(),
body('environment').exists().trim(),
body('isActive').exists().isBoolean(),
body('target').exists(),
body('context').exists(),
body('siteId').exists(),
body('appId').exists(),
body('targetEnvironment').exists(),
body('owner').exists(),
validateRequest,
integrationController.updateIntegration

@ -34,6 +34,22 @@ router.post(
integrationAuthController.oAuthExchange
);
router.post(
'/access-token',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
location: 'body'
}),
body('workspaceId').exists().trim().notEmpty(),
body('accessToken').exists().trim().notEmpty(),
body('integration').exists().trim().notEmpty(),
validateRequest,
integrationAuthController.saveIntegrationAccessToken
);
router.get(
'/:integrationAuthId/apps',
requireAuth({

@ -3,14 +3,15 @@ const router = express.Router();
import { body, param } from 'express-validator';
import { requireAuth, validateRequest } from '../../middleware';
import { membershipController } from '../../controllers/v1';
import { membershipController as EEMembershipControllers } from '../../ee/controllers/v1';
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
router.get( // used for old CLI (deprecate)
'/:workspaceId/connect',
requireAuth({
acceptedAuthModes: ['jwt']
}),
acceptedAuthModes: ['jwt']
}),
param('workspaceId').exists().trim(),
validateRequest,
membershipController.validateMembership
@ -19,8 +20,8 @@ router.get( // used for old CLI (deprecate)
router.delete(
'/:membershipId',
requireAuth({
acceptedAuthModes: ['jwt']
}),
acceptedAuthModes: ['jwt']
}),
param('membershipId').exists().trim(),
validateRequest,
membershipController.deleteMembership
@ -29,11 +30,22 @@ router.delete(
router.post(
'/:membershipId/change-role',
requireAuth({
acceptedAuthModes: ['jwt']
}),
acceptedAuthModes: ['jwt']
}),
body('role').exists().trim(),
validateRequest,
membershipController.changeMembershipRole
);
router.post(
'/:membershipId/deny-permissions',
requireAuth({
acceptedAuthModes: ['jwt']
}),
param('membershipId').isMongoId().exists().trim(),
body('permissions').isArray().exists(),
validateRequest,
EEMembershipControllers.denyMembershipPermissions
);
export default router;

@ -54,4 +54,17 @@ router.delete(
environmentController.deleteWorkspaceEnvironment
);
router.get(
'/:workspaceId/environments',
requireAuth({
acceptedAuthModes: ['jwt'],
}),
requireWorkspaceAuth({
acceptedRoles: [MEMBER, ADMIN],
}),
param('workspaceId').exists().trim(),
validateRequest,
environmentController.getAllAccessibleEnvironmentsOfWorkspace
);
export default router;

@ -32,7 +32,7 @@ router.post(
!secret.secretKeyCiphertext ||
!secret.secretKeyIV ||
!secret.secretKeyTag ||
!secret.secretValueCiphertext ||
(typeof secret.secretValueCiphertext !== 'string') ||
!secret.secretValueIV ||
!secret.secretValueTag
) {

@ -122,7 +122,7 @@ class IntegrationService {
* @param {Object} obj
* @param {String} obj.integrationAuthId - id of integration auth
* @param {String} obj.accessToken - access token
* @param {String} obj.accessExpiresAt - expiration date of access token
* @param {Date} obj.accessExpiresAt - expiration date of access token
* @returns {IntegrationAuth} - updated integration auth
*/
static async setIntegrationAuthAccess({
@ -132,7 +132,7 @@ class IntegrationService {
}: {
integrationAuthId: string;
accessToken: string;
accessExpiresAt: Date;
accessExpiresAt: Date | undefined;
}) {
return await setIntegrationAuthAccessHelper({
integrationAuthId,

@ -0,0 +1,140 @@
/************************************************************************************************
*
* Attention: The credentials below are only for development purposes, it should never be used for production
*
************************************************************************************************/
import { NODE_ENV } from "../config"
import { Key, Membership, MembershipOrg, Organization, User, Workspace } from "../models";
import { Types } from 'mongoose';
export const createTestUserForDevelopment = async () => {
if (NODE_ENV === "development") {
const testUserEmail = "test@localhost.local"
const testUserPassword = "testInfisical1"
const testUserId = "63cefa6ec8d3175601cfa980"
const testWorkspaceId = "63cefb15c8d3175601cfa989"
const testOrgId = "63cefb15c8d3175601cfa985"
const testMembershipId = "63cefb159185d9aa3ef0cf35"
const testMembershipOrgId = "63cefb159185d9aa3ef0cf31"
const testWorkspaceKeyId = "63cf48f0225e6955acec5eff"
const testUser = {
_id: testUserId,
email: testUserEmail,
refreshVersion: 0,
encryptedPrivateKey: 'ITMdDXtLoxib4+53U/qzvIV/T/UalRwimogFCXv/UsulzEoiKM+aK2aqOb0=',
firstName: 'Jake',
iv: '9fp0dZHI+UuHeKkWMDvD6w==',
lastName: 'Moni',
publicKey: 'cf44BhkybbBfsE0fZHe2jvqtCj6KLXvSq4hVjV0svzk=',
salt: 'd8099dc70958090346910fb9639262b83cf526fc9b4555a171b36a9e1bcd0240',
tag: 'bQ/UTghqcQHRoSMpLQD33g==',
verifier: '12271fcd50937ca4512e1e3166adaf9d9fc7a5cd0e4c4cb3eda89f35572ede4d9eef23f64aef9220367abff9437b0b6fa55792c442f177201d87051cf77dadade254ff667170440327355fb7d6ac4745d4db302f4843632c2ed5919ebdcff343287a4cd552255d9e3ce81177edefe089617b7616683901475d393405f554634b9bf9230c041ac85624f37a60401be20b78044932580ae0868323be3749fbf856df1518153ba375fec628275f0c445f237446ea4aa7f12c1aa1d6b5fd74b7f2e88d062845a19819ec63f2d2ed9e9f37c055149649461d997d2ae1482f53b04f9de7493efbb9686fb19b2d559b9aa2b502c22dec83f9fc43290dfea89a1dc6f03580b3642b3824513853e81a441be9a0b2fde2231bac60f3287872617a36884697805eeea673cf1a351697834484ada0f282e4745015c9c2928d61e6d092f1b9c3a27eda8413175d23bb2edae62f82ccaf52bf5a6a90344a766c7e4ebf65dae9ae90b2ad4ae65dbf16e3a6948e429771cc50307ae86d454f71a746939ed061f080dd3ae369c1a0739819aca17af46a085bac1f2a5d936d198e7951a8ac3bb38b893665fe7312835abd3f61811f81efa2a8761af5070085f9b6adcca80bf9b0d81899c3d41487fba90728bb24eceb98bd69770360a232624133700ceb4d153f2ad702e0a5b7dfaf97d20bc8aa71dc8c20024a58c06a8fecdad18cb5a2f89c51eaf7'
}
const testWorkspaceKey = {
_id: new Types.ObjectId(testWorkspaceKeyId),
workspace: testWorkspaceId,
encryptedKey: '96ZIRSU21CjVzIQ4Yp994FGWQvDdyK3gq+z+NCaJLK0ByTlvUePmf+AYGFJjkAdz',
nonce: '1jhCGqg9Wx3n0OtVxbDgiYYGq4S3EdgO',
sender: '63cefa6ec8d3175601cfa980',
receiver: '63cefa6ec8d3175601cfa980',
}
const testWorkspace = {
_id: new Types.ObjectId(testWorkspaceId),
name: 'Example Project',
organization: testOrgId,
environments: [
{
_id: '63cefb15c8d3175601cfa98a',
name: 'Development',
slug: 'dev'
},
{
_id: '63cefb15c8d3175601cfa98b',
name: 'Test',
slug: 'test'
},
{
_id: '63cefb15c8d3175601cfa98c',
name: 'Staging',
slug: 'staging'
},
{
_id: '63cefb15c8d3175601cfa98d',
name: 'Production',
slug: 'prod'
}
],
}
const testOrg = {
_id: testOrgId,
name: 'Jake\'s organization'
}
const testMembershipOrg = {
_id: testMembershipOrgId,
organization: testOrgId,
role: 'owner',
status: 'accepted',
user: testUserId,
}
const testMembership = {
_id: testMembershipId,
role: 'admin',
user: testUserId,
workspace: testWorkspaceId
}
try {
// create user if not exist
const userInDB = await User.findById(testUserId)
if (!userInDB) {
await User.create(testUser)
}
// create org if not exist
const orgInDB = await Organization.findById(testOrgId)
if (!orgInDB) {
await Organization.create(testOrg)
}
// create membership org if not exist
const membershipOrgInDB = await MembershipOrg.findById(testMembershipOrgId)
if (!membershipOrgInDB) {
await MembershipOrg.create(testMembershipOrg)
}
// create membership
const membershipInDB = await Membership.findById(testMembershipId)
if (!membershipInDB) {
await Membership.create(testMembership)
}
// create workspace if not exist
const workspaceInDB = await Workspace.findById(testWorkspaceId)
if (!workspaceInDB) {
await Workspace.create(testWorkspace)
}
// create workspace key if not exist
const workspaceKeyInDB = await Key.findById(testWorkspaceKeyId)
if (!workspaceKeyInDB) {
await Key.create(testWorkspaceKey)
}
/* eslint-disable no-console */
console.info(`DEVELOPMENT MODE DETECTED: You may login with test user with email: ${testUserEmail} and password: ${testUserPassword}`)
/* eslint-enable no-console */
} catch (e) {
/* eslint-disable no-console */
console.error(`Unable to create test user while booting up [err=${e}]`)
/* eslint-enable no-console */
}
}
}

@ -10,6 +10,8 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_SET,
INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL,
@ -19,6 +21,8 @@ import {
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL,
INTEGRATION_FLYIO_API_URL,
INTEGRATION_OPTIONS
} from './integration';
import {
@ -56,6 +60,8 @@ export {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_SET,
INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL,
@ -65,6 +71,8 @@ export {
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL,
INTEGRATION_FLYIO_API_URL,
EVENT_PUSH_SECRETS,
EVENT_PULL_SECRETS,
ACTION_ADD_SECRETS,

@ -10,11 +10,15 @@ const INTEGRATION_HEROKU = 'heroku';
const INTEGRATION_VERCEL = 'vercel';
const INTEGRATION_NETLIFY = 'netlify';
const INTEGRATION_GITHUB = 'github';
const INTEGRATION_RENDER = 'render';
const INTEGRATION_FLYIO = 'flyio';
const INTEGRATION_SET = new Set([
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
INTEGRATION_GITHUB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO
]);
// integration types
@ -32,23 +36,25 @@ const INTEGRATION_GITHUB_TOKEN_URL =
const INTEGRATION_HEROKU_API_URL = 'https://api.heroku.com';
const INTEGRATION_VERCEL_API_URL = 'https://api.vercel.com';
const INTEGRATION_NETLIFY_API_URL = 'https://api.netlify.com';
const INTEGRATION_RENDER_API_URL = 'https://api.render.com';
const INTEGRATION_FLYIO_API_URL = 'https://api.fly.io/graphql';
const INTEGRATION_OPTIONS = [
{
name: 'Heroku',
slug: 'heroku',
image: 'Heroku',
image: 'Heroku.png',
isAvailable: true,
type: 'oauth2',
type: 'oauth',
clientId: CLIENT_ID_HEROKU,
docsLink: ''
},
{
name: 'Vercel',
slug: 'vercel',
image: 'Vercel',
image: 'Vercel.png',
isAvailable: true,
type: 'vercel',
type: 'oauth',
clientId: '',
clientSlug: CLIENT_SLUG_VERCEL,
docsLink: ''
@ -56,26 +62,43 @@ const INTEGRATION_OPTIONS = [
{
name: 'Netlify',
slug: 'netlify',
image: 'Netlify',
image: 'Netlify.png',
isAvailable: true,
type: 'oauth2',
type: 'oauth',
clientId: CLIENT_ID_NETLIFY,
docsLink: ''
},
{
name: 'GitHub',
slug: 'github',
image: 'GitHub',
image: 'GitHub.png',
isAvailable: true,
type: 'oauth2',
type: 'oauth',
clientId: CLIENT_ID_GITHUB,
docsLink: ''
},
{
name: 'Render',
slug: 'render',
image: 'Render.png',
isAvailable: true,
type: 'pat',
clientId: '',
docsLink: ''
},
{
name: 'Fly.io',
slug: 'flyio',
image: 'Flyio.svg',
isAvailable: true,
type: 'pat',
clientId: '',
docsLink: ''
},
{
name: 'Google Cloud Platform',
slug: 'gcp',
image: 'Google Cloud Platform',
image: 'Google Cloud Platform.png',
isAvailable: false,
type: '',
clientId: '',
@ -84,7 +107,7 @@ const INTEGRATION_OPTIONS = [
{
name: 'Amazon Web Services',
slug: 'aws',
image: 'Amazon Web Services',
image: 'Amazon Web Services.png',
isAvailable: false,
type: '',
clientId: '',
@ -93,7 +116,7 @@ const INTEGRATION_OPTIONS = [
{
name: 'Microsoft Azure',
slug: 'azure',
image: 'Microsoft Azure',
image: 'Microsoft Azure.png',
isAvailable: false,
type: '',
clientId: '',
@ -102,7 +125,7 @@ const INTEGRATION_OPTIONS = [
{
name: 'Travis CI',
slug: 'travisci',
image: 'Travis CI',
image: 'Travis CI.png',
isAvailable: false,
type: '',
clientId: '',
@ -111,7 +134,7 @@ const INTEGRATION_OPTIONS = [
{
name: 'Circle CI',
slug: 'circleci',
image: 'Circle CI',
image: 'Circle CI.png',
isAvailable: false,
type: '',
clientId: '',
@ -124,6 +147,8 @@ export {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_SET,
INTEGRATION_OAUTH2,
INTEGRATION_HEROKU_TOKEN_URL,
@ -133,5 +158,7 @@ export {
INTEGRATION_HEROKU_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL,
INTEGRATION_FLYIO_API_URL,
INTEGRATION_OPTIONS
};

@ -6,6 +6,10 @@ const MEMBER = 'member';
// membership statuses
const INVITED = 'invited';
// membership permissions ability
const ABILITY_READ = 'read';
const ABILITY_WRITE = 'write';
// -- organization
const ACCEPTED = 'accepted';
@ -14,5 +18,7 @@ export {
ADMIN,
MEMBER,
INVITED,
ACCEPTED
ACCEPTED,
ABILITY_READ,
ABILITY_WRITE
}

@ -5,6 +5,7 @@ import (
"github.com/Infisical/infisical-merge/packages/config"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
const USER_AGENT = "cli"
@ -144,3 +145,24 @@ func CallGetAllWorkSpacesUserBelongsTo(httpClient *resty.Client) (GetWorkSpacesR
return workSpacesResponse, nil
}
func CallIsAuthenticated(httpClient *resty.Client) bool {
var workSpacesResponse GetWorkSpacesResponse
response, err := httpClient.
R().
SetResult(&workSpacesResponse).
SetHeader("User-Agent", USER_AGENT).
Post(fmt.Sprintf("%v/v1/auth/checkAuth", config.INFISICAL_URL))
log.Debugln(fmt.Errorf("CallIsAuthenticated: Unsuccessful response: [response=%v]", response))
if err != nil {
return false
}
if response.IsError() {
return false
}
return true
}

@ -33,13 +33,13 @@ var loginCmd = &cobra.Command{
PreRun: toggleDebug,
Run: func(cmd *cobra.Command, args []string) {
currentLoggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
if err != nil && strings.Contains(err.Error(), "The specified item could not be found in the keyring") { // if the key can't be found allow them to override
if err != nil && (strings.Contains(err.Error(), "The specified item could not be found in the keyring") || strings.Contains(err.Error(), "unable to get key from Keyring")) { // if the key can't be found allow them to override
log.Debug(err)
} else if err != nil {
util.HandleError(err)
}
if currentLoggedInUserDetails.IsUserLoggedIn {
if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired { // if you are logged in but not expired
shouldOverride, err := shouldOverrideLoginPrompt(currentLoggedInUserDetails.UserCredentials.Email)
if err != nil {
util.HandleError(err)

@ -6,8 +6,10 @@ package cmd
import (
"os"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/spf13/cobra"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/util"
)
var rootCmd = &cobra.Command{
@ -15,7 +17,7 @@ var rootCmd = &cobra.Command{
Short: "Infisical CLI is used to inject environment variables into any process",
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
CompletionOptions: cobra.CompletionOptions{HiddenDefaultCmd: true},
Version: "0.2.3",
Version: util.CLI_VERSION,
}
// Execute adds all child commands to the root command and sets flags appropriately.
@ -30,7 +32,17 @@ func Execute() {
func init() {
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging")
rootCmd.PersistentFlags().StringVar(&config.INFISICAL_URL, "domain", "https://app.infisical.com/api", "Point the CLI to your own backend")
// rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
// }
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 config.INFISICAL_URL is set to the default value, check if INFISICAL_URL is set in the environment
// this is used to allow overrides of the default value
if !rootCmd.Flag("domain").Changed {
if envInfisicalBackendUrl, ok := os.LookupEnv("INFISICAL_API_URL"); ok {
config.INFISICAL_URL = envInfisicalBackendUrl
}
}
}

@ -58,9 +58,9 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
if !util.IsSecretEnvironmentValid(envName) {
util.PrintMessageAndExit("Invalid environment name passed. Environment names can only be prod, dev, test or staging")
}
// if !util.IsSecretEnvironmentValid(envName) {
// util.PrintMessageAndExit("Invalid environment name passed. Environment names can only be prod, dev, test or staging")
// }
secretOverriding, err := cmd.Flags().GetBool("secret-overriding")
if err != nil {
@ -166,7 +166,10 @@ func executeMultipleCommandWithEnvs(fullCommand string, secretsCount int, env []
if runtime.GOOS == "windows" {
shell = [2]string{"cmd", "/C"}
} else {
shell[0] = os.Getenv("SHELL")
currentShell := os.Getenv("SHELL")
if currentShell != "" {
shell[0] = currentShell
}
}
cmd := exec.Command(shell[0], shell[1], fullCommand)

@ -1,3 +1,3 @@
package config
var INFISICAL_URL = "http://localhost:8080/api"
var INFISICAL_URL string

@ -0,0 +1,46 @@
package util
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"errors"
)
func CheckForUpdate() {
latestVersion, err := getLatestTag("Infisical", "infisical")
if err != nil {
// do nothing and continue
return
}
if latestVersion != CLI_VERSION {
PrintWarning(fmt.Sprintf("Please update your CLI. You are running version %s but the latest version is %s", CLI_VERSION, latestVersion))
}
}
func getLatestTag(repoOwner string, repoName string) (string, error) {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/tags", repoOwner, repoName)
resp, err := http.Get(url)
if err != nil {
return "", err
}
if resp.StatusCode != 200 {
return "", errors.New(fmt.Sprintf("GitHub API returned status code %d", resp.StatusCode))
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
var tags []struct {
Name string `json:"name"`
}
json.Unmarshal(body, &tags)
return tags[0].Name, nil
}

@ -3,6 +3,7 @@ package util
const (
CONFIG_FILE_NAME = "infisical-config.json"
CONFIG_FOLDER_NAME = ".infisical"
INFISICAL_DEFAULT_API_URL = "https://app.infisical.com/api"
INFISICAL_WORKSPACE_CONFIG_FILE_NAME = ".infisical.json"
INFISICAL_TOKEN_NAME = "INFISICAL_TOKEN"
SECRET_TYPE_PERSONAL = "personal"
@ -11,3 +12,7 @@ const (
PERSONAL_SECRET_TYPE_NAME = "personal"
SHARED_SECRET_TYPE_NAME = "shared"
)
var (
CLI_VERSION = "devel"
)

@ -5,7 +5,7 @@ import (
"fmt"
"github.com/99designs/keyring"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/go-resty/resty/v2"
)
@ -87,17 +87,10 @@ func GetCurrentLoggedInUserDetails() (LoggedInUserDetails, error) {
SetAuthToken(userCreds.JTWToken).
SetHeader("Accept", "application/json")
response, err := httpClient.
R().
Post(fmt.Sprintf("%v/v1/auth/checkAuth", config.INFISICAL_URL))
if err != nil {
return LoggedInUserDetails{}, err
}
if response.StatusCode() > 299 {
isAuthenticated := api.CallIsAuthenticated(httpClient)
if !isAuthenticated {
return LoggedInUserDetails{
IsUserLoggedIn: true,
IsUserLoggedIn: true, // was logged in
LoginExpired: true,
UserCredentials: userCreds,
}, nil

@ -46,11 +46,7 @@ services:
context: ./frontend
dockerfile: Dockerfile.dev
volumes:
- ./frontend/src/pages:/app/src/pages
- ./frontend/src/components:/app/src/components
- ./frontend/src/ee:/app/src/ee
- ./frontend/src/locales:/app/src/locales
- ./frontend/src/styles:/app/src/styles
- ./frontend/src:/app/src/ # mounted whole src to avoid missing reload on new files
- ./frontend/public:/app/public
- ./frontend/next-i18next.config.js:/app/next-i18next.config.js
env_file: .env
@ -58,6 +54,7 @@ services:
- NEXT_PUBLIC_ENV=development
- INFISICAL_TELEMETRY_ENABLED=${TELEMETRY_ENABLED}
- NEXT_PUBLIC_STRIPE_PRODUCT_PRO=${STRIPE_PRODUCT_PRO}
- NEXT_PUBLIC_STRIPE_PRODUCT_TEAM=${STRIPE_PRODUCT_TEAM}
- NEXT_PUBLIC_STRIPE_PRODUCT_STARTER=${STRIPE_PRODUCT_STARTER}
networks:
- infisical-dev
@ -93,7 +90,7 @@ services:
smtp-server:
container_name: infisical-dev-smtp-server
image: mailhog/mailhog
image: lytrax/mailhog:latest # https://github.com/mailhog/MailHog/issues/353#issuecomment-821137362
restart: always
logging:
driver: 'none' # disable saving logs

@ -40,6 +40,7 @@ services:
# - NEXT_PUBLIC_POSTHOG_API_KEY=${POSTHOG_PROJECT_API_KEY}
- INFISICAL_TELEMETRY_ENABLED=${TELEMETRY_ENABLED}
- NEXT_PUBLIC_STRIPE_PRODUCT_PRO=${STRIPE_PRODUCT_PRO}
- NEXT_PUBLIC_STRIPE_PRODUCT_TEAM=${STRIPE_PRODUCT_TEAM}
- NEXT_PUBLIC_STRIPE_PRODUCT_STARTER=${STRIPE_PRODUCT_STARTER}
networks:
- infisical

@ -1,5 +1,6 @@
---
title: "Commands"
description: "Infisical CLI command overview"
---
## Commands

@ -1,5 +1,6 @@
---
title: "infisical export"
description: "Export Infisical secrets from CLI into different file formats"
---
```bash

@ -1,5 +1,6 @@
---
title: "infisical init"
description: "Switch between Infisical projects within CLI"
---
```bash

@ -1,5 +1,6 @@
---
title: "infisical login"
description: "Login into Infisical from the CLI"
---
```bash

@ -1,5 +1,6 @@
---
title: "infisical run"
description: "The command that injects your secrets into local environment"
---
<Tabs>

@ -1,5 +1,6 @@
---
title: "infisical secrets"
description: "Perform CRUD operations with Infisical secrets"
---
```

@ -1,5 +1,6 @@
---
title: "infisical vault"
description: "Change the vault type in Infisical"
---
<Tabs>

@ -1,5 +1,6 @@
---
title: "FAQ"
description: "Frequently Asked Questions about Infisical"
---
Frequently asked questions about the CLI can be found on this page.

@ -1,5 +1,6 @@
---
title: 'Install'
description: "Infisical's CLI is one of the best way to manage environments and secrets. Install it here"
---
Prerequisite: Set up an account with [Infisical Cloud](https://app.infisical.com) or via a [self-hosted installation](/self-hosting/overview).
@ -16,7 +17,7 @@ The Infisical CLI provides a way to inject environment variables from the platfo
brew install infisical/get-cli/infisical
```
## Updates
### Updates
```bash
brew upgrade infisical
@ -34,7 +35,7 @@ The Infisical CLI provides a way to inject environment variables from the platfo
scoop install infisical
```
## Updates
### Updates
```bash
scoop update infisical
@ -99,8 +100,28 @@ The Infisical CLI provides a way to inject environment variables from the platfo
</Tab>
</Tabs>
## Log in to the Infisical CLI
### Log in to the Infisical CLI
```bash
infisical login
```
<Accordion title="Optional: point CLI to self-hosted">
The CLI is set to connect to Infisical Cloud by default, but if you're running your own instance of Infisical, you can direct the CLI to it using one of the methods provided below.
#### Export environment variable
You can point the CLI to the self hosted Infisical instance by exporting the environment variable `INFISICAL_API_URL` in your terminal.
```bash
# Example
export INFISICAL_API_URL="https://your-self-hosted-infisical.com/api"
```
#### Set manually on every command
Another option to point the CLI to your self hosted Infisical instance is to set it via a flag on every command you run.
```bash
# Example
infisical <any-command> --domain="https://your-self-hosted-infisical.com/api"
```
</Accordion>

@ -1,5 +1,6 @@
---
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).

@ -1,5 +1,6 @@
---
title: "Usage"
description: "How to manage you secrets with Infisical's CLI?"
---
Prerequisite: [Install the CLI](/cli/overview)

@ -1,6 +1,6 @@
---
title: "Code of Conduct"
description: ""
description: "What you should know before contributing to Infisical?"
---
## Our Pledge

@ -1,13 +1,12 @@
---
title: 'Developing'
title: 'Local development'
description: 'This guide will help you set up and run Infisical in development mode.'
---
## Clone the repo
```bash
# change location to the path you want Infisical to be installed
cd ~
# navigate location to the path you want Infisical to be installed
# clone the repo and cd to Infisical dir
git clone https://github.com/Infisical/infisical
@ -16,57 +15,71 @@ cd infisical
## Set up environment variables
Start by creating a .env file at the root of the Infisical directory. It's best to start with the provided [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) template containing the necessary envars to fill out your .env file — you only have to modify the SMTP parameters.
Start by creating a .env file at the root of the Infisical directory then copy the contents of the file below into the .env file.
<Accordion title=".env file content">
```env
# Keys
# Required key for platform encryption/decryption ops
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
# JWT
# Required secrets to sign JWT tokens
JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a
JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff
JWT_AUTH_SECRET=4be6ba5602e0fa0ac6ac05c3cd4d247f
JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
# MongoDB
# Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref
# to the MongoDB container instance or Mongo Cloud
# Required
MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
# Optional credentials for MongoDB container instance and Mongo-Express
MONGO_USERNAME=root
MONGO_PASSWORD=example
# Website URL
# Required
SITE_URL=http://localhost:8080
# Mail/SMTP
SMTP_HOST='smtp-server'
SMTP_PORT='1025'
SMTP_NAME='local'
SMTP_USERNAME='team@infisical.com'
SMTP_PASSWORD=
```
</Accordion>
<Warning>
The pre-populated environment variable values in the `.env.example` file are meant to be used in development only.
You'll want to fill in your own values in production, especially concerning encryption keys, secrets, and SMTP parameters.
The pre-populated environment variable values above are meant to be used in development only. They should never be used in production.
</Warning>
Refer to the [environment variable list](https://infisical.com/docs/self-hosting/configuration/envars) for guidance on each envar.
View all available [environment variables](https://infisical.com/docs/self-hosting/configuration/envars) and guidance for each.
### Helpful tips for developing with Infisical:
## Starting Infisical for development
<Tip>
Use the `ENCRYPTION_KEY`, JWT-secret envars, `MONGO_URL`, `MONGO_USERNAME`, `MONGO_PASSWORD` provided in the `.env.example` file.
We use use Docker to easily spin up all required services to have Infisical up and running for development. If you are unfamiliar with Docker, don't worry, all you have to do is install [Docker](https://docs.docker.com/get-docker/) for your
machine and run the commands below to start up the development server.
If setting your own values:
- `ENCRYPTION_KEY` should be a [32-byte random hex](https://www.browserling.com/tools/random-hex)
- `MONGO_URL` should take the form: `mongodb://[MONGO_USERNAME]:[MONGO_PASSWORD]@mongo:27017/?authSource=admin`.
</Tip>
<Tip>
Bring and configure your own SMTP server by following our [email configuration guide](https://infisical.com/docs/self-hosting/configuration/email) (we recommend using either SendGrid or Mailgun).
Alternatively, you can use the provided development (Mailhog) SMTP server to send and browse emails sent by the backend on http://localhost:8025; to use this option, set the following `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM_NAME`, `SMTP_USERNAME`, `SMTP_PASSWORD` below.
</Tip>
```
SMTP_HOST=smtp-server
SMTP_PORT=1025
SMTP_FROM_ADDRESS=team@infisical.com
SMTP_FROM_NAME=Infisical
SMTP_USERNAME=team@infisical.com
SMTP_PASSWORD=
```
<Warning>
If using Mailhog, make sure to leave the `SMTP_PASSWORD` blank so the backend can connect to MailHog.
</Warning>
## Docker for development
#### Start local server
```bash
# build and start the services
docker-compose -f docker-compose.dev.yml up --build --force-recreate
```
#### Access local server
Then browse http://localhost:8080
Once all the services have spun up, browse to http://localhost:8080. To sign in, you may use the default credentials listed below.
Email: `test@localhost.local`
Password: `testInfisical1`
#### Shutdown local server
```bash
# To stop environment use Control+C (on Mac) CTRL+C (on Win) or
docker-compose -f docker-compose.dev.yml down
# start services
docker-compose -f docker-compose.dev.yml up
```

@ -1,5 +1,6 @@
---
title: "Activity Logs"
description: "See which events are triggered within your Infisical project."
---
Activity logs record all actions going through Infisical including who performed which CRUD operations on environment variables and from what IP address. They help answer questions like:

@ -1,5 +1,6 @@
---
title: "Sign up"
description: "How to create an account in Infisical?"
---
## Self-hosted

@ -1,13 +1,14 @@
---
title: "Integrations"
description: "How to sync your secrets among various 3rd-party services with Infisical."
---
Integrations allow environment variables to be synced across your entire infrastructure from local development to CI/CD and production.
We're still early with integrations, but expect more soon.
We're still relatively early with integrations. 6+ integrations are already avaiable but expect more coming very soon.
<Card title="View integrations" icon="link" href="/integrations/overview">
View all available integrations and their guides
</Card>
![integrations](../../images/project-integrations.png)
![integrations](../../images/integrations.png)

@ -1,5 +1,6 @@
---
title: "Organization"
description: "How Infisical structures its organizations."
---
An organization houses projects and members.

@ -1,5 +1,6 @@
---
title: "Point-in-Time Recovery"
description: "How to rollback secrets and configs to any commit with Infisical."
---
Point-in-time recovery allows environment variables to be rolled back to any point in time. It's powered by snapshots that get captured after mutations to environment variables.

@ -1,5 +1,6 @@
---
title: "Project"
description: "How Infisical organizes secrets into projects."
---
A project houses environment variables for an application.

@ -1,5 +1,6 @@
---
title: "Secret Versioning"
description: "Version secrets and configurations with Infisical"
---
Secret versioning records changes made to every secret.

@ -1,5 +1,6 @@
---
title: "Infisical Token"
description: "Use Infisical service token as one of the authentication methods."
---
An Infisical Token is needed to authenticate the CLI when there isn't an easy way to input your login credentials.

@ -1,5 +1,6 @@
---
title: "Features"
description: "A list of features that Infisical has to offer."
---
This is a non-exhaustive list of features that Infisical offers:
@ -30,9 +31,9 @@ We're building the future of secret management, one that's comprehensive and acc
| More hosting options | Ongoing |
| 1-Click Deploys | Ongoing |
| Account recovery: Backup key | Ongoing |
| Access logs | Ongoing |
| Account recovery: Member-assisted | Coming soon |
| Slack & MS teams integrations | Coming soon |
| Access logs | Coming soon |
| Version control for secrets | Coming soon |
| 2FA | Coming soon |
| Restricted IPs | Coming soon |

@ -1,5 +1,6 @@
---
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 manager that enables teams to easily manage and sync their environment variables.
@ -35,3 +36,8 @@ Start syncing environment variables with [Infisical Cloud](https://app.infisical
Explore integrations for Docker, AWS, Heroku, etc.
</Card>
</CardGroup>
<Card title="Set up a 1x1 with an Infisical Engineer" iconType="duotone" color="#ca8b04" href="https://calendly.com/maidull/30min">
Our team is happy to help you get started with Infisical. If you have any questions or want to learn how you can leverage Infisical within your infrastructure, **[set up a 1-on-1 with an Infisical engineer](https://cal.com/maidul/15min)**.
</Card>

@ -1,5 +1,6 @@
---
title: "Quickstart"
description: "Start managing your developer secrets and configs with Infisical in 10 minutes."
---
This example demonstrates how to store and inject environment variables from [Infisical Cloud](https://app.infisical.com) into your application.

Binary file not shown.

After

(image error) Size: 350 KiB

Binary file not shown.

After

(image error) Size: 285 KiB

Binary file not shown.

After

(image error) Size: 259 KiB

Binary file not shown.

After

(image error) Size: 399 KiB

Binary file not shown.

Before

(image error) Size: 1.2 MiB

After

(image error) Size: 398 KiB

Binary file not shown.

Before

(image error) Size: 1.3 MiB

After

(image error) Size: 402 KiB

Binary file not shown.

Before

(image error) Size: 1.3 MiB

After

(image error) Size: 404 KiB

Binary file not shown.

After

(image error) Size: 351 KiB

Binary file not shown.

After

(image error) Size: 204 KiB

Binary file not shown.

After

(image error) Size: 266 KiB

Binary file not shown.

After

(image error) Size: 398 KiB

Binary file not shown.

Before

(image error) Size: 1.3 MiB

After

(image error) Size: 403 KiB

Binary file not shown.

Before

(image error) Size: 1.3 MiB

After

(image error) Size: 418 KiB

@ -1,5 +1,6 @@
---
title: "GitHub Actions"
description: "How to automatically sync secrets from Infisical into your GitHub Actions."
---
<Warning>

@ -1,5 +1,36 @@
---
title: "Fly.io"
description: "How to automatically sync secrets from Infisical into your Fly.io project."
---
Coming soon.
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Authorize Infisical for Fly.io
Obtain a Fly.io access token in Access Tokens
![integrations fly dashboard](../../images/integrations-flyio-dashboard.png)
![integrations fly token](../../images/integrations-flyio-token.png)
Press on the Fly.io tile and input your Fly.io access token to grant Infisical access to your Fly.io account.
![integrations fly authorization](../../images/integrations-flyio-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to which Fly.io app and press start integration to start syncing secrets to Fly.io.
![integrations fly](../../images/integrations-flyio.png)

@ -1,5 +1,6 @@
---
title: "Heroku"
description: "How to automatically sync secrets from Infisical into your Heroku project."
---
Prerequisites:

@ -1,5 +1,6 @@
---
title: "Netlify"
description: "How to automatically sync secrets from Infisical into your Netlify project."
---
<Warning>

@ -1,5 +1,36 @@
---
title: "Render"
description: "How to automatically sync secrets from Infisical into your Render project."
---
Coming soon.
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Enter your Render API Key
Obtain a Render API Key in your Render Account Settings > API Keys.
![integrations render dashboard](../../images/integrations-render-dashboard.png)
![integrations render token](../../images/integrations-render-token.png)
Press on the Render tile and input your Render API Key to grant Infisical access to your Render account.
![integrations render authorization](../../images/integrations-render-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to which Render service and press start integration to start syncing secrets to Render.
![integrations heroku](../../images/integrations-render.png)

@ -1,5 +1,6 @@
---
title: "Vercel"
description: "How to automatically sync secrets from Infisical into your Vercel project."
---
Prerequisites:
@ -20,4 +21,4 @@ Press on the Vercel tile and grant Infisical access to your Vercel account.
Select which Infisical environment secrets you want to sync to which Vercel app and environment. Lastly, press start integration to start syncing secrets to Vercel.
![integrations vercel](../../images/integrations-vercel.png)
![integrations vercel](../../images/integrations-vercel.png)

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