mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-22 07:12:17 +00:00
Compare commits
103 Commits
v0.4.1
...
infisical-
Author | SHA1 | Date | |
---|---|---|---|
3dd21374e7 | |||
c5fe41ae57 | |||
9f0313f50b | |||
a6e670e93a | |||
ec97e1a930 | |||
55ca6938db | |||
1401c7f6bc | |||
bb6d0fd7c6 | |||
e4b4126971 | |||
04b04cba5c | |||
89e5f644a4 | |||
c5619d27d7 | |||
12a1d8e822 | |||
a85a7d1b00 | |||
fc2846534f | |||
2b605856a3 | |||
191582ef26 | |||
213b5d465b | |||
75f550caf2 | |||
daabf5ab70 | |||
7b11976a60 | |||
39be52c6b2 | |||
bced5d0151 | |||
939d7eb433 | |||
6de25174aa | |||
2aa79d4ad6 | |||
44b4de754a | |||
db0f0d0d9c | |||
3471e387ae | |||
aadd964409 | |||
102e45891c | |||
b9ae224aef | |||
e5cb0cbca3 | |||
330968c7af | |||
68e8e727cd | |||
3b94ee42e9 | |||
09286b4421 | |||
04a9604ba9 | |||
d86f88db92 | |||
fc53c094b7 | |||
6726ca1882 | |||
ddbe4d7040 | |||
3f6b0a9e66 | |||
c3a47597b6 | |||
a696a99232 | |||
8b1e64f75e | |||
f137087ef1 | |||
2157fab181 | |||
d2acab57e0 | |||
811929987b | |||
4ac13f61e0 | |||
3d2b0fa3fc | |||
242809ce26 | |||
492bf39243 | |||
dbfa4f5277 | |||
3fd2e22cbd | |||
150eb1f5ee | |||
6314a949f8 | |||
660c5806e3 | |||
c6d2828262 | |||
8dedfad22d | |||
7a3456ca1d | |||
a946031d6f | |||
f0075e8d09 | |||
3b00df6662 | |||
a263d7481b | |||
6f91331549 | |||
13ecc22159 | |||
a5c5ec1f4d | |||
cbb28dc373 | |||
e00aad4159 | |||
fb8aaa9d9f | |||
4bda67c9f7 | |||
e5c5e4cca2 | |||
803a97fdfc | |||
9e42a7a33e | |||
7127b60867 | |||
bcba2e9c2c | |||
34c79b08bc | |||
aacdaf4556 | |||
a7484f8be5 | |||
51154925fd | |||
e1bf31b371 | |||
3817831577 | |||
3846c42c00 | |||
03110c8a83 | |||
e0d5644b3a | |||
c7172337ed | |||
7183546e7e | |||
d717430947 | |||
7fc01df93e | |||
9f944135b9 | |||
ad5852fe3a | |||
acb90ee0f7 | |||
b62ea41e02 | |||
763ec1aa0f | |||
338d287d35 | |||
df83e8ceb9 | |||
d9afe90885 | |||
fcb677d990 | |||
3eb810b979 | |||
3dfb85b03f | |||
e5e15d26bf |
.github/workflows
.goreleaser.yamlREADME.mdSECURITY.mdbackend
package-lock.jsonpackage.jsonspec.json
src
config
controllers
v1
authController.tsbotController.tsintegrationAuthController.tsintegrationController.tsmembershipController.tsmembershipOrgController.tsorganizationController.tspasswordController.tssecretController.tssecretsFolderController.tsserviceTokenController.tssignupController.tsstripeController.ts
v2
apiKeyDataController.tsauthController.tssecretController.tssecretsController.tsserviceAccountsController.tsserviceTokenDataController.tssignupController.tsworkspaceController.ts
v3
ee
controllers/v1
helpers
models
services
events
helpers
auth.tsbot.tsdatabase.tsevent.tsintegration.tsnodemailer.tsorganization.tssecret.tssecrets.tsserviceTokenData.tstoken.tsworkspace.ts
index.tsintegrations
interfaces
middleware
requestErrorHandler.tsrequireAuth.tsrequireMfaAuth.tsrequireServiceTokenAuth.tsrequireSignupAuth.tsrequireWorkspaceAuth.ts
models
routes
services
BotService.tsDatabaseService.tsEventService.tsIntegrationService.tsSecretService.tsTelemetryService.tshealth.tsindex.tssmtp.ts
templates
types
utils
variables
swagger
tests/integration-tests/routes/v2
cli/packages/util
docs
api-reference
endpoints/secrets
overview
cli
getting-started
images
integrations/platforms
mint.jsonsdks
security
self-hosting
spec.yamlfrontend
package-lock.jsonpackage.json
public/lotties
system-outline-109-slider-toggle-settings.jsonsystem-outline-168-view-headline.jsonsystem-outline-82-extension.jsonsystem-outline-90-lock-closed.jsonsystem-outline-96-groups.json
src
components
basic/buttons
dashboard
navigation
utilities/secrets
v2
helpers
hooks/api/workspace
layouts/AppLayout
pages
views
helm-charts/infisical/templates
i18n
5
.github/workflows/release_build.yml
vendored
5
.github/workflows/release_build.yml
vendored
@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
# run only against tags
|
||||
tags:
|
||||
- "v*"
|
||||
- "infisical-cli/v*.*.*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@ -41,13 +41,14 @@ jobs:
|
||||
git clone https://github.com/plentico/osxcross-target.git ../../osxcross/target
|
||||
- uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
|
||||
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
- uses: actions/setup-python@v4
|
||||
- run: pip install --upgrade cloudsmith-cli
|
||||
- name: Publish to CloudSmith
|
||||
|
@ -11,6 +11,10 @@ before:
|
||||
- ./cli/scripts/completions.sh
|
||||
- ./cli/scripts/manpages.sh
|
||||
|
||||
monorepo:
|
||||
tag_prefix: infisical-cli/
|
||||
dir: cli
|
||||
|
||||
builds:
|
||||
- id: darwin-build
|
||||
binary: infisical
|
||||
@ -74,14 +78,7 @@ checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-devel"
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
name_template: "{{ .Version }}-devel"
|
||||
|
||||
# publishers:
|
||||
# - name: fury.io
|
||||
|
347
README.md
347
README.md
File diff suppressed because one or more lines are too long
10
SECURITY.md
10
SECURITY.md
@ -1,9 +1,13 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
## Supported versions
|
||||
|
||||
We always recommend using the latest version of Infisical to ensure you get all security updates.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
## Reporting vulnerabilities
|
||||
|
||||
Please report security vulnerabilities or concerns to team@infisical.com.
|
||||
Please do not file GitHub issues or post on our public forum for security vulnerabilities, as they are public!
|
||||
|
||||
Infisical takes security issues very seriously. If you have any concerns about Infisical or believe you have uncovered a vulnerability, please get in touch via the e-mail address security@infisical.com. In the message, try to provide a description of the issue and ideally a way of reproducing it. The security team will get back to you as soon as possible.
|
||||
|
||||
Note that this security address should be used only for undisclosed vulnerabilities. Please report any security problems to us before disclosing it publicly.
|
1864
backend/package-lock.json
generated
1864
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,16 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.303.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.309.0",
|
||||
"@godaddy/terminus": "^4.11.2",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.45.0",
|
||||
"@sentry/tracing": "^7.46.0",
|
||||
"@sentry/node": "^7.41.0",
|
||||
"@sentry/tracing": "^7.47.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"argon2": "^0.30.3",
|
||||
"await-to-js": "^3.0.0",
|
||||
"aws-sdk": "^2.1338.0",
|
||||
"axios": "^1.1.3",
|
||||
"axios": "^1.3.5",
|
||||
"axios-retry": "^3.4.0",
|
||||
"bcrypt": "^5.1.0",
|
||||
"bigint-conversion": "^2.2.2",
|
||||
@ -24,7 +24,7 @@
|
||||
"express-validator": "^6.14.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"helmet": "^5.1.1",
|
||||
"infisical-node": "^1.0.37",
|
||||
"infisical-node": "^1.1.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jsrp": "^0.2.4",
|
||||
|
@ -1532,7 +1532,15 @@
|
||||
"/api/v1/invite-org/signup": {
|
||||
"post": {
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "host",
|
||||
"in": "header",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
@ -2071,6 +2079,15 @@
|
||||
"targetEnvironment": {
|
||||
"example": "any"
|
||||
},
|
||||
"targetEnvironmentId": {
|
||||
"example": "any"
|
||||
},
|
||||
"targetService": {
|
||||
"example": "any"
|
||||
},
|
||||
"targetServiceId": {
|
||||
"example": "any"
|
||||
},
|
||||
"owner": {
|
||||
"example": "any"
|
||||
},
|
||||
@ -2297,6 +2314,13 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@ -2309,6 +2333,107 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/integration-auth/{integrationAuthId}/teams": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "integrationAuthId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/integration-auth/{integrationAuthId}/vercel/branches": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "integrationAuthId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "appId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/integration-auth/{integrationAuthId}/railway/environments": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "integrationAuthId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "appId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/integration-auth/{integrationAuthId}/railway/services": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "integrationAuthId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "appId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/signup/complete-account/signup": {
|
||||
"post": {
|
||||
"description": "",
|
||||
@ -2870,9 +2995,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
@ -2882,6 +3004,26 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v2/organizations/{organizationId}/service-accounts": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "organizationId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/workspace/{workspaceId}/environments": {
|
||||
"post": {
|
||||
"description": "",
|
||||
@ -4018,9 +4160,6 @@
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
@ -4073,6 +4212,138 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/service-accounts/me": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/service-accounts/{serviceAccountId}": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "serviceAccountId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "serviceAccountId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/service-accounts/": {
|
||||
"post": {
|
||||
"description": "",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/service-accounts/{serviceAccountId}/name": {
|
||||
"patch": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "serviceAccountId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/service-accounts/{serviceAccountId}/permissions/workspace": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "serviceAccountId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "serviceAccountId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
@ -4080,6 +4351,90 @@
|
||||
"400": {
|
||||
"description": "Bad Request"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"environment": {
|
||||
"example": "any"
|
||||
},
|
||||
"workspaceId": {
|
||||
"example": "any"
|
||||
},
|
||||
"read": {
|
||||
"example": "any"
|
||||
},
|
||||
"write": {
|
||||
"example": "any"
|
||||
},
|
||||
"encryptedKey": {
|
||||
"example": "any"
|
||||
},
|
||||
"nonce": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/service-accounts/{serviceAccountId}/permissions/workspace/{serviceAccountWorkspacePermissionId}": {
|
||||
"delete": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "serviceAccountId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "serviceAccountWorkspacePermissionId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/service-accounts/{serviceAccountId}/keys": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "serviceAccountId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "workspaceId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -4149,6 +4504,297 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/secrets/": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "workspaceId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "environment",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/secrets/{secretName}": {
|
||||
"post": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "secretName",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspaceId": {
|
||||
"example": "any"
|
||||
},
|
||||
"environment": {
|
||||
"example": "any"
|
||||
},
|
||||
"type": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretKeyCiphertext": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretKeyIV": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretKeyTag": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretValueCiphertext": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretValueIV": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretValueTag": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretCommentCiphertext": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretCommentIV": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretCommentTag": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "secretName",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "workspaceId",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "environment",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "secretName",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspaceId": {
|
||||
"example": "any"
|
||||
},
|
||||
"environment": {
|
||||
"example": "any"
|
||||
},
|
||||
"type": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretValueCiphertext": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretValueIV": {
|
||||
"example": "any"
|
||||
},
|
||||
"secretValueTag": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "secretName",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"workspaceId": {
|
||||
"example": "any"
|
||||
},
|
||||
"environment": {
|
||||
"example": "any"
|
||||
},
|
||||
"type": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/workspaces/{workspaceId}/secrets/blind-index-status": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "workspaceId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/workspaces/{workspaceId}/secrets": {
|
||||
"get": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "workspaceId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v3/workspaces/{workspaceId}/secrets/names": {
|
||||
"post": {
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "workspaceId",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"secretsToUpdate": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/status": {
|
||||
"get": {
|
||||
"description": "",
|
||||
|
@ -1,64 +1,69 @@
|
||||
import infisical from 'infisical-node';
|
||||
export const getPort = () => infisical.get('PORT')! || 4000;
|
||||
export const getInviteOnlySignup = () => infisical.get('INVITE_ONLY_SIGNUP')! == undefined ? false : infisical.get('INVITE_ONLY_SIGNUP');
|
||||
export const getEncryptionKey = () => infisical.get('ENCRYPTION_KEY')!;
|
||||
export const getSaltRounds = () => parseInt(infisical.get('SALT_ROUNDS')!) || 10;
|
||||
export const getJwtAuthLifetime = () => infisical.get('JWT_AUTH_LIFETIME')! || '10d';
|
||||
export const getJwtAuthSecret = () => infisical.get('JWT_AUTH_SECRET')!;
|
||||
export const getJwtMfaLifetime = () => infisical.get('JWT_MFA_LIFETIME')! || '5m';
|
||||
export const getJwtMfaSecret = () => infisical.get('JWT_MFA_LIFETIME')! || '5m';
|
||||
export const getJwtRefreshLifetime = () => infisical.get('JWT_REFRESH_LIFETIME')! || '90d';
|
||||
export const getJwtRefreshSecret = () => infisical.get('JWT_REFRESH_SECRET')!;
|
||||
export const getJwtServiceSecret = () => infisical.get('JWT_SERVICE_SECRET')!;
|
||||
export const getJwtSignupLifetime = () => infisical.get('JWT_SIGNUP_LIFETIME')! || '15m';
|
||||
export const getJwtSignupSecret = () => infisical.get('JWT_SIGNUP_SECRET')!;
|
||||
export const getMongoURL = () => infisical.get('MONGO_URL')!;
|
||||
export const getNodeEnv = () => infisical.get('NODE_ENV')! || 'production';
|
||||
export const getVerboseErrorOutput = () => infisical.get('VERBOSE_ERROR_OUTPUT')! === 'true' && true;
|
||||
export const getLokiHost = () => infisical.get('LOKI_HOST')!;
|
||||
export const getClientIdAzure = () => infisical.get('CLIENT_ID_AZURE')!;
|
||||
export const getClientIdHeroku = () => infisical.get('CLIENT_ID_HEROKU')!;
|
||||
export const getClientIdVercel = () => infisical.get('CLIENT_ID_VERCEL')!;
|
||||
export const getClientIdNetlify = () => infisical.get('CLIENT_ID_NETLIFY')!;
|
||||
export const getClientIdGitHub = () => infisical.get('CLIENT_ID_GITHUB')!;
|
||||
export const getClientIdGitLab = () => infisical.get('CLIENT_ID_GITLAB')!;
|
||||
export const getClientSecretAzure = () => infisical.get('CLIENT_SECRET_AZURE')!;
|
||||
export const getClientSecretHeroku = () => infisical.get('CLIENT_SECRET_HEROKU')!;
|
||||
export const getClientSecretVercel = () => infisical.get('CLIENT_SECRET_VERCEL')!;
|
||||
export const getClientSecretNetlify = () => infisical.get('CLIENT_SECRET_NETLIFY')!;
|
||||
export const getClientSecretGitHub = () => infisical.get('CLIENT_SECRET_GITHUB')!;
|
||||
export const getClientSecretGitLab = () => infisical.get('CLIENT_SECRET_GITLAB')!;
|
||||
export const getClientSlugVercel = () => infisical.get('CLIENT_SLUG_VERCEL')!;
|
||||
export const getPostHogHost = () => infisical.get('POSTHOG_HOST')! || 'https://app.posthog.com';
|
||||
export const getPostHogProjectApiKey = () => infisical.get('POSTHOG_PROJECT_API_KEY')! || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
|
||||
export const getSentryDSN = () => infisical.get('SENTRY_DSN')!;
|
||||
export const getSiteURL = () => infisical.get('SITE_URL')!;
|
||||
export const getSmtpHost = () => infisical.get('SMTP_HOST')!;
|
||||
export const getSmtpSecure = () => infisical.get('SMTP_SECURE')! === 'true' || false;
|
||||
export const getSmtpPort = () => parseInt(infisical.get('SMTP_PORT')!) || 587;
|
||||
export const getSmtpUsername = () => infisical.get('SMTP_USERNAME')!;
|
||||
export const getSmtpPassword = () => infisical.get('SMTP_PASSWORD')!;
|
||||
export const getSmtpFromAddress = () => infisical.get('SMTP_FROM_ADDRESS')!;
|
||||
export const getSmtpFromName = () => infisical.get('SMTP_FROM_NAME')! || 'Infisical';
|
||||
export const getStripeProductStarter = () => infisical.get('STRIPE_PRODUCT_STARTER')!;
|
||||
export const getStripeProductPro = () => infisical.get('STRIPE_PRODUCT_PRO')!;
|
||||
export const getStripeProductTeam = () => infisical.get('STRIPE_PRODUCT_TEAM')!;
|
||||
export const getStripePublishableKey = () => infisical.get('STRIPE_PUBLISHABLE_KEY')!;
|
||||
export const getStripeSecretKey = () => infisical.get('STRIPE_SECRET_KEY')!;
|
||||
export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!;
|
||||
export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true;
|
||||
export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!;
|
||||
export const getSmtpConfigured = () => infisical.get('SMTP_HOST') == '' || infisical.get('SMTP_HOST') == undefined ? false : true
|
||||
export const getHttpsEnabled = () => {
|
||||
if (getNodeEnv() != "production") {
|
||||
import InfisicalClient from 'infisical-node';
|
||||
|
||||
const client = new InfisicalClient({
|
||||
token: process.env.INFISICAL_TOKEN!
|
||||
});
|
||||
|
||||
export const getPort = async () => (await client.getSecret('PORT')).secretValue || 4000;
|
||||
export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue == undefined ? false : (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue;
|
||||
export const getEncryptionKey = async () => (await client.getSecret('ENCRYPTION_KEY')).secretValue;
|
||||
export const getSaltRounds = async () => parseInt((await client.getSecret('SALT_ROUNDS')).secretValue) || 10;
|
||||
export const getJwtAuthLifetime = async () => (await client.getSecret('JWT_AUTH_LIFETIME')).secretValue || '10d';
|
||||
export const getJwtAuthSecret = async () => (await client.getSecret('JWT_AUTH_SECRET')).secretValue;
|
||||
export const getJwtMfaLifetime = async () => (await client.getSecret('JWT_MFA_LIFETIME')).secretValue || '5m';
|
||||
export const getJwtMfaSecret = async () => (await client.getSecret('JWT_MFA_LIFETIME')).secretValue || '5m';
|
||||
export const getJwtRefreshLifetime = async () => (await client.getSecret('JWT_REFRESH_LIFETIME')).secretValue || '90d';
|
||||
export const getJwtRefreshSecret = async () => (await client.getSecret('JWT_REFRESH_SECRET')).secretValue;
|
||||
export const getJwtServiceSecret = async () => (await client.getSecret('JWT_SERVICE_SECRET')).secretValue;
|
||||
export const getJwtSignupLifetime = async () => (await client.getSecret('JWT_SIGNUP_LIFETIME')).secretValue || '15m';
|
||||
export const getJwtSignupSecret = async () => (await client.getSecret('JWT_SIGNUP_SECRET')).secretValue;
|
||||
export const getMongoURL = async () => (await client.getSecret('MONGO_URL')).secretValue;
|
||||
export const getNodeEnv = async () => (await client.getSecret('NODE_ENV')).secretValue || 'production';
|
||||
export const getVerboseErrorOutput = async () => (await client.getSecret('VERBOSE_ERROR_OUTPUT')).secretValue === 'true' && true;
|
||||
export const getLokiHost = async () => (await client.getSecret('LOKI_HOST')).secretValue;
|
||||
export const getClientIdAzure = async () => (await client.getSecret('CLIENT_ID_AZURE')).secretValue;
|
||||
export const getClientIdHeroku = async () => (await client.getSecret('CLIENT_ID_HEROKU')).secretValue;
|
||||
export const getClientIdVercel = async () => (await client.getSecret('CLIENT_ID_VERCEL')).secretValue;
|
||||
export const getClientIdNetlify = async () => (await client.getSecret('CLIENT_ID_NETLIFY')).secretValue;
|
||||
export const getClientIdGitHub = async () => (await client.getSecret('CLIENT_ID_GITHUB')).secretValue;
|
||||
export const getClientIdGitLab = async () => (await client.getSecret('CLIENT_ID_GITLAB')).secretValue;
|
||||
export const getClientSecretAzure = async () => (await client.getSecret('CLIENT_SECRET_AZURE')).secretValue;
|
||||
export const getClientSecretHeroku = async () => (await client.getSecret('CLIENT_SECRET_HEROKU')).secretValue;
|
||||
export const getClientSecretVercel = async () => (await client.getSecret('CLIENT_SECRET_VERCEL')).secretValue;
|
||||
export const getClientSecretNetlify = async () => (await client.getSecret('CLIENT_SECRET_NETLIFY')).secretValue;
|
||||
export const getClientSecretGitHub = async () => (await client.getSecret('CLIENT_SECRET_GITHUB')).secretValue;
|
||||
export const getClientSecretGitLab = async () => (await client.getSecret('CLIENT_SECRET_GITLAB')).secretValue;
|
||||
export const getClientSlugVercel = async () => (await client.getSecret('CLIENT_SLUG_VERCEL')).secretValue;
|
||||
export const getPostHogHost = async () => (await client.getSecret('POSTHOG_HOST')).secretValue || 'https://app.posthog.com';
|
||||
export const getPostHogProjectApiKey = async () => (await client.getSecret('POSTHOG_PROJECT_API_KEY')).secretValue || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
|
||||
export const getSentryDSN = async () => (await client.getSecret('SENTRY_DSN')).secretValue;
|
||||
export const getSiteURL = async () => (await client.getSecret('SITE_URL')).secretValue;
|
||||
export const getSmtpHost = async () => (await client.getSecret('SMTP_HOST')).secretValue;
|
||||
export const getSmtpSecure = async () => (await client.getSecret('SMTP_SECURE')).secretValue === 'true' || false;
|
||||
export const getSmtpPort = async () => parseInt((await client.getSecret('SMTP_PORT')).secretValue) || 587;
|
||||
export const getSmtpUsername = async () => (await client.getSecret('SMTP_USERNAME')).secretValue;
|
||||
export const getSmtpPassword = async () => (await client.getSecret('SMTP_PASSWORD')).secretValue;
|
||||
export const getSmtpFromAddress = async () => (await client.getSecret('SMTP_FROM_ADDRESS')).secretValue;
|
||||
export const getSmtpFromName = async () => (await client.getSecret('SMTP_FROM_NAME')).secretValue || 'Infisical';
|
||||
export const getStripeProductStarter = async () => (await client.getSecret('STRIPE_PRODUCT_STARTER')).secretValue;
|
||||
export const getStripeProductPro = async () => (await client.getSecret('STRIPE_PRODUCT_PRO')).secretValue;
|
||||
export const getStripeProductTeam = async () => (await client.getSecret('STRIPE_PRODUCT_TEAM')).secretValue;
|
||||
export const getStripePublishableKey = async () => (await client.getSecret('STRIPE_PUBLISHABLE_KEY')).secretValue;
|
||||
export const getStripeSecretKey = async () => (await client.getSecret('STRIPE_SECRET_KEY')).secretValue;
|
||||
export const getStripeWebhookSecret = async () => (await client.getSecret('STRIPE_WEBHOOK_SECRET')).secretValue;
|
||||
export const getTelemetryEnabled = async () => (await client.getSecret('TELEMETRY_ENABLED')).secretValue !== 'false' && true;
|
||||
export const getLoopsApiKey = async () => (await client.getSecret('LOOPS_API_KEY')).secretValue;
|
||||
export const getSmtpConfigured = async () => (await client.getSecret('SMTP_HOST')).secretValue == '' || (await client.getSecret('SMTP_HOST')).secretValue == undefined ? false : true
|
||||
export const getHttpsEnabled = async () => {
|
||||
if ((await getNodeEnv()) != "production") {
|
||||
// no https for anything other than prod
|
||||
return false
|
||||
}
|
||||
|
||||
if (infisical.get('HTTPS_ENABLED') == undefined || infisical.get('HTTPS_ENABLED') == "") {
|
||||
if ((await client.getSecret('HTTPS_ENABLED')).secretValue == undefined || (await client.getSecret('HTTPS_ENABLED')).secretValue == "") {
|
||||
// default when no value present
|
||||
return true
|
||||
}
|
||||
|
||||
return infisical.get('HTTPS_ENABLED') === 'true' && true
|
||||
return (await client.getSecret('HTTPS_ENABLED')).secretValue === 'true' && true
|
||||
}
|
@ -126,7 +126,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'strict',
|
||||
secure: getHttpsEnabled()
|
||||
secure: await getHttpsEnabled()
|
||||
});
|
||||
|
||||
const loginAction = await EELogService.createAction({
|
||||
@ -182,7 +182,7 @@ export const logout = async (req: Request, res: Response) => {
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'strict',
|
||||
secure: getHttpsEnabled() as boolean
|
||||
secure: (await getHttpsEnabled()) as boolean
|
||||
});
|
||||
|
||||
const logoutAction = await EELogService.createAction({
|
||||
@ -237,7 +237,7 @@ export const getNewToken = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(refreshToken, getJwtRefreshSecret())
|
||||
jwt.verify(refreshToken, await getJwtRefreshSecret())
|
||||
);
|
||||
|
||||
const user = await User.findOne({
|
||||
@ -252,8 +252,8 @@ export const getNewToken = async (req: Request, res: Response) => {
|
||||
payload: {
|
||||
userId: decodedToken.userId
|
||||
},
|
||||
expiresIn: getJwtAuthLifetime(),
|
||||
secret: getJwtAuthSecret()
|
||||
expiresIn: await getJwtAuthLifetime(),
|
||||
secret: await getJwtAuthSecret()
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Bot, BotKey } from '../../models';
|
||||
import { createBot } from '../../helpers/bot';
|
||||
@ -29,7 +30,7 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
|
||||
// -> create a new bot and return it
|
||||
bot = await createBot({
|
||||
name: 'Infisical Bot',
|
||||
workspaceId
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -44,7 +44,7 @@ export const getIntegrationAuth = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
export const getIntegrationOptions = async (req: Request, res: Response) => {
|
||||
const INTEGRATION_OPTIONS = getIntegrationOptionsFunc();
|
||||
const INTEGRATION_OPTIONS = await getIntegrationOptionsFunc();
|
||||
|
||||
return res.status(200).send({
|
||||
integrationOptions: INTEGRATION_OPTIONS,
|
||||
|
@ -56,7 +56,8 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
// trigger event - push secrets
|
||||
EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: integration.workspace.toString()
|
||||
workspaceId: integration.workspace,
|
||||
environment: sourceEnvironment
|
||||
})
|
||||
});
|
||||
}
|
||||
@ -117,7 +118,8 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
||||
// trigger event - push secrets
|
||||
EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: integration.workspace.toString(),
|
||||
workspaceId: integration.workspace,
|
||||
environment
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
|
||||
inviterFirstName: req.user.firstName,
|
||||
inviterEmail: req.user.email,
|
||||
workspaceName: req.membership.workspace.name,
|
||||
callback_url: getSiteURL() + '/login'
|
||||
callback_url: (await getSiteURL()) + '/login'
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Types } from 'mongoose';
|
||||
import { Request, Response } from 'express';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { MembershipOrg, Organization, User } from '../../models';
|
||||
@ -139,7 +140,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
inviteEmail: inviteeEmail,
|
||||
organization: organizationId,
|
||||
role: MEMBER,
|
||||
status: invitee?.publicKey ? ACCEPTED : INVITED
|
||||
status: INVITED
|
||||
}).save();
|
||||
}
|
||||
} else {
|
||||
@ -164,6 +165,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
const organization = await Organization.findOne({ _id: organizationId });
|
||||
|
||||
if (organization) {
|
||||
|
||||
const token = await TokenService.createToken({
|
||||
type: TOKEN_EMAIL_ORG_INVITATION,
|
||||
email: inviteeEmail,
|
||||
@ -179,12 +181,13 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
inviterEmail: req.user.email,
|
||||
organizationName: organization.name,
|
||||
email: inviteeEmail,
|
||||
organizationId: organization._id.toString(),
|
||||
token,
|
||||
callback_url: getSiteURL() + '/signupinvite'
|
||||
callback_url: (await getSiteURL()) + '/signupinvite'
|
||||
}
|
||||
});
|
||||
|
||||
if (!getSmtpConfigured()) {
|
||||
if (!(await getSmtpConfigured())) {
|
||||
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}`
|
||||
}
|
||||
}
|
||||
@ -214,13 +217,18 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
export const verifyUserToOrganization = async (req: Request, res: Response) => {
|
||||
let user, token;
|
||||
try {
|
||||
const { email, code } = req.body;
|
||||
const {
|
||||
email,
|
||||
organizationId,
|
||||
code
|
||||
} = req.body;
|
||||
|
||||
user = await User.findOne({ email }).select('+publicKey');
|
||||
|
||||
const membershipOrg = await MembershipOrg.findOne({
|
||||
inviteEmail: email,
|
||||
status: INVITED
|
||||
status: INVITED,
|
||||
organization: new Types.ObjectId(organizationId)
|
||||
});
|
||||
|
||||
if (!membershipOrg)
|
||||
@ -257,8 +265,8 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
|
||||
payload: {
|
||||
userId: user._id.toString()
|
||||
},
|
||||
expiresIn: getJwtSignupLifetime(),
|
||||
secret: getJwtSignupSecret()
|
||||
expiresIn: await getJwtSignupLifetime(),
|
||||
secret: await getJwtSignupSecret()
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
|
@ -85,7 +85,7 @@ export const createOrganization = async (req: Request, res: Response) => {
|
||||
export const getOrganization = async (req: Request, res: Response) => {
|
||||
let organization;
|
||||
try {
|
||||
organization = req.membershipOrg.organization;
|
||||
organization = req.organization
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
@ -317,29 +317,29 @@ export const createOrganizationPortalSession = async (
|
||||
) => {
|
||||
let session;
|
||||
try {
|
||||
const stripe = new Stripe(getStripeSecretKey(), {
|
||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||
apiVersion: '2022-08-01'
|
||||
});
|
||||
|
||||
// check if there is a payment method on file
|
||||
const paymentMethods = await stripe.paymentMethods.list({
|
||||
customer: req.membershipOrg.organization.customerId,
|
||||
customer: req.organization.customerId,
|
||||
type: 'card'
|
||||
});
|
||||
|
||||
|
||||
if (paymentMethods.data.length < 1) {
|
||||
// case: no payment method on file
|
||||
session = await stripe.checkout.sessions.create({
|
||||
customer: req.membershipOrg.organization.customerId,
|
||||
customer: req.organization.customerId,
|
||||
mode: 'setup',
|
||||
payment_method_types: ['card'],
|
||||
success_url: getSiteURL() + '/dashboard',
|
||||
cancel_url: getSiteURL() + '/dashboard'
|
||||
success_url: (await getSiteURL()) + '/dashboard',
|
||||
cancel_url: (await getSiteURL()) + '/dashboard'
|
||||
});
|
||||
} else {
|
||||
session = await stripe.billingPortal.sessions.create({
|
||||
customer: req.membershipOrg.organization.customerId,
|
||||
return_url: getSiteURL() + '/dashboard'
|
||||
customer: req.organization.customerId,
|
||||
return_url: (await getSiteURL()) + '/dashboard'
|
||||
});
|
||||
}
|
||||
|
||||
@ -365,12 +365,12 @@ export const getOrganizationSubscriptions = async (
|
||||
) => {
|
||||
let subscriptions;
|
||||
try {
|
||||
const stripe = new Stripe(getStripeSecretKey(), {
|
||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||
apiVersion: '2022-08-01'
|
||||
});
|
||||
|
||||
subscriptions = await stripe.subscriptions.list({
|
||||
customer: req.membershipOrg.organization.customerId
|
||||
customer: req.organization.customerId
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
|
@ -44,7 +44,7 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
|
||||
substitutions: {
|
||||
email,
|
||||
token,
|
||||
callback_url: getSiteURL() + '/password-reset'
|
||||
callback_url: (await getSiteURL()) + '/password-reset'
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
@ -91,8 +91,8 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
|
||||
payload: {
|
||||
userId: user._id.toString()
|
||||
},
|
||||
expiresIn: getJwtSignupLifetime(),
|
||||
secret: getJwtSignupSecret()
|
||||
expiresIn: await getJwtSignupLifetime(),
|
||||
secret: await getJwtSignupSecret()
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Types } from 'mongoose';
|
||||
import { Key, Secret } from '../../models';
|
||||
import {
|
||||
v1PushSecrets as push,
|
||||
@ -38,7 +39,7 @@ export const pushSecrets = async (req: Request, res: Response) => {
|
||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||
|
||||
try {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
let { secrets }: { secrets: PushSecret[] } = req.body;
|
||||
const { keys, environment, channel } = req.body;
|
||||
const { workspaceId } = req.params;
|
||||
@ -84,7 +85,8 @@ export const pushSecrets = async (req: Request, res: Response) => {
|
||||
// trigger event - push secrets
|
||||
EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
})
|
||||
});
|
||||
|
||||
@ -112,7 +114,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
||||
let secrets;
|
||||
let key;
|
||||
try {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
const { workspaceId } = req.params;
|
||||
@ -181,7 +183,7 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
|
||||
let secrets;
|
||||
let key;
|
||||
try {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
const { workspaceId } = req.params;
|
||||
|
89
backend/src/controllers/v1/secretsFolderController.ts
Normal file
89
backend/src/controllers/v1/secretsFolderController.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Secret } from '../../models';
|
||||
import Folder from '../../models/folder';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
import { ROOT_FOLDER_PATH, getFolderPath, getParentPath, normalizePath, validateFolderName } from '../../utils/folder';
|
||||
import { ADMIN, MEMBER } from '../../variables';
|
||||
import { validateMembership } from '../../helpers/membership';
|
||||
|
||||
// TODO
|
||||
// verify workspace id/environment
|
||||
export const createFolder = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment, folderName, parentFolderId } = req.body
|
||||
if (!validateFolderName(folderName)) {
|
||||
throw BadRequestError({ message: "Folder name cannot contain spaces. Only underscore and dashes" })
|
||||
}
|
||||
|
||||
if (parentFolderId) {
|
||||
const parentFolder = await Folder.find({ environment: environment, workspace: workspaceId, id: parentFolderId });
|
||||
if (!parentFolder) {
|
||||
throw BadRequestError({ message: "The parent folder doesn't exist" })
|
||||
}
|
||||
}
|
||||
|
||||
let completePath = await getFolderPath(parentFolderId)
|
||||
if (completePath == ROOT_FOLDER_PATH) {
|
||||
completePath = ""
|
||||
}
|
||||
|
||||
const currentFolderPath = completePath + "/" + folderName // construct new path with current folder to be created
|
||||
const normalizedCurrentPath = normalizePath(currentFolderPath)
|
||||
const normalizedParentPath = getParentPath(normalizedCurrentPath)
|
||||
|
||||
const existingFolder = await Folder.findOne({
|
||||
name: folderName,
|
||||
workspace: workspaceId,
|
||||
environment: environment,
|
||||
parent: parentFolderId,
|
||||
path: normalizedCurrentPath
|
||||
});
|
||||
|
||||
if (existingFolder) {
|
||||
return res.json(existingFolder)
|
||||
}
|
||||
|
||||
const newFolder = new Folder({
|
||||
name: folderName,
|
||||
workspace: workspaceId,
|
||||
environment: environment,
|
||||
parent: parentFolderId,
|
||||
path: normalizedCurrentPath,
|
||||
parentPath: normalizedParentPath
|
||||
});
|
||||
|
||||
await newFolder.save();
|
||||
|
||||
return res.json(newFolder)
|
||||
}
|
||||
|
||||
export const deleteFolder = async (req: Request, res: Response) => {
|
||||
const { folderId } = req.params
|
||||
const queue: any[] = [folderId];
|
||||
|
||||
const folder = await Folder.findById(folderId);
|
||||
if (!folder) {
|
||||
throw BadRequestError({ message: "The folder doesn't exist" })
|
||||
}
|
||||
|
||||
// check that user is a member of the workspace
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: folder.workspace as any,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
|
||||
while (queue.length > 0) {
|
||||
const currentFolderId = queue.shift();
|
||||
|
||||
const childFolders = await Folder.find({ parent: currentFolderId });
|
||||
for (const childFolder of childFolders) {
|
||||
queue.push(childFolder._id);
|
||||
}
|
||||
|
||||
await Secret.deleteMany({ folder: currentFolderId });
|
||||
|
||||
await Folder.deleteOne({ _id: currentFolderId });
|
||||
}
|
||||
|
||||
res.send()
|
||||
}
|
@ -61,7 +61,7 @@ export const createServiceToken = async (req: Request, res: Response) => {
|
||||
workspaceId
|
||||
},
|
||||
expiresIn: expiresIn,
|
||||
secret: getJwtServiceSecret()
|
||||
secret: await getJwtServiceSecret()
|
||||
});
|
||||
} catch (err) {
|
||||
return res.status(400).send({
|
||||
|
@ -21,7 +21,7 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
|
||||
try {
|
||||
email = req.body.email;
|
||||
|
||||
if (getInviteOnlySignup()) {
|
||||
if (await getInviteOnlySignup()) {
|
||||
// Only one user can create an account without being invited. The rest need to be invited in order to make an account
|
||||
const userCount = await User.countDocuments({})
|
||||
if (userCount != 0) {
|
||||
@ -75,7 +75,7 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// verify email
|
||||
if (getSmtpConfigured()) {
|
||||
if (await getSmtpConfigured()) {
|
||||
await checkEmailVerification({
|
||||
email,
|
||||
code
|
||||
@ -93,8 +93,8 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
|
||||
payload: {
|
||||
userId: user._id.toString()
|
||||
},
|
||||
expiresIn: getJwtSignupLifetime(),
|
||||
secret: getJwtSignupSecret()
|
||||
expiresIn: await getJwtSignupLifetime(),
|
||||
secret: await getJwtSignupSecret()
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
|
@ -13,7 +13,7 @@ export const handleWebhook = async (req: Request, res: Response) => {
|
||||
let event;
|
||||
try {
|
||||
// check request for valid stripe signature
|
||||
const stripe = new Stripe(getStripeSecretKey(), {
|
||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||
apiVersion: '2022-08-01'
|
||||
});
|
||||
|
||||
@ -21,7 +21,7 @@ export const handleWebhook = async (req: Request, res: Response) => {
|
||||
event = stripe.webhooks.constructEvent(
|
||||
req.body,
|
||||
sig,
|
||||
getStripeWebhookSecret()
|
||||
await getStripeWebhookSecret()
|
||||
);
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
|
@ -43,7 +43,7 @@ export const createAPIKeyData = async (req: Request, res: Response) => {
|
||||
const { name, expiresIn } = req.body;
|
||||
|
||||
const secret = crypto.randomBytes(16).toString('hex');
|
||||
const secretHash = await bcrypt.hash(secret, getSaltRounds());
|
||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||
|
@ -124,8 +124,8 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
payload: {
|
||||
userId: user._id.toString()
|
||||
},
|
||||
expiresIn: getJwtMfaLifetime(),
|
||||
secret: getJwtMfaSecret()
|
||||
expiresIn: await getJwtMfaLifetime(),
|
||||
secret: await getJwtMfaSecret()
|
||||
});
|
||||
|
||||
const code = await TokenService.createToken({
|
||||
@ -163,7 +163,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'strict',
|
||||
secure: getHttpsEnabled()
|
||||
secure: await getHttpsEnabled()
|
||||
});
|
||||
|
||||
// case: user does not have MFA enablgged
|
||||
@ -302,7 +302,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'strict',
|
||||
secure: getHttpsEnabled()
|
||||
secure: await getHttpsEnabled()
|
||||
});
|
||||
|
||||
interface VerifyMfaTokenRes {
|
||||
|
@ -8,6 +8,8 @@ import { BadRequestError, InternalServerError, UnauthorizedRequestError, Validat
|
||||
import { AnyBulkWriteOperation } from 'mongodb';
|
||||
import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
|
||||
import { TelemetryService } from '../../services';
|
||||
import { User } from "../../models";
|
||||
import { AccountNotFoundError } from '../../utils/errors';
|
||||
|
||||
/**
|
||||
* Create secret for workspace with id [workspaceId] and environment [environment]
|
||||
@ -15,7 +17,7 @@ import { TelemetryService } from '../../services';
|
||||
* @param res
|
||||
*/
|
||||
export const createSecret = async (req: Request, res: Response) => {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const secretToCreate: CreateSecretRequestBody = req.body.secret;
|
||||
const { workspaceId, environment } = req.params
|
||||
const sanitizedSecret: SanitizedSecretForCreate = {
|
||||
@ -68,7 +70,7 @@ export const createSecret = async (req: Request, res: Response) => {
|
||||
* @param res
|
||||
*/
|
||||
export const createSecrets = async (req: Request, res: Response) => {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
|
||||
const { workspaceId, environment } = req.params
|
||||
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = []
|
||||
@ -130,7 +132,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
* @param res
|
||||
*/
|
||||
export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const { workspaceId, environmentName } = req.params
|
||||
const secretIdsToDelete: string[] = req.body.secretIds
|
||||
|
||||
@ -184,7 +186,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
* @param res
|
||||
*/
|
||||
export const deleteSecret = async (req: Request, res: Response) => {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
await Secret.findByIdAndDelete(req._secret._id)
|
||||
|
||||
if (postHogClient) {
|
||||
@ -213,7 +215,7 @@ export const deleteSecret = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const updateSecrets = async (req: Request, res: Response) => {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const { workspaceId, environmentName } = req.params
|
||||
const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets;
|
||||
const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then())
|
||||
@ -281,7 +283,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const updateSecret = async (req: Request, res: Response) => {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const { workspaceId, environmentName } = req.params
|
||||
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
|
||||
|
||||
@ -335,20 +337,23 @@ export const updateSecret = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const getSecrets = async (req: Request, res: Response) => {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const { environment } = req.query;
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user
|
||||
let userEmail: Types.ObjectId | undefined = undefined // used for posthog
|
||||
let userEmail: string | undefined = undefined // used for posthog
|
||||
if (req.user) {
|
||||
userId = req.user._id;
|
||||
userEmail = req.user.email;
|
||||
}
|
||||
|
||||
if (req.serviceTokenData) {
|
||||
userId = req.serviceTokenData.user._id
|
||||
userEmail = req.serviceTokenData.user.email;
|
||||
userId = req.serviceTokenData.user;
|
||||
|
||||
const user = await User.findById(req.serviceTokenData.user, 'email');
|
||||
if (!user) throw AccountNotFoundError();
|
||||
userEmail = user.email;
|
||||
}
|
||||
|
||||
const [err, secrets] = await to(Secret.find(
|
||||
|
@ -2,7 +2,7 @@ import to from 'await-to-js';
|
||||
import { Types } from 'mongoose';
|
||||
import { Request, Response } from 'express';
|
||||
import { ISecret, Secret } from '../../models';
|
||||
import { IAction } from '../../ee/models';
|
||||
import { IAction, SecretVersion } from '../../ee/models';
|
||||
import {
|
||||
SECRET_PERSONAL,
|
||||
SECRET_SHARED,
|
||||
@ -15,7 +15,7 @@ import { UnauthorizedRequestError, ValidationError } from '../../utils/errors';
|
||||
import { EventService } from '../../services';
|
||||
import { eventPushSecrets } from '../../events';
|
||||
import { EESecretService, EELogService } from '../../ee/services';
|
||||
import { TelemetryService } from '../../services';
|
||||
import { TelemetryService, SecretService } from '../../services';
|
||||
import { getChannelFromUserAgent } from '../../utils/posthog';
|
||||
import { PERMISSION_WRITE_SECRETS } from '../../variables';
|
||||
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
|
||||
@ -25,6 +25,8 @@ import {
|
||||
BatchSecretRequest,
|
||||
BatchSecret
|
||||
} from '../../types/secret';
|
||||
import { getFolderPath, getFoldersInDirectory, normalizePath } from '../../utils/folder';
|
||||
import { ROOT_FOLDER_PATH } from '../../utils/folder';
|
||||
|
||||
/**
|
||||
* Peform a batch of any specified CUD secret operations
|
||||
@ -33,8 +35,9 @@ import {
|
||||
* @param res
|
||||
*/
|
||||
export const batchSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers['user-agent']);
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
const {
|
||||
workspaceId,
|
||||
@ -51,28 +54,55 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
const deleteSecrets: Types.ObjectId[] = [];
|
||||
const actions: IAction[] = [];
|
||||
|
||||
requests.forEach((request) => {
|
||||
// get secret blind index salt
|
||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
for await (const request of requests) {
|
||||
const folderId = request.secret.folderId
|
||||
|
||||
// TODO: need to auth folder
|
||||
const fullFolderPath = await getFolderPath(folderId)
|
||||
|
||||
let secretBlindIndex = '';
|
||||
switch (request.method) {
|
||||
case 'POST':
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName: request.secret.secretName,
|
||||
salt
|
||||
});
|
||||
|
||||
createSecrets.push({
|
||||
...request.secret,
|
||||
version: 1,
|
||||
user: request.secret.type === SECRET_PERSONAL ? req.user : undefined,
|
||||
environment,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
path: fullFolderPath,
|
||||
folder: folderId,
|
||||
secretBlindIndex
|
||||
});
|
||||
break;
|
||||
case 'PATCH':
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName: request.secret.secretName,
|
||||
salt,
|
||||
});
|
||||
|
||||
updateSecrets.push({
|
||||
...request.secret,
|
||||
_id: new Types.ObjectId(request.secret._id)
|
||||
_id: new Types.ObjectId(request.secret._id),
|
||||
secretBlindIndex,
|
||||
folder: folderId,
|
||||
path: fullFolderPath,
|
||||
});
|
||||
break;
|
||||
case 'DELETE':
|
||||
deleteSecrets.push(new Types.ObjectId(request.secret._id));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// handle create secrets
|
||||
let createdSecrets: ISecret[] = [];
|
||||
@ -133,7 +163,10 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
const updateOperations = updateSecrets.map((u) => ({
|
||||
updateOne: {
|
||||
filter: { _id: new Types.ObjectId(u._id) },
|
||||
filter: {
|
||||
_id: new Types.ObjectId(u._id),
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
},
|
||||
update: {
|
||||
$inc: {
|
||||
version: 1
|
||||
@ -146,13 +179,14 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
await Secret.bulkWrite(updateOperations);
|
||||
|
||||
const secretVersions = updateSecrets.map((u) => ({
|
||||
const secretVersions = updateSecrets.map((u) => new SecretVersion({
|
||||
secret: new Types.ObjectId(u._id),
|
||||
version: listedSecretsObj[u._id.toString()].version,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
type: listedSecretsObj[u._id.toString()].type,
|
||||
environment,
|
||||
isDeleted: false,
|
||||
secretBlindIndex: u.secretBlindIndex,
|
||||
secretKeyCiphertext: u.secretKeyCiphertext,
|
||||
secretKeyIV: u.secretKeyIV,
|
||||
secretKeyTag: u.secretKeyTag,
|
||||
@ -247,13 +281,13 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
// // trigger event - push secrets
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
})
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const resObj: { [key: string]: ISecret[] | string[] } = {}
|
||||
@ -351,8 +385,14 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
listOfSecretsToCreate = [req.body.secrets];
|
||||
}
|
||||
|
||||
// get secret blind index salt
|
||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
type secretsToCreateType = {
|
||||
type: string;
|
||||
secretName?: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
@ -365,25 +405,10 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
const secretsToInsert: ISecret[] = listOfSecretsToCreate.map(({
|
||||
type,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
}: secretsToCreateType) => {
|
||||
return ({
|
||||
version: 1,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
const secretsToInsert: ISecret[] = await Promise.all(
|
||||
listOfSecretsToCreate.map(async ({
|
||||
type,
|
||||
user: (req.user && type === SECRET_PERSONAL) ? req.user : undefined,
|
||||
environment,
|
||||
secretName,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
@ -394,8 +419,35 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
});
|
||||
});
|
||||
}: secretsToCreateType) => {
|
||||
let secretBlindIndex;
|
||||
if (secretName) {
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName,
|
||||
salt
|
||||
});
|
||||
}
|
||||
|
||||
return ({
|
||||
version: 1,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
type,
|
||||
...(secretBlindIndex ? { secretBlindIndex } : {}),
|
||||
user: (req.user && type === SECRET_PERSONAL) ? req.user : undefined,
|
||||
environment,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map((insertedSecret) => insertedSecret.toObject());
|
||||
|
||||
@ -403,7 +455,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
// trigger event - push secrets
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
})
|
||||
});
|
||||
}, 5000);
|
||||
@ -417,35 +469,28 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
type,
|
||||
user,
|
||||
environment,
|
||||
secretBlindIndex,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
}) => ({
|
||||
_id: new Types.ObjectId(),
|
||||
secretValueTag
|
||||
}) => new SecretVersion({
|
||||
secret: _id,
|
||||
version,
|
||||
workspace,
|
||||
type,
|
||||
user,
|
||||
environment,
|
||||
secretBlindIndex,
|
||||
isDeleted: false,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
secretValueTag
|
||||
}))
|
||||
});
|
||||
|
||||
@ -471,17 +516,15 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets added',
|
||||
distinctId: TelemetryService.getDistinctId({
|
||||
user: req.user,
|
||||
serviceAccount: req.serviceAccount,
|
||||
serviceTokenData: req.serviceTokenData
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: listOfSecretsToCreate.length,
|
||||
@ -546,9 +589,11 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
*/
|
||||
|
||||
const { tagSlugs } = req.query;
|
||||
const { tagSlugs, secretsPath } = req.query;
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
const normalizedPath = normalizePath(secretsPath as string)
|
||||
const folders = await getFoldersInDirectory(workspaceId as string, environment as string, normalizedPath)
|
||||
|
||||
// secrets to return
|
||||
let secrets: ISecret[] = [];
|
||||
@ -581,6 +626,12 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
]
|
||||
}
|
||||
|
||||
if (normalizedPath == ROOT_FOLDER_PATH) {
|
||||
secretQuery.path = { $in: [ROOT_FOLDER_PATH, null, undefined] }
|
||||
} else if (normalizedPath) {
|
||||
secretQuery.path = normalizedPath
|
||||
}
|
||||
|
||||
if (tagIds.length > 0) {
|
||||
secretQuery.tags = { $in: tagIds };
|
||||
}
|
||||
@ -595,7 +646,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// case: client authorization is via service token
|
||||
if (req.serviceTokenData) {
|
||||
const userId = req.serviceTokenData.user._id
|
||||
const userId = req.serviceTokenData.user;
|
||||
|
||||
const secretQuery: any = {
|
||||
workspace: workspaceId,
|
||||
@ -606,6 +657,13 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
]
|
||||
}
|
||||
|
||||
// TODO: check if user can query for given path
|
||||
if (normalizedPath == ROOT_FOLDER_PATH) {
|
||||
secretQuery.path = { $in: [ROOT_FOLDER_PATH, null, undefined] }
|
||||
} else if (normalizedPath) {
|
||||
secretQuery.path = normalizedPath
|
||||
}
|
||||
|
||||
if (tagIds.length > 0) {
|
||||
secretQuery.tags = { $in: tagIds };
|
||||
}
|
||||
@ -623,6 +681,12 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
user: { $exists: false } // shared secrets only from workspace
|
||||
}
|
||||
|
||||
if (normalizedPath == ROOT_FOLDER_PATH) {
|
||||
secretQuery.path = { $in: [ROOT_FOLDER_PATH, null, undefined] }
|
||||
} else if (normalizedPath) {
|
||||
secretQuery.path = normalizedPath
|
||||
}
|
||||
|
||||
if (tagIds.length > 0) {
|
||||
secretQuery.tags = { $in: tagIds };
|
||||
}
|
||||
@ -651,14 +715,12 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
ipAddress: req.ip
|
||||
});
|
||||
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets pulled',
|
||||
distinctId: TelemetryService.getDistinctId({
|
||||
user: req.user,
|
||||
serviceAccount: req.serviceAccount,
|
||||
serviceTokenData: req.serviceTokenData
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
@ -671,7 +733,8 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets
|
||||
secrets,
|
||||
folders
|
||||
});
|
||||
}
|
||||
|
||||
@ -845,7 +908,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
setTimeout(async () => {
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: key
|
||||
workspaceId: new Types.ObjectId(key)
|
||||
})
|
||||
});
|
||||
}, 10000);
|
||||
@ -872,17 +935,15 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: key
|
||||
workspaceId: new Types.ObjectId(key)
|
||||
})
|
||||
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets modified',
|
||||
distinctId: TelemetryService.getDistinctId({
|
||||
user: req.user,
|
||||
serviceAccount: req.serviceAccount,
|
||||
serviceTokenData: req.serviceTokenData
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: workspaceSecretObj[key].length,
|
||||
@ -955,10 +1016,6 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
*/
|
||||
|
||||
return res.status(200).send({
|
||||
message: 'delete secrets!!'
|
||||
});
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers['user-agent'])
|
||||
const toDelete = req.secrets.map((s: any) => s._id);
|
||||
|
||||
@ -987,7 +1044,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
// trigger event - push secrets
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: key
|
||||
workspaceId: new Types.ObjectId(key)
|
||||
})
|
||||
});
|
||||
const deleteAction = await EELogService.createAction({
|
||||
@ -1012,17 +1069,15 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: key
|
||||
})
|
||||
workspaceId: new Types.ObjectId(key)
|
||||
});
|
||||
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets deleted',
|
||||
distinctId: TelemetryService.getDistinctId({
|
||||
user: req.user,
|
||||
serviceAccount: req.serviceAccount,
|
||||
serviceTokenData: req.serviceTokenData
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: workspaceSecretObj[key].length,
|
||||
|
@ -72,7 +72,7 @@ export const createServiceAccount = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
const secret = crypto.randomBytes(16).toString('base64');
|
||||
const secretHash = await bcrypt.hash(secret, getSaltRounds());
|
||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||
|
||||
// create service account
|
||||
const serviceAccount = await new ServiceAccount({
|
||||
|
@ -11,9 +11,11 @@ import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissi
|
||||
import {
|
||||
PERMISSION_READ_SECRETS,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN
|
||||
} from '../../variables';
|
||||
import { getSaltRounds } from '../../config';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
|
||||
/**
|
||||
* Return service token data associated with service token on request
|
||||
@ -48,7 +50,16 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
|
||||
}
|
||||
*/
|
||||
|
||||
return res.status(200).json(req.serviceTokenData);
|
||||
if (!(req.authData.authPayload instanceof ServiceTokenData)) throw BadRequestError({
|
||||
message: 'Failed accepted client validation for service token data'
|
||||
});
|
||||
|
||||
const serviceTokenData = await ServiceTokenData
|
||||
.findById(req.authData.authPayload._id)
|
||||
.select('+encryptedKey +iv +tag')
|
||||
.populate('user');
|
||||
|
||||
return res.status(200).json(serviceTokenData);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,10 +84,10 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
} = req.body;
|
||||
|
||||
const secret = crypto.randomBytes(16).toString('hex');
|
||||
const secretHash = await bcrypt.hash(secret, getSaltRounds());
|
||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||
|
||||
let expiresAt;
|
||||
if (!!expiresIn) {
|
||||
if (expiresIn) {
|
||||
expiresAt = new Date()
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
token = tokens.token;
|
||||
|
||||
// sending a welcome email to new users
|
||||
if (getLoopsApiKey()) {
|
||||
if (await getLoopsApiKey()) {
|
||||
await request.post("https://app.loops.so/api/v1/events/send", {
|
||||
"email": email,
|
||||
"eventName": "Sign Up",
|
||||
@ -117,7 +117,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
}, {
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Authorization": "Bearer " + getLoopsApiKey()
|
||||
"Authorization": "Bearer " + (await getLoopsApiKey())
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -127,7 +127,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'strict',
|
||||
secure: getHttpsEnabled()
|
||||
secure: await getHttpsEnabled()
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
@ -232,7 +232,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'strict',
|
||||
secure: getHttpsEnabled()
|
||||
secure: await getHttpsEnabled()
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
|
@ -48,7 +48,7 @@ interface V2PushSecret {
|
||||
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||
try {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
||||
const { keys, environment, channel } = req.body;
|
||||
const { workspaceId } = req.params;
|
||||
@ -95,7 +95,8 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
// trigger event - push secrets
|
||||
EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
})
|
||||
});
|
||||
|
||||
@ -122,7 +123,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
export const pullSecrets = async (req: Request, res: Response) => {
|
||||
let secrets;
|
||||
try {
|
||||
const postHogClient = TelemetryService.getPostHogClient();
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
const { workspaceId } = req.params;
|
||||
@ -131,7 +132,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
||||
if (req.user) {
|
||||
userId = req.user._id.toString();
|
||||
} else if (req.serviceTokenData) {
|
||||
userId = req.serviceTokenData.user._id
|
||||
userId = req.serviceTokenData.user.toString();
|
||||
}
|
||||
// validate environment
|
||||
const workspaceEnvs = req.membership.workspace.environments;
|
||||
|
7
backend/src/controllers/v3/index.ts
Normal file
7
backend/src/controllers/v3/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import * as secretsController from './secretsController';
|
||||
import * as workspacesController from './workspacesController';
|
||||
|
||||
export {
|
||||
secretsController,
|
||||
workspacesController
|
||||
}
|
183
backend/src/controllers/v3/secretsController.ts
Normal file
183
backend/src/controllers/v3/secretsController.ts
Normal file
@ -0,0 +1,183 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
SecretService,
|
||||
TelemetryService,
|
||||
EventService
|
||||
} from '../../services';
|
||||
import { eventPushSecrets } from '../../events';
|
||||
import { getAuthDataPayloadIdObj } from '../../utils/auth';
|
||||
import { BadRequestError } from '../../utils/errors';
|
||||
|
||||
/**
|
||||
* Get secrets for workspace with id [workspaceId] and environment
|
||||
* [environment]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getSecrets = async (req: Request, res: Response) => {
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
|
||||
const secrets = await SecretService.getSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
authData: req.authData
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secrets
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secret with name [secretName]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getSecretByName = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
const type = req.query.type as 'shared' | 'personal' | undefined;
|
||||
|
||||
const secret = await SecretService.getSecret({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create secret with name [secretName]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const createSecret = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
} = req.body;
|
||||
|
||||
const secret = await SecretService.createSecret({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
...((secretCommentCiphertext && secretCommentIV && secretCommentTag) ? {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
} : {})
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
})
|
||||
});
|
||||
|
||||
const secretWithoutBlindIndex = secret.toObject();
|
||||
delete secretWithoutBlindIndex.secretBlindIndex;
|
||||
|
||||
return res.status(200).send({
|
||||
secret: secretWithoutBlindIndex
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update secret with name [secretName]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
} = req.body;
|
||||
|
||||
const secret = await SecretService.updateSecret({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
})
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete secret with name [secretName]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type
|
||||
} = req.body;
|
||||
|
||||
const { secret, secrets } = await SecretService.deleteSecret({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
})
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret
|
||||
});
|
||||
}
|
90
backend/src/controllers/v3/workspacesController.ts
Normal file
90
backend/src/controllers/v3/workspacesController.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Types } from 'mongoose';
|
||||
import { Secret } from '../../models';
|
||||
import { SecretService } from'../../services';
|
||||
|
||||
/**
|
||||
* Return whether or not all secrets in workspace with id [workspaceId]
|
||||
* are blind-indexed
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
const secretsWithoutBlindIndex = await Secret.countDocuments({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
secretBlindIndex: {
|
||||
$exists: false
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(200).send(secretsWithoutBlindIndex === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all secrets for workspace with id [workspaceId]
|
||||
*/
|
||||
export const getWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
const secrets = await Secret.find({
|
||||
workspace: new Types.ObjectId (workspaceId)
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secrets
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update blind indices for secrets in workspace with id [workspaceId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
interface SecretToUpdate {
|
||||
secretName: string;
|
||||
_id: string;
|
||||
}
|
||||
|
||||
const { workspaceId } = req.params;
|
||||
const {
|
||||
secretsToUpdate
|
||||
}: {
|
||||
secretsToUpdate: SecretToUpdate[];
|
||||
} = req.body;
|
||||
|
||||
// get secret blind index salt
|
||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
// update secret blind indices
|
||||
const operations = await Promise.all(
|
||||
secretsToUpdate.map(async (secretToUpdate: SecretToUpdate) => {
|
||||
const secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName: secretToUpdate.secretName,
|
||||
salt
|
||||
});
|
||||
|
||||
return ({
|
||||
updateOne: {
|
||||
filter: {
|
||||
_id: new Types.ObjectId(secretToUpdate._id)
|
||||
},
|
||||
update: {
|
||||
secretBlindIndex
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
await Secret.bulkWrite(operations);
|
||||
|
||||
return res.status(200).send({
|
||||
message: 'Successfully named workspace secrets'
|
||||
});
|
||||
}
|
@ -146,7 +146,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
const oldSecretVersion = await SecretVersion.findOne({
|
||||
secret: secretId,
|
||||
version
|
||||
});
|
||||
}).select('+secretBlindIndex')
|
||||
|
||||
if (!oldSecretVersion) throw new Error('Failed to find secret version');
|
||||
|
||||
@ -155,6 +155,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
type,
|
||||
user,
|
||||
environment,
|
||||
secretBlindIndex,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
@ -174,6 +175,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
type,
|
||||
user,
|
||||
environment,
|
||||
...(secretBlindIndex ? { secretBlindIndex } : {}),
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
@ -197,6 +199,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
user,
|
||||
environment,
|
||||
isDeleted: false,
|
||||
...(secretBlindIndex ? { secretBlindIndex } : {}),
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
@ -207,7 +210,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
|
||||
// take secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: secret.workspace.toString()
|
||||
workspaceId: secret.workspace
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
|
@ -15,16 +15,10 @@ export const getSecretSnapshot = async (req: Request, res: Response) => {
|
||||
|
||||
secretSnapshot = await SecretSnapshot
|
||||
.findById(secretSnapshotId)
|
||||
.populate({
|
||||
path: 'secretVersions',
|
||||
populate: {
|
||||
path: 'tags',
|
||||
model: 'Tag',
|
||||
}
|
||||
});
|
||||
.populate('secretVersions');
|
||||
|
||||
if (!secretSnapshot) throw new Error('Failed to find secret snapshot');
|
||||
|
||||
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
Sentry.captureException(err);
|
||||
|
@ -12,7 +12,7 @@ import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config';
|
||||
export const handleWebhook = async (req: Request, res: Response) => {
|
||||
let event;
|
||||
try {
|
||||
const stripe = new Stripe(getStripeSecretKey(), {
|
||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||
apiVersion: '2022-08-01'
|
||||
});
|
||||
|
||||
@ -21,7 +21,7 @@ export const handleWebhook = async (req: Request, res: Response) => {
|
||||
event = stripe.webhooks.constructEvent(
|
||||
req.body,
|
||||
sig,
|
||||
getStripeWebhookSecret()
|
||||
await getStripeWebhookSecret()
|
||||
);
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
|
@ -173,6 +173,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
let secrets;
|
||||
try {
|
||||
const { workspaceId } = req.params;
|
||||
@ -182,7 +183,10 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
const secretSnapshot = await SecretSnapshot.findOne({
|
||||
workspace: workspaceId,
|
||||
version
|
||||
}).populate<{ secretVersions: ISecretVersion[]}>('secretVersions');
|
||||
}).populate<{ secretVersions: ISecretVersion[]}>({
|
||||
path: 'secretVersions',
|
||||
select: '+secretBlindIndex'
|
||||
});
|
||||
|
||||
if (!secretSnapshot) throw new Error('Failed to find secret snapshot');
|
||||
|
||||
@ -222,6 +226,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
type,
|
||||
user,
|
||||
environment,
|
||||
secretBlindIndex,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
@ -240,6 +245,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
type,
|
||||
user,
|
||||
environment,
|
||||
secretBlindIndex: secretBlindIndex ?? undefined,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
@ -257,7 +263,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
);
|
||||
|
||||
// add secret versions
|
||||
await SecretVersion.insertMany(
|
||||
const secretV = await SecretVersion.insertMany(
|
||||
secrets.map(({
|
||||
_id,
|
||||
version,
|
||||
@ -265,6 +271,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
type,
|
||||
user,
|
||||
environment,
|
||||
secretBlindIndex,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
@ -282,6 +289,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
user,
|
||||
environment,
|
||||
isDeleted: false,
|
||||
secretBlindIndex: secretBlindIndex ?? undefined,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
@ -304,7 +312,7 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
|
||||
// take secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser({ email: req.user.email });
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
Log,
|
||||
IAction
|
||||
} from '../models';
|
||||
|
||||
/**
|
||||
* Create an (audit) log
|
||||
* @param {Object} obj
|
||||
|
@ -2,7 +2,7 @@ import { Types } from 'mongoose';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import {
|
||||
Secret,
|
||||
ISecret
|
||||
ISecret,
|
||||
} from '../../models';
|
||||
import {
|
||||
SecretSnapshot,
|
||||
@ -21,7 +21,7 @@ import {
|
||||
const takeSecretSnapshotHelper = async ({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
|
||||
let secretSnapshot;
|
||||
@ -143,7 +143,7 @@ const initSecretVersioningHelper = async () => {
|
||||
|
||||
if (unversionedSecrets.length > 0) {
|
||||
await addSecretVersionsHelper({
|
||||
secretVersions: unversionedSecrets.map((s, idx) => ({
|
||||
secretVersions: unversionedSecrets.map((s, idx) => new SecretVersion({
|
||||
...s,
|
||||
secret: s._id,
|
||||
version: s.version ? s.version : 1,
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
} from '../../variables';
|
||||
|
||||
export interface ISecretVersion {
|
||||
_id: Types.ObjectId;
|
||||
secret: Types.ObjectId;
|
||||
version: number;
|
||||
workspace: Types.ObjectId; // new
|
||||
@ -12,13 +13,13 @@ export interface ISecretVersion {
|
||||
user?: Types.ObjectId; // new
|
||||
environment: string; // new
|
||||
isDeleted: boolean;
|
||||
secretBlindIndex?: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
const secretVersionSchema = new Schema<ISecretVersion>(
|
||||
@ -57,6 +58,10 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
||||
default: false,
|
||||
required: true
|
||||
},
|
||||
secretBlindIndex: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
secretKeyCiphertext: {
|
||||
type: String,
|
||||
required: true
|
||||
@ -80,12 +85,7 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
||||
secretValueTag: {
|
||||
type: String, // symmetric
|
||||
required: true
|
||||
},
|
||||
tags: {
|
||||
ref: 'Tag',
|
||||
type: [Schema.Types.ObjectId],
|
||||
default: []
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
|
@ -25,7 +25,7 @@ class EESecretService {
|
||||
static async takeSecretSnapshot({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
if (!EELicenseService.isLicenseValid) return;
|
||||
return await takeSecretSnapshotHelper({ workspaceId });
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
EVENT_PUSH_SECRETS,
|
||||
EVENT_PULL_SECRETS
|
||||
@ -22,13 +23,16 @@ interface PushSecret {
|
||||
* @returns
|
||||
*/
|
||||
const eventPushSecrets = ({
|
||||
workspaceId
|
||||
workspaceId,
|
||||
environment
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
}) => {
|
||||
return ({
|
||||
name: EVENT_PUSH_SECRETS,
|
||||
workspaceId,
|
||||
environment,
|
||||
payload: {
|
||||
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ const getAuthUserPayload = async ({
|
||||
authTokenValue: string;
|
||||
}) => {
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(authTokenValue, getJwtAuthSecret())
|
||||
jwt.verify(authTokenValue, await getJwtAuthSecret())
|
||||
);
|
||||
|
||||
const user = await User.findOne({
|
||||
@ -157,7 +157,7 @@ const getAuthSTDPayload = async ({
|
||||
}, {
|
||||
new: true
|
||||
})
|
||||
.select('+encryptedKey +iv +tag').populate('user serviceAccount');
|
||||
.select('+encryptedKey +iv +tag');
|
||||
|
||||
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
|
||||
|
||||
@ -263,16 +263,16 @@ const issueAuthTokens = async ({ userId }: { userId: string }) => {
|
||||
payload: {
|
||||
userId
|
||||
},
|
||||
expiresIn: getJwtAuthLifetime(),
|
||||
secret: getJwtAuthSecret()
|
||||
expiresIn: await getJwtAuthLifetime(),
|
||||
secret: await getJwtAuthSecret()
|
||||
});
|
||||
|
||||
const refreshToken = createToken({
|
||||
payload: {
|
||||
userId
|
||||
},
|
||||
expiresIn: getJwtRefreshLifetime(),
|
||||
secret: getJwtRefreshSecret()
|
||||
expiresIn: await getJwtRefreshLifetime(),
|
||||
secret: await getJwtRefreshSecret()
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -112,14 +112,14 @@ const createBot = async ({
|
||||
workspaceId,
|
||||
}: {
|
||||
name: string;
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
let bot;
|
||||
try {
|
||||
const { publicKey, privateKey } = generateKeyPair();
|
||||
const { ciphertext, iv, tag } = encryptSymmetric({
|
||||
plaintext: privateKey,
|
||||
key: getEncryptionKey()
|
||||
key: await getEncryptionKey()
|
||||
});
|
||||
|
||||
bot = await new Bot({
|
||||
@ -151,7 +151,7 @@ const getSecretsHelper = async ({
|
||||
workspaceId,
|
||||
environment
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
}) => {
|
||||
const content = {} as any;
|
||||
@ -196,7 +196,7 @@ const getSecretsHelper = async ({
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @returns {String} key - decrypted workspace key
|
||||
*/
|
||||
const getKey = async ({ workspaceId }: { workspaceId: string }) => {
|
||||
const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) => {
|
||||
let key;
|
||||
try {
|
||||
const botKey = await BotKey.findOne({
|
||||
@ -216,7 +216,7 @@ const getKey = async ({ workspaceId }: { workspaceId: string }) => {
|
||||
ciphertext: bot.encryptedPrivateKey,
|
||||
iv: bot.iv,
|
||||
tag: bot.tag,
|
||||
key: getEncryptionKey()
|
||||
key: await getEncryptionKey()
|
||||
});
|
||||
|
||||
key = decryptAsymmetric({
|
||||
@ -245,7 +245,7 @@ const encryptSymmetricHelper = async ({
|
||||
workspaceId,
|
||||
plaintext
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
plaintext: string;
|
||||
}) => {
|
||||
|
||||
@ -282,7 +282,7 @@ const decryptSymmetricHelper = async ({
|
||||
iv,
|
||||
tag
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { EESecretService } from '../ee/services';
|
||||
import { SecretService } from '../services';
|
||||
import { getLogger } from '../utils/logger';
|
||||
|
||||
/**
|
||||
@ -19,11 +20,12 @@ const initDatabaseHelper = async ({
|
||||
// allow empty strings to pass the required validator
|
||||
mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string');
|
||||
|
||||
getLogger("database").info("Database connection established");
|
||||
(await getLogger("database")).info("Database connection established");
|
||||
|
||||
await EESecretService.initSecretVersioning();
|
||||
await SecretService.initSecretBlindIndexDataHelper();
|
||||
} catch (err) {
|
||||
getLogger("database").error(`Unable to establish Database connection due to the error.\n${err}`);
|
||||
(await getLogger("database")).error(`Unable to establish Database connection due to the error.\n${err}`);
|
||||
}
|
||||
|
||||
return mongoose.connection;
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { Bot, IBot } from '../models';
|
||||
import { Types } from 'mongoose';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { Bot, IBot } from '../models';
|
||||
import { EVENT_PUSH_SECRETS } from '../variables';
|
||||
import { IntegrationService } from '../services';
|
||||
|
||||
interface Event {
|
||||
name: string;
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
@ -22,7 +24,10 @@ const handleEventHelper = async ({
|
||||
}: {
|
||||
event: Event;
|
||||
}) => {
|
||||
const { workspaceId } = event;
|
||||
const {
|
||||
workspaceId,
|
||||
environment
|
||||
} = event;
|
||||
|
||||
// TODO: moduralize bot check into separate function
|
||||
const bot = await Bot.findOne({
|
||||
@ -36,7 +41,8 @@ const handleEventHelper = async ({
|
||||
switch (event.name) {
|
||||
case EVENT_PUSH_SECRETS:
|
||||
IntegrationService.syncIntegrations({
|
||||
workspaceId
|
||||
workspaceId,
|
||||
environment
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
@ -217,14 +217,19 @@ const handleOAuthExchangeHelper = async ({
|
||||
* @param {Object} obj.workspaceId - id of workspace
|
||||
*/
|
||||
const syncIntegrationsHelper = async ({
|
||||
workspaceId
|
||||
workspaceId,
|
||||
environment
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
}) => {
|
||||
let integrations;
|
||||
try {
|
||||
integrations = await Integration.find({
|
||||
workspace: workspaceId,
|
||||
...(environment ? {
|
||||
environment
|
||||
} : {}),
|
||||
isActive: true,
|
||||
app: { $ne: null }
|
||||
});
|
||||
@ -234,7 +239,7 @@ const syncIntegrationsHelper = async ({
|
||||
for await (const integration of integrations) {
|
||||
// get workspace, environment (shared) secrets
|
||||
const secrets = await BotService.getSecrets({ // issue here?
|
||||
workspaceId: integration.workspace.toString(),
|
||||
workspaceId: integration.workspace,
|
||||
environment: integration.environment
|
||||
});
|
||||
|
||||
@ -281,7 +286,7 @@ const syncIntegrationsHelper = async ({
|
||||
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
||||
|
||||
refreshToken = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace.toString(),
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.refreshCiphertext as string,
|
||||
iv: integrationAuth.refreshIV as string,
|
||||
tag: integrationAuth.refreshTag as string
|
||||
@ -318,7 +323,7 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
|
||||
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
|
||||
|
||||
accessToken = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace.toString(),
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.accessCiphertext as string,
|
||||
iv: integrationAuth.accessIV as string,
|
||||
tag: integrationAuth.accessTag as string
|
||||
@ -340,7 +345,7 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
|
||||
|
||||
if (integrationAuth?.accessIdCiphertext && integrationAuth?.accessIdIV && integrationAuth?.accessIdTag) {
|
||||
accessId = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace.toString(),
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.accessIdCiphertext as string,
|
||||
iv: integrationAuth.accessIdIV as string,
|
||||
tag: integrationAuth.accessIdTag as string
|
||||
@ -386,7 +391,7 @@ const setIntegrationAuthRefreshHelper = async ({
|
||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
||||
|
||||
const obj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace.toString(),
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: refreshToken
|
||||
});
|
||||
|
||||
@ -435,14 +440,14 @@ const setIntegrationAuthAccessHelper = async ({
|
||||
if (!integrationAuth) throw new Error('Failed to find integration auth');
|
||||
|
||||
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace.toString(),
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: accessToken
|
||||
});
|
||||
|
||||
let encryptedAccessIdObj;
|
||||
if (accessId) {
|
||||
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace.toString(),
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: accessId
|
||||
});
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ const sendMail = async ({
|
||||
recipients: string[];
|
||||
substitutions: any;
|
||||
}) => {
|
||||
if (getSmtpConfigured()) {
|
||||
if (await getSmtpConfigured()) {
|
||||
try {
|
||||
const html = fs.readFileSync(
|
||||
path.resolve(__dirname, '../templates/' + template),
|
||||
@ -35,7 +35,7 @@ const sendMail = async ({
|
||||
const htmlToSend = temp(substitutions);
|
||||
|
||||
await smtpTransporter.sendMail({
|
||||
from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`,
|
||||
from: `"${await getSmtpFromName()}" <${await getSmtpFromAddress()}>`,
|
||||
to: recipients.join(', '),
|
||||
subject: subjectLine,
|
||||
html: htmlToSend
|
||||
|
@ -123,11 +123,11 @@ const createOrganization = async ({
|
||||
let organization;
|
||||
try {
|
||||
// register stripe account
|
||||
const stripe = new Stripe(getStripeSecretKey(), {
|
||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||
apiVersion: '2022-08-01'
|
||||
});
|
||||
|
||||
if (getStripeSecretKey()) {
|
||||
if (await getStripeSecretKey()) {
|
||||
const customer = await stripe.customers.create({
|
||||
email,
|
||||
description: name
|
||||
@ -177,14 +177,14 @@ const initSubscriptionOrg = async ({
|
||||
if (organization) {
|
||||
if (organization.customerId) {
|
||||
// initialize starter subscription with quantity of 0
|
||||
const stripe = new Stripe(getStripeSecretKey(), {
|
||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||
apiVersion: '2022-08-01'
|
||||
});
|
||||
|
||||
const productToPriceMap = {
|
||||
starter: getStripeProductStarter(),
|
||||
team: getStripeProductTeam(),
|
||||
pro: getStripeProductPro()
|
||||
starter: await getStripeProductStarter(),
|
||||
team: await getStripeProductTeam(),
|
||||
pro: await getStripeProductPro()
|
||||
};
|
||||
|
||||
stripeSubscription = await stripe.subscriptions.create({
|
||||
@ -239,7 +239,7 @@ const updateSubscriptionOrgQuantity = async ({
|
||||
status: ACCEPTED
|
||||
});
|
||||
|
||||
const stripe = new Stripe(getStripeSecretKey(), {
|
||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
||||
apiVersion: '2022-08-01'
|
||||
});
|
||||
|
||||
|
@ -10,7 +10,8 @@ import {
|
||||
EELogService
|
||||
} from '../ee/services';
|
||||
import {
|
||||
IAction
|
||||
IAction,
|
||||
SecretVersion
|
||||
} from '../ee/models';
|
||||
import {
|
||||
SECRET_SHARED,
|
||||
@ -189,8 +190,7 @@ const v1PushSecrets = async ({
|
||||
secretKeyHash,
|
||||
}) => {
|
||||
const newSecret = newSecretsObj[`${type}-${secretKeyHash}`];
|
||||
return ({
|
||||
_id: new Types.ObjectId(),
|
||||
return new SecretVersion({
|
||||
secret: _id,
|
||||
version: version ? version + 1 : 1,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
@ -261,8 +261,7 @@ const v1PushSecrets = async ({
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueHash
|
||||
}) => ({
|
||||
_id: new Types.ObjectId(),
|
||||
}) => new SecretVersion({
|
||||
secret: _id,
|
||||
version,
|
||||
workspace,
|
||||
@ -284,7 +283,7 @@ const v1PushSecrets = async ({
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
@ -475,12 +474,12 @@ const v2PushSecrets = async ({
|
||||
|
||||
// (EE) add secret versions for new secrets
|
||||
EESecretService.addSecretVersions({
|
||||
secretVersions: newSecrets.map((secretDocument) => {
|
||||
return {
|
||||
...secretDocument.toObject(),
|
||||
secretVersions: newSecrets.map((secretDocument: ISecret) => {
|
||||
return new SecretVersion({
|
||||
...secretDocument,
|
||||
secret: secretDocument._id,
|
||||
isDeleted: false
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
@ -495,7 +494,7 @@ const v2PushSecrets = async ({
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
})
|
||||
|
||||
// (EE) create (audit) log
|
||||
|
@ -1,14 +1,24 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
CreateSecretParams,
|
||||
GetSecretsParams,
|
||||
GetSecretParams,
|
||||
UpdateSecretParams,
|
||||
DeleteSecretParams
|
||||
} from '../interfaces/services/SecretService';
|
||||
import {
|
||||
AuthData
|
||||
} from '../interfaces/middleware';
|
||||
import {
|
||||
User,
|
||||
IUser,
|
||||
Workspace,
|
||||
ServiceAccount,
|
||||
IServiceAccount,
|
||||
ServiceTokenData,
|
||||
IServiceTokenData,
|
||||
Secret,
|
||||
ISecret
|
||||
ISecret,
|
||||
SecretBlindIndexData,
|
||||
} from '../models';
|
||||
import { SecretVersion } from '../ee/models';
|
||||
import {
|
||||
validateMembership
|
||||
} from '../helpers/membership';
|
||||
@ -17,7 +27,8 @@ import {
|
||||
validateUserClientForSecrets
|
||||
} from '../helpers/user';
|
||||
import {
|
||||
validateServiceTokenDataClientForSecrets, validateServiceTokenDataClientForWorkspace
|
||||
validateServiceTokenDataClientForSecrets,
|
||||
validateServiceTokenDataClientForWorkspace
|
||||
} from '../helpers/serviceTokenData';
|
||||
import {
|
||||
validateServiceAccountClientForSecrets,
|
||||
@ -26,14 +37,37 @@ import {
|
||||
import {
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError,
|
||||
SecretNotFoundError
|
||||
SecretNotFoundError,
|
||||
SecretBlindIndexDataNotFoundError
|
||||
} from '../utils/errors';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
AUTH_MODE_API_KEY,
|
||||
SECRET_PERSONAL,
|
||||
SECRET_SHARED,
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_READ_SECRETS,
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ACTION_DELETE_SECRETS
|
||||
} from '../variables';
|
||||
import crypto from 'crypto';
|
||||
import * as argon2 from 'argon2';
|
||||
import {
|
||||
encryptSymmetric,
|
||||
decryptSymmetric
|
||||
} from '../utils/crypto';
|
||||
import { getEncryptionKey } from '../config';
|
||||
import { TelemetryService } from '../services';
|
||||
import {
|
||||
EESecretService,
|
||||
EELogService
|
||||
} from '../ee/services';
|
||||
import {
|
||||
getAuthDataPayloadIdObj,
|
||||
getAuthDataPayloadUserObj
|
||||
} from '../utils/auth';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for secrets with id [secretId] based
|
||||
@ -50,10 +84,7 @@ const validateClientForSecret = async ({
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
},
|
||||
authData: AuthData;
|
||||
secretId: Types.ObjectId;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
requiredPermissions: string[];
|
||||
@ -127,10 +158,7 @@ const validateClientForSecrets = async ({
|
||||
secretIds,
|
||||
requiredPermissions
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
},
|
||||
authData: AuthData;
|
||||
secretIds: Types.ObjectId[];
|
||||
requiredPermissions: string[];
|
||||
}) => {
|
||||
@ -192,7 +220,728 @@ const validateClientForSecrets = async ({
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize secret blind index data by setting previously
|
||||
* un-initialized projects to have secret blind index data
|
||||
* (Ensures that all projects have associated blind index data)
|
||||
*/
|
||||
const initSecretBlindIndexDataHelper = async () => {
|
||||
const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct('workspace');
|
||||
const workspaceIdsToBlindIndex = await Workspace.distinct('_id', {
|
||||
_id: {
|
||||
$nin: workspaceIdsBlindIndexed
|
||||
}
|
||||
});
|
||||
|
||||
const secretBlindIndexDataToInsert = await Promise.all(
|
||||
workspaceIdsToBlindIndex.map(async (workspaceToBlindIndex) => {
|
||||
const salt = crypto.randomBytes(16).toString('base64');
|
||||
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = encryptSymmetric({
|
||||
plaintext: salt,
|
||||
key: await getEncryptionKey()
|
||||
});
|
||||
|
||||
const secretBlindIndexData = new SecretBlindIndexData({
|
||||
workspace: workspaceToBlindIndex,
|
||||
encryptedSaltCiphertext,
|
||||
saltIV,
|
||||
saltTag
|
||||
})
|
||||
|
||||
return secretBlindIndexData;
|
||||
})
|
||||
);
|
||||
|
||||
if (secretBlindIndexDataToInsert.length > 0) {
|
||||
await SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create secret blind index data containing encrypted blind index [salt]
|
||||
* for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId
|
||||
*/
|
||||
const createSecretBlindIndexDataHelper = async ({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
// initialize random blind index salt for workspace
|
||||
const salt = crypto.randomBytes(16).toString('base64');
|
||||
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag
|
||||
} = encryptSymmetric({
|
||||
plaintext: salt,
|
||||
key: await getEncryptionKey()
|
||||
});
|
||||
|
||||
const secretBlindIndexData = await new SecretBlindIndexData({
|
||||
workspace: workspaceId,
|
||||
encryptedSaltCiphertext,
|
||||
saltIV,
|
||||
saltTag
|
||||
}).save();
|
||||
|
||||
return secretBlindIndexData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secret blind index salt for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get salt for
|
||||
* @returns
|
||||
*/
|
||||
const getSecretBlindIndexSaltHelper = async ({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
// check if workspace blind index data exists
|
||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||
workspace: workspaceId
|
||||
});
|
||||
|
||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||
|
||||
// decrypt workspace salt
|
||||
const salt = decryptSymmetric({
|
||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||
iv: secretBlindIndexData.saltIV,
|
||||
tag: secretBlindIndexData.saltTag,
|
||||
key: await getEncryptionKey()
|
||||
});
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate blind index for secret with name [secretName]
|
||||
* and salt [salt]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to generate blind index for
|
||||
* @param {String} obj.salt - base64-salt
|
||||
*/
|
||||
const generateSecretBlindIndexWithSaltHelper = async ({
|
||||
secretName,
|
||||
salt
|
||||
}: {
|
||||
secretName: string;
|
||||
salt: string;
|
||||
}) => {
|
||||
|
||||
// generate secret blind index
|
||||
const secretBlindIndex = (await argon2.hash(secretName, {
|
||||
type: argon2.argon2id,
|
||||
salt: Buffer.from(salt, 'base64'),
|
||||
saltLength: 16, // default 16 bytes
|
||||
memoryCost: 65536, // default pool of 64 MiB per thread.
|
||||
hashLength: 32,
|
||||
parallelism: 1,
|
||||
raw: true
|
||||
})).toString('base64');
|
||||
|
||||
return secretBlindIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate blind index for secret with name [secretName]
|
||||
* for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {Stringj} obj.secretName - name of secret to generate blind index for
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
*/
|
||||
const generateSecretBlindIndexHelper = async ({
|
||||
secretName,
|
||||
workspaceId
|
||||
}: {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
|
||||
// check if workspace blind index data exists
|
||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||
workspace: workspaceId
|
||||
});
|
||||
|
||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||
|
||||
// decrypt workspace salt
|
||||
const salt = decryptSymmetric({
|
||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||
iv: secretBlindIndexData.saltIV,
|
||||
tag: secretBlindIndexData.saltTag,
|
||||
key: await getEncryptionKey()
|
||||
});
|
||||
|
||||
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||
secretName,
|
||||
salt
|
||||
});
|
||||
|
||||
return secretBlindIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to create
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to create secret for
|
||||
* @param {String} obj.environment - environment in workspace to create secret for
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
const createSecretHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
authData,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
}: CreateSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const exists = await Secret.exists({
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
type,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||
});
|
||||
|
||||
if (exists) throw BadRequestError({
|
||||
message: 'Failed to create secret that already exists'
|
||||
});
|
||||
|
||||
if (type === SECRET_PERSONAL) {
|
||||
// case: secret type is personal -> check if a corresponding shared secret
|
||||
// with the same blind index [secretBlindIndex] exists
|
||||
|
||||
const exists = await Secret.exists({
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
type: SECRET_SHARED
|
||||
});
|
||||
|
||||
if (!exists) throw BadRequestError({
|
||||
message: 'Failed to create personal secret override for no corresponding shared secret'
|
||||
});
|
||||
}
|
||||
|
||||
// create secret
|
||||
const secret = await new Secret({
|
||||
version: 1,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||
secretBlindIndex,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
}).save();
|
||||
|
||||
const secretVersion = new SecretVersion({
|
||||
secret: secret._id,
|
||||
version: secret.version,
|
||||
workspace: secret.workspace,
|
||||
type,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||
environment: secret.environment,
|
||||
isDeleted: false,
|
||||
secretBlindIndex,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
});
|
||||
|
||||
// // (EE) add version for new secret
|
||||
await EESecretService.addSecretVersions({
|
||||
secretVersions: [secretVersion]
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
const action = await EELogService.createAction({
|
||||
name: ACTION_ADD_SECRETS,
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
secretIds: [secret._id]
|
||||
});
|
||||
|
||||
action && await EELogService.createLog({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId
|
||||
});
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets added',
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secrets for workspace with id [workspaceId] and environment [environment]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.environment - environment in workspace
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
const getSecretsHelper = async ({
|
||||
workspaceId,
|
||||
environment,
|
||||
authData
|
||||
}: GetSecretsParams) => {
|
||||
let secrets: ISecret[] = [];
|
||||
|
||||
// get personal secrets first
|
||||
secrets = await Secret.find({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type: SECRET_PERSONAL,
|
||||
...getAuthDataPayloadUserObj(authData)
|
||||
});
|
||||
|
||||
// concat with shared secrets
|
||||
secrets = secrets.concat(await Secret.find({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type: SECRET_SHARED,
|
||||
secretBlindIndex: {
|
||||
$nin: secrets.map((secret) => secret.secretBlindIndex)
|
||||
}
|
||||
}));
|
||||
|
||||
// (EE) create (audit) log
|
||||
const action = await EELogService.createAction({
|
||||
name: ACTION_READ_SECRETS,
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
secretIds: secrets.map((secret) => secret._id)
|
||||
});
|
||||
|
||||
action && await EELogService.createLog({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
});
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets pulled',
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return secrets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to get
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
const getSecretHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
authData
|
||||
}: GetSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
let secret: ISecret | null = null;
|
||||
|
||||
// try getting personal secret first (if exists)
|
||||
secret = await Secret.findOne({
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type: type ?? SECRET_PERSONAL,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||
});
|
||||
|
||||
if (!secret) {
|
||||
// case: failed to find personal secret matching criteria
|
||||
// -> find shared secret matching criteria
|
||||
secret = await Secret.findOne({
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type: SECRET_SHARED
|
||||
});
|
||||
}
|
||||
|
||||
if (!secret) throw SecretNotFoundError();
|
||||
|
||||
// (EE) create (audit) log
|
||||
const action = await EELogService.createAction({
|
||||
name: ACTION_READ_SECRETS,
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
secretIds: [secret._id]
|
||||
});
|
||||
|
||||
action && await EELogService.createLog({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
});
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets pull',
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to update
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {String} obj.secretValueCiphertext - ciphertext of secret value
|
||||
* @param {String} obj.secretValueIV - IV of secret value
|
||||
* @param {String} obj.secretValueTag - tag of secret value
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
const updateSecretHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
authData,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
}: UpdateSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
let secret: ISecret | null = null;
|
||||
|
||||
if (type === SECRET_SHARED) {
|
||||
// case: update shared secret
|
||||
secret = await Secret.findOneAndUpdate(
|
||||
{
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type
|
||||
},
|
||||
{
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
$inc: { version: 1 }
|
||||
},
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// case: update personal secret
|
||||
|
||||
secret = await Secret.findOneAndUpdate(
|
||||
{
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
...getAuthDataPayloadUserObj(authData)
|
||||
},
|
||||
{
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
$inc: { version: 1 }
|
||||
},
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (!secret) throw SecretNotFoundError();
|
||||
|
||||
const secretVersion = new SecretVersion({
|
||||
secret: secret._id,
|
||||
version: secret.version,
|
||||
workspace: secret.workspace,
|
||||
type,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||
environment: secret.environment,
|
||||
isDeleted: false,
|
||||
secretBlindIndex,
|
||||
secretKeyCiphertext: secret.secretKeyCiphertext,
|
||||
secretKeyIV: secret.secretKeyIV,
|
||||
secretKeyTag: secret.secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag
|
||||
});
|
||||
|
||||
// (EE) add version for new secret
|
||||
await EESecretService.addSecretVersions({
|
||||
secretVersions: [secretVersion]
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
const action = await EELogService.createAction({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
secretIds: [secret._id]
|
||||
});
|
||||
|
||||
action && await EELogService.createLog({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId
|
||||
});
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets modified',
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to delete
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
const deleteSecretHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
authData
|
||||
}: DeleteSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
let secrets: ISecret[] = [];
|
||||
let secret: ISecret | null = null;
|
||||
|
||||
if (type === SECRET_SHARED) {
|
||||
secrets = await Secret.find({
|
||||
secretBlindIndex,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
});
|
||||
|
||||
secret = await Secret.findOneAndDelete({
|
||||
secretBlindIndex,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type
|
||||
});
|
||||
|
||||
await Secret.deleteMany({
|
||||
secretBlindIndex,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
});
|
||||
} else {
|
||||
secret = await Secret.findOneAndDelete({
|
||||
secretBlindIndex,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
...getAuthDataPayloadUserObj(authData)
|
||||
});
|
||||
|
||||
if (secret) {
|
||||
secrets = [secret];
|
||||
}
|
||||
}
|
||||
|
||||
if (!secret) throw SecretNotFoundError();
|
||||
|
||||
await EESecretService.markDeletedSecretVersions({
|
||||
secretIds: secrets.map((secret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
const action = await EELogService.createAction({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
secretIds: secrets.map((secret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
action && await EELogService.createLog({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId
|
||||
});
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: 'secrets deleted',
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ({
|
||||
secrets,
|
||||
secret
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
validateClientForSecret,
|
||||
validateClientForSecrets
|
||||
validateClientForSecrets,
|
||||
initSecretBlindIndexDataHelper,
|
||||
createSecretBlindIndexDataHelper,
|
||||
getSecretBlindIndexSaltHelper,
|
||||
generateSecretBlindIndexWithSaltHelper,
|
||||
generateSecretBlindIndexHelper,
|
||||
createSecretHelper,
|
||||
getSecretsHelper,
|
||||
getSecretHelper,
|
||||
updateSecretHelper,
|
||||
deleteSecretHelper
|
||||
}
|
@ -111,7 +111,6 @@ const validateClientForServiceTokenData = async ({
|
||||
environment?: string;
|
||||
requiredPermissions?: string[];
|
||||
}) => {
|
||||
|
||||
if (!serviceTokenData.workspace.equals(workspaceId)) {
|
||||
// case: invalid workspaceId passed
|
||||
throw UnauthorizedRequestError({
|
||||
|
@ -84,7 +84,7 @@ const createTokenHelper = async ({
|
||||
const query: TokenDataQuery = { type };
|
||||
const update: TokenDataUpdate = {
|
||||
type,
|
||||
tokenHash: await bcrypt.hash(token, getSaltRounds()),
|
||||
tokenHash: await bcrypt.hash(token, await getSaltRounds()),
|
||||
expiresAt
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as Sentry from '@sentry/node';
|
||||
import crypto from 'crypto';
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
Workspace,
|
||||
@ -13,6 +14,7 @@ import {
|
||||
IServiceAccount,
|
||||
ServiceTokenData,
|
||||
IServiceTokenData,
|
||||
SecretBlindIndexData
|
||||
} from '../models';
|
||||
import { createBot } from '../helpers/bot';
|
||||
import { validateUserClientForWorkspace } from '../helpers/user';
|
||||
@ -26,6 +28,8 @@ import {
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
import { encryptSymmetric } from '../utils/crypto';
|
||||
import { SecretService } from '../services';
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for workspace with id [workspaceId] based
|
||||
@ -42,7 +46,8 @@ const validateClientForWorkspace = async ({
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
requiredPermissions,
|
||||
requireBlindIndicesEnabled
|
||||
}: {
|
||||
authData: {
|
||||
authMode: string;
|
||||
@ -52,6 +57,7 @@ const validateClientForWorkspace = async ({
|
||||
environment?: string;
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
requiredPermissions?: string[];
|
||||
requireBlindIndicesEnabled: boolean;
|
||||
}) => {
|
||||
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
@ -60,6 +66,20 @@ const validateClientForWorkspace = async ({
|
||||
message: 'Failed to find workspace'
|
||||
});
|
||||
|
||||
if (requireBlindIndicesEnabled) {
|
||||
// case: blind indices are not enabled for secrets in this workspace
|
||||
// (i.e. workspace was created before blind indices were introduced
|
||||
// and no admin has enabled it)
|
||||
|
||||
const secretBlindIndexData = await SecretBlindIndexData.exists({
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (!secretBlindIndexData) throw UnauthorizedRequestError({
|
||||
message: 'Failed workspace authorization due to blind indices not being enabled'
|
||||
});
|
||||
}
|
||||
|
||||
if (authData.authMode === AUTH_MODE_JWT && authData.authPayload instanceof User) {
|
||||
const membership = await validateUserClientForWorkspace({
|
||||
user: authData.authPayload,
|
||||
@ -130,13 +150,21 @@ const createWorkspace = async ({
|
||||
// create workspace
|
||||
workspace = await new Workspace({
|
||||
name,
|
||||
organization: organizationId
|
||||
organization: organizationId,
|
||||
autoCapitalization: true
|
||||
}).save();
|
||||
|
||||
const bot = await createBot({
|
||||
// initialize bot for workspace
|
||||
await createBot({
|
||||
name: 'Infisical Bot',
|
||||
workspaceId: workspace._id.toString()
|
||||
workspaceId: workspace._id
|
||||
});
|
||||
|
||||
// initialize blind index salt for workspace
|
||||
await SecretService.createSecretBlindIndexData({
|
||||
workspaceId: workspace._id
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import mongoose from 'mongoose';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import infisical from 'infisical-node';
|
||||
import express from 'express';
|
||||
import helmet from 'helmet';
|
||||
import cors from 'cors';
|
||||
@ -45,7 +44,8 @@ import {
|
||||
password as v1PasswordRouter,
|
||||
stripe as v1StripeRouter,
|
||||
integration as v1IntegrationRouter,
|
||||
integrationAuth as v1IntegrationAuthRouter
|
||||
integrationAuth as v1IntegrationAuthRouter,
|
||||
secretsFolder as v1SecretsFolder
|
||||
} from './routes/v1';
|
||||
import {
|
||||
signup as v2SignupRouter,
|
||||
@ -61,6 +61,10 @@ import {
|
||||
environment as v2EnvironmentRouter,
|
||||
tags as v2TagsRouter,
|
||||
} from './routes/v2';
|
||||
import {
|
||||
secrets as v3SecretsRouter,
|
||||
workspaces as v3WorkspacesRouter
|
||||
} from './routes/v3';
|
||||
import { healthCheck } from './routes/status';
|
||||
import { getLogger } from './utils/logger';
|
||||
import { RouteNotFoundError } from './utils/errors';
|
||||
@ -75,22 +79,16 @@ import {
|
||||
} from './config';
|
||||
|
||||
const main = async () => {
|
||||
if (process.env.INFISICAL_TOKEN != "" || process.env.INFISICAL_TOKEN != undefined) {
|
||||
await infisical.connect({
|
||||
token: process.env.INFISICAL_TOKEN!
|
||||
});
|
||||
}
|
||||
|
||||
TelemetryService.logTelemetryMessage();
|
||||
setTransporter(initSmtp());
|
||||
setTransporter(await initSmtp());
|
||||
|
||||
await DatabaseService.initDatabase(getMongoURL());
|
||||
if (getNodeEnv() !== 'test') {
|
||||
await DatabaseService.initDatabase(await getMongoURL());
|
||||
if ((await getNodeEnv()) !== 'test') {
|
||||
Sentry.init({
|
||||
dsn: getSentryDSN(),
|
||||
dsn: await getSentryDSN(),
|
||||
tracesSampleRate: 1.0,
|
||||
debug: getNodeEnv() === 'production' ? false : true,
|
||||
environment: getNodeEnv()
|
||||
debug: await getNodeEnv() === 'production' ? false : true,
|
||||
environment: await getNodeEnv()
|
||||
});
|
||||
}
|
||||
|
||||
@ -102,13 +100,13 @@ const main = async () => {
|
||||
app.use(
|
||||
cors({
|
||||
credentials: true,
|
||||
origin: getSiteURL()
|
||||
origin: await getSiteURL()
|
||||
})
|
||||
);
|
||||
|
||||
app.use(requestIp.mw());
|
||||
|
||||
if (getNodeEnv() === 'production') {
|
||||
if ((await getNodeEnv()) === 'production') {
|
||||
// enable app-wide rate-limiting + helmet security
|
||||
// in production
|
||||
app.disable('x-powered-by');
|
||||
@ -122,7 +120,7 @@ const main = async () => {
|
||||
app.use('/api/v1/workspace', eeWorkspaceRouter);
|
||||
app.use('/api/v1/action', eeActionRouter);
|
||||
|
||||
// v1 routes
|
||||
// v1 routes (default)
|
||||
app.use('/api/v1/signup', v1SignupRouter);
|
||||
app.use('/api/v1/auth', v1AuthRouter);
|
||||
app.use('/api/v1/bot', v1BotRouter);
|
||||
@ -140,8 +138,9 @@ const main = async () => {
|
||||
app.use('/api/v1/stripe', v1StripeRouter);
|
||||
app.use('/api/v1/integration', v1IntegrationRouter);
|
||||
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
||||
app.use('/api/v1/folder', v1SecretsFolder)
|
||||
|
||||
// v2 routes
|
||||
// v2 routes (improvements)
|
||||
app.use('/api/v2/signup', v2SignupRouter);
|
||||
app.use('/api/v2/auth', v2AuthRouter);
|
||||
app.use('/api/v2/users', v2UsersRouter);
|
||||
@ -155,6 +154,10 @@ const main = async () => {
|
||||
app.use('/api/v2/service-accounts', v2ServiceAccountsRouter); // new
|
||||
app.use('/api/v2/api-key', v2APIKeyDataRouter);
|
||||
|
||||
// v3 routes (experimental)
|
||||
app.use('/api/v3/secrets', v3SecretsRouter);
|
||||
app.use('/api/v3/workspaces', v3WorkspacesRouter);
|
||||
|
||||
// api docs
|
||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
|
||||
|
||||
@ -169,8 +172,8 @@ const main = async () => {
|
||||
|
||||
app.use(requestErrorHandler)
|
||||
|
||||
const server = app.listen(getPort(), () => {
|
||||
getLogger("backend-main").info(`Server started listening at port ${getPort()}`)
|
||||
const server = app.listen(await getPort(), async () => {
|
||||
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`)
|
||||
});
|
||||
|
||||
await createTestUserForDevelopment();
|
||||
|
@ -270,28 +270,59 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
|
||||
const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
|
||||
let apps;
|
||||
try {
|
||||
interface GitHubApp {
|
||||
id: string;
|
||||
name: string;
|
||||
permissions: {
|
||||
admin: boolean;
|
||||
};
|
||||
owner: {
|
||||
login: string;
|
||||
}
|
||||
}
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: accessToken,
|
||||
});
|
||||
|
||||
const repos = (
|
||||
await octokit.request(
|
||||
"GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}",
|
||||
{
|
||||
per_page: 100,
|
||||
const getAllRepos = async () => {
|
||||
let repos: GitHubApp[] = [];
|
||||
let page = 1;
|
||||
const per_page = 100;
|
||||
let hasMore = true;
|
||||
|
||||
while (hasMore) {
|
||||
const response = await octokit.request(
|
||||
"GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}",
|
||||
{
|
||||
per_page,
|
||||
page,
|
||||
}
|
||||
);
|
||||
|
||||
if (response.data.length > 0) {
|
||||
repos = repos.concat(response.data);
|
||||
page++;
|
||||
} else {
|
||||
hasMore = false;
|
||||
}
|
||||
)
|
||||
).data;
|
||||
}
|
||||
|
||||
return repos;
|
||||
};
|
||||
|
||||
const repos = await getAllRepos();
|
||||
|
||||
apps = repos
|
||||
.filter((a: any) => a.permissions.admin === true)
|
||||
.map((a: any) => {
|
||||
return ({
|
||||
.filter((a: GitHubApp) => a.permissions.admin === true)
|
||||
.map((a: GitHubApp) => {
|
||||
return {
|
||||
appId: a.id,
|
||||
name: a.name,
|
||||
owner: a.owner.login,
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
|
@ -159,9 +159,9 @@ const exchangeCodeAzure = async ({
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
scope: 'https://vault.azure.net/.default openid offline_access',
|
||||
client_id: getClientIdAzure(),
|
||||
client_secret: getClientSecretAzure(),
|
||||
redirect_uri: `${getSiteURL()}/integrations/azure-key-vault/oauth2/callback`
|
||||
client_id: await getClientIdAzure(),
|
||||
client_secret: await getClientSecretAzure(),
|
||||
redirect_uri: `${await getSiteURL()}/integrations/azure-key-vault/oauth2/callback`
|
||||
} as any)
|
||||
)).data;
|
||||
|
||||
@ -204,7 +204,7 @@ const exchangeCodeHeroku = async ({
|
||||
new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
client_secret: getClientSecretHeroku()
|
||||
client_secret: await getClientSecretHeroku()
|
||||
} as any)
|
||||
)).data;
|
||||
|
||||
@ -242,9 +242,9 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => {
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
code: code,
|
||||
client_id: getClientIdVercel(),
|
||||
client_secret: getClientSecretVercel(),
|
||||
redirect_uri: `${getSiteURL()}/integrations/vercel/oauth2/callback`
|
||||
client_id: await getClientIdVercel(),
|
||||
client_secret: await getClientSecretVercel(),
|
||||
redirect_uri: `${await getSiteURL()}/integrations/vercel/oauth2/callback`
|
||||
} as any)
|
||||
)
|
||||
).data;
|
||||
@ -282,9 +282,9 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
|
||||
new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
client_id: getClientIdNetlify(),
|
||||
client_secret: getClientSecretNetlify(),
|
||||
redirect_uri: `${getSiteURL()}/integrations/netlify/oauth2/callback`
|
||||
client_id: await getClientIdNetlify(),
|
||||
client_secret: await getClientSecretNetlify(),
|
||||
redirect_uri: `${await getSiteURL()}/integrations/netlify/oauth2/callback`
|
||||
} as any)
|
||||
)
|
||||
).data;
|
||||
@ -333,10 +333,10 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
|
||||
res = (
|
||||
await request.get(INTEGRATION_GITHUB_TOKEN_URL, {
|
||||
params: {
|
||||
client_id: getClientIdGitHub(),
|
||||
client_secret: getClientSecretGitHub(),
|
||||
client_id: await getClientIdGitHub(),
|
||||
client_secret: await getClientSecretGitHub(),
|
||||
code: code,
|
||||
redirect_uri: `${getSiteURL()}/integrations/github/oauth2/callback`
|
||||
redirect_uri: `${await getSiteURL()}/integrations/github/oauth2/callback`
|
||||
},
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
@ -379,9 +379,9 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
||||
new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: code,
|
||||
client_id: getClientIdGitLab(),
|
||||
client_secret: getClientSecretGitLab(),
|
||||
redirect_uri: `${getSiteURL()}/integrations/gitlab/oauth2/callback`
|
||||
client_id: await getClientIdGitLab(),
|
||||
client_secret: await getClientSecretGitLab(),
|
||||
redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback`
|
||||
} as any),
|
||||
{
|
||||
headers: {
|
||||
|
@ -133,11 +133,11 @@ const exchangeRefreshAzure = async ({
|
||||
const { data }: { data: RefreshTokenAzureResponse } = await request.post(
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
client_id: getClientIdAzure(),
|
||||
client_id: await getClientIdAzure(),
|
||||
scope: 'openid offline_access',
|
||||
refresh_token: refreshToken,
|
||||
grant_type: 'refresh_token',
|
||||
client_secret: getClientSecretAzure()
|
||||
client_secret: await getClientSecretAzure()
|
||||
} as any)
|
||||
);
|
||||
|
||||
@ -180,7 +180,7 @@ const exchangeRefreshHeroku = async ({
|
||||
new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refreshToken,
|
||||
client_secret: getClientSecretHeroku()
|
||||
client_secret: await getClientSecretHeroku()
|
||||
} as any)
|
||||
);
|
||||
|
||||
@ -223,9 +223,9 @@ const exchangeRefreshGitLab = async ({
|
||||
new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: refreshToken,
|
||||
client_id: getClientIdGitLab,
|
||||
client_secret: getClientSecretGitLab(),
|
||||
redirect_uri: `${getSiteURL()}/integrations/gitlab/oauth2/callback`
|
||||
client_id: await getClientIdGitLab,
|
||||
client_secret: await getClientSecretGitLab(),
|
||||
redirect_uri: `${await getSiteURL()}/integrations/gitlab/oauth2/callback`
|
||||
} as any),
|
||||
{
|
||||
headers: {
|
||||
|
13
backend/src/interfaces/middleware/index.ts
Normal file
13
backend/src/interfaces/middleware/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import {
|
||||
IUser,
|
||||
IServiceAccount,
|
||||
IServiceTokenData
|
||||
} from '../../models';
|
||||
|
||||
export interface AuthData {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
authChannel: string;
|
||||
authIP: string;
|
||||
authUserAgent: string;
|
||||
}
|
52
backend/src/interfaces/services/SecretService/index.ts
Normal file
52
backend/src/interfaces/services/SecretService/index.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Types } from 'mongoose';
|
||||
import { AuthData } from '../../middleware';
|
||||
|
||||
export interface CreateSecretParams {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
type: 'shared' | 'personal';
|
||||
authData: AuthData;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
secretCommentCiphertext?: string;
|
||||
secretCommentIV?: string;
|
||||
secretCommentTag?: string;
|
||||
}
|
||||
|
||||
export interface GetSecretsParams {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
authData: AuthData;
|
||||
}
|
||||
|
||||
export interface GetSecretParams {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
type?: 'shared' | 'personal';
|
||||
authData: AuthData;
|
||||
}
|
||||
|
||||
export interface UpdateSecretParams {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
type: 'shared' | 'personal',
|
||||
authData: AuthData
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
}
|
||||
|
||||
export interface DeleteSecretParams {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
type: 'shared' | 'personal';
|
||||
authData: AuthData;
|
||||
}
|
@ -5,9 +5,9 @@ import { getLogger } from "../utils/logger";
|
||||
import RequestError, { LogLevel } from "../utils/requestError";
|
||||
import { getNodeEnv } from '../config';
|
||||
|
||||
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => {
|
||||
export const requestErrorHandler: ErrorRequestHandler = async (error: RequestError | Error, req, res, next) => {
|
||||
if (res.headersSent) return next();
|
||||
if (getNodeEnv() !== "production") {
|
||||
if ((await getNodeEnv()) !== "production") {
|
||||
/* eslint-disable no-console */
|
||||
console.log(error)
|
||||
/* eslint-enable no-console */
|
||||
@ -15,8 +15,8 @@ export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | E
|
||||
|
||||
//TODO: Find better way to type check for error. In current setting you need to cast type to get the functions and variables from RequestError
|
||||
if (!(error instanceof RequestError)) {
|
||||
error = InternalServerError({ context: { exception: error.message }, stack: error.stack })
|
||||
getLogger('backend-main').log((<RequestError>error).levelName.toLowerCase(), (<RequestError>error).message)
|
||||
error = InternalServerError({ context: { exception: error.message }, stack: error.stack });
|
||||
(await getLogger('backend-main')).log((<RequestError>error).levelName.toLowerCase(), (<RequestError>error).message)
|
||||
}
|
||||
|
||||
//* Set Sentry user identification if req.user is populated
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_API_KEY
|
||||
} from '../variables';
|
||||
import { getChannelFromUserAgent } from '../utils/posthog';
|
||||
|
||||
declare module 'jsonwebtoken' {
|
||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||
@ -88,7 +89,10 @@ const requireAuth = ({
|
||||
|
||||
req.authData = {
|
||||
authMode,
|
||||
authPayload // User, ServiceAccount, ServiceTokenData
|
||||
authPayload, // User, ServiceAccount, ServiceTokenData
|
||||
authChannel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||
authIP: req.ip,
|
||||
authUserAgent: req.headers['user-agent'] ?? 'other'
|
||||
}
|
||||
|
||||
return next();
|
||||
|
@ -26,7 +26,7 @@ const requireMfaAuth = async (
|
||||
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
|
||||
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(AUTH_TOKEN_VALUE, getJwtMfaSecret())
|
||||
jwt.verify(AUTH_TOKEN_VALUE, await getJwtMfaSecret())
|
||||
);
|
||||
|
||||
const user = await User.findOne({
|
||||
|
@ -33,7 +33,7 @@ const requireServiceTokenAuth = async (
|
||||
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
|
||||
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(AUTH_TOKEN_VALUE, getJwtServiceSecret())
|
||||
jwt.verify(AUTH_TOKEN_VALUE, await getJwtServiceSecret())
|
||||
);
|
||||
|
||||
const serviceToken = await ServiceToken.findOne({
|
||||
|
@ -27,7 +27,7 @@ const requireSignupAuth = async (
|
||||
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
|
||||
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(AUTH_TOKEN_VALUE, getJwtSignupSecret())
|
||||
jwt.verify(AUTH_TOKEN_VALUE, await getJwtSignupSecret())
|
||||
);
|
||||
|
||||
const user = await User.findOne({
|
||||
|
@ -17,12 +17,14 @@ const requireWorkspaceAuth = ({
|
||||
acceptedRoles,
|
||||
locationWorkspaceId,
|
||||
locationEnvironment = undefined,
|
||||
requiredPermissions = []
|
||||
requiredPermissions = [],
|
||||
requireBlindIndicesEnabled = false
|
||||
}: {
|
||||
acceptedRoles: Array<'admin' | 'member'>;
|
||||
locationWorkspaceId: req;
|
||||
locationEnvironment?: req | undefined;
|
||||
requiredPermissions?: string[];
|
||||
requireBlindIndicesEnabled?: boolean;
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const workspaceId = req[locationWorkspaceId]?.workspaceId;
|
||||
@ -34,7 +36,8 @@ const requireWorkspaceAuth = ({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
requiredPermissions,
|
||||
requireBlindIndicesEnabled
|
||||
});
|
||||
|
||||
if (membership) {
|
||||
|
36
backend/src/models/folder.ts
Normal file
36
backend/src/models/folder.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Schema, Types, model } from 'mongoose';
|
||||
|
||||
const folderSchema = new Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Workspace',
|
||||
required: true,
|
||||
},
|
||||
environment: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parent: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Folder',
|
||||
required: false, // optional for root folders
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
parentPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
const Folder = model('Folder', folderSchema);
|
||||
|
||||
export default Folder;
|
@ -9,6 +9,7 @@ import Membership, { IMembership } from './membership';
|
||||
import MembershipOrg, { IMembershipOrg } from './membershipOrg';
|
||||
import Organization, { IOrganization } from './organization';
|
||||
import Secret, { ISecret } from './secret';
|
||||
import SecretBlindIndexData, { ISecretBlindIndexData } from './secretBlindIndexData';
|
||||
import ServiceToken, { IServiceToken } from './serviceToken';
|
||||
import ServiceAccount, { IServiceAccount } from './serviceAccount'; // new
|
||||
import ServiceAccountKey, { IServiceAccountKey } from './serviceAccountKey'; // new
|
||||
@ -45,6 +46,8 @@ export {
|
||||
IOrganization,
|
||||
Secret,
|
||||
ISecret,
|
||||
SecretBlindIndexData,
|
||||
ISecretBlindIndexData,
|
||||
ServiceToken,
|
||||
IServiceToken,
|
||||
ServiceAccount,
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
SECRET_SHARED,
|
||||
SECRET_PERSONAL,
|
||||
} from '../variables';
|
||||
import { ROOT_FOLDER_PATH } from '../utils/folder';
|
||||
|
||||
export interface ISecret {
|
||||
_id: Types.ObjectId;
|
||||
@ -11,6 +12,7 @@ export interface ISecret {
|
||||
type: string;
|
||||
user: Types.ObjectId;
|
||||
environment: string;
|
||||
secretBlindIndex?: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
@ -24,6 +26,8 @@ export interface ISecret {
|
||||
secretCommentTag?: string;
|
||||
secretCommentHash?: string;
|
||||
tags?: string[];
|
||||
path?: string;
|
||||
folder?: Types.ObjectId;
|
||||
}
|
||||
|
||||
const secretSchema = new Schema<ISecret>(
|
||||
@ -57,6 +61,10 @@ const secretSchema = new Schema<ISecret>(
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
secretBlindIndex: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
secretKeyCiphertext: {
|
||||
type: String,
|
||||
required: true
|
||||
@ -102,7 +110,18 @@ const secretSchema = new Schema<ISecret>(
|
||||
secretCommentHash: {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
// the full path to the secret in relation to folders
|
||||
path: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ROOT_FOLDER_PATH
|
||||
},
|
||||
folder: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Folder',
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
|
35
backend/src/models/secretBlindIndexData.ts
Normal file
35
backend/src/models/secretBlindIndexData.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Schema, model, Types, Document } from 'mongoose';
|
||||
|
||||
export interface ISecretBlindIndexData extends Document {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
encryptedSaltCiphertext: string;
|
||||
saltIV: string;
|
||||
saltTag: string;
|
||||
}
|
||||
|
||||
const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'Workspace',
|
||||
required: true
|
||||
},
|
||||
encryptedSaltCiphertext: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
saltIV: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
saltTag: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const SecretBlindIndexData = model<ISecretBlindIndexData>('SecretBlindIndexData', secretBlindIndexDataSchema);
|
||||
|
||||
export default SecretBlindIndexData;
|
@ -33,7 +33,8 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'User'
|
||||
ref: 'User',
|
||||
required: true
|
||||
},
|
||||
serviceAccount: {
|
||||
type: Schema.Types.ObjectId,
|
||||
|
@ -55,4 +55,4 @@ const workspaceSchema = new Schema<IWorkspace>({
|
||||
|
||||
const Workspace = model<IWorkspace>('Workspace', workspaceSchema);
|
||||
|
||||
export default Workspace;
|
||||
export default Workspace;
|
@ -5,11 +5,11 @@ const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/status',
|
||||
(req: Request, res: Response) => {
|
||||
async (req: Request, res: Response) => {
|
||||
res.status(200).json({
|
||||
date: new Date(),
|
||||
message: 'Ok',
|
||||
emailConfigured: getSmtpConfigured()
|
||||
emailConfigured: await getSmtpConfigured()
|
||||
})
|
||||
}
|
||||
);
|
||||
|
@ -15,6 +15,7 @@ import password from './password';
|
||||
import stripe from './stripe';
|
||||
import integration from './integration';
|
||||
import integrationAuth from './integrationAuth';
|
||||
import secretsFolder from './secretsFolder'
|
||||
|
||||
export {
|
||||
signup,
|
||||
@ -33,5 +34,6 @@ export {
|
||||
password,
|
||||
stripe,
|
||||
integration,
|
||||
integrationAuth
|
||||
integrationAuth,
|
||||
secretsFolder
|
||||
};
|
||||
|
@ -19,6 +19,7 @@ router.post(
|
||||
router.post(
|
||||
'/verify',
|
||||
body('email').exists().trim().notEmpty(),
|
||||
body('organizationId').exists().trim().notEmpty(),
|
||||
body('code').exists().trim().notEmpty(),
|
||||
validateRequest,
|
||||
membershipOrgController.verifyUserToOrganization
|
||||
|
40
backend/src/routes/v1/secretsFolder.ts
Normal file
40
backend/src/routes/v1/secretsFolder.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import express, { Request, Response } from 'express';
|
||||
const router = express.Router();
|
||||
import {
|
||||
requireAuth,
|
||||
requireWorkspaceAuth,
|
||||
validateRequest
|
||||
} from '../../middleware';
|
||||
import { body, param } from 'express-validator';
|
||||
import { createFolder, deleteFolder } from '../../controllers/v1/secretsFolderController';
|
||||
import { ADMIN, MEMBER } from '../../variables';
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'body'
|
||||
}),
|
||||
body('workspaceId').exists(),
|
||||
body('environment').exists(),
|
||||
body('folderName').exists(),
|
||||
body('parentFolderId'),
|
||||
validateRequest,
|
||||
createFolder
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:folderId',
|
||||
requireAuth({
|
||||
acceptedAuthModes: ['jwt']
|
||||
}),
|
||||
param('folderId').exists(),
|
||||
validateRequest,
|
||||
deleteFolder
|
||||
);
|
||||
|
||||
|
||||
export default router;
|
7
backend/src/routes/v3/index.ts
Normal file
7
backend/src/routes/v3/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import secrets from './secrets';
|
||||
import workspaces from './workspaces';
|
||||
|
||||
export {
|
||||
secrets,
|
||||
workspaces
|
||||
}
|
@ -1,8 +1,157 @@
|
||||
import express from 'express';
|
||||
const router = express.Router();
|
||||
import {
|
||||
requireAuth, validateRequest
|
||||
requireAuth,
|
||||
requireWorkspaceAuth,
|
||||
validateRequest
|
||||
} from '../../middleware';
|
||||
import { body } from 'express-validator';
|
||||
import { body, param, query } from 'express-validator';
|
||||
import { secretsController } from '../../controllers/v3';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
ADMIN,
|
||||
MEMBER,
|
||||
PERMISSION_WRITE_SECRETS,
|
||||
SECRET_SHARED,
|
||||
SECRET_PERSONAL,
|
||||
PERMISSION_READ_SECRETS
|
||||
} from '../../variables';
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
query('workspaceId').exists().isString().trim(),
|
||||
query('environment').exists().isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'query',
|
||||
locationEnvironment: 'query',
|
||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.getSecrets
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:secretName',
|
||||
body('workspaceId').exists().isString().trim(),
|
||||
body('environment').exists().isString().trim(),
|
||||
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
body('secretKeyCiphertext').exists().isString().trim(),
|
||||
body('secretKeyIV').exists().isString().trim(),
|
||||
body('secretKeyTag').exists().isString().trim(),
|
||||
body('secretValueCiphertext').exists().isString().trim(),
|
||||
body('secretValueIV').exists().isString().trim(),
|
||||
body('secretValueTag').exists().isString().trim(),
|
||||
body('secretCommentCiphertext').optional().isString().trim(),
|
||||
body('secretCommentIV').optional().isString().trim(),
|
||||
body('secretCommentTag').optional().isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'body',
|
||||
locationEnvironment: 'body',
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.createSecret
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:secretName',
|
||||
param('secretName').exists().isString().trim(),
|
||||
query('workspaceId').exists().isString().trim(),
|
||||
query('environment').exists().isString().trim(),
|
||||
query('type').optional().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'query',
|
||||
locationEnvironment: 'query',
|
||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.getSecretByName
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:secretName',
|
||||
param('secretName').exists().isString().trim(),
|
||||
body('workspaceId').exists().isString().trim(),
|
||||
body('environment').exists().isString().trim(),
|
||||
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
body('secretValueCiphertext').exists().isString().trim(),
|
||||
body('secretValueIV').exists().isString().trim(),
|
||||
body('secretValueTag').exists().isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'body',
|
||||
locationEnvironment: 'body',
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.updateSecretByName
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:secretName',
|
||||
param('secretName').exists().isString().trim(),
|
||||
body('workspaceId').exists().isString().trim(),
|
||||
body('environment').exists().isString().trim(),
|
||||
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AUTH_MODE_SERVICE_ACCOUNT
|
||||
]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: 'body',
|
||||
locationEnvironment: 'body',
|
||||
requiredPermissions: [PERMISSION_WRITE_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
}),
|
||||
secretsController.deleteSecretByName
|
||||
);
|
||||
|
||||
export default router;
|
80
backend/src/routes/v3/workspaces.ts
Normal file
80
backend/src/routes/v3/workspaces.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import express from 'express';
|
||||
const router = express.Router();
|
||||
import {
|
||||
requireAuth,
|
||||
requireWorkspaceAuth,
|
||||
validateRequest
|
||||
} from '../../middleware';
|
||||
import { workspacesController } from '../../controllers/v3';
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
ADMIN,
|
||||
PERMISSION_READ_SECRETS
|
||||
} from '../../variables';
|
||||
import { param, body, validationResult } from 'express-validator';
|
||||
|
||||
// -- migration to blind indices endpoints
|
||||
|
||||
router.get(
|
||||
'/:workspaceId/secrets/blind-index-status',
|
||||
param('workspaceId').exists().isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN],
|
||||
locationWorkspaceId: 'params',
|
||||
}),
|
||||
workspacesController.getWorkspaceBlindIndexStatus
|
||||
);
|
||||
|
||||
router.get( // allow admins to get all workspace secrets (part of blind indices migration)
|
||||
'/:workspaceId/secrets',
|
||||
param('workspaceId').exists().isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN],
|
||||
locationWorkspaceId: 'params',
|
||||
}),
|
||||
workspacesController.getWorkspaceSecrets
|
||||
);
|
||||
|
||||
router.post( // allow admins to name all workspace secrets (part of blind indices migration)
|
||||
'/:workspaceId/secrets/names',
|
||||
param('workspaceId').exists().isString().trim(),
|
||||
body('secretsToUpdate')
|
||||
.exists()
|
||||
.isArray()
|
||||
.withMessage('secretsToUpdate must be an array')
|
||||
.customSanitizer((value) => {
|
||||
return value.map((secret: any) => ({
|
||||
secretName: secret.secretName,
|
||||
_id: secret._id
|
||||
}));
|
||||
}),
|
||||
body('secretsToUpdate.*.secretName')
|
||||
.exists()
|
||||
.isString()
|
||||
.withMessage('secretName must be a string'),
|
||||
body('secretsToUpdate.*._id')
|
||||
.exists()
|
||||
.isString()
|
||||
.withMessage('secretId must be a string'),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN],
|
||||
locationWorkspaceId: 'params'
|
||||
}),
|
||||
workspacesController.nameWorkspaceSecrets
|
||||
);
|
||||
|
||||
// --
|
||||
|
||||
export default router;
|
@ -1,3 +1,4 @@
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
getSecretsHelper,
|
||||
encryptSymmetricHelper,
|
||||
@ -21,7 +22,7 @@ class BotService {
|
||||
workspaceId,
|
||||
environment
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
}) {
|
||||
return await getSecretsHelper({
|
||||
@ -41,7 +42,7 @@ class BotService {
|
||||
workspaceId,
|
||||
plaintext
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
plaintext: string;
|
||||
}) {
|
||||
return await encryptSymmetricHelper({
|
||||
@ -65,7 +66,7 @@ class BotService {
|
||||
iv,
|
||||
tag
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
ciphertext: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
|
@ -1,5 +1,3 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { getLogger } from '../utils/logger';
|
||||
import {
|
||||
initDatabaseHelper,
|
||||
closeDatabaseHelper
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Types } from 'mongoose';
|
||||
import { handleEventHelper } from '../helpers/event';
|
||||
|
||||
interface Event {
|
||||
name: string;
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
|
@ -52,9 +52,11 @@ class IntegrationService {
|
||||
* @param {Object} obj.workspaceId - id of workspace
|
||||
*/
|
||||
static async syncIntegrations({
|
||||
workspaceId
|
||||
workspaceId,
|
||||
environment
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
}) {
|
||||
return await syncIntegrationsHelper({
|
||||
workspaceId
|
||||
|
183
backend/src/services/SecretService.ts
Normal file
183
backend/src/services/SecretService.ts
Normal file
@ -0,0 +1,183 @@
|
||||
// WIP
|
||||
import { Types } from 'mongoose';
|
||||
import {
|
||||
ISecret
|
||||
} from '../models';
|
||||
import {
|
||||
CreateSecretParams,
|
||||
GetSecretsParams,
|
||||
GetSecretParams,
|
||||
UpdateSecretParams,
|
||||
DeleteSecretParams
|
||||
} from '../interfaces/services/SecretService';
|
||||
import {
|
||||
initSecretBlindIndexDataHelper,
|
||||
createSecretBlindIndexDataHelper,
|
||||
getSecretBlindIndexSaltHelper,
|
||||
generateSecretBlindIndexWithSaltHelper,
|
||||
generateSecretBlindIndexHelper,
|
||||
createSecretHelper,
|
||||
getSecretsHelper,
|
||||
getSecretHelper,
|
||||
updateSecretHelper,
|
||||
deleteSecretHelper
|
||||
} from '../helpers/secrets';
|
||||
|
||||
class SecretService {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param param0 h
|
||||
* @returns
|
||||
*/
|
||||
|
||||
static async initSecretBlindIndexDataHelper() {
|
||||
return await initSecretBlindIndexDataHelper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create secret blind index data containing encrypted blind index salt
|
||||
* for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {Buffer} obj.salt - 16-byte random salt
|
||||
* @param {Types.ObjectId} obj.workspaceId
|
||||
*/
|
||||
static async createSecretBlindIndexData({
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await createSecretBlindIndexDataHelper({
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secret blind index salt for workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to get salt for
|
||||
* @returns
|
||||
*/
|
||||
static async getSecretBlindIndexSalt({
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await getSecretBlindIndexSaltHelper({
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate blind index for secret with name [secretName]
|
||||
* and salt [salt]
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.secretName - name of secret to generate blind index for
|
||||
* @param {String} obj.salt - base64-salt
|
||||
*/
|
||||
static async generateSecretBlindIndexWithSalt({
|
||||
secretName,
|
||||
salt
|
||||
}: {
|
||||
secretName: string;
|
||||
salt: string;
|
||||
}) {
|
||||
return await generateSecretBlindIndexWithSaltHelper({
|
||||
secretName,
|
||||
salt
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return blind index for secret with
|
||||
* name [secretName] part of workspace with id [workspaceId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to generate blind index for
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
*/
|
||||
static async generateSecretBlindIndex({
|
||||
secretName,
|
||||
workspaceId,
|
||||
}: {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) {
|
||||
return await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to create
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace to create secret for
|
||||
* @param {String} obj.environment - environment in workspace to create secret for
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async createSecret(createSecretParams: CreateSecretParams) {
|
||||
return await createSecretHelper(createSecretParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secrets for workspace with id [workspaceId] and environment [environment]
|
||||
* @param {Object} obj
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.environment - environment in workspace
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async getSecrets(getSecretsParams: GetSecretsParams) {
|
||||
return await getSecretsHelper(getSecretsParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to get
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async getSecret(getSecretParams: GetSecretParams) {
|
||||
return await getSecretHelper(getSecretParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to update
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {String} obj.secretValueCiphertext - ciphertext of secret value
|
||||
* @param {String} obj.secretValueIV - IV of secret value
|
||||
* @param {String} obj.secretValueTag - tag of secret value
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async updateSecret(updateSecretParams: UpdateSecretParams) {
|
||||
return await updateSecretHelper(updateSecretParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete secret with name [secretName]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.secretName - name of secret to delete
|
||||
* @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
|
||||
* @param {String} obj.environment - environment in workspace that secret belongs to
|
||||
* @param {'shared' | 'personal'} obj.type - type of secret
|
||||
* @param {AuthData} obj.authData - authentication data on request
|
||||
* @returns
|
||||
*/
|
||||
static async deleteSecret(deleteSecretParams: DeleteSecretParams) {
|
||||
return await deleteSecretHelper(deleteSecretParams);
|
||||
}
|
||||
}
|
||||
|
||||
export default SecretService;
|
@ -1,5 +1,6 @@
|
||||
import { PostHog } from 'posthog-node';
|
||||
import { getLogger } from '../utils/logger';
|
||||
import { AuthData } from '../interfaces/middleware';
|
||||
import {
|
||||
getNodeEnv,
|
||||
getTelemetryEnabled,
|
||||
@ -11,9 +12,11 @@ import {
|
||||
User,
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
IServiceTokenData
|
||||
IServiceTokenData,
|
||||
ServiceTokenData
|
||||
} from '../models';
|
||||
import {
|
||||
AccountNotFoundError,
|
||||
BadRequestError
|
||||
} from '../utils/errors';
|
||||
|
||||
@ -21,9 +24,9 @@ class Telemetry {
|
||||
/**
|
||||
* Logs telemetry enable/disable notice.
|
||||
*/
|
||||
static logTelemetryMessage = () => {
|
||||
if(!getTelemetryEnabled()){
|
||||
getLogger("backend-main").info([
|
||||
static logTelemetryMessage = async () => {
|
||||
if(!(await getTelemetryEnabled())){
|
||||
(await getLogger("backend-main")).info([
|
||||
"",
|
||||
"To improve, Infisical collects telemetry data about general usage.",
|
||||
"This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.",
|
||||
@ -36,51 +39,42 @@ class Telemetry {
|
||||
* Return an instance of the PostHog client initialized.
|
||||
* @returns
|
||||
*/
|
||||
static getPostHogClient = () => {
|
||||
static getPostHogClient = async () => {
|
||||
let postHogClient: any;
|
||||
if (getNodeEnv() === 'production' && getTelemetryEnabled()) {
|
||||
if ((await getNodeEnv()) === 'production' && (await getTelemetryEnabled())) {
|
||||
// case: enable opt-out telemetry in production
|
||||
postHogClient = new PostHog(getPostHogProjectApiKey(), {
|
||||
host: getPostHogHost()
|
||||
postHogClient = new PostHog(await getPostHogProjectApiKey(), {
|
||||
host: await getPostHogHost()
|
||||
});
|
||||
}
|
||||
|
||||
return postHogClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a distinct id for client to be used for logging telemetry
|
||||
*/
|
||||
static getDistinctId = ({
|
||||
user,
|
||||
serviceAccount,
|
||||
serviceTokenData
|
||||
static getDistinctId = async ({
|
||||
authData
|
||||
}: {
|
||||
user?: IUser;
|
||||
serviceAccount?: IServiceAccount;
|
||||
serviceTokenData?: any; // TODO: fix (it's ServiceTokenData with user populated)
|
||||
authData: AuthData;
|
||||
}) => {
|
||||
let distinctId = '';
|
||||
|
||||
if (user) {
|
||||
distinctId = user.email;
|
||||
if (authData.authPayload instanceof User) {
|
||||
distinctId = authData.authPayload.email;
|
||||
} else if (authData.authPayload instanceof ServiceAccount) {
|
||||
distinctId = `sa.${authData.authPayload._id.toString()}`;
|
||||
} else if (authData.authPayload instanceof ServiceTokenData) {
|
||||
|
||||
if (authData.authPayload.user) {
|
||||
const user = await User.findById(authData.authPayload.user, 'email');
|
||||
if (!user) throw AccountNotFoundError();
|
||||
distinctId = user.email;
|
||||
} else if (authData.authPayload.serviceAccount) {
|
||||
distinctId = distinctId = `sa.${authData.authPayload.serviceAccount.toString()}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (serviceAccount) {
|
||||
distinctId = `sa.${serviceAccount._id.toString()}`;
|
||||
}
|
||||
|
||||
if (serviceTokenData?.user && serviceTokenData?.user instanceof User) {
|
||||
distinctId = serviceTokenData.user.email;
|
||||
} else if (serviceTokenData?.serviceAccount && serviceTokenData?.serviceAccount instanceof ServiceAccount) {
|
||||
distinctId = `sa.${serviceTokenData.serviceAccount._id.toString()}`;
|
||||
}
|
||||
|
||||
if (distinctId === '') {
|
||||
throw BadRequestError({
|
||||
message: 'Failed to obtain distinct id for logging telemetry'
|
||||
});
|
||||
}
|
||||
if (distinctId === '') throw BadRequestError({
|
||||
message: 'Failed to obtain distinct id for logging telemetry'
|
||||
});
|
||||
|
||||
return distinctId;
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ import { createTerminus } from '@godaddy/terminus';
|
||||
import { getLogger } from '../utils/logger';
|
||||
|
||||
export const setUpHealthEndpoint = <T>(server: T) => {
|
||||
const onSignal = () => {
|
||||
getLogger('backend-main').info('Server is starting clean-up');
|
||||
const onSignal = async () => {
|
||||
(await getLogger('backend-main')).info('Server is starting clean-up');
|
||||
return Promise.all([
|
||||
new Promise((resolve) => {
|
||||
if (mongoose.connection && mongoose.connection.readyState == 1) {
|
||||
|
@ -5,14 +5,14 @@ import BotService from './BotService';
|
||||
import EventService from './EventService';
|
||||
import IntegrationService from './IntegrationService';
|
||||
import TokenService from './TokenService';
|
||||
import SecretService from './SecretService';
|
||||
|
||||
export {
|
||||
TelemetryService,
|
||||
// logTelemetryMessage,
|
||||
// getPostHogClient,
|
||||
DatabaseService,
|
||||
BotService,
|
||||
EventService,
|
||||
IntegrationService,
|
||||
TokenService
|
||||
TokenService,
|
||||
SecretService
|
||||
}
|
@ -15,21 +15,21 @@ import {
|
||||
getSmtpPort
|
||||
} from '../config';
|
||||
|
||||
export const initSmtp = () => {
|
||||
export const initSmtp = async () => {
|
||||
const mailOpts: SMTPConnection.Options = {
|
||||
host: getSmtpHost(),
|
||||
port: getSmtpPort()
|
||||
host: await getSmtpHost(),
|
||||
port: await getSmtpPort()
|
||||
};
|
||||
|
||||
if (getSmtpUsername() && getSmtpPassword()) {
|
||||
if ((await getSmtpUsername()) && (await getSmtpPassword())) {
|
||||
mailOpts.auth = {
|
||||
user: getSmtpUsername(),
|
||||
pass: getSmtpPassword()
|
||||
user: await getSmtpUsername(),
|
||||
pass: await getSmtpPassword()
|
||||
};
|
||||
}
|
||||
|
||||
if (getSmtpSecure() ? getSmtpSecure() : false) {
|
||||
switch (getSmtpHost()) {
|
||||
if ((await getSmtpSecure()) ? (await getSmtpSecure()) : false) {
|
||||
switch (await getSmtpHost()) {
|
||||
case SMTP_HOST_SENDGRID:
|
||||
mailOpts.requireTLS = true;
|
||||
break;
|
||||
@ -52,7 +52,7 @@ export const initSmtp = () => {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (getSmtpHost().includes('amazonaws.com')) {
|
||||
if ((await getSmtpHost()).includes('amazonaws.com')) {
|
||||
mailOpts.tls = {
|
||||
ciphers: 'TLSv1.2'
|
||||
}
|
||||
@ -70,10 +70,10 @@ export const initSmtp = () => {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureMessage('SMTP - Successfully connected');
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(async (err) => {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(
|
||||
`SMTP - Failed to connect to ${getSmtpHost()}:${getSmtpPort()} \n\t${err}`
|
||||
`SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()} \n\t${err}`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
<body>
|
||||
<h2>Join your organization on Infisical</h2>
|
||||
<p>{{inviterFirstName}} ({{inviterEmail}}) has invited you to their Infisical organization — {{organizationName}}</p>
|
||||
<a href="{{callback_url}}?token={{token}}&to={{email}}">Join now</a>
|
||||
<a href="{{callback_url}}?token={{token}}&to={{email}}&organization_id={{organizationId}}">Join now</a>
|
||||
<h3>What is Infisical?</h3>
|
||||
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
|
||||
</body>
|
||||
|
8
backend/src/types/express/index.d.ts
vendored
8
backend/src/types/express/index.d.ts
vendored
@ -5,6 +5,9 @@ import {
|
||||
IServiceTokenData,
|
||||
ISecret
|
||||
} from '../../models';
|
||||
import {
|
||||
AuthData
|
||||
} from '../../interfaces/middleware';
|
||||
|
||||
// TODO: fix (any) types
|
||||
declare global {
|
||||
@ -29,10 +32,7 @@ declare global {
|
||||
serviceTokenData: any;
|
||||
apiKeyData: any;
|
||||
query?: any;
|
||||
authData: {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
};
|
||||
authData: AuthData;
|
||||
requestData: {
|
||||
[key: string]: string
|
||||
};
|
||||
|
1
backend/src/types/secret/index.d.ts
vendored
1
backend/src/types/secret/index.d.ts
vendored
@ -24,6 +24,7 @@ export interface BatchSecretRequest {
|
||||
export interface BatchSecret {
|
||||
_id: string;
|
||||
type: 'shared' | 'personal',
|
||||
secretBlindIndex: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
|
@ -19,7 +19,7 @@ export const testWorkspaceKeyId = "63cf48f0225e6955acec5eff"
|
||||
export const plainTextWorkspaceKey = "543fef8224813a46230b0a50a46c5fb2"
|
||||
|
||||
export const createTestUserForDevelopment = async () => {
|
||||
if (getNodeEnv() === "development" || getNodeEnv() === "test") {
|
||||
if ((await getNodeEnv()) === "development" || (await getNodeEnv()) === "test") {
|
||||
const testUser = {
|
||||
_id: testUserId,
|
||||
email: testUserEmail,
|
||||
|
54
backend/src/utils/auth.ts
Normal file
54
backend/src/utils/auth.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { AuthData } from '../interfaces/middleware';
|
||||
import {
|
||||
User,
|
||||
ServiceAccount,
|
||||
ServiceTokenData,
|
||||
ServiceToken
|
||||
} from '../models';
|
||||
|
||||
// TODO: find a more optimal folder structure to store these types of functions
|
||||
|
||||
/**
|
||||
* Returns an object containing the id of the authentication data payload
|
||||
* @param {AuthData} authData - authentication data object
|
||||
* @returns
|
||||
*/
|
||||
const getAuthDataPayloadIdObj = (authData: AuthData) => {
|
||||
if (authData.authPayload instanceof User) {
|
||||
return { userId: authData.authPayload._id };
|
||||
}
|
||||
|
||||
if (authData.authPayload instanceof ServiceAccount) {
|
||||
return { serviceAccountId: authData.authPayload._id };
|
||||
}
|
||||
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
return { serviceTokenDataId: authData.authPayload._id };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object containing the user associated with the authentication data payload
|
||||
* @param {AuthData} authData - authentication data object
|
||||
* @returns
|
||||
*/
|
||||
const getAuthDataPayloadUserObj = (authData: AuthData) => {
|
||||
|
||||
if (authData.authPayload instanceof User) {
|
||||
return { user: authData.authPayload._id };
|
||||
}
|
||||
|
||||
if (authData.authPayload instanceof ServiceAccount) {
|
||||
return { user: authData.authPayload.user };
|
||||
}
|
||||
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
return { user: authData.authPayload.user };
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getAuthDataPayloadIdObj,
|
||||
getAuthDataPayloadUserObj
|
||||
}
|
@ -153,6 +153,16 @@ export const SecretNotFoundError = (error?: Partial<RequestErrorContext>) => new
|
||||
stack: error?.stack
|
||||
});
|
||||
|
||||
//* ----->[SECRET BLIND INDEX DATA ERRORS]<-----
|
||||
export const SecretBlindIndexDataNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||
statusCode: error?.statusCode ?? 404,
|
||||
type: error?.type ?? 'secret_blind_index_data_not_found_error',
|
||||
message: error?.message ?? 'The requested secret was not found',
|
||||
context: error?.context,
|
||||
stack: error?.stack
|
||||
});
|
||||
|
||||
//* ----->[SECRET SNAPSHOT ERRORS]<-----
|
||||
export const SecretSnapshotNotFoundError = (error?: Partial<RequestErrorContext>) => new RequestError({
|
||||
logLevel: error?.logLevel ?? LogLevel.ERROR,
|
||||
|
87
backend/src/utils/folder.ts
Normal file
87
backend/src/utils/folder.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import Folder from "../models/folder";
|
||||
|
||||
export const ROOT_FOLDER_PATH = "/"
|
||||
|
||||
export const getFolderPath = async (folderId: string) => {
|
||||
let currentFolder = await Folder.findById(folderId);
|
||||
const pathSegments = [];
|
||||
|
||||
while (currentFolder) {
|
||||
pathSegments.unshift(currentFolder.name);
|
||||
currentFolder = currentFolder.parent ? await Folder.findById(currentFolder.parent) : null;
|
||||
}
|
||||
|
||||
return '/' + pathSegments.join('/');
|
||||
};
|
||||
|
||||
/**
|
||||
Returns the folder ID associated with the specified secret path in the given workspace and environment.
|
||||
@param workspaceId - The ID of the workspace to search in.
|
||||
@param environment - The environment to search in.
|
||||
@param secretPath - The secret path to search for.
|
||||
@returns The folder ID associated with the specified secret path, or undefined if the path is at the root folder level.
|
||||
@throws Error if the specified secret path is not found.
|
||||
*/
|
||||
export const getFolderIdFromPath = async (workspaceId: string, environment: string, secretPath: string) => {
|
||||
const secretPathParts = secretPath.split("/").filter(path => path != "")
|
||||
if (secretPathParts.length <= 1) {
|
||||
return undefined // root folder, so no folder id
|
||||
}
|
||||
|
||||
const folderId = await Folder.find({ path: secretPath, workspace: workspaceId, environment: environment })
|
||||
if (!folderId) {
|
||||
throw Error("Secret path not found")
|
||||
}
|
||||
|
||||
return folderId
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up a path by removing empty parts, duplicate slashes,
|
||||
* and ensuring it starts with ROOT_FOLDER_PATH.
|
||||
* @param path - The input path to clean up.
|
||||
* @returns The cleaned-up path string.
|
||||
*/
|
||||
export const normalizePath = (path: string) => {
|
||||
if (path == undefined || path == "" || path == ROOT_FOLDER_PATH) {
|
||||
return ROOT_FOLDER_PATH
|
||||
}
|
||||
|
||||
const pathParts = path.split("/").filter(part => part != "")
|
||||
const cleanPathString = ROOT_FOLDER_PATH + pathParts.join("/")
|
||||
|
||||
return cleanPathString
|
||||
}
|
||||
|
||||
export const getFoldersInDirectory = async (workspaceId: string, environment: string, pathString: string) => {
|
||||
const normalizedPath = normalizePath(pathString)
|
||||
const foldersInDirectory = await Folder.find({
|
||||
workspace: workspaceId,
|
||||
environment: environment,
|
||||
parentPath: normalizedPath,
|
||||
});
|
||||
|
||||
return foldersInDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent path of the given path.
|
||||
* @param path - The input path.
|
||||
* @returns The parent path string.
|
||||
*/
|
||||
export const getParentPath = (path: string) => {
|
||||
const normalizedPath = normalizePath(path);
|
||||
const folderParts = normalizedPath.split('/').filter(part => part !== '');
|
||||
|
||||
let folderParent = ROOT_FOLDER_PATH;
|
||||
if (folderParts.length > 1) {
|
||||
folderParent = ROOT_FOLDER_PATH + folderParts.slice(0, folderParts.length - 1).join('/');
|
||||
}
|
||||
|
||||
return folderParent;
|
||||
}
|
||||
|
||||
export const validateFolderName = (folderName: string) => {
|
||||
const validNameRegex = /^[a-zA-Z0-9-_]+$/;
|
||||
return validNameRegex.test(folderName);
|
||||
}
|
@ -12,7 +12,7 @@ const logFormat = (prefix: string) => combine(
|
||||
printf((info) => `${info.timestamp} ${info.label} ${info.level}: ${info.message}`)
|
||||
);
|
||||
|
||||
const createLoggerWithLabel = (level: string, label: string) => {
|
||||
const createLoggerWithLabel = async (level: string, label: string) => {
|
||||
const _level = level.toLowerCase() || 'info'
|
||||
//* Always add Console output to transports
|
||||
const _transports: any[] = [
|
||||
@ -25,10 +25,10 @@ const createLoggerWithLabel = (level: string, label: string) => {
|
||||
})
|
||||
]
|
||||
//* Add LokiTransport if it's enabled
|
||||
if(getLokiHost() !== undefined){
|
||||
if((await getLokiHost()) !== undefined){
|
||||
_transports.push(
|
||||
new LokiTransport({
|
||||
host: getLokiHost(),
|
||||
host: await getLokiHost(),
|
||||
handleExceptions: true,
|
||||
handleRejections: true,
|
||||
batching: true,
|
||||
@ -40,7 +40,7 @@ const createLoggerWithLabel = (level: string, label: string) => {
|
||||
labels: {
|
||||
app: process.env.npm_package_name,
|
||||
version: process.env.npm_package_version,
|
||||
environment: getNodeEnv()
|
||||
environment: await getNodeEnv()
|
||||
},
|
||||
onConnectionError: (err: Error)=> console.error('Connection error while connecting to Loki Server.\n', err)
|
||||
})
|
||||
@ -58,12 +58,10 @@ const createLoggerWithLabel = (level: string, label: string) => {
|
||||
});
|
||||
}
|
||||
|
||||
const DEFAULT_LOGGERS = {
|
||||
"backend-main": createLoggerWithLabel('info', '[IFSC:backend-main]'),
|
||||
"database": createLoggerWithLabel('info', '[IFSC:database]'),
|
||||
}
|
||||
type LoggerNames = keyof typeof DEFAULT_LOGGERS
|
||||
|
||||
export const getLogger = (loggerName: LoggerNames) => {
|
||||
return DEFAULT_LOGGERS[loggerName]
|
||||
export const getLogger = async (loggerName: 'backend-main' | 'database') => {
|
||||
const logger = {
|
||||
"backend-main": await createLoggerWithLabel('info', '[IFSC:backend-main]'),
|
||||
"database": await createLoggerWithLabel('info', '[IFSC:database]'),
|
||||
}
|
||||
return logger[loggerName]
|
||||
}
|
||||
|
@ -81,13 +81,13 @@ export default class RequestError extends Error{
|
||||
return obj
|
||||
}
|
||||
|
||||
public format(req: Request){
|
||||
public async format(req: Request){
|
||||
let _context = Object.assign({
|
||||
stacktrace: this.stacktrace
|
||||
}, this.context)
|
||||
|
||||
//* Omit sensitive information from context that can leak internal workings of this program if user is not developer
|
||||
if(!getVerboseErrorOutput()){
|
||||
if(!(await getVerboseErrorOutput())){
|
||||
_context = this._omit(_context, [
|
||||
'stacktrace',
|
||||
'exception',
|
||||
|
@ -61,7 +61,7 @@ const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
|
||||
const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
|
||||
const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com';
|
||||
|
||||
const getIntegrationOptions = () => {
|
||||
const getIntegrationOptions = async () => {
|
||||
const INTEGRATION_OPTIONS = [
|
||||
{
|
||||
name: 'Heroku',
|
||||
@ -69,7 +69,7 @@ const getIntegrationOptions = () => {
|
||||
image: 'Heroku.png',
|
||||
isAvailable: true,
|
||||
type: 'oauth',
|
||||
clientId: getClientIdHeroku(),
|
||||
clientId: await getClientIdHeroku(),
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
@ -79,7 +79,7 @@ const getIntegrationOptions = () => {
|
||||
isAvailable: true,
|
||||
type: 'oauth',
|
||||
clientId: '',
|
||||
clientSlug: getClientSlugVercel(),
|
||||
clientSlug: await getClientSlugVercel(),
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
@ -88,7 +88,7 @@ const getIntegrationOptions = () => {
|
||||
image: 'Netlify.png',
|
||||
isAvailable: true,
|
||||
type: 'oauth',
|
||||
clientId: getClientIdNetlify(),
|
||||
clientId: await getClientIdNetlify(),
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
@ -97,7 +97,7 @@ const getIntegrationOptions = () => {
|
||||
image: 'GitHub.png',
|
||||
isAvailable: true,
|
||||
type: 'oauth',
|
||||
clientId: getClientIdGitHub(),
|
||||
clientId: await getClientIdGitHub(),
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
@ -151,7 +151,7 @@ const getIntegrationOptions = () => {
|
||||
image: 'Microsoft Azure.png',
|
||||
isAvailable: true,
|
||||
type: 'oauth',
|
||||
clientId: getClientIdAzure(),
|
||||
clientId: await getClientIdAzure(),
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
@ -169,7 +169,7 @@ const getIntegrationOptions = () => {
|
||||
image: 'GitLab.png',
|
||||
isAvailable: true,
|
||||
type: 'custom',
|
||||
clientId: getClientIdGitLab(),
|
||||
clientId: await getClientIdGitLab(),
|
||||
docsLink: ''
|
||||
},
|
||||
{
|
||||
|
0
backend/src/variables/workspace.ts
Normal file
0
backend/src/variables/workspace.ts
Normal file
@ -220,7 +220,7 @@ const generateOpenAPISpec = async () => {
|
||||
|
||||
const outputJSONFile = '../spec.json';
|
||||
const outputYAMLFile = '../docs/spec.yaml';
|
||||
const endpointsFiles = ['../src/app.ts'];
|
||||
const endpointsFiles = ['../src/index.ts'];
|
||||
|
||||
const spec = await swaggerAutogen(outputJSONFile, endpointsFiles, doc);
|
||||
await fs.writeFile(outputYAMLFile, yaml.dump(spec.data));
|
||||
|
@ -1,408 +1,408 @@
|
||||
import request from 'supertest'
|
||||
import main from '../../../../src/index'
|
||||
import { testWorkspaceId } from '../../../../src/utils/addDevelopmentUser';
|
||||
import { deleteAllSecrets, getAllSecrets, getJWTFromTestUser, getServiceTokenFromTestUser } from '../../../helper/helper';
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const batchSecretRequestWithNoOverride = require('../../../data/batch-secrets-no-override.json');
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const batchSecretRequestWithOverrides = require('../../../data/batch-secrets-with-overrides.json');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const batchSecretRequestWithBadRequest = require('../../../data/batch-create-secrets-with-some-missing-params.json');
|
||||
|
||||
let server: any;
|
||||
beforeAll(async () => {
|
||||
server = await main;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
describe("GET /api/v2/secrets", () => {
|
||||
describe("Get secrets via JTW", () => {
|
||||
test("should create secrets and read secrets via jwt", async () => {
|
||||
try {
|
||||
// get login details
|
||||
const loginResponse = await getJWTFromTestUser()
|
||||
|
||||
// create creates
|
||||
const createSecretsResponse = await request(server)
|
||||
.post("/api/v2/secrets/batch")
|
||||
.set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
.send({
|
||||
workspaceId: testWorkspaceId,
|
||||
environment: "dev",
|
||||
requests: batchSecretRequestWithNoOverride
|
||||
})
|
||||
|
||||
expect(createSecretsResponse.statusCode).toBe(200)
|
||||
|
||||
|
||||
const getSecrets = await request(server)
|
||||
.get("/api/v2/secrets")
|
||||
.set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
.query({
|
||||
workspaceId: testWorkspaceId,
|
||||
environment: "dev"
|
||||
})
|
||||
|
||||
expect(getSecrets.statusCode).toBe(200)
|
||||
expect(getSecrets.body).toHaveProperty("secrets")
|
||||
expect(getSecrets.body.secrets).toHaveLength(3)
|
||||
expect(getSecrets.body.secrets).toBeInstanceOf(Array);
|
||||
|
||||
getSecrets.body.secrets.forEach((secret: any) => {
|
||||
expect(secret).toHaveProperty('_id');
|
||||
expect(secret._id).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('version');
|
||||
expect(secret.version).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('workspace');
|
||||
expect(secret.workspace).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('type');
|
||||
expect(secret.type).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('tags');
|
||||
expect(secret.tags).toHaveLength(0);
|
||||
|
||||
expect(secret).toHaveProperty('environment');
|
||||
expect(secret.environment).toEqual("dev");
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyCiphertext');
|
||||
expect(secret.secretKeyCiphertext).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyIV');
|
||||
expect(secret.secretKeyIV).toBeTruthy();
|
||||
// import request from 'supertest'
|
||||
// import main from '../../../../src/index'
|
||||
// import { testWorkspaceId } from '../../../../src/utils/addDevelopmentUser';
|
||||
// import { deleteAllSecrets, getAllSecrets, getJWTFromTestUser, getServiceTokenFromTestUser } from '../../../helper/helper';
|
||||
// // eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// const batchSecretRequestWithNoOverride = require('../../../data/batch-secrets-no-override.json');
|
||||
// // eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// const batchSecretRequestWithOverrides = require('../../../data/batch-secrets-with-overrides.json');
|
||||
|
||||
// // eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
// const batchSecretRequestWithBadRequest = require('../../../data/batch-create-secrets-with-some-missing-params.json');
|
||||
|
||||
// let server: any;
|
||||
// beforeAll(async () => {
|
||||
// server = await main;
|
||||
// });
|
||||
|
||||
// afterAll(async () => {
|
||||
// server.close();
|
||||
// });
|
||||
|
||||
// describe("GET /api/v2/secrets", () => {
|
||||
// describe("Get secrets via JTW", () => {
|
||||
// test("should create secrets and read secrets via jwt", async () => {
|
||||
// try {
|
||||
// // get login details
|
||||
// const loginResponse = await getJWTFromTestUser()
|
||||
|
||||
// // create creates
|
||||
// const createSecretsResponse = await request(server)
|
||||
// .post("/api/v2/secrets/batch")
|
||||
// .set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
// .send({
|
||||
// workspaceId: testWorkspaceId,
|
||||
// environment: "dev",
|
||||
// requests: batchSecretRequestWithNoOverride
|
||||
// })
|
||||
|
||||
// expect(createSecretsResponse.statusCode).toBe(200)
|
||||
|
||||
|
||||
// const getSecrets = await request(server)
|
||||
// .get("/api/v2/secrets")
|
||||
// .set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
// .query({
|
||||
// workspaceId: testWorkspaceId,
|
||||
// environment: "dev"
|
||||
// })
|
||||
|
||||
// expect(getSecrets.statusCode).toBe(200)
|
||||
// expect(getSecrets.body).toHaveProperty("secrets")
|
||||
// expect(getSecrets.body.secrets).toHaveLength(3)
|
||||
// expect(getSecrets.body.secrets).toBeInstanceOf(Array);
|
||||
|
||||
// getSecrets.body.secrets.forEach((secret: any) => {
|
||||
// expect(secret).toHaveProperty('_id');
|
||||
// expect(secret._id).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('version');
|
||||
// expect(secret.version).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('workspace');
|
||||
// expect(secret.workspace).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('type');
|
||||
// expect(secret.type).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('tags');
|
||||
// expect(secret.tags).toHaveLength(0);
|
||||
|
||||
// expect(secret).toHaveProperty('environment');
|
||||
// expect(secret.environment).toEqual("dev");
|
||||
|
||||
// expect(secret).toHaveProperty('secretKeyCiphertext');
|
||||
// expect(secret.secretKeyCiphertext).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('secretKeyIV');
|
||||
// expect(secret.secretKeyIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyTag');
|
||||
expect(secret.secretKeyTag).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretKeyTag');
|
||||
// expect(secret.secretKeyTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueCiphertext');
|
||||
expect(secret.secretValueCiphertext).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretValueCiphertext');
|
||||
// expect(secret.secretValueCiphertext).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueIV');
|
||||
expect(secret.secretValueIV).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretValueIV');
|
||||
// expect(secret.secretValueIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueTag');
|
||||
expect(secret.secretValueTag).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretValueTag');
|
||||
// expect(secret.secretValueTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentCiphertext');
|
||||
expect(secret.secretCommentCiphertext).toBeFalsy();
|
||||
// expect(secret).toHaveProperty('secretCommentCiphertext');
|
||||
// expect(secret.secretCommentCiphertext).toBeFalsy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentIV');
|
||||
expect(secret.secretCommentIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentTag');
|
||||
expect(secret.secretCommentTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('createdAt');
|
||||
expect(secret.createdAt).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('updatedAt');
|
||||
expect(secret.updatedAt).toBeTruthy();
|
||||
});
|
||||
} finally {
|
||||
// clean up
|
||||
await deleteAllSecrets()
|
||||
}
|
||||
})
|
||||
|
||||
test("Get secrets via jwt when personal overrides exist", async () => {
|
||||
try {
|
||||
// get login details
|
||||
const loginResponse = await getJWTFromTestUser()
|
||||
|
||||
// create creates
|
||||
const createSecretsResponse = await request(server)
|
||||
.post("/api/v2/secrets/batch")
|
||||
.set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
.send({
|
||||
workspaceId: testWorkspaceId,
|
||||
environment: "dev",
|
||||
requests: batchSecretRequestWithOverrides
|
||||
})
|
||||
|
||||
expect(createSecretsResponse.statusCode).toBe(200)
|
||||
|
||||
const getSecrets = await request(server)
|
||||
.get("/api/v2/secrets")
|
||||
.set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
.query({
|
||||
workspaceId: testWorkspaceId,
|
||||
environment: "dev"
|
||||
})
|
||||
// expect(secret).toHaveProperty('secretCommentIV');
|
||||
// expect(secret.secretCommentIV).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('secretCommentTag');
|
||||
// expect(secret.secretCommentTag).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('createdAt');
|
||||
// expect(secret.createdAt).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('updatedAt');
|
||||
// expect(secret.updatedAt).toBeTruthy();
|
||||
// });
|
||||
// } finally {
|
||||
// // clean up
|
||||
// await deleteAllSecrets()
|
||||
// }
|
||||
// })
|
||||
|
||||
// test("Get secrets via jwt when personal overrides exist", async () => {
|
||||
// try {
|
||||
// // get login details
|
||||
// const loginResponse = await getJWTFromTestUser()
|
||||
|
||||
// // create creates
|
||||
// const createSecretsResponse = await request(server)
|
||||
// .post("/api/v2/secrets/batch")
|
||||
// .set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
// .send({
|
||||
// workspaceId: testWorkspaceId,
|
||||
// environment: "dev",
|
||||
// requests: batchSecretRequestWithOverrides
|
||||
// })
|
||||
|
||||
// expect(createSecretsResponse.statusCode).toBe(200)
|
||||
|
||||
// const getSecrets = await request(server)
|
||||
// .get("/api/v2/secrets")
|
||||
// .set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
// .query({
|
||||
// workspaceId: testWorkspaceId,
|
||||
// environment: "dev"
|
||||
// })
|
||||
|
||||
expect(getSecrets.statusCode).toBe(200)
|
||||
expect(getSecrets.body).toHaveProperty("secrets")
|
||||
expect(getSecrets.body.secrets).toHaveLength(2)
|
||||
expect(getSecrets.body.secrets).toBeInstanceOf(Array);
|
||||
|
||||
getSecrets.body.secrets.forEach((secret: any) => {
|
||||
expect(secret).toHaveProperty('_id');
|
||||
expect(secret._id).toBeTruthy();
|
||||
// expect(getSecrets.statusCode).toBe(200)
|
||||
// expect(getSecrets.body).toHaveProperty("secrets")
|
||||
// expect(getSecrets.body.secrets).toHaveLength(2)
|
||||
// expect(getSecrets.body.secrets).toBeInstanceOf(Array);
|
||||
|
||||
// getSecrets.body.secrets.forEach((secret: any) => {
|
||||
// expect(secret).toHaveProperty('_id');
|
||||
// expect(secret._id).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('version');
|
||||
expect(secret.version).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('version');
|
||||
// expect(secret.version).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('workspace');
|
||||
expect(secret.workspace).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('workspace');
|
||||
// expect(secret.workspace).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('type');
|
||||
expect(secret.type).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('type');
|
||||
// expect(secret.type).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('tags');
|
||||
expect(secret.tags).toHaveLength(0);
|
||||
// expect(secret).toHaveProperty('tags');
|
||||
// expect(secret.tags).toHaveLength(0);
|
||||
|
||||
expect(secret).toHaveProperty('environment');
|
||||
expect(secret.environment).toEqual("dev");
|
||||
// expect(secret).toHaveProperty('environment');
|
||||
// expect(secret.environment).toEqual("dev");
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyCiphertext');
|
||||
expect(secret.secretKeyCiphertext).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretKeyCiphertext');
|
||||
// expect(secret.secretKeyCiphertext).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyIV');
|
||||
expect(secret.secretKeyIV).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretKeyIV');
|
||||
// expect(secret.secretKeyIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyTag');
|
||||
expect(secret.secretKeyTag).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretKeyTag');
|
||||
// expect(secret.secretKeyTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueCiphertext');
|
||||
expect(secret.secretValueCiphertext).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretValueCiphertext');
|
||||
// expect(secret.secretValueCiphertext).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueIV');
|
||||
expect(secret.secretValueIV).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretValueIV');
|
||||
// expect(secret.secretValueIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueTag');
|
||||
expect(secret.secretValueTag).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretValueTag');
|
||||
// expect(secret.secretValueTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentCiphertext');
|
||||
expect(secret.secretCommentCiphertext).toBeFalsy();
|
||||
// expect(secret).toHaveProperty('secretCommentCiphertext');
|
||||
// expect(secret.secretCommentCiphertext).toBeFalsy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentIV');
|
||||
expect(secret.secretCommentIV).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretCommentIV');
|
||||
// expect(secret.secretCommentIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentTag');
|
||||
expect(secret.secretCommentTag).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretCommentTag');
|
||||
// expect(secret.secretCommentTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('createdAt');
|
||||
expect(secret.createdAt).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('createdAt');
|
||||
// expect(secret.createdAt).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('updatedAt');
|
||||
expect(secret.updatedAt).toBeTruthy();
|
||||
});
|
||||
} finally {
|
||||
// clean up
|
||||
await deleteAllSecrets()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetch secrets via service token", () => {
|
||||
test("Get secrets via jwt when personal overrides exist", async () => {
|
||||
try {
|
||||
// get login details
|
||||
const loginResponse = await getJWTFromTestUser()
|
||||
|
||||
// create creates
|
||||
const createSecretsResponse = await request(server)
|
||||
.post("/api/v2/secrets/batch")
|
||||
.set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
.send({
|
||||
workspaceId: testWorkspaceId,
|
||||
environment: "dev",
|
||||
requests: batchSecretRequestWithOverrides
|
||||
})
|
||||
// expect(secret).toHaveProperty('updatedAt');
|
||||
// expect(secret.updatedAt).toBeTruthy();
|
||||
// });
|
||||
// } finally {
|
||||
// // clean up
|
||||
// await deleteAllSecrets()
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe("fetch secrets via service token", () => {
|
||||
// test("Get secrets via jwt when personal overrides exist", async () => {
|
||||
// try {
|
||||
// // get login details
|
||||
// const loginResponse = await getJWTFromTestUser()
|
||||
|
||||
// // create creates
|
||||
// const createSecretsResponse = await request(server)
|
||||
// .post("/api/v2/secrets/batch")
|
||||
// .set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
// .send({
|
||||
// workspaceId: testWorkspaceId,
|
||||
// environment: "dev",
|
||||
// requests: batchSecretRequestWithOverrides
|
||||
// })
|
||||
|
||||
expect(createSecretsResponse.statusCode).toBe(200)
|
||||
|
||||
// now use the service token to fetch secrets
|
||||
const serviceToken = await getServiceTokenFromTestUser()
|
||||
// expect(createSecretsResponse.statusCode).toBe(200)
|
||||
|
||||
// // now use the service token to fetch secrets
|
||||
// const serviceToken = await getServiceTokenFromTestUser()
|
||||
|
||||
const getSecrets = await request(server)
|
||||
.get("/api/v2/secrets")
|
||||
.set('Authorization', `Bearer ${serviceToken}`)
|
||||
.query({
|
||||
workspaceId: testWorkspaceId,
|
||||
environment: "dev"
|
||||
})
|
||||
|
||||
expect(getSecrets.statusCode).toBe(200)
|
||||
expect(getSecrets.body).toHaveProperty("secrets")
|
||||
expect(getSecrets.body.secrets).toHaveLength(2)
|
||||
expect(getSecrets.body.secrets).toBeInstanceOf(Array);
|
||||
// const getSecrets = await request(server)
|
||||
// .get("/api/v2/secrets")
|
||||
// .set('Authorization', `Bearer ${serviceToken}`)
|
||||
// .query({
|
||||
// workspaceId: testWorkspaceId,
|
||||
// environment: "dev"
|
||||
// })
|
||||
|
||||
// expect(getSecrets.statusCode).toBe(200)
|
||||
// expect(getSecrets.body).toHaveProperty("secrets")
|
||||
// expect(getSecrets.body.secrets).toHaveLength(2)
|
||||
// expect(getSecrets.body.secrets).toBeInstanceOf(Array);
|
||||
|
||||
getSecrets.body.secrets.forEach((secret: any) => {
|
||||
expect(secret).toHaveProperty('_id');
|
||||
expect(secret._id).toBeTruthy();
|
||||
// getSecrets.body.secrets.forEach((secret: any) => {
|
||||
// expect(secret).toHaveProperty('_id');
|
||||
// expect(secret._id).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('version');
|
||||
expect(secret.version).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('version');
|
||||
// expect(secret.version).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('workspace');
|
||||
expect(secret.workspace).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('workspace');
|
||||
// expect(secret.workspace).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('type');
|
||||
expect(secret.type).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('type');
|
||||
// expect(secret.type).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('tags');
|
||||
expect(secret.tags).toHaveLength(0);
|
||||
// expect(secret).toHaveProperty('tags');
|
||||
// expect(secret.tags).toHaveLength(0);
|
||||
|
||||
expect(secret).toHaveProperty('environment');
|
||||
expect(secret.environment).toEqual("dev");
|
||||
// expect(secret).toHaveProperty('environment');
|
||||
// expect(secret.environment).toEqual("dev");
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyCiphertext');
|
||||
expect(secret.secretKeyCiphertext).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretKeyCiphertext');
|
||||
// expect(secret.secretKeyCiphertext).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyIV');
|
||||
expect(secret.secretKeyIV).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretKeyIV');
|
||||
// expect(secret.secretKeyIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyTag');
|
||||
expect(secret.secretKeyTag).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretKeyTag');
|
||||
// expect(secret.secretKeyTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueCiphertext');
|
||||
expect(secret.secretValueCiphertext).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretValueCiphertext');
|
||||
// expect(secret.secretValueCiphertext).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueIV');
|
||||
expect(secret.secretValueIV).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretValueIV');
|
||||
// expect(secret.secretValueIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueTag');
|
||||
expect(secret.secretValueTag).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretValueTag');
|
||||
// expect(secret.secretValueTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentCiphertext');
|
||||
expect(secret.secretCommentCiphertext).toBeFalsy();
|
||||
// expect(secret).toHaveProperty('secretCommentCiphertext');
|
||||
// expect(secret.secretCommentCiphertext).toBeFalsy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentIV');
|
||||
expect(secret.secretCommentIV).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretCommentIV');
|
||||
// expect(secret.secretCommentIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentTag');
|
||||
expect(secret.secretCommentTag).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretCommentTag');
|
||||
// expect(secret.secretCommentTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('createdAt');
|
||||
expect(secret.createdAt).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('createdAt');
|
||||
// expect(secret.createdAt).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('updatedAt');
|
||||
expect(secret.updatedAt).toBeTruthy();
|
||||
});
|
||||
} finally {
|
||||
// clean up
|
||||
await deleteAllSecrets()
|
||||
}
|
||||
})
|
||||
|
||||
test("should create secrets and read secrets via service token when no overrides", async () => {
|
||||
try {
|
||||
// get login details
|
||||
const loginResponse = await getJWTFromTestUser()
|
||||
|
||||
// create secrets
|
||||
const createSecretsResponse = await request(server)
|
||||
.post("/api/v2/secrets/batch")
|
||||
.set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
.send({
|
||||
workspaceId: testWorkspaceId,
|
||||
environment: "dev",
|
||||
requests: batchSecretRequestWithNoOverride
|
||||
})
|
||||
|
||||
expect(createSecretsResponse.statusCode).toBe(200)
|
||||
// expect(secret).toHaveProperty('updatedAt');
|
||||
// expect(secret.updatedAt).toBeTruthy();
|
||||
// });
|
||||
// } finally {
|
||||
// // clean up
|
||||
// await deleteAllSecrets()
|
||||
// }
|
||||
// })
|
||||
|
||||
// test("should create secrets and read secrets via service token when no overrides", async () => {
|
||||
// try {
|
||||
// // get login details
|
||||
// const loginResponse = await getJWTFromTestUser()
|
||||
|
||||
// // create secrets
|
||||
// const createSecretsResponse = await request(server)
|
||||
// .post("/api/v2/secrets/batch")
|
||||
// .set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
// .send({
|
||||
// workspaceId: testWorkspaceId,
|
||||
// environment: "dev",
|
||||
// requests: batchSecretRequestWithNoOverride
|
||||
// })
|
||||
|
||||
// expect(createSecretsResponse.statusCode).toBe(200)
|
||||
|
||||
|
||||
// now use the service token to fetch secrets
|
||||
const serviceToken = await getServiceTokenFromTestUser()
|
||||
// // now use the service token to fetch secrets
|
||||
// const serviceToken = await getServiceTokenFromTestUser()
|
||||
|
||||
const getSecrets = await request(server)
|
||||
.get("/api/v2/secrets")
|
||||
.set('Authorization', `Bearer ${serviceToken}`)
|
||||
.query({
|
||||
workspaceId: testWorkspaceId,
|
||||
environment: "dev"
|
||||
})
|
||||
// const getSecrets = await request(server)
|
||||
// .get("/api/v2/secrets")
|
||||
// .set('Authorization', `Bearer ${serviceToken}`)
|
||||
// .query({
|
||||
// workspaceId: testWorkspaceId,
|
||||
// environment: "dev"
|
||||
// })
|
||||
|
||||
expect(getSecrets.statusCode).toBe(200)
|
||||
expect(getSecrets.body).toHaveProperty("secrets")
|
||||
expect(getSecrets.body.secrets).toHaveLength(3)
|
||||
expect(getSecrets.body.secrets).toBeInstanceOf(Array);
|
||||
// expect(getSecrets.statusCode).toBe(200)
|
||||
// expect(getSecrets.body).toHaveProperty("secrets")
|
||||
// expect(getSecrets.body.secrets).toHaveLength(3)
|
||||
// expect(getSecrets.body.secrets).toBeInstanceOf(Array);
|
||||
|
||||
getSecrets.body.secrets.forEach((secret: any) => {
|
||||
expect(secret).toHaveProperty('_id');
|
||||
expect(secret._id).toBeTruthy();
|
||||
// getSecrets.body.secrets.forEach((secret: any) => {
|
||||
// expect(secret).toHaveProperty('_id');
|
||||
// expect(secret._id).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('version');
|
||||
expect(secret.version).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('version');
|
||||
// expect(secret.version).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('workspace');
|
||||
expect(secret.workspace).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('workspace');
|
||||
// expect(secret.workspace).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('type');
|
||||
expect(secret.type).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('type');
|
||||
// expect(secret.type).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('tags');
|
||||
expect(secret.tags).toHaveLength(0);
|
||||
// expect(secret).toHaveProperty('tags');
|
||||
// expect(secret.tags).toHaveLength(0);
|
||||
|
||||
expect(secret).toHaveProperty('environment');
|
||||
expect(secret.environment).toEqual("dev");
|
||||
// expect(secret).toHaveProperty('environment');
|
||||
// expect(secret.environment).toEqual("dev");
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyCiphertext');
|
||||
expect(secret.secretKeyCiphertext).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretKeyCiphertext');
|
||||
// expect(secret.secretKeyCiphertext).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyIV');
|
||||
expect(secret.secretKeyIV).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretKeyIV');
|
||||
// expect(secret.secretKeyIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretKeyTag');
|
||||
expect(secret.secretKeyTag).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretKeyTag');
|
||||
// expect(secret.secretKeyTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueCiphertext');
|
||||
expect(secret.secretValueCiphertext).toBeTruthy();
|
||||
// expect(secret).toHaveProperty('secretValueCiphertext');
|
||||
// expect(secret.secretValueCiphertext).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueIV');
|
||||
expect(secret.secretValueIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretValueTag');
|
||||
expect(secret.secretValueTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentCiphertext');
|
||||
expect(secret.secretCommentCiphertext).toBeFalsy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentIV');
|
||||
expect(secret.secretCommentIV).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('secretCommentTag');
|
||||
expect(secret.secretCommentTag).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('createdAt');
|
||||
expect(secret.createdAt).toBeTruthy();
|
||||
|
||||
expect(secret).toHaveProperty('updatedAt');
|
||||
expect(secret.updatedAt).toBeTruthy();
|
||||
});
|
||||
} finally {
|
||||
// clean up
|
||||
await deleteAllSecrets()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("create secrets via JWT", () => {
|
||||
test("Create secrets via jwt when some requests have missing required parameters", async () => {
|
||||
// get login details
|
||||
const loginResponse = await getJWTFromTestUser()
|
||||
|
||||
// create creates
|
||||
const createSecretsResponse = await request(server)
|
||||
.post("/api/v2/secrets/batch")
|
||||
.set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
.send({
|
||||
workspaceId: testWorkspaceId,
|
||||
environment: "dev",
|
||||
requests: batchSecretRequestWithBadRequest
|
||||
})
|
||||
|
||||
const allSecretsInDB = await getAllSecrets()
|
||||
// expect(secret).toHaveProperty('secretValueIV');
|
||||
// expect(secret.secretValueIV).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('secretValueTag');
|
||||
// expect(secret.secretValueTag).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('secretCommentCiphertext');
|
||||
// expect(secret.secretCommentCiphertext).toBeFalsy();
|
||||
|
||||
// expect(secret).toHaveProperty('secretCommentIV');
|
||||
// expect(secret.secretCommentIV).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('secretCommentTag');
|
||||
// expect(secret.secretCommentTag).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('createdAt');
|
||||
// expect(secret.createdAt).toBeTruthy();
|
||||
|
||||
// expect(secret).toHaveProperty('updatedAt');
|
||||
// expect(secret.updatedAt).toBeTruthy();
|
||||
// });
|
||||
// } finally {
|
||||
// // clean up
|
||||
// await deleteAllSecrets()
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
|
||||
// describe("create secrets via JWT", () => {
|
||||
// test("Create secrets via jwt when some requests have missing required parameters", async () => {
|
||||
// // get login details
|
||||
// const loginResponse = await getJWTFromTestUser()
|
||||
|
||||
// // create creates
|
||||
// const createSecretsResponse = await request(server)
|
||||
// .post("/api/v2/secrets/batch")
|
||||
// .set('Authorization', `Bearer ${loginResponse.token}`)
|
||||
// .send({
|
||||
// workspaceId: testWorkspaceId,
|
||||
// environment: "dev",
|
||||
// requests: batchSecretRequestWithBadRequest
|
||||
// })
|
||||
|
||||
// const allSecretsInDB = await getAllSecrets()
|
||||
|
||||
expect(createSecretsResponse.statusCode).toBe(500) // TODO should be set to 400
|
||||
expect(allSecretsInDB).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
// expect(createSecretsResponse.statusCode).toBe(500) // TODO should be set to 400
|
||||
// expect(allSecretsInDB).toHaveLength(0)
|
||||
// })
|
||||
// })
|
||||
// })
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user