mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-22 13:07:43 +00:00
Compare commits
408 Commits
infisical-
...
infisical-
Author | SHA1 | Date | |
---|---|---|---|
bd8397bda7 | |||
9dac06744b | |||
bd80c2ccc3 | |||
62fa59619b | |||
7accaeffcf | |||
7f69a3b23f | |||
285a6d633a | |||
12b71bcf67 | |||
6c0be52ffa | |||
9df51424a2 | |||
531938a3f1 | |||
941a8699b5 | |||
6e42da9063 | |||
69903c0d5c | |||
8ff33a4e63 | |||
1d71864092 | |||
4b1a27b301 | |||
b78150e78d | |||
a0f08c73af | |||
59ebe0c22e | |||
6729caeb75 | |||
3543a15c09 | |||
33e0f13eea | |||
e9cff4fe69 | |||
26867f7328 | |||
233459d063 | |||
ba6355e4d2 | |||
e961a30937 | |||
53ff420304 | |||
196a613f16 | |||
cc4b749ce8 | |||
8cc5f2ef43 | |||
06bc02c392 | |||
3682c4d044 | |||
52892c26e5 | |||
5ce67bf750 | |||
ed2cf68935 | |||
386bc09d49 | |||
353c6e9166 | |||
1f69467207 | |||
5ab218f1f8 | |||
e1b25aaa54 | |||
9193e7ef58 | |||
3f998296fe | |||
6f7601f2c4 | |||
b7c7544baf | |||
4b7ae2477a | |||
e548883bba | |||
a7ece1830e | |||
6502d232c9 | |||
f31e8ddfe9 | |||
7bbbdcc58b | |||
bca14dd5c4 | |||
b6b3c8a736 | |||
d458bd7948 | |||
239989ceab | |||
7ff13242c0 | |||
7db8555b65 | |||
980a578bd5 | |||
adb27bb729 | |||
d89d360880 | |||
8ed5dbb26a | |||
221a43e8a4 | |||
e8a2575f7e | |||
41c1828324 | |||
c2c8cf90b7 | |||
00b4d6bd45 | |||
f5a6270d2a | |||
bc9d6253be | |||
a5b37c80ad | |||
7b1a4fa8e4 | |||
7457f573e9 | |||
d67e96507a | |||
46545c1462 | |||
8331cd4de8 | |||
3447074eb5 | |||
5a708ee931 | |||
9913b2fb6c | |||
2c021f852f | |||
8dbc894ce9 | |||
511904605f | |||
7ae6d1610f | |||
7da6d72f13 | |||
ad33356994 | |||
cfa2461479 | |||
bf08bfacb5 | |||
cf77820059 | |||
1ca90f56b8 | |||
5899d7aee9 | |||
b565194c43 | |||
86e04577c9 | |||
f4b3cafc5b | |||
18aad7d520 | |||
54c79012db | |||
4b720bf940 | |||
993866bb8b | |||
8c39fa2438 | |||
7bccfaefac | |||
e2b666345b | |||
90910819a3 | |||
8b070484dd | |||
a764087c83 | |||
27d5fa5aa0 | |||
2e7705999c | |||
428bf8e252 | |||
264740d84d | |||
723bcd4d83 | |||
9ed516ccb6 | |||
067ade94c8 | |||
446edb6ed9 | |||
896529b7c6 | |||
5c836d1c10 | |||
409d46aa10 | |||
682c63bc2a | |||
1419371588 | |||
77fdb6307c | |||
c61bba2b6b | |||
2dc0563042 | |||
b5fb2ef354 | |||
dc01758946 | |||
1f8683f59e | |||
a5273cb86f | |||
d48b5157d4 | |||
94a23bfa23 | |||
fcdfa424bc | |||
3fba1b3ff7 | |||
953eed70b2 | |||
39ba795604 | |||
5b36227321 | |||
70d04be978 | |||
c2be6674b1 | |||
565f234921 | |||
ab43e32982 | |||
be677fd6c2 | |||
3d93c6a995 | |||
edb201e11f | |||
1807b3e029 | |||
c02c8e67d3 | |||
d4c5be5f48 | |||
5f33c9a389 | |||
c9acb22261 | |||
33f0510995 | |||
25b239a18b | |||
504e0f6dc3 | |||
f450be3a00 | |||
d9f6c27e4d | |||
9cef35e9e6 | |||
2621ccdcf1 | |||
75e90201c0 | |||
fd3cf70e13 | |||
44108621b4 | |||
5ee65359bf | |||
241dceb845 | |||
af650ef4c7 | |||
817ddd228c | |||
15d81233b4 | |||
705b1833d0 | |||
beb8d2634a | |||
fb3ceb4581 | |||
2df33dc84f | |||
c62504d658 | |||
ce08512ab5 | |||
8abe7c7f99 | |||
043133444d | |||
b3baaac5c8 | |||
aa019e1501 | |||
0f8b505c78 | |||
5b7e23cdc5 | |||
df25657715 | |||
79c2baba1a | |||
52a2a782f1 | |||
eda095b55f | |||
93761f6487 | |||
c5438fbe6d | |||
e8fdaf571c | |||
846e2e037f | |||
a0a7ff8715 | |||
ec1e842202 | |||
83d5291998 | |||
638e011cc0 | |||
d2d23a7aba | |||
a52c2f03bf | |||
51c12e0202 | |||
4db7b0c05e | |||
284608762b | |||
8960773150 | |||
4684c9f8b1 | |||
abbf3e80f9 | |||
d272f580cf | |||
da9cb70184 | |||
1f3f0375b9 | |||
8ad851d4b0 | |||
edef22d28e | |||
3b5bc151ba | |||
678cdd3308 | |||
76f43ab6b4 | |||
33554f4057 | |||
c539d4d243 | |||
124e6dd998 | |||
cef29f5dd7 | |||
95c914631a | |||
49ae61da08 | |||
993abd0921 | |||
f37b497e48 | |||
0d2e55a06f | |||
040243d4f7 | |||
c450b01763 | |||
4cd203c194 | |||
178d444deb | |||
139ca9022e | |||
34d3e80d17 | |||
deac5fe101 | |||
216f3a0d1b | |||
6ee7081640 | |||
43f4110c94 | |||
56d430afd6 | |||
f681f0a98d | |||
23cd6fd861 | |||
cf45c3dc8b | |||
45584e0c1a | |||
202900a7a3 | |||
38b6a48bee | |||
04611d980b | |||
6125246794 | |||
52e26fc6fa | |||
06bd98bf56 | |||
7c24e0181a | |||
53abce5780 | |||
8c844fb188 | |||
ceeebc24fa | |||
df7ad9e645 | |||
a9135cdbcd | |||
9b96daa185 | |||
9919d3ee6a | |||
dfcd6b1efd | |||
07bc4c4a3a | |||
d69465517f | |||
6d807c0c74 | |||
868cc80210 | |||
3d4a616147 | |||
bd3f9130e4 | |||
f607841acf | |||
55d813043d | |||
b2a3a3a0e6 | |||
67d5f52aca | |||
a34047521c | |||
7ff806e8a6 | |||
9763353d59 | |||
4382935cb5 | |||
7e3646ddcd | |||
f7766fc182 | |||
3176370ef6 | |||
9bed1682fc | |||
daf2e2036e | |||
0f81c78639 | |||
8a19cfe0c6 | |||
a00fec9bca | |||
209f224517 | |||
0b7f2b7d4b | |||
eff15fc3d0 | |||
2614459772 | |||
4e926746cf | |||
f022f6d3ee | |||
1133ae4ae9 | |||
edd5afa13b | |||
442f572acc | |||
be58f3c429 | |||
3eea5d9322 | |||
e4e87163e8 | |||
d3aeb729e0 | |||
112d4ec9c0 | |||
2e7c7cf1da | |||
5d39416532 | |||
af95adb589 | |||
0fc4f96773 | |||
0a9adf33c8 | |||
f9110cedfa | |||
88ec55fc49 | |||
98b2a2a5c1 | |||
27eeafbf36 | |||
0cf63028df | |||
0b52b3cf58 | |||
e1764880a2 | |||
d3a47ffcdd | |||
9c1f88bb9c | |||
ae2f3184e2 | |||
3f1db47c30 | |||
3e3bbe298d | |||
46dc357651 | |||
07d25cb673 | |||
264f75ce8e | |||
9713a19405 | |||
a3836b970a | |||
ccfb8771f1 | |||
5e2b31cb6c | |||
b36801652f | |||
9e5b9cbdb5 | |||
bdf4ebd1bc | |||
e91e7f96c2 | |||
34fef4aaad | |||
09330458e5 | |||
ed95b99ed1 | |||
dc1e1e8dcb | |||
13a81c9222 | |||
6354464859 | |||
ec26404b94 | |||
3c45941474 | |||
91e172fd79 | |||
5ef2508736 | |||
93264fd2d0 | |||
7020c7aeab | |||
25b1673321 | |||
628bc711c2 | |||
a3b4228685 | |||
374c8e4a1a | |||
5afcf2798f | |||
1657cf0a7e | |||
c9820d0071 | |||
3e975dc4f0 | |||
b53c046eef | |||
fd10d7ed34 | |||
c5aae44249 | |||
83aa6127ec | |||
5a2299f758 | |||
57cdab0727 | |||
f82fa1b3b3 | |||
e95eef2071 | |||
53efdac0f0 | |||
f5eafc39c5 | |||
0f72ccf82e | |||
c191eb74fd | |||
f9fca42c5b | |||
11a19eef07 | |||
8a237af4ac | |||
24413e1edd | |||
5aba0c60b8 | |||
5599132efe | |||
7f9e27e3d3 | |||
7d36360111 | |||
d350297ce1 | |||
18d4e42d1f | |||
9faf5a3d5c | |||
da113612eb | |||
e9e2eade89 | |||
3cbc9c1b5c | |||
0772510e47 | |||
f389aa07eb | |||
27a110a93a | |||
13eaa4e9a1 | |||
7ec7d05fb0 | |||
7fe4089bb0 | |||
0cee453202 | |||
088d8097a9 | |||
4e6fae03ff | |||
732d0dfdca | |||
93e0232c21 | |||
37707c422a | |||
2f1bd9ca61 | |||
3d9ddbf9bc | |||
7c9140dcec | |||
a63d179a0d | |||
95dd8718bd | |||
ff2c9e98c0 | |||
d9ab38c590 | |||
23f4a350e7 | |||
696225d8d2 | |||
6c1ccc17b3 | |||
aa60f3a664 | |||
f01fb2830a | |||
9f6aa6b13e | |||
b2ee15a4ff | |||
42de0fbe73 | |||
553c986aa8 | |||
9a1e2260a0 | |||
98f7ce2585 | |||
c30ec8cb5f | |||
104c752f9a | |||
b66bea5671 | |||
f9313204a7 | |||
cb5c371a4f | |||
a32df58f46 | |||
e2658cc8dd | |||
1fbec20c6f | |||
ddff8be53c | |||
114d488345 | |||
c4da5a6ead | |||
056f5a4555 | |||
dfc88d99f6 | |||
033f41a7d5 | |||
5612a01039 | |||
f1d609cf40 | |||
0e9c71ae9f | |||
d1af399489 | |||
f445bac42f | |||
8381944bb2 | |||
81c69d92b3 | |||
072f6c737c | |||
5f683dd389 | |||
2526cbe6ca | |||
6959fc52ac | |||
81bd684305 | |||
7c77cc4ea4 | |||
9c0e32a790 | |||
611fae785a | |||
0ef4ac1cdc | |||
c04ea7e731 | |||
079d68c042 | |||
4b800202fb |
.env.examplemint.json
.github
.goreleaser.yaml.husky
README.mdbackend
.eslintrcDockerfilepackage-lock.jsonpackage.json
src
config
controllers
v1
index.tsintegrationAuthController.tsintegrationController.tsmembershipOrgController.tsorganizationController.tssecretController.tssecretImportController.tssecretScanningController.tswebhookController.ts
v2
apiKeyDataController.tsenvironmentController.tsindex.tssecretController.tssecretsController.tsserviceAccountsController.tsserviceTokenDataController.tssignupController.tstagController.tsusersController.tsworkspaceController.ts
v3
ee
controllers/v1
index.tsorganizationsController.tssecretController.tsssoController.tsusersController.tsworkspaceController.ts
helpers
middleware
models
routes/v1
services
events
helpers
index.tsintegrations
middleware
models
botOrg.tsgitAppInstallationSession.tsgitAppOrganizationInstallation.tsgitRisks.tsindex.tsintegration.tsintegrationAuth.tssecretImports.tsserviceTokenData.tsuser.tswebhooks.ts
routes
status
v1
v2
v3
services
BotOrgService.tsBotService.tsGithubSecretScanningService.tsSecretImportService.tsWebhookService.tsindex.tssmtp.ts
templates
types/express
utils
validation
variables
cli
docker-compose.ymldocs
api-reference/overview/examples
changelog
cli
contributing
documentation
getting-started
platform
images
example-secret-referencing.pngintegrations-bitbucket-auth.pngintegrations-bitbucket-create.pngintegrations-bitbucket.pngintegrations-cloud-66-access-token.pngintegrations-cloud-66-copy-pat.pngintegrations-cloud-66-create.pngintegrations-cloud-66-dashboard.pngintegrations-cloud-66-done.pngintegrations-cloud-66-infisical-dashboard.pngintegrations-cloud-66-paste-pat.pngintegrations-cloud-66-pat-setup.pngintegrations-cloud-66-pat.pngintegrations-codefresh-auth.pngintegrations-codefresh-create.pngintegrations-codefresh-dashboard.pngintegrations-codefresh-token.pngintegrations-codefresh.pngintegrations-do-dashboard.pngintegrations-do-enter-token.pngintegrations-do-select-projects.pngintegrations-do-success.pngintegrations-do-token-modal.pngintegrations-laravelforge-api.pngintegrations-laravelforge-auth.pngintegrations-laravelforge-create.pngintegrations-laravelforge-dashboard.pngintegrations-laravelforge-serverid.pngintegrations-laravelforge-servers.pngintegrations-laravelforge.pngintegrations-northflank-auth.pngintegrations-northflank-create.pngintegrations-northflank-dashboard.pngintegrations-northflank-token.pngintegrations-northflank.pngintegrations-teamcity-auth.pngintegrations-teamcity-create.pngintegrations-teamcity-dashboard.pngintegrations-teamcity-projects.pngintegrations-teamcity-serverurl.pngintegrations-teamcity-tokens.pngintegrations-teamcity.pngintegrations-terraformcloud-auth.pngintegrations-terraformcloud-create.pngintegrations-terraformcloud-dashboard.pngintegrations-terraformcloud-tokens.pngintegrations-terraformcloud-workspaceid.pngintegrations-terraformcloud-workspaces.pngintegrations-terraformcloud.pngintegrations-windmill-auth.pngintegrations-windmill-create.pngintegrations-windmill-dashboard.pngintegrations-windmill-token.pngintegrations-windmill.pngintegrations.pngproject-folder-token.pngproject-ip-whitelist-add.pngproject-ip-whitelist.pngsecret-import-add.pngsecret-import-change-order.pngservice-token-permissions.pngwebhooks.png
sso
azure
assignment.pngcreate-own-application.pngedit-attributes-claims-2.pngedit-attributes-claims.pngedit-basic-config-2.pngedit-basic-config.pngedit-saml-certificate-2.pngedit-saml-certificate.pngenable-saml.pngenterprise-applications.pngidp-values-2.pngidp-values.pnginit-config.pngnew-application.pngsso-method.png
jumpcloud
assignment.pngattribute-statements.pngcustom-saml-app.pngdownload-saml-certificate.pngedit-basic-config-2.pngedit-basic-config.pngenable-saml.pnggeneral-info.pngidp-values.pnginit-config.pngnew-application.png
okta
integrations
cicd
cloud
cloud-66.mdxdigital-ocean-app-platform.mdxlaravel-forge.mdxnorthflank.mdxteamcity.mdxterraform-cloud.mdxwindmill.mdx
frameworks
overview.mdxplatforms
security
self-hosting
frontend
.eslintrc.jsindex.tsuseProviderAuth.tsuseSyntaxHighlight.tsxtailwind.config.js
.storybook
package-lock.jsonpackage.jsonpublic
data
images
integrations
BitBucket.pngCloud 66.pngCodefresh.pngLaravel Forge.pngNorthflank.pngTeamCity.pngTerraform Cloud.pngWindmill.png
kubernetes-asset.pnglocales/en
lotties
src
components
basic
billing
dashboard
login
navigation
signup
utilities
v2
Button
DeleteActionModal
Dropdown
EmptyState
FormControl
Input
Menu
SecretInput
Table
Tabs
Tooltip
UpgradePlanModal
index.tsxconfig
const.tscontext
ee/components
hooks
api
auth
bots
index.tsxintegrationAuth
integrations
organization
secretFolders
secretImports
secrets
serverDetails
serviceTokens
ssoConfig
subscriptions
trustedIps
types.tsusers
webhooks
workspace
layouts/AppLayout
pages
404.tsx_app.tsxcli-redirect.tsxdashboard.tsx
api
apiKey
auth
integrations
secret-scanning
home
integrations
[id].tsx
login.tsxbitbucket
checkly
cloud-66
codefresh
digital-ocean-app-platform
laravel-forge
northflank
teamcity
terraform-cloud
windmill
login
noprojects.tsxorg/[id]
password-reset.tsxpersonal-settings.tsxproject/[id]
saml-sso.tsxsecret-scanning.tsxsettings/personal
signup
signupinvite.tsxverify-email.tsxstyles
views
DashboardPage
DashboardEnvOverview.tsxDashboardPage.tsxDashboardPage.utils.ts
components
EnvComparisonRow
FolderSection
SecretDetailDrawer
SecretImportForm
SecretImportSection
SecretInputRow
IntegrationsPage
IntegrationPage.utils.tsxIntegrationsPage.tsxindex.tsx
components
CloudIntegrationSection
FrameworkIntegrationSection
IntegrationsSection
Login
Org/MembersPage
MembersPage.tsx
components
index.tsxProject/IPAllowListPage
SecretOverviewPage
SecretOverviewPage.tsxindex.tsx
components
FolderBreadCrumbs
SecretOverviewFolderRow
SecretOverviewTableRow
SecretScanning/components
Settings
BillingSettingsPage
BillingSettingsPage.tsx
components
BillingCloudTab
BillingDetailsTab
CompanyNameSection.tsxInvoiceEmailSection.tsxPmtMethodsSection.tsxPmtMethodsTable.tsxTaxIDModal.tsxTaxIDSection.tsxTaxIDTable.tsx
BillingReceiptsTab
BillingTabGroup
CreateServiceAccountPage/components/SAProjectLevelPermissionsTable
OrgSettingsPage
OrgSettingsPage.tsx
components
OrgAuthTab
OrgGeneralTab
OrgIncidentContactsSection
OrgNameChangeSection
OrgServiceAccountsTable
OrgTabGroup
index.tsxPersonalSettingsPage
APIKeySection
AuthMethodSection
ChangeLanguageSection
ChangePasswordSection
EmergencyKitSection
PersonalAPIKeyTab
PersonalAuthTab
PersonalGeneralTab
PersonalSettingsPage.tsxPersonalTabGroup
SecuritySection
SessionsSection
UserNameSection
index.tsxProjectSettingsPage
ProjectSettingsPage.tsx
components
AutoCapitalizationSection
CopyProjectIDSection
DeleteProjectSection
E2EESection
EnvironmentSection
ProjectGeneralTab
ProjectIndexSecretsSection
ProjectNameChangeSection
ProjectServiceTokensTab
SecretTagsSection
ServiceTokenSection
WebhooksTab
index.tsxSignup
helm-charts
k8-operator
api/v1alpha1
config
controllers
kubectl-install
packages
nginx
package-lock.json@ -47,11 +47,13 @@ CLIENT_ID_VERCEL=
|
||||
CLIENT_ID_NETLIFY=
|
||||
CLIENT_ID_GITHUB=
|
||||
CLIENT_ID_GITLAB=
|
||||
CLIENT_ID_BITBUCKET=
|
||||
CLIENT_SECRET_HEROKU=
|
||||
CLIENT_SECRET_VERCEL=
|
||||
CLIENT_SECRET_NETLIFY=
|
||||
CLIENT_SECRET_GITHUB=
|
||||
CLIENT_SECRET_GITLAB=
|
||||
CLIENT_SECRET_BITBUCKET=
|
||||
CLIENT_SLUG_VERCEL=
|
||||
|
||||
# Sentry (optional) for monitoring errors
|
||||
|
BIN
.github/images/Deploy to AWS.png
vendored
Normal file
BIN
.github/images/Deploy to AWS.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.3 KiB |
BIN
.github/images/deploy-to-aws.png
vendored
Normal file
BIN
.github/images/deploy-to-aws.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.8 KiB |
11
.github/values.yaml
vendored
11
.github/values.yaml
vendored
@ -1,3 +1,10 @@
|
||||
# secretScanningGitApp:
|
||||
# enabled: false
|
||||
# deploymentAnnotations:
|
||||
# secrets.infisical.com/auto-reload: "true"
|
||||
# image:
|
||||
# repository: infisical/staging_deployment_secret-scanning-git-app
|
||||
|
||||
frontend:
|
||||
enabled: true
|
||||
name: frontend
|
||||
@ -51,8 +58,8 @@ mongodbConnection:
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "nginx"
|
||||
# annotations:
|
||||
# kubernetes.io/ingress.class: "nginx"
|
||||
# cert-manager.io/issuer: letsencrypt-nginx
|
||||
hostName: gamma.infisical.com ## <- Replace with your own domain
|
||||
frontend:
|
||||
|
2
.github/workflows/build-staging-img.yml
vendored
2
.github/workflows/build-staging-img.yml
vendored
@ -135,7 +135,7 @@ jobs:
|
||||
- name: Download helm values to file and upgrade gamma deploy
|
||||
run: |
|
||||
wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml
|
||||
helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --wait
|
||||
helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --wait --install
|
||||
if [[ $(helm status infisical) == *"FAILED"* ]]; then
|
||||
echo "Helm upgrade failed"
|
||||
exit 1
|
||||
|
@ -1,11 +1,17 @@
|
||||
name: Release standalone docker image
|
||||
on: [workflow_dispatch]
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "infisical/v*.*.*"
|
||||
|
||||
jobs:
|
||||
infisical-standalone:
|
||||
name: Build infisical standalone image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Extract version from tag
|
||||
id: extract_version
|
||||
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical/}"
|
||||
- name: ☁️ Checkout source
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
@ -64,5 +70,6 @@ jobs:
|
||||
tags: |
|
||||
infisical/infisical:latest
|
||||
infisical/infisical:${{ steps.commit.outputs.short }}
|
||||
infisical/infisical:${{ steps.extract_version.outputs.version }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: Dockerfile.standalone-infisical
|
||||
|
@ -1,10 +1,16 @@
|
||||
name: Release Docker image for K8 operator
|
||||
on: [workflow_dispatch]
|
||||
name: Release Docker image for K8 operator
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "infisical-k8-operator/v*.*.*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Extract version from tag
|
||||
id: extract_version
|
||||
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical-k8-operator/}"
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: 🔧 Set up QEMU
|
||||
@ -26,4 +32,6 @@ jobs:
|
||||
context: k8-operator
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: infisical/kubernetes-operator:latest
|
||||
tags: |
|
||||
infisical/kubernetes-operator:latest
|
||||
infisical/kubernetes-operator:${{ steps.extract_version.outputs.version }}
|
||||
|
@ -108,6 +108,22 @@ brews:
|
||||
zsh_completion.install "completions/infisical.zsh" => "_infisical"
|
||||
fish_completion.install "completions/infisical.fish"
|
||||
man1.install "manpages/infisical.1.gz"
|
||||
- name: 'infisical@{{.Version}}'
|
||||
tap:
|
||||
owner: Infisical
|
||||
name: homebrew-get-cli
|
||||
commit_author:
|
||||
name: "Infisical"
|
||||
email: ai@infisical.com
|
||||
folder: Formula
|
||||
homepage: "https://infisical.com"
|
||||
description: "The official Infisical CLI"
|
||||
install: |-
|
||||
bin.install "infisical"
|
||||
bash_completion.install "completions/infisical.bash" => "infisical"
|
||||
zsh_completion.install "completions/infisical.zsh" => "_infisical"
|
||||
fish_completion.install "completions/infisical.fish"
|
||||
man1.install "manpages/infisical.1.gz"
|
||||
|
||||
nfpms:
|
||||
- id: infisical
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
|
100
README.md
100
README.md
File diff suppressed because one or more lines are too long
@ -1,21 +1,40 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint", "unused-imports"],
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"unused-imports"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"no-empty-function": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"no-console": 2,
|
||||
"quotes": ["error", "double", { "avoidEscape": true }],
|
||||
"comma-dangle": ["error", "only-multiline"],
|
||||
"quotes": [
|
||||
"error",
|
||||
"double",
|
||||
{
|
||||
"avoidEscape": true
|
||||
}
|
||||
],
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
"only-multiline"
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
|
||||
{
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^_",
|
||||
"args": "after-used",
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"sort-imports": ["error", { "ignoreDeclarationSort": true }]
|
||||
"sort-imports": 1
|
||||
}
|
||||
}
|
||||
}
|
@ -19,6 +19,10 @@ RUN npm ci --only-production
|
||||
|
||||
COPY --from=build /app .
|
||||
|
||||
RUN apk add --no-cache bash curl && curl -1sLf \
|
||||
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
|
||||
&& apk add infisical=0.8.1
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||
CMD node healthcheck.js
|
||||
|
||||
|
9670
backend/package-lock.json
generated
9670
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-secrets-manager": "^3.319.0",
|
||||
"@godaddy/terminus": "^4.12.0",
|
||||
"@node-saml/passport-saml": "^4.0.4",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.49.0",
|
||||
"@sentry/tracing": "^7.48.0",
|
||||
@ -29,13 +30,15 @@
|
||||
"jsrp": "^0.2.4",
|
||||
"libsodium-wrappers": "^0.7.10",
|
||||
"lodash": "^4.17.21",
|
||||
"mongoose": "^6.10.5",
|
||||
"mongodb": "^5.7.0",
|
||||
"mongoose": "^7.4.1",
|
||||
"nanoid": "^3.3.6",
|
||||
"node-cache": "^5.1.2",
|
||||
"nodemailer": "^6.8.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"posthog-node": "^2.6.0",
|
||||
"probot": "^12.3.1",
|
||||
"query-string": "^7.1.3",
|
||||
"rate-limit-mongo": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
@ -89,6 +92,7 @@
|
||||
"@types/node": "^18.11.3",
|
||||
"@types/nodemailer": "^6.4.6",
|
||||
"@types/passport": "^1.0.12",
|
||||
"@types/picomatch": "^2.3.0",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@types/swagger-jsdoc": "^6.0.1",
|
||||
"@types/swagger-ui-express": "^4.1.3",
|
||||
@ -102,6 +106,7 @@
|
||||
"jest-junit": "^15.0.0",
|
||||
"nodemon": "^2.0.19",
|
||||
"npm": "^8.19.3",
|
||||
"smee-client": "^1.2.3",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-node": "^10.9.1"
|
||||
|
@ -10,7 +10,7 @@ export const getEncryptionKey = async () => {
|
||||
return secretValue === "" ? undefined : secretValue;
|
||||
}
|
||||
export const getRootEncryptionKey = async () => {
|
||||
const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue;
|
||||
const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue;
|
||||
return secretValue === "" ? undefined : secretValue;
|
||||
}
|
||||
export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true"
|
||||
@ -37,6 +37,7 @@ export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID
|
||||
export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
|
||||
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
|
||||
export const getClientIdGoogle = async () => (await client.getSecret("CLIENT_ID_GOOGLE")).secretValue;
|
||||
export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).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;
|
||||
@ -44,6 +45,7 @@ export const getClientSecretNetlify = async () => (await client.getSecret("CLIEN
|
||||
export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue;
|
||||
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue;
|
||||
export const getClientSecretGoogle = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE")).secretValue;
|
||||
export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).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";
|
||||
@ -57,6 +59,11 @@ export const getSmtpPassword = async () => (await client.getSecret("SMTP_PASSWOR
|
||||
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 getSecretScanningWebhookProxy = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue;
|
||||
export const getSecretScanningWebhookSecret = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue;
|
||||
export const getSecretScanningGitAppId = async () => (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue;
|
||||
export const getSecretScanningPrivateKey = async () => (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue;
|
||||
|
||||
export const getLicenseKey = async () => {
|
||||
const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue;
|
||||
return secretValue === "" ? undefined : secretValue;
|
||||
@ -82,4 +89,4 @@ export const getHttpsEnabled = async () => {
|
||||
}
|
||||
|
||||
return (await client.getSecret("HTTPS_ENABLED")).secretValue === "true" && true
|
||||
}
|
||||
}
|
||||
|
@ -13,21 +13,27 @@ import * as signupController from "./signupController";
|
||||
import * as userActionController from "./userActionController";
|
||||
import * as userController from "./userController";
|
||||
import * as workspaceController from "./workspaceController";
|
||||
import * as secretScanningController from "./secretScanningController";
|
||||
import * as webhookController from "./webhookController";
|
||||
import * as secretImportController from "./secretImportController";
|
||||
|
||||
export {
|
||||
authController,
|
||||
botController,
|
||||
integrationAuthController,
|
||||
integrationController,
|
||||
keyController,
|
||||
membershipController,
|
||||
membershipOrgController,
|
||||
organizationController,
|
||||
passwordController,
|
||||
secretController,
|
||||
serviceTokenController,
|
||||
signupController,
|
||||
userActionController,
|
||||
userController,
|
||||
workspaceController,
|
||||
authController,
|
||||
botController,
|
||||
integrationAuthController,
|
||||
integrationController,
|
||||
keyController,
|
||||
membershipController,
|
||||
membershipOrgController,
|
||||
organizationController,
|
||||
passwordController,
|
||||
secretController,
|
||||
serviceTokenController,
|
||||
signupController,
|
||||
userActionController,
|
||||
userController,
|
||||
workspaceController,
|
||||
secretScanningController,
|
||||
webhookController,
|
||||
secretImportController
|
||||
};
|
||||
|
@ -7,6 +7,8 @@ import { IntegrationService } from "../../services";
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_BITBUCKET_API_URL,
|
||||
INTEGRATION_NORTHFLANK_API_URL,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
@ -141,12 +143,14 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) =>
|
||||
*/
|
||||
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
||||
const teamId = req.query.teamId as string;
|
||||
const workspaceSlug = req.query.workspaceSlug as string;
|
||||
|
||||
const apps = await getApps({
|
||||
integrationAuth: req.integrationAuth,
|
||||
accessToken: req.accessToken,
|
||||
accessId: req.accessId,
|
||||
...(teamId && { teamId })
|
||||
...(teamId && { teamId }),
|
||||
...(workspaceSlug && { workspaceSlug })
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
@ -382,6 +386,139 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of workspaces allowed for Bitbucket integration
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: Response) => {
|
||||
|
||||
interface WorkspaceResponse {
|
||||
size: number;
|
||||
page: number;
|
||||
pageLen: number;
|
||||
next: string;
|
||||
previous: string;
|
||||
values: Array<Workspace>;
|
||||
}
|
||||
|
||||
interface Workspace {
|
||||
type: string;
|
||||
uuid: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
is_private: boolean;
|
||||
created_on: string;
|
||||
updated_on: string;
|
||||
}
|
||||
|
||||
const workspaces: Workspace[] = [];
|
||||
let hasNextPage = true;
|
||||
let workspaceUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/workspaces`
|
||||
|
||||
while (hasNextPage) {
|
||||
const { data }: { data: WorkspaceResponse } = await standardRequest.get(
|
||||
workspaceUrl,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${req.accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (data?.values.length > 0) {
|
||||
data.values.forEach((workspace) => {
|
||||
workspaces.push(workspace)
|
||||
})
|
||||
}
|
||||
|
||||
if (data.next) {
|
||||
workspaceUrl = data.next
|
||||
} else {
|
||||
hasNextPage = false
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
workspaces
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of secret groups for Northflank project with id [appId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res: Response) => {
|
||||
const appId = req.query.appId as string;
|
||||
|
||||
interface NorthflankSecretGroup {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
priority: number;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
interface SecretGroup {
|
||||
name: string;
|
||||
groupId: string;
|
||||
}
|
||||
|
||||
const secretGroups: SecretGroup[] = [];
|
||||
|
||||
if (appId && appId !== "") {
|
||||
let page = 1;
|
||||
const perPage = 10;
|
||||
let hasMorePages = true;
|
||||
|
||||
while(hasMorePages) {
|
||||
const params = new URLSearchParams({
|
||||
page: String(page),
|
||||
per_page: String(perPage),
|
||||
filter: "all",
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
data: {
|
||||
secrets
|
||||
}
|
||||
}
|
||||
} = await standardRequest.get<{ data: { secrets: NorthflankSecretGroup[] }}>(
|
||||
`${INTEGRATION_NORTHFLANK_API_URL}/v1/projects/${appId}/secrets`,
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${req.accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
secrets.forEach((a: any) => {
|
||||
secretGroups.push({
|
||||
name: a.name,
|
||||
groupId: a.id
|
||||
});
|
||||
});
|
||||
|
||||
if (secrets.length < perPage) {
|
||||
hasMorePages = false;
|
||||
}
|
||||
|
||||
page++;
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secretGroups
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete integration authorization with id [integrationAuthId]
|
||||
* @param req
|
||||
@ -398,3 +535,4 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
||||
integrationAuth
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { Integration } from "../../models";
|
||||
import { EventService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
import { eventStartIntegration } from "../../events";
|
||||
import Folder from "../../models/folder";
|
||||
import { getFolderByPath } from "../../services/FolderService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
@ -27,19 +27,19 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
owner,
|
||||
path,
|
||||
region,
|
||||
secretPath,
|
||||
secretPath
|
||||
} = req.body;
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
workspace: req.integrationAuth.workspace._id,
|
||||
environment: sourceEnvironment,
|
||||
environment: sourceEnvironment
|
||||
});
|
||||
|
||||
if (folders) {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||
if (!folder) {
|
||||
throw BadRequestError({
|
||||
message: "Path for service token does not exist",
|
||||
message: "Path for service token does not exist"
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -62,21 +62,21 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
region,
|
||||
secretPath,
|
||||
integration: req.integrationAuth.integration,
|
||||
integrationAuth: new Types.ObjectId(integrationAuthId),
|
||||
integrationAuth: new Types.ObjectId(integrationAuthId)
|
||||
}).save();
|
||||
|
||||
if (integration) {
|
||||
// trigger event - push secrets
|
||||
EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
event: eventStartIntegration({
|
||||
workspaceId: integration.workspace,
|
||||
environment: sourceEnvironment,
|
||||
}),
|
||||
environment: sourceEnvironment
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
integration,
|
||||
integration
|
||||
});
|
||||
};
|
||||
|
||||
@ -97,26 +97,26 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
||||
appId,
|
||||
targetEnvironment,
|
||||
owner, // github-specific integration param
|
||||
secretPath,
|
||||
secretPath
|
||||
} = req.body;
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
workspace: req.integration.workspace,
|
||||
environment,
|
||||
environment
|
||||
});
|
||||
|
||||
if (folders) {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||
if (!folder) {
|
||||
throw BadRequestError({
|
||||
message: "Path for service token does not exist",
|
||||
message: "Path for service token does not exist"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const integration = await Integration.findOneAndUpdate(
|
||||
{
|
||||
_id: req.integration._id,
|
||||
_id: req.integration._id
|
||||
},
|
||||
{
|
||||
environment,
|
||||
@ -125,25 +125,25 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
||||
appId,
|
||||
targetEnvironment,
|
||||
owner,
|
||||
secretPath,
|
||||
secretPath
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
if (integration) {
|
||||
// trigger event - push secrets
|
||||
EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
event: eventStartIntegration({
|
||||
workspaceId: integration.workspace,
|
||||
environment,
|
||||
}),
|
||||
environment
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
integration,
|
||||
integration
|
||||
});
|
||||
};
|
||||
|
||||
@ -158,12 +158,12 @@ export const deleteIntegration = async (req: Request, res: Response) => {
|
||||
const { integrationId } = req.params;
|
||||
|
||||
const integration = await Integration.findOneAndDelete({
|
||||
_id: integrationId,
|
||||
_id: integrationId
|
||||
});
|
||||
|
||||
if (!integration) throw new Error("Failed to find integration");
|
||||
|
||||
return res.status(200).send({
|
||||
integration,
|
||||
integration
|
||||
});
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Request, Response } from "express";
|
||||
import { MembershipOrg, Organization, User } from "../../models";
|
||||
import { SSOConfig } from "../../ee/models";
|
||||
import { deleteMembershipOrg as deleteMemberFromOrg } from "../../helpers/membershipOrg";
|
||||
import { createToken } from "../../helpers/auth";
|
||||
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
||||
@ -110,6 +111,18 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
const plan = await EELicenseService.getPlan(organizationId);
|
||||
|
||||
const ssoConfig = await SSOConfig.findOne({
|
||||
organization: new Types.ObjectId(organizationId)
|
||||
});
|
||||
|
||||
if (ssoConfig && ssoConfig.isActive) {
|
||||
// case: SAML SSO is enabled for the organization
|
||||
return res.status(400).send({
|
||||
message:
|
||||
"Failed to invite member due to SAML SSO configured for organization"
|
||||
});
|
||||
}
|
||||
|
||||
if (plan.memberLimit !== null) {
|
||||
// case: limit imposed on number of members allowed
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
import { createOrganization as create } from "../../helpers/organization";
|
||||
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
||||
import { ACCEPTED, OWNER } from "../../variables";
|
||||
import { getSiteURL, getLicenseServerUrl } from "../../config";
|
||||
import { getLicenseServerUrl, getSiteURL } from "../../config";
|
||||
import { licenseServerKeyRequest } from "../../config/request";
|
||||
|
||||
export const getOrganizations = async (req: Request, res: Response) => {
|
||||
|
@ -80,7 +80,8 @@ export const pushSecrets = async (req: Request, res: Response) => {
|
||||
EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment
|
||||
environment,
|
||||
secretPath: "/"
|
||||
})
|
||||
});
|
||||
|
||||
|
116
backend/src/controllers/v1/secretImportController.ts
Normal file
116
backend/src/controllers/v1/secretImportController.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { Request, Response } from "express";
|
||||
import { validateMembership } from "../../helpers";
|
||||
import SecretImport from "../../models/secretImports";
|
||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { ADMIN, MEMBER } from "../../variables";
|
||||
|
||||
export const createSecretImport = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment, folderId, secretImport } = req.body;
|
||||
const importSecDoc = await SecretImport.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
folderId
|
||||
});
|
||||
|
||||
if (!importSecDoc) {
|
||||
const doc = new SecretImport({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
folderId,
|
||||
imports: [{ environment: secretImport.environment, secretPath: secretImport.secretPath }]
|
||||
});
|
||||
await doc.save();
|
||||
return res.status(200).json({ message: "successfully created secret import" });
|
||||
}
|
||||
|
||||
const doesImportExist = importSecDoc.imports.find(
|
||||
(el) => el.environment === secretImport.environment && el.secretPath === secretImport.secretPath
|
||||
);
|
||||
if (doesImportExist) {
|
||||
throw BadRequestError({ message: "Secret import already exist" });
|
||||
}
|
||||
importSecDoc.imports.push({
|
||||
environment: secretImport.environment,
|
||||
secretPath: secretImport.secretPath
|
||||
});
|
||||
await importSecDoc.save();
|
||||
return res.status(200).json({ message: "successfully created secret import" });
|
||||
};
|
||||
|
||||
// to keep the ordering, you must pass all the imports in here not the only updated one
|
||||
// this is because the order decide which import gets overriden
|
||||
export const updateSecretImport = async (req: Request, res: Response) => {
|
||||
const { id } = req.params;
|
||||
const { secretImports } = req.body;
|
||||
const importSecDoc = await SecretImport.findById(id);
|
||||
if (!importSecDoc) {
|
||||
throw BadRequestError({ message: "Import not found" });
|
||||
}
|
||||
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: importSecDoc.workspace,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
|
||||
importSecDoc.imports = secretImports;
|
||||
await importSecDoc.save();
|
||||
return res.status(200).json({ message: "successfully updated secret import" });
|
||||
};
|
||||
|
||||
export const deleteSecretImport = async (req: Request, res: Response) => {
|
||||
const { id } = req.params;
|
||||
const { secretImportEnv, secretImportPath } = req.body;
|
||||
const importSecDoc = await SecretImport.findById(id);
|
||||
if (!importSecDoc) {
|
||||
throw BadRequestError({ message: "Import not found" });
|
||||
}
|
||||
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: importSecDoc.workspace,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
importSecDoc.imports = importSecDoc.imports.filter(
|
||||
({ environment, secretPath }) =>
|
||||
!(environment === secretImportEnv && secretPath === secretImportPath)
|
||||
);
|
||||
await importSecDoc.save();
|
||||
return res.status(200).json({ message: "successfully delete secret import" });
|
||||
};
|
||||
|
||||
export const getSecretImports = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment, folderId } = req.query;
|
||||
const importSecDoc = await SecretImport.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
folderId
|
||||
});
|
||||
|
||||
if (!importSecDoc) {
|
||||
return res.status(200).json({ secretImport: {} });
|
||||
}
|
||||
|
||||
return res.status(200).json({ secretImport: importSecDoc });
|
||||
};
|
||||
|
||||
export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment, folderId } = req.query as {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
folderId: string;
|
||||
};
|
||||
const importSecDoc = await SecretImport.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
folderId
|
||||
});
|
||||
|
||||
if (!importSecDoc) {
|
||||
return res.status(200).json({ secrets: [] });
|
||||
}
|
||||
|
||||
const secrets = await getAllImportedSecrets(workspaceId, environment, folderId);
|
||||
return res.status(200).json({ secrets });
|
||||
};
|
91
backend/src/controllers/v1/secretScanningController.ts
Normal file
91
backend/src/controllers/v1/secretScanningController.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import { Request, Response } from "express";
|
||||
import GitAppInstallationSession from "../../models/gitAppInstallationSession";
|
||||
import crypto from "crypto";
|
||||
import { Types } from "mongoose";
|
||||
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||
import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation";
|
||||
import { MembershipOrg } from "../../models";
|
||||
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../models/gitRisks";
|
||||
|
||||
export const createInstallationSession = async (req: Request, res: Response) => {
|
||||
const sessionId = crypto.randomBytes(16).toString("hex");
|
||||
await GitAppInstallationSession.findByIdAndUpdate(
|
||||
req.organization,
|
||||
{
|
||||
organization: new Types.ObjectId(req.organization),
|
||||
sessionId: sessionId,
|
||||
user: new Types.ObjectId(req.user._id)
|
||||
},
|
||||
{ upsert: true }
|
||||
).lean();
|
||||
|
||||
res.send({
|
||||
sessionId: sessionId
|
||||
})
|
||||
}
|
||||
|
||||
export const linkInstallationToOrganization = async (req: Request, res: Response) => {
|
||||
const { installationId, sessionId } = req.body
|
||||
|
||||
const installationSession = await GitAppInstallationSession.findOneAndDelete({ sessionId: sessionId })
|
||||
if (!installationSession) {
|
||||
throw UnauthorizedRequestError()
|
||||
}
|
||||
|
||||
const userMembership = await MembershipOrg.find({ user: req.user._id, organization: installationSession.organization })
|
||||
if (!userMembership) {
|
||||
throw UnauthorizedRequestError()
|
||||
}
|
||||
|
||||
const installationLink = await GitAppOrganizationInstallation.findOneAndUpdate({
|
||||
organizationId: installationSession.organization,
|
||||
}, {
|
||||
installationId: installationId,
|
||||
organizationId: installationSession.organization,
|
||||
user: installationSession.user
|
||||
}, {
|
||||
upsert: true
|
||||
}).lean()
|
||||
|
||||
res.json(installationLink)
|
||||
}
|
||||
|
||||
export const getCurrentOrganizationInstallationStatus = async (req: Request, res: Response) => {
|
||||
const { organizationId } = req.params
|
||||
try {
|
||||
const appInstallation = await GitAppOrganizationInstallation.findOne({ organizationId: organizationId }).lean()
|
||||
if (!appInstallation) {
|
||||
res.json({
|
||||
appInstallationComplete: false
|
||||
})
|
||||
}
|
||||
|
||||
res.json({
|
||||
appInstallationComplete: true
|
||||
})
|
||||
} catch {
|
||||
res.json({
|
||||
appInstallationComplete: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const getRisksForOrganization = async (req: Request, res: Response) => {
|
||||
const { organizationId } = req.params
|
||||
const risks = await GitRisks.find({ organization: organizationId }).sort({ createdAt: -1 }).lean()
|
||||
res.json({
|
||||
risks: risks
|
||||
})
|
||||
}
|
||||
|
||||
export const updateRisksStatus = async (req: Request, res: Response) => {
|
||||
const { riskId } = req.params
|
||||
const { status } = req.body
|
||||
const isRiskResolved = status == STATUS_RESOLVED_FALSE_POSITIVE || status == STATUS_RESOLVED_REVOKED || status == STATUS_RESOLVED_NOT_REVOKED ? true : false
|
||||
const risk = await GitRisks.findByIdAndUpdate(riskId, {
|
||||
status: status,
|
||||
isResolved: isRiskResolved
|
||||
}).lean()
|
||||
|
||||
res.json(risk)
|
||||
}
|
141
backend/src/controllers/v1/webhookController.ts
Normal file
141
backend/src/controllers/v1/webhookController.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { client, getRootEncryptionKey } from "../../config";
|
||||
import { validateMembership } from "../../helpers";
|
||||
import Webhook from "../../models/webhooks";
|
||||
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { ADMIN, ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, MEMBER } from "../../variables";
|
||||
|
||||
export const createWebhook = async (req: Request, res: Response) => {
|
||||
const { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath } = req.body;
|
||||
const webhook = new Webhook({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
url: webhookUrl,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_BASE64
|
||||
});
|
||||
|
||||
if (webhookSecretKey) {
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey);
|
||||
webhook.iv = iv;
|
||||
webhook.tag = tag;
|
||||
webhook.encryptedSecretKey = ciphertext;
|
||||
}
|
||||
|
||||
await webhook.save();
|
||||
|
||||
return res.status(200).send({
|
||||
webhook,
|
||||
message: "successfully created webhook"
|
||||
});
|
||||
};
|
||||
|
||||
export const updateWebhook = async (req: Request, res: Response) => {
|
||||
const { webhookId } = req.params;
|
||||
const { isDisabled } = req.body;
|
||||
const webhook = await Webhook.findById(webhookId);
|
||||
if (!webhook) {
|
||||
throw BadRequestError({ message: "Webhook not found!!" });
|
||||
}
|
||||
|
||||
// check that user is a member of the workspace
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: webhook.workspace,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
|
||||
if (typeof isDisabled !== undefined) {
|
||||
webhook.isDisabled = isDisabled;
|
||||
}
|
||||
await webhook.save();
|
||||
|
||||
return res.status(200).send({
|
||||
webhook,
|
||||
message: "successfully updated webhook"
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteWebhook = async (req: Request, res: Response) => {
|
||||
const { webhookId } = req.params;
|
||||
const webhook = await Webhook.findById(webhookId);
|
||||
if (!webhook) {
|
||||
throw BadRequestError({ message: "Webhook not found!!" });
|
||||
}
|
||||
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: webhook.workspace,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
|
||||
await webhook.deleteOne();
|
||||
|
||||
return res.status(200).send({
|
||||
message: "successfully removed webhook"
|
||||
});
|
||||
};
|
||||
|
||||
export const testWebhook = async (req: Request, res: Response) => {
|
||||
const { webhookId } = req.params;
|
||||
const webhook = await Webhook.findById(webhookId);
|
||||
if (!webhook) {
|
||||
throw BadRequestError({ message: "Webhook not found!!" });
|
||||
}
|
||||
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: webhook.workspace,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
|
||||
try {
|
||||
await triggerWebhookRequest(
|
||||
webhook,
|
||||
getWebhookPayload(
|
||||
"test",
|
||||
webhook.workspace.toString(),
|
||||
webhook.environment,
|
||||
webhook.secretPath
|
||||
)
|
||||
);
|
||||
await Webhook.findByIdAndUpdate(webhookId, {
|
||||
lastStatus: "success",
|
||||
lastRunErrorMessage: null
|
||||
});
|
||||
} catch (err) {
|
||||
await Webhook.findByIdAndUpdate(webhookId, {
|
||||
lastStatus: "failed",
|
||||
lastRunErrorMessage: (err as Error).message
|
||||
});
|
||||
return res.status(400).send({
|
||||
message: "Failed to receive response",
|
||||
error: (err as Error).message
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully received response"
|
||||
});
|
||||
};
|
||||
|
||||
export const listWebhooks = async (req: Request, res: Response) => {
|
||||
const { environment, workspaceId, secretPath } = req.query;
|
||||
|
||||
const optionalFilters: Record<string, string> = {};
|
||||
if (environment) optionalFilters.environment = environment as string;
|
||||
if (secretPath) optionalFilters.secretPath = secretPath as string;
|
||||
|
||||
const webhooks = await Webhook.find({
|
||||
workspace: new Types.ObjectId(workspaceId as string),
|
||||
...optionalFilters
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
webhooks
|
||||
});
|
||||
};
|
@ -1,72 +0,0 @@
|
||||
import { Request, Response } from "express";
|
||||
import crypto from "crypto";
|
||||
import bcrypt from "bcrypt";
|
||||
import { APIKeyData } from "../../models";
|
||||
import { getSaltRounds } from "../../config";
|
||||
|
||||
/**
|
||||
* Return API key data for user with id [req.user_id]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getAPIKeyData = async (req: Request, res: Response) => {
|
||||
const apiKeyData = await APIKeyData.find({
|
||||
user: req.user._id,
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
apiKeyData,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new API key data for user with id [req.user._id]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
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, await getSaltRounds());
|
||||
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||
|
||||
let apiKeyData = await new APIKeyData({
|
||||
name,
|
||||
lastUsed: new Date(),
|
||||
expiresAt,
|
||||
user: req.user._id,
|
||||
secretHash,
|
||||
}).save();
|
||||
|
||||
// return api key data without sensitive data
|
||||
// FIX: fix this any
|
||||
apiKeyData = (await APIKeyData.findById(apiKeyData._id)) as any;
|
||||
|
||||
if (!apiKeyData) throw new Error("Failed to find API key data");
|
||||
|
||||
const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;
|
||||
|
||||
return res.status(200).send({
|
||||
apiKey,
|
||||
apiKeyData,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete API key data with id [apiKeyDataId].
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const deleteAPIKeyData = async (req: Request, res: Response) => {
|
||||
const { apiKeyDataId } = req.params;
|
||||
const apiKeyData = await APIKeyData.findByIdAndDelete(apiKeyDataId);
|
||||
|
||||
return res.status(200).send({
|
||||
apiKeyData,
|
||||
});
|
||||
};
|
@ -27,16 +27,16 @@ export const createWorkspaceEnvironment = async (
|
||||
const { workspaceId } = req.params;
|
||||
const { environmentName, environmentSlug } = req.body;
|
||||
const workspace = await Workspace.findById(workspaceId).exec();
|
||||
|
||||
|
||||
if (!workspace) throw WorkspaceNotFoundError();
|
||||
|
||||
|
||||
const plan = await EELicenseService.getPlan(workspace.organization.toString());
|
||||
|
||||
|
||||
if (plan.environmentLimit !== null) {
|
||||
// case: limit imposed on number of environments allowed
|
||||
if (workspace.environments.length >= plan.environmentLimit) {
|
||||
// case: number of environments used exceeds the number of environments allowed
|
||||
|
||||
|
||||
return res.status(400).send({
|
||||
message: "Failed to create environment due to environment limit reached. Upgrade plan to create more environments.",
|
||||
});
|
||||
@ -191,14 +191,21 @@ export const deleteWorkspaceEnvironment = async (
|
||||
workspace: workspaceId,
|
||||
environment: environmentSlug,
|
||||
});
|
||||
await ServiceToken.deleteMany({
|
||||
workspace: workspaceId,
|
||||
environment: environmentSlug,
|
||||
});
|
||||
await ServiceTokenData.deleteMany({
|
||||
workspace: workspaceId,
|
||||
environment: environmentSlug,
|
||||
});
|
||||
|
||||
// await ServiceToken.deleteMany({
|
||||
// workspace: workspaceId,
|
||||
// environment: environmentSlug,
|
||||
// });
|
||||
|
||||
const result = await ServiceTokenData.updateMany(
|
||||
{ workspace: workspaceId },
|
||||
{ $pull: { scopes: { environment: environmentSlug } } }
|
||||
);
|
||||
|
||||
if (result.modifiedCount > 0) {
|
||||
await ServiceTokenData.deleteMany({ workspace: workspaceId, scopes: { $size: 0 } });
|
||||
}
|
||||
|
||||
await Integration.deleteMany({
|
||||
workspace: workspaceId,
|
||||
environment: environmentSlug,
|
||||
|
@ -4,7 +4,6 @@ import * as usersController from "./usersController";
|
||||
import * as organizationsController from "./organizationsController";
|
||||
import * as workspaceController from "./workspaceController";
|
||||
import * as serviceTokenDataController from "./serviceTokenDataController";
|
||||
import * as apiKeyDataController from "./apiKeyDataController";
|
||||
import * as secretController from "./secretController";
|
||||
import * as secretsController from "./secretsController";
|
||||
import * as serviceAccountsController from "./serviceAccountsController";
|
||||
@ -18,7 +17,6 @@ export {
|
||||
organizationsController,
|
||||
workspaceController,
|
||||
serviceTokenDataController,
|
||||
apiKeyDataController,
|
||||
secretController,
|
||||
secretsController,
|
||||
serviceAccountsController,
|
||||
|
@ -9,8 +9,6 @@ import {
|
||||
} from "../../types/secret";
|
||||
const { ValidationError } = mongoose.Error;
|
||||
import {
|
||||
BadRequestError,
|
||||
InternalServerError,
|
||||
ValidationError as RouteValidationError,
|
||||
UnauthorizedRequestError
|
||||
} from "../../utils/errors";
|
||||
@ -290,7 +288,7 @@ export const updateSecret = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environmentName } = req.params;
|
||||
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
|
||||
|
||||
const secretIdUserCanModify = await Secret.findOne({ workspace: workspaceId, environment: environmentName }, { _id: 1 });
|
||||
await Secret.findOne({ workspace: workspaceId, environment: environmentName }, { _id: 1 });
|
||||
|
||||
const sanitizedSecret: SanitizedSecretModify = {
|
||||
secretKeyCiphertext: secretModificationsRequested.secretKeyCiphertext,
|
||||
@ -311,16 +309,16 @@ export const updateSecret = async (req: Request, res: Response) => {
|
||||
{ _id: secretModificationsRequested._id, workspace: workspaceId },
|
||||
{ $inc: { version: 1 }, $set: sanitizedSecret }
|
||||
)
|
||||
.catch((error) => {
|
||||
if (error instanceof ValidationError) {
|
||||
throw RouteValidationError({
|
||||
message: "Unable to apply modifications, please try again",
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
.catch((error) => {
|
||||
if (error instanceof ValidationError) {
|
||||
throw RouteValidationError({
|
||||
message: "Unable to apply modifications, please try again",
|
||||
stack: error.stack
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
@ -372,12 +370,12 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
$or: [{ user: userId }, { user: { $exists: false } }],
|
||||
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
|
||||
})
|
||||
.catch((err) => {
|
||||
throw RouteValidationError({
|
||||
message: "Failed to get secrets, please try again",
|
||||
stack: err.stack
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
throw RouteValidationError({
|
||||
message: "Failed to get secrets, please try again",
|
||||
stack: err.stack
|
||||
});
|
||||
})
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
SECRET_PERSONAL,
|
||||
SECRET_PERSONAL
|
||||
} from "../../variables";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { EventService } from "../../services";
|
||||
@ -21,7 +21,7 @@ import { PERMISSION_WRITE_SECRETS } from "../../variables";
|
||||
import {
|
||||
userHasNoAbility,
|
||||
userHasWorkspaceAccess,
|
||||
userHasWriteOnlyAbility,
|
||||
userHasWriteOnlyAbility
|
||||
} from "../../ee/helpers/checkMembershipPermissions";
|
||||
import Tag from "../../models/tag";
|
||||
import _ from "lodash";
|
||||
@ -31,7 +31,11 @@ import {
|
||||
getFolderByPath,
|
||||
getFolderIdFromServiceToken,
|
||||
searchByFolderId,
|
||||
searchByFolderIdWithDir
|
||||
} from "../../services/FolderService";
|
||||
import { isValidScope } from "../../helpers/secrets";
|
||||
import path from "path";
|
||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||
|
||||
/**
|
||||
* Peform a batch of any specified CUD secret operations
|
||||
@ -46,14 +50,13 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
requests,
|
||||
secretPath,
|
||||
requests
|
||||
}: {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
requests: BatchSecretRequest[];
|
||||
secretPath: string;
|
||||
} = req.body;
|
||||
let secretPath = req.body.secretPath as string;
|
||||
let folderId = req.body.folderId as string;
|
||||
|
||||
const createSecrets: BatchSecret[] = [];
|
||||
@ -63,31 +66,31 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// get secret blind index salt
|
||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
if (folders && folderId !== "root") {
|
||||
const folder = searchByFolderId(folders.nodes, folderId as string);
|
||||
if (!folder) throw BadRequestError({ message: "Folder not found" });
|
||||
}
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
|
||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
|
||||
|
||||
// in service token when not giving secretpath folderid must be root
|
||||
// this is to avoid giving folderid when service tokens are used
|
||||
if (
|
||||
(!secretPath && folderId !== "root") ||
|
||||
(secretPath && secretPath !== serviceTkScopedSecretPath)
|
||||
) {
|
||||
if ((!secretPath && folderId !== "root") || (secretPath && !isValidScopeAccess)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
if (secretPath) {
|
||||
folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
}
|
||||
|
||||
if (folders && folderId !== "root") {
|
||||
const folder = searchByFolderIdWithDir(folders.nodes, folderId as string);
|
||||
if (!folder?.folder) throw BadRequestError({ message: "Folder not found" });
|
||||
secretPath = path.join(
|
||||
"/",
|
||||
...folder.dir.map(({ name }) => name).filter((name) => name !== "root")
|
||||
);
|
||||
}
|
||||
|
||||
@ -97,12 +100,10 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
let secretBlindIndex = "";
|
||||
switch (request.method) {
|
||||
case "POST":
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt(
|
||||
{
|
||||
secretName: request.secret.secretName,
|
||||
salt,
|
||||
}
|
||||
);
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName: request.secret.secretName,
|
||||
salt
|
||||
});
|
||||
|
||||
createSecrets.push({
|
||||
...request.secret,
|
||||
@ -113,16 +114,14 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
folder: folderId,
|
||||
secretBlindIndex,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
break;
|
||||
case "PATCH":
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt(
|
||||
{
|
||||
secretName: request.secret.secretName,
|
||||
salt,
|
||||
}
|
||||
);
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName: request.secret.secretName,
|
||||
salt
|
||||
});
|
||||
|
||||
updateSecrets.push({
|
||||
...request.secret,
|
||||
@ -130,7 +129,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
secretBlindIndex,
|
||||
folder: folderId,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
break;
|
||||
case "DELETE":
|
||||
@ -150,9 +149,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
...n._doc,
|
||||
_id: new Types.ObjectId(),
|
||||
secret: n._id,
|
||||
isDeleted: false,
|
||||
isDeleted: false
|
||||
};
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
const addAction = (await EELogService.createAction({
|
||||
@ -161,7 +160,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: createdSecrets.map((n) => n._id),
|
||||
secretIds: createdSecrets.map((n) => n._id)
|
||||
})) as IAction;
|
||||
actions.push(addAction);
|
||||
|
||||
@ -175,8 +174,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -195,7 +194,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
listedSecretsObj = req.secrets.reduce(
|
||||
(obj: any, secret: ISecret) => ({
|
||||
...obj,
|
||||
[secret._id.toString()]: secret,
|
||||
[secret._id.toString()]: secret
|
||||
}),
|
||||
{}
|
||||
);
|
||||
@ -204,16 +203,16 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
updateOne: {
|
||||
filter: {
|
||||
_id: new Types.ObjectId(u._id),
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
},
|
||||
update: {
|
||||
$inc: {
|
||||
version: 1,
|
||||
version: 1
|
||||
},
|
||||
...u,
|
||||
_id: new Types.ObjectId(u._id),
|
||||
},
|
||||
},
|
||||
_id: new Types.ObjectId(u._id)
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
await Secret.bulkWrite(updateOperations);
|
||||
@ -240,25 +239,25 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags: u.tags,
|
||||
folder: u.folder,
|
||||
folder: u.folder
|
||||
})
|
||||
);
|
||||
|
||||
await EESecretService.addSecretVersions({
|
||||
secretVersions,
|
||||
secretVersions
|
||||
});
|
||||
|
||||
updatedSecrets = await Secret.find({
|
||||
_id: {
|
||||
$in: updateSecrets.map((u) => new Types.ObjectId(u._id)),
|
||||
},
|
||||
$in: updateSecrets.map((u) => new Types.ObjectId(u._id))
|
||||
}
|
||||
});
|
||||
|
||||
const updateAction = (await EELogService.createAction({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
userId: req.user._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: updatedSecrets.map((u) => u._id),
|
||||
secretIds: updatedSecrets.map((u) => u._id)
|
||||
})) as IAction;
|
||||
actions.push(updateAction);
|
||||
|
||||
@ -272,8 +271,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -282,19 +281,19 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
if (deleteSecrets.length > 0) {
|
||||
await Secret.deleteMany({
|
||||
_id: {
|
||||
$in: deleteSecrets,
|
||||
},
|
||||
$in: deleteSecrets
|
||||
}
|
||||
});
|
||||
|
||||
await EESecretService.markDeletedSecretVersions({
|
||||
secretIds: deleteSecrets,
|
||||
secretIds: deleteSecrets
|
||||
});
|
||||
|
||||
const deleteAction = (await EELogService.createAction({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
userId: req.user._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: deleteSecrets,
|
||||
secretIds: deleteSecrets
|
||||
})) as IAction;
|
||||
actions.push(deleteAction);
|
||||
|
||||
@ -307,8 +306,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -320,7 +319,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
actions,
|
||||
channel,
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
}
|
||||
|
||||
@ -328,14 +327,17 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
}),
|
||||
environment,
|
||||
// root condition else this will be filled according to the path or folderid
|
||||
secretPath: secretPath || "/"
|
||||
})
|
||||
});
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folderId,
|
||||
folderId
|
||||
});
|
||||
|
||||
const resObj: { [key: string]: ISecret[] | string[] } = {};
|
||||
@ -418,7 +420,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
secretPath
|
||||
}: {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
@ -435,8 +437,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
);
|
||||
if (!hasAccess) {
|
||||
throw UnauthorizedRequestError({
|
||||
message:
|
||||
"You do not have the necessary permission(s) perform this action",
|
||||
message: "You do not have the necessary permission(s) perform this action"
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -449,28 +450,27 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
// case: create 1 secret
|
||||
listOfSecretsToCreate = [req.body.secrets];
|
||||
}
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
|
||||
const isValidScopeAccess = isValidScope(
|
||||
req.authData.authPayload,
|
||||
environment,
|
||||
secretPath || "/"
|
||||
);
|
||||
|
||||
// in service token when not giving secretpath folderid must be root
|
||||
// this is to avoid giving folderid when service tokens are used
|
||||
if (
|
||||
(!secretPath && folderId !== "root") ||
|
||||
(secretPath && secretPath !== serviceTkScopedSecretPath)
|
||||
) {
|
||||
if ((!secretPath && folderId !== "root") || (secretPath && !isValidScopeAccess)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
if (secretPath) {
|
||||
folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
}
|
||||
|
||||
// get secret blind index salt
|
||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
type secretsToCreateType = {
|
||||
@ -502,15 +502,14 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags,
|
||||
tags
|
||||
}: secretsToCreateType) => {
|
||||
let secretBlindIndex;
|
||||
if (secretName) {
|
||||
secretBlindIndex =
|
||||
await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName,
|
||||
salt,
|
||||
});
|
||||
secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
|
||||
secretName,
|
||||
salt
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@ -532,22 +531,24 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags,
|
||||
tags
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const newlyCreatedSecrets: ISecret[] = (
|
||||
await Secret.insertMany(secretsToInsert)
|
||||
).map((insertedSecret) => insertedSecret.toObject());
|
||||
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map(
|
||||
(insertedSecret) => insertedSecret.toObject()
|
||||
);
|
||||
|
||||
setTimeout(async () => {
|
||||
// trigger event - push secrets
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
}),
|
||||
environment,
|
||||
secretPath: secretPath || "/"
|
||||
})
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
@ -567,7 +568,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretValueTag
|
||||
}) =>
|
||||
new SecretVersion({
|
||||
secret: _id,
|
||||
@ -586,9 +587,9 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
secretValueTag,
|
||||
folder: folderId,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
})
|
||||
),
|
||||
)
|
||||
});
|
||||
|
||||
const addAction = await EELogService.createAction({
|
||||
@ -597,7 +598,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: newlyCreatedSecrets.map((n) => n._id),
|
||||
secretIds: newlyCreatedSecrets.map((n) => n._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
@ -609,14 +610,14 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
actions: [addAction],
|
||||
channel,
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folderId,
|
||||
folderId
|
||||
});
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
@ -624,7 +625,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
postHogClient.capture({
|
||||
event: "secrets added",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: listOfSecretsToCreate.length,
|
||||
@ -632,13 +633,13 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
channel: channel,
|
||||
folderId,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets: newlyCreatedSecrets,
|
||||
secrets: newlyCreatedSecrets
|
||||
});
|
||||
};
|
||||
|
||||
@ -690,16 +691,13 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
*/
|
||||
|
||||
const { tagSlugs, secretPath } = req.query;
|
||||
const { tagSlugs, secretPath, include_imports } = req.query;
|
||||
let { folderId } = req.query;
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
if (
|
||||
(!folders && folderId && folderId !== "root") ||
|
||||
(!folders && secretPath)
|
||||
) {
|
||||
if ((!folders && folderId && folderId !== "root") || (!folders && secretPath)) {
|
||||
res.send({ secrets: [] });
|
||||
return;
|
||||
}
|
||||
@ -712,13 +710,15 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = req.authData.authPayload;
|
||||
const isValidScopeAccess = isValidScope(
|
||||
req.authData.authPayload,
|
||||
environment,
|
||||
(secretPath as string) || "/"
|
||||
);
|
||||
|
||||
// in service token when not giving secretpath folderid must be root
|
||||
// this is to avoid giving folderid when service tokens are used
|
||||
if (
|
||||
(!secretPath && folderId !== "root") ||
|
||||
(secretPath && secretPath !== serviceTkScopedSecretPath)
|
||||
) {
|
||||
if ((!secretPath && folderId !== "root") || (secretPath && !isValidScopeAccess)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
@ -738,8 +738,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// query tags table to get all tags ids for the tag names for the given workspace
|
||||
let tagIds = [];
|
||||
const tagNamesList =
|
||||
typeof tagSlugs === "string" && tagSlugs !== "" ? tagSlugs.split(",") : [];
|
||||
const tagNamesList = typeof tagSlugs === "string" && tagSlugs !== "" ? tagSlugs.split(",") : [];
|
||||
if (tagNamesList != undefined && tagNamesList.length != 0) {
|
||||
const workspaceFromDB = await Tag.find({ workspace: workspaceId });
|
||||
tagIds = _.map(tagNamesList, (tagName: string) => {
|
||||
@ -762,8 +761,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
);
|
||||
if (hasNoAccess) {
|
||||
throw UnauthorizedRequestError({
|
||||
message:
|
||||
"You do not have the necessary permission(s) perform this action",
|
||||
message: "You do not have the necessary permission(s) perform this action"
|
||||
});
|
||||
}
|
||||
|
||||
@ -773,8 +771,8 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
folder: folderId,
|
||||
$or: [
|
||||
{ user: req.user._id }, // personal secrets for this user
|
||||
{ user: { $exists: false } }, // shared secrets from workspace
|
||||
],
|
||||
{ user: { $exists: false } } // shared secrets from workspace
|
||||
]
|
||||
};
|
||||
|
||||
if (tagIds.length > 0) {
|
||||
@ -801,8 +799,8 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
$or: [
|
||||
{ user: userId }, // personal secrets for this user
|
||||
{ user: { $exists: false } }, // shared secrets from workspace
|
||||
],
|
||||
{ user: { $exists: false } } // shared secrets from workspace
|
||||
]
|
||||
};
|
||||
|
||||
if (tagIds.length > 0) {
|
||||
@ -820,7 +818,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
folder: folderId,
|
||||
user: { $exists: false }, // shared secrets only from workspace
|
||||
user: { $exists: false } // shared secrets only from workspace
|
||||
};
|
||||
|
||||
if (tagIds.length > 0) {
|
||||
@ -830,6 +828,12 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
secrets = await Secret.find(secretQuery).populate("tags");
|
||||
}
|
||||
|
||||
// TODO(akhilmhdh) - secret-imp change this to org type
|
||||
let importedSecrets: any[] = [];
|
||||
if (include_imports === "true") {
|
||||
importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId as string);
|
||||
}
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers["user-agent"]);
|
||||
|
||||
const readAction = await EELogService.createAction({
|
||||
@ -838,7 +842,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||
secretIds: secrets.map((n: any) => n._id),
|
||||
secretIds: secrets.map((n: any) => n._id)
|
||||
});
|
||||
|
||||
readAction &&
|
||||
@ -849,7 +853,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(workspaceId as string),
|
||||
actions: [readAction],
|
||||
channel,
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
@ -857,7 +861,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
postHogClient.capture({
|
||||
event: "secrets pulled",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
@ -865,13 +869,14 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
channel,
|
||||
folderId,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets,
|
||||
...(include_imports && { imports: importedSecrets })
|
||||
});
|
||||
};
|
||||
|
||||
@ -925,9 +930,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
const channel = req.headers?.["user-agent"]?.toLowerCase().includes("mozilla")
|
||||
? "web"
|
||||
: "cli";
|
||||
const channel = req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli";
|
||||
|
||||
interface PatchSecret {
|
||||
id: string;
|
||||
@ -943,51 +946,47 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
const updateOperationsToPerform = req.body.secrets.map(
|
||||
(secret: PatchSecret) => {
|
||||
const {
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags,
|
||||
} = secret;
|
||||
const updateOperationsToPerform = req.body.secrets.map((secret: PatchSecret) => {
|
||||
const {
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags
|
||||
} = secret;
|
||||
|
||||
return {
|
||||
updateOne: {
|
||||
filter: { _id: new Types.ObjectId(secret.id) },
|
||||
update: {
|
||||
$inc: {
|
||||
version: 1,
|
||||
},
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags,
|
||||
...(secretCommentCiphertext !== undefined &&
|
||||
secretCommentIV &&
|
||||
secretCommentTag
|
||||
? {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
}
|
||||
: {}),
|
||||
return {
|
||||
updateOne: {
|
||||
filter: { _id: new Types.ObjectId(secret.id) },
|
||||
update: {
|
||||
$inc: {
|
||||
version: 1
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
tags,
|
||||
...(secretCommentCiphertext !== undefined && secretCommentIV && secretCommentTag
|
||||
? {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
await Secret.bulkWrite(updateOperationsToPerform);
|
||||
|
||||
@ -1009,7 +1008,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
tags,
|
||||
tags
|
||||
} = secretModificationsBySecretId[secret._id.toString()];
|
||||
|
||||
return {
|
||||
@ -1018,9 +1017,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
workspace: secret.workspace,
|
||||
type: secret.type,
|
||||
environment: secret.environment,
|
||||
secretKeyCiphertext: secretKeyCiphertext
|
||||
? secretKeyCiphertext
|
||||
: secret.secretKeyCiphertext,
|
||||
secretKeyCiphertext: secretKeyCiphertext ? secretKeyCiphertext : secret.secretKeyCiphertext,
|
||||
secretKeyIV: secretKeyIV ? secretKeyIV : secret.secretKeyIV,
|
||||
secretKeyTag: secretKeyTag ? secretKeyTag : secret.secretKeyTag,
|
||||
secretValueCiphertext: secretValueCiphertext
|
||||
@ -1031,17 +1028,13 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext: secretCommentCiphertext
|
||||
? secretCommentCiphertext
|
||||
: secret.secretCommentCiphertext,
|
||||
secretCommentIV: secretCommentIV
|
||||
? secretCommentIV
|
||||
: secret.secretCommentIV,
|
||||
secretCommentTag: secretCommentTag
|
||||
? secretCommentTag
|
||||
: secret.secretCommentTag,
|
||||
secretCommentIV: secretCommentIV ? secretCommentIV : secret.secretCommentIV,
|
||||
secretCommentTag: secretCommentTag ? secretCommentTag : secret.secretCommentTag,
|
||||
tags: tags ? tags : secret.tags,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
};
|
||||
}),
|
||||
})
|
||||
};
|
||||
|
||||
await EESecretService.addSecretVersions(secretVersions);
|
||||
@ -1059,13 +1052,16 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
Object.keys(workspaceSecretObj).forEach(async (key) => {
|
||||
// trigger event - push secrets
|
||||
setTimeout(async () => {
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
}),
|
||||
});
|
||||
}, 10000);
|
||||
// This route is not used anymore thus keep it commented out as it does not expose environment
|
||||
// it will end up creating a lot of requests from the server
|
||||
// setTimeout(async () => {
|
||||
// await EventService.handleEvent({
|
||||
// event: eventPushSecrets({
|
||||
// workspaceId: new Types.ObjectId(key),
|
||||
// environment,
|
||||
// })
|
||||
// });
|
||||
// }, 10000);
|
||||
|
||||
const updateAction = await EELogService.createAction({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
@ -1073,7 +1069,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
@ -1085,7 +1081,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
actions: [updateAction],
|
||||
channel,
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
@ -1101,15 +1097,15 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
postHogClient.capture({
|
||||
event: "secrets modified",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: workspaceSecretObj[key].length,
|
||||
environment: workspaceSecretObj[key][0].environment,
|
||||
workspaceId: key,
|
||||
channel: channel,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -1117,9 +1113,9 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
secrets: await Secret.find({
|
||||
_id: {
|
||||
$in: req.secrets.map((secret: ISecret) => secret._id),
|
||||
},
|
||||
}),
|
||||
$in: req.secrets.map((secret: ISecret) => secret._id)
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
@ -1179,12 +1175,12 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
await Secret.deleteMany({
|
||||
_id: {
|
||||
$in: toDelete,
|
||||
},
|
||||
$in: toDelete
|
||||
}
|
||||
});
|
||||
|
||||
await EESecretService.markDeletedSecretVersions({
|
||||
secretIds: toDelete,
|
||||
secretIds: toDelete
|
||||
});
|
||||
|
||||
// group secrets into workspaces so deleted secrets can
|
||||
@ -1200,18 +1196,20 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
Object.keys(workspaceSecretObj).forEach(async (key) => {
|
||||
// trigger event - push secrets
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
}),
|
||||
});
|
||||
// DEPRECIATED(akhilmhdh): as this would cause server to send so many request
|
||||
// and this route is not used anymore thus like snapshot keeping it commented out
|
||||
// await EventService.handleEvent({
|
||||
// event: eventPushSecrets({
|
||||
// workspaceId: new Types.ObjectId(key)
|
||||
// })
|
||||
// });
|
||||
const deleteAction = await EELogService.createAction({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
userId: req.user?._id,
|
||||
serviceAccountId: req.serviceAccount?._id,
|
||||
serviceTokenDataId: req.serviceTokenData?._id,
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id),
|
||||
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
@ -1223,7 +1221,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId: new Types.ObjectId(key),
|
||||
actions: [deleteAction],
|
||||
channel,
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
@ -1237,20 +1235,20 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
postHogClient.capture({
|
||||
event: "secrets deleted",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: workspaceSecretObj[key].length,
|
||||
environment: workspaceSecretObj[key][0].environment,
|
||||
workspaceId: key,
|
||||
channel: channel,
|
||||
userAgent: req.headers?.["user-agent"],
|
||||
},
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secrets: req.secrets,
|
||||
secrets: req.secrets
|
||||
});
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import crypto from "crypto";
|
||||
import bcrypt from "bcrypt";
|
||||
import {
|
||||
import {
|
||||
ServiceAccount,
|
||||
ServiceAccountKey,
|
||||
ServiceAccountOrganizationPermission,
|
||||
@ -21,11 +21,11 @@ import { getSaltRounds } from "../../config";
|
||||
*/
|
||||
export const getCurrentServiceAccount = async (req: Request, res: Response) => {
|
||||
const serviceAccount = await ServiceAccount.findById(req.serviceAccount._id);
|
||||
|
||||
|
||||
if (!serviceAccount) {
|
||||
throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
|
||||
}
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
serviceAccount,
|
||||
});
|
||||
@ -38,13 +38,13 @@ export const getCurrentServiceAccount = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const getServiceAccountById = async (req: Request, res: Response) => {
|
||||
const { serviceAccountId } = req.params;
|
||||
|
||||
|
||||
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
|
||||
|
||||
|
||||
if (!serviceAccount) {
|
||||
throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
|
||||
}
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
serviceAccount,
|
||||
});
|
||||
@ -73,7 +73,7 @@ export const createServiceAccount = async (req: Request, res: Response) => {
|
||||
|
||||
const secret = crypto.randomBytes(16).toString("base64");
|
||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||
|
||||
|
||||
// create service account
|
||||
const serviceAccount = await new ServiceAccount({
|
||||
name,
|
||||
@ -83,17 +83,17 @@ export const createServiceAccount = async (req: Request, res: Response) => {
|
||||
lastUsed: new Date(),
|
||||
expiresAt,
|
||||
secretHash,
|
||||
}).save();
|
||||
|
||||
}).save()
|
||||
|
||||
const serviceAccountObj = serviceAccount.toObject();
|
||||
|
||||
delete serviceAccountObj.secretHash;
|
||||
|
||||
delete (serviceAccountObj as any).secretHash;
|
||||
|
||||
// provision default org-level permission for service account
|
||||
await new ServiceAccountOrganizationPermission({
|
||||
serviceAccount: serviceAccount._id,
|
||||
}).save();
|
||||
|
||||
|
||||
const secretId = Buffer.from(serviceAccount._id.toString(), "hex").toString("base64");
|
||||
|
||||
return res.status(200).send({
|
||||
@ -111,7 +111,7 @@ export const createServiceAccount = async (req: Request, res: Response) => {
|
||||
export const changeServiceAccountName = async (req: Request, res: Response) => {
|
||||
const { serviceAccountId } = req.params;
|
||||
const { name } = req.body;
|
||||
|
||||
|
||||
const serviceAccount = await ServiceAccount.findOneAndUpdate(
|
||||
{
|
||||
_id: new Types.ObjectId(serviceAccountId),
|
||||
@ -123,7 +123,7 @@ export const changeServiceAccountName = async (req: Request, res: Response) => {
|
||||
new: true,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
serviceAccount,
|
||||
});
|
||||
@ -142,7 +142,7 @@ export const addServiceAccountKey = async (req: Request, res: Response) => {
|
||||
encryptedKey,
|
||||
nonce,
|
||||
} = req.body;
|
||||
|
||||
|
||||
const serviceAccountKey = await new ServiceAccountKey({
|
||||
encryptedKey,
|
||||
nonce,
|
||||
@ -163,7 +163,7 @@ export const getServiceAccountWorkspacePermissions = async (req: Request, res: R
|
||||
const serviceAccountWorkspacePermissions = await ServiceAccountWorkspacePermission.find({
|
||||
serviceAccount: req.serviceAccount._id,
|
||||
}).populate("workspace");
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
serviceAccountWorkspacePermissions,
|
||||
});
|
||||
@ -184,19 +184,19 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
|
||||
encryptedKey,
|
||||
nonce,
|
||||
} = req.body;
|
||||
|
||||
|
||||
if (!req.membership.workspace.environments.some((e: { name: string; slug: string }) => e.slug === environment)) {
|
||||
return res.status(400).send({
|
||||
message: "Failed to validate workspace environment",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const existingPermission = await ServiceAccountWorkspacePermission.findOne({
|
||||
serviceAccount: new Types.ObjectId(serviceAccountId),
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
});
|
||||
|
||||
|
||||
if (existingPermission) throw BadRequestError({ message: "Failed to add workspace permission to service account due to already-existing " });
|
||||
|
||||
const serviceAccountWorkspacePermission = await new ServiceAccountWorkspacePermission({
|
||||
@ -206,12 +206,12 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
|
||||
read,
|
||||
write,
|
||||
}).save();
|
||||
|
||||
|
||||
const existingServiceAccountKey = await ServiceAccountKey.findOne({
|
||||
serviceAccount: new Types.ObjectId(serviceAccountId),
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
});
|
||||
|
||||
|
||||
if (!existingServiceAccountKey) {
|
||||
await new ServiceAccountKey({
|
||||
encryptedKey,
|
||||
@ -242,7 +242,7 @@ export const deleteServiceAccountWorkspacePermission = async (req: Request, res:
|
||||
serviceAccount,
|
||||
workspace,
|
||||
});
|
||||
|
||||
|
||||
if (count === 0) {
|
||||
await ServiceAccountKey.findOneAndDelete({
|
||||
serviceAccount,
|
||||
@ -294,12 +294,12 @@ export const deleteServiceAccount = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const getServiceAccountKeys = async (req: Request, res: Response) => {
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
|
||||
|
||||
const serviceAccountKeys = await ServiceAccountKey.find({
|
||||
serviceAccount: req.serviceAccount._id,
|
||||
...(workspaceId ? { workspace: new Types.ObjectId(workspaceId) } : {}),
|
||||
});
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
serviceAccountKeys,
|
||||
});
|
||||
|
@ -2,14 +2,9 @@ import { Request, Response } from "express";
|
||||
import crypto from "crypto";
|
||||
import bcrypt from "bcrypt";
|
||||
import { ServiceAccount, ServiceTokenData, User } from "../../models";
|
||||
import {
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
} from "../../variables";
|
||||
import { AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT } from "../../variables";
|
||||
import { getSaltRounds } from "../../config";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import Folder from "../../models/folder";
|
||||
import { getFolderByPath } from "../../services/FolderService";
|
||||
|
||||
/**
|
||||
* Return service token data associated with service token on request
|
||||
@ -46,14 +41,13 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
|
||||
|
||||
if (!(req.authData.authPayload instanceof ServiceTokenData))
|
||||
throw BadRequestError({
|
||||
message: "Failed accepted client validation for service token data",
|
||||
message: "Failed accepted client validation for service token data"
|
||||
});
|
||||
|
||||
const serviceTokenData = await ServiceTokenData.findById(
|
||||
req.authData.authPayload._id
|
||||
)
|
||||
const serviceTokenData = await ServiceTokenData.findById(req.authData.authPayload._id)
|
||||
.select("+encryptedKey +iv +tag")
|
||||
.populate("user").lean();
|
||||
.populate("user")
|
||||
.lean();
|
||||
|
||||
return res.status(200).json(serviceTokenData);
|
||||
};
|
||||
@ -68,29 +62,7 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
|
||||
export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
let serviceTokenData;
|
||||
|
||||
const {
|
||||
name,
|
||||
workspaceId,
|
||||
environment,
|
||||
encryptedKey,
|
||||
iv,
|
||||
tag,
|
||||
expiresIn,
|
||||
secretPath,
|
||||
permissions,
|
||||
} = req.body;
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
});
|
||||
|
||||
if (folders) {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||
if (folder == undefined) {
|
||||
throw BadRequestError({ message: "Path for service token does not exist" })
|
||||
}
|
||||
}
|
||||
const { name, workspaceId, encryptedKey, iv, tag, expiresIn, permissions, scopes } = req.body;
|
||||
|
||||
const secret = crypto.randomBytes(16).toString("hex");
|
||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||
@ -103,10 +75,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
|
||||
let user, serviceAccount;
|
||||
|
||||
if (
|
||||
req.authData.authMode === AUTH_MODE_JWT &&
|
||||
req.authData.authPayload instanceof User
|
||||
) {
|
||||
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
|
||||
user = req.authData.authPayload._id;
|
||||
}
|
||||
|
||||
@ -120,17 +89,16 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
serviceTokenData = await new ServiceTokenData({
|
||||
name,
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
user,
|
||||
serviceAccount,
|
||||
scopes,
|
||||
lastUsed: new Date(),
|
||||
expiresAt,
|
||||
secretHash,
|
||||
encryptedKey,
|
||||
iv,
|
||||
tag,
|
||||
secretPath,
|
||||
permissions,
|
||||
permissions
|
||||
}).save();
|
||||
|
||||
// return service token data without sensitive data
|
||||
@ -142,7 +110,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
|
||||
return res.status(200).send({
|
||||
serviceToken,
|
||||
serviceTokenData,
|
||||
serviceTokenData
|
||||
});
|
||||
};
|
||||
|
||||
@ -155,11 +123,9 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
||||
const { serviceTokenDataId } = req.params;
|
||||
|
||||
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(
|
||||
serviceTokenDataId
|
||||
);
|
||||
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
|
||||
|
||||
return res.status(200).send({
|
||||
serviceTokenData,
|
||||
serviceTokenData
|
||||
});
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
||||
* @returns
|
||||
*/
|
||||
export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
let user, token, refreshToken;
|
||||
let user;
|
||||
const {
|
||||
email,
|
||||
firstName,
|
||||
@ -119,7 +119,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
});
|
||||
|
||||
token = tokens.token;
|
||||
const token = tokens.token;
|
||||
|
||||
// sending a welcome email to new users
|
||||
if (await getLoopsApiKey()) {
|
||||
@ -159,7 +159,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const completeAccountInvite = async (req: Request, res: Response) => {
|
||||
let user, token, refreshToken;
|
||||
let user;
|
||||
const {
|
||||
email,
|
||||
firstName,
|
||||
@ -244,7 +244,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
});
|
||||
|
||||
token = tokens.token;
|
||||
const token = tokens.token;
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
res.cookie("jid", tokens.refreshToken, {
|
||||
|
@ -3,7 +3,6 @@ import { Types } from "mongoose";
|
||||
import { Membership, Secret } from "../../models";
|
||||
import Tag from "../../models/tag";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { MongoError } from "mongodb";
|
||||
|
||||
export const createWorkspaceTag = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
@ -16,7 +15,7 @@ export const createWorkspaceTag = async (req: Request, res: Response) => {
|
||||
user: new Types.ObjectId(req.user._id),
|
||||
};
|
||||
|
||||
const createdTag = await new Tag(tagToCreate);
|
||||
const createdTag = await new Tag(tagToCreate).save();
|
||||
|
||||
res.json(createdTag);
|
||||
};
|
||||
@ -49,7 +48,11 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => {
|
||||
|
||||
export const getWorkspaceTags = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
const workspaceTags = await Tag.find({ workspace: workspaceId });
|
||||
|
||||
const workspaceTags = await Tag.find({
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
return res.json({
|
||||
workspaceTags
|
||||
});
|
||||
|
@ -1,8 +1,15 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import crypto from "crypto";
|
||||
import bcrypt from "bcrypt";
|
||||
import {
|
||||
APIKeyData,
|
||||
AuthProvider,
|
||||
MembershipOrg,
|
||||
User,
|
||||
TokenVersion,
|
||||
User
|
||||
} from "../../models";
|
||||
import { getSaltRounds } from "../../config";
|
||||
|
||||
/**
|
||||
* Return the current user.
|
||||
@ -74,6 +81,67 @@ export const updateMyMfaEnabled = async (req: Request, res: Response) => {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update name of the current user to [firstName, lastName].
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const updateName = async (req: Request, res: Response) => {
|
||||
const {
|
||||
firstName,
|
||||
lastName
|
||||
}: {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
} = req.body;
|
||||
|
||||
const user = await User.findByIdAndUpdate(
|
||||
req.user._id.toString(),
|
||||
{
|
||||
firstName,
|
||||
lastName: lastName ?? ""
|
||||
},
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
user,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update auth provider of the current user to [authProvider]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const updateAuthProvider = async (req: Request, res: Response) => {
|
||||
const {
|
||||
authProvider
|
||||
} = req.body;
|
||||
|
||||
if (req.user?.authProvider === AuthProvider.OKTA_SAML) return res.status(400).send({
|
||||
message: "Failed to update user authentication method because SAML SSO is enforced"
|
||||
});
|
||||
|
||||
const user = await User.findByIdAndUpdate(
|
||||
req.user._id.toString(),
|
||||
{
|
||||
authProvider
|
||||
},
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
user
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return organizations that the current user is part of.
|
||||
* @param req
|
||||
@ -117,3 +185,106 @@ export const getMyOrganizations = async (req: Request, res: Response) => {
|
||||
organizations,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return API keys belonging to current user.
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getMyAPIKeys = async (req: Request, res: Response) => {
|
||||
const apiKeyData = await APIKeyData.find({
|
||||
user: req.user._id,
|
||||
});
|
||||
|
||||
return res.status(200).send(apiKeyData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new API key for current user.
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const createAPIKey = async (req: Request, res: Response) => {
|
||||
const { name, expiresIn } = req.body;
|
||||
|
||||
const secret = crypto.randomBytes(16).toString("hex");
|
||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||
|
||||
let apiKeyData = await new APIKeyData({
|
||||
name,
|
||||
lastUsed: new Date(),
|
||||
expiresAt,
|
||||
user: req.user._id,
|
||||
secretHash,
|
||||
}).save();
|
||||
|
||||
// return api key data without sensitive data
|
||||
apiKeyData = (await APIKeyData.findById(apiKeyData._id)) as any;
|
||||
|
||||
if (!apiKeyData) throw new Error("Failed to find API key data");
|
||||
|
||||
const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;
|
||||
|
||||
return res.status(200).send({
|
||||
apiKey,
|
||||
apiKeyData,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete API key with id [apiKeyDataId] belonging to current user
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const deleteAPIKey = async (req: Request, res: Response) => {
|
||||
const { apiKeyDataId } = req.params;
|
||||
|
||||
const apiKeyData = await APIKeyData.findOneAndDelete({
|
||||
_id: new Types.ObjectId(apiKeyDataId),
|
||||
user: req.user._id
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
apiKeyData
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return active sessions (TokenVersion) belonging to user
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getMySessions = async (req: Request, res: Response) => {
|
||||
const tokenVersions = await TokenVersion.find({
|
||||
user: req.user._id
|
||||
});
|
||||
|
||||
return res.status(200).send(tokenVersions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke all active sessions belong to user
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const deleteMySessions = async (req: Request, res: Response) => {
|
||||
await TokenVersion.updateMany({
|
||||
user: req.user._id,
|
||||
}, {
|
||||
$inc: {
|
||||
refreshVersion: 1,
|
||||
accessVersion: 1,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully revoked all sessions"
|
||||
});
|
||||
}
|
@ -1,34 +1,29 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { Key, Membership, ServiceTokenData, Workspace } from "../../models";
|
||||
import {
|
||||
Key,
|
||||
Membership,
|
||||
ServiceTokenData,
|
||||
Workspace,
|
||||
} from "../../models";
|
||||
import {
|
||||
pullSecrets as pull,
|
||||
v2PushSecrets as push,
|
||||
reformatPullSecrets,
|
||||
pullSecrets as pull,
|
||||
v2PushSecrets as push,
|
||||
reformatPullSecrets
|
||||
} from "../../helpers/secret";
|
||||
import { pushKeys } from "../../helpers/key";
|
||||
import { EventService, TelemetryService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
|
||||
interface V2PushSecret {
|
||||
type: string; // personal or shared
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretKeyHash: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
secretValueHash: string;
|
||||
secretCommentCiphertext?: string;
|
||||
secretCommentIV?: string;
|
||||
secretCommentTag?: string;
|
||||
secretCommentHash?: string;
|
||||
type: string; // personal or shared
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
secretKeyTag: string;
|
||||
secretKeyHash: string;
|
||||
secretValueCiphertext: string;
|
||||
secretValueIV: string;
|
||||
secretValueTag: string;
|
||||
secretValueHash: string;
|
||||
secretCommentCiphertext?: string;
|
||||
secretCommentIV?: string;
|
||||
secretCommentTag?: string;
|
||||
secretCommentHash?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,7 +34,7 @@ interface V2PushSecret {
|
||||
* @returns
|
||||
*/
|
||||
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
||||
const { keys, environment, channel } = req.body;
|
||||
@ -62,13 +57,13 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
secrets,
|
||||
channel: channel ? channel : "cli",
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
|
||||
await pushKeys({
|
||||
userId: req.user._id,
|
||||
workspaceId,
|
||||
keys,
|
||||
keys
|
||||
});
|
||||
|
||||
if (postHogClient) {
|
||||
@ -79,8 +74,8 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel ? channel : "cli",
|
||||
},
|
||||
channel: channel ? channel : "cli"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -89,12 +84,13 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
}),
|
||||
secretPath: "/"
|
||||
})
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully uploaded workspace secrets",
|
||||
});
|
||||
return res.status(200).send({
|
||||
message: "Successfully uploaded workspace secrets"
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -105,7 +101,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const pullSecrets = async (req: Request, res: Response) => {
|
||||
let secrets;
|
||||
let secrets;
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
const environment: string = req.query.environment as string;
|
||||
const channel: string = req.query.channel as string;
|
||||
@ -128,7 +124,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
environment,
|
||||
channel: channel ? channel : "cli",
|
||||
ipAddress: req.realIP,
|
||||
ipAddress: req.realIP
|
||||
});
|
||||
|
||||
if (channel !== "cli") {
|
||||
@ -144,18 +140,18 @@ export const pullSecrets = async (req: Request, res: Response) => {
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
channel: channel ? channel : "cli",
|
||||
},
|
||||
channel: channel ? channel : "cli"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets,
|
||||
});
|
||||
return res.status(200).send({
|
||||
secrets
|
||||
});
|
||||
};
|
||||
|
||||
export const getWorkspaceKey = async (req: Request, res: Response) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.summary = 'Return encrypted project key'
|
||||
#swagger.description = 'Return encrypted project key'
|
||||
|
||||
@ -183,43 +179,37 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
let key;
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
key = await Key.findOne({
|
||||
const key = await Key.findOne({
|
||||
workspace: workspaceId,
|
||||
receiver: req.user._id,
|
||||
receiver: req.user._id
|
||||
}).populate("sender", "+publicKey");
|
||||
|
||||
if (!key) throw new Error("Failed to find workspace key");
|
||||
|
||||
return res.status(200).json(key);
|
||||
}
|
||||
export const getWorkspaceServiceTokenData = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
) => {
|
||||
return res.status(200).json(key);
|
||||
};
|
||||
export const getWorkspaceServiceTokenData = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
const serviceTokenData = await ServiceTokenData
|
||||
.find({
|
||||
workspace: workspaceId,
|
||||
})
|
||||
.select("+encryptedKey +iv +tag");
|
||||
const serviceTokenData = await ServiceTokenData.find({
|
||||
workspace: workspaceId
|
||||
}).select("+encryptedKey +iv +tag");
|
||||
|
||||
return res.status(200).send({
|
||||
serviceTokenData,
|
||||
});
|
||||
}
|
||||
return res.status(200).send({
|
||||
serviceTokenData
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return memberships for workspace with id [workspaceId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.summary = 'Return project memberships'
|
||||
#swagger.description = 'Return project memberships'
|
||||
|
||||
@ -255,22 +245,22 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
const memberships = await Membership.find({
|
||||
workspace: workspaceId,
|
||||
workspace: workspaceId
|
||||
}).populate("user", "+publicKey");
|
||||
|
||||
return res.status(200).send({
|
||||
memberships,
|
||||
});
|
||||
}
|
||||
return res.status(200).send({
|
||||
memberships
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update role of membership with id [membershipId] to role [role]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const updateWorkspaceMembership = async (req: Request, res: Response) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.summary = 'Update project membership'
|
||||
#swagger.description = 'Update project membership'
|
||||
|
||||
@ -323,33 +313,32 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
||||
}
|
||||
}
|
||||
*/
|
||||
const {
|
||||
membershipId,
|
||||
} = req.params;
|
||||
const { membershipId } = req.params;
|
||||
const { role } = req.body;
|
||||
|
||||
|
||||
const membership = await Membership.findByIdAndUpdate(
|
||||
membershipId,
|
||||
{
|
||||
role,
|
||||
}, {
|
||||
new: true,
|
||||
role
|
||||
},
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
membership,
|
||||
});
|
||||
}
|
||||
return res.status(200).send({
|
||||
membership
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete workspace membership with id [membershipId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
|
||||
/*
|
||||
/*
|
||||
#swagger.summary = 'Delete project membership'
|
||||
#swagger.description = 'Delete project membership'
|
||||
|
||||
@ -385,23 +374,21 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
|
||||
}
|
||||
}
|
||||
*/
|
||||
const {
|
||||
membershipId,
|
||||
} = req.params;
|
||||
|
||||
const { membershipId } = req.params;
|
||||
|
||||
const membership = await Membership.findByIdAndDelete(membershipId);
|
||||
|
||||
|
||||
if (!membership) throw new Error("Failed to delete workspace membership");
|
||||
|
||||
|
||||
await Key.deleteMany({
|
||||
receiver: membership.user,
|
||||
workspace: membership.workspace,
|
||||
workspace: membership.workspace
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
membership,
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
membership
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Change autoCapitilzation Rule of workspace
|
||||
@ -415,18 +402,18 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
||||
|
||||
const workspace = await Workspace.findOneAndUpdate(
|
||||
{
|
||||
_id: workspaceId,
|
||||
_id: workspaceId
|
||||
},
|
||||
{
|
||||
autoCapitalization,
|
||||
autoCapitalization
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully changed autoCapitalization setting",
|
||||
workspace,
|
||||
});
|
||||
return res.status(200).send({
|
||||
message: "Successfully changed autoCapitalization setting",
|
||||
workspace
|
||||
});
|
||||
};
|
||||
|
@ -56,7 +56,7 @@ export const login1 = async (req: Request, res: Response) => {
|
||||
|
||||
if (!user) throw new Error("Failed to find user");
|
||||
|
||||
if (user.authProvider) {
|
||||
if (user.authProvider && user.authProvider !== AuthProvider.EMAIL) {
|
||||
await validateProviderAuthToken({
|
||||
email,
|
||||
user,
|
||||
@ -106,7 +106,6 @@ export const login1 = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const login2 = async (req: Request, res: Response) => {
|
||||
try {
|
||||
|
||||
if (!req.headers["user-agent"]) throw InternalServerError({ message: "User-Agent header is required" });
|
||||
|
||||
const { email, clientProof, providerAuthToken } = req.body;
|
||||
@ -117,7 +116,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
|
||||
if (!user) throw new Error("Failed to find user");
|
||||
|
||||
if (user.authProvider) {
|
||||
if (user.authProvider && user.authProvider !== AuthProvider.EMAIL) {
|
||||
await validateProviderAuthToken({
|
||||
email,
|
||||
user,
|
||||
@ -189,7 +188,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
});
|
||||
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
res.cookie("jid", tokens.refreshToken, {
|
||||
httpOnly: true,
|
||||
|
@ -3,8 +3,15 @@ import { Types } from "mongoose";
|
||||
import { EventService, SecretService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
import { BotService } from "../../services";
|
||||
import { repackageSecretToRaw } from "../../helpers/secrets";
|
||||
import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets";
|
||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||
import Folder from "../../models/folder";
|
||||
import { getFolderByPath } from "../../services/FolderService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { IServiceTokenData } from "../../models";
|
||||
import { requireWorkspaceAuth } from "../../middleware";
|
||||
import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables";
|
||||
|
||||
/**
|
||||
* Return secrets for workspace with id [workspaceId] and environment
|
||||
@ -13,30 +20,75 @@ import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
||||
* @param res
|
||||
*/
|
||||
export const getSecretsRaw = async (req: Request, res: Response) => {
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
const secretPath = req.query.secretPath as string;
|
||||
let workspaceId = req.query.workspaceId as string;
|
||||
let environment = req.query.environment as string;
|
||||
let secretPath = req.query.secretPath as string;
|
||||
const includeImports = req.query.include_imports as string;
|
||||
|
||||
// if the service token has single scope, it will get all secrets for that scope by default
|
||||
const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData;
|
||||
if (serviceTokenDetails && serviceTokenDetails.scopes.length == 1 && !containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath)) {
|
||||
const scope = serviceTokenDetails.scopes[0];
|
||||
secretPath = scope.secretPath;
|
||||
environment = scope.environment;
|
||||
workspaceId = serviceTokenDetails.workspace.toString();
|
||||
} else {
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "query",
|
||||
locationEnvironment: "query",
|
||||
requiredPermissions: [PERMISSION_READ_SECRETS],
|
||||
requireBlindIndicesEnabled: true,
|
||||
requireE2EEOff: true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const secrets = await SecretService.getSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
});
|
||||
|
||||
const key = await BotService.getWorkspaceKeyWithBot({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (includeImports === "true") {
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
let folderId = "root";
|
||||
// if folder exist get it and replace folderid with new one
|
||||
if (folders) {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath as string);
|
||||
if (!folder) {
|
||||
throw BadRequestError({ message: "Folder not found" });
|
||||
}
|
||||
folderId = folder.id;
|
||||
}
|
||||
const importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId);
|
||||
return res.status(200).send({
|
||||
secrets: secrets.map((secret) =>
|
||||
repackageSecretToRaw({
|
||||
secret,
|
||||
key
|
||||
})
|
||||
),
|
||||
imports: importedSecrets.map((el) => ({
|
||||
...el,
|
||||
secrets: el.secrets.map((secret) => repackageSecretToRaw({ secret, key }))
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets: secrets.map((secret) => {
|
||||
const rep = repackageSecretToRaw({
|
||||
secret,
|
||||
key,
|
||||
key
|
||||
});
|
||||
|
||||
return rep;
|
||||
}),
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
@ -58,54 +110,47 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
type,
|
||||
secretPath,
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
});
|
||||
|
||||
const key = await BotService.getWorkspaceKeyWithBot({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret: repackageSecretToRaw({
|
||||
secret,
|
||||
key,
|
||||
}),
|
||||
key
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create secret with name [secretName] in plaintext
|
||||
* @param req
|
||||
* @param res
|
||||
* @param res
|
||||
*/
|
||||
export const createSecretRaw = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretValue,
|
||||
secretComment,
|
||||
secretPath = "/",
|
||||
} = req.body;
|
||||
const { workspaceId, environment, type, secretValue, secretComment, secretPath = "/" } = req.body;
|
||||
|
||||
const key = await BotService.getWorkspaceKeyWithBot({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: secretName,
|
||||
key,
|
||||
key
|
||||
});
|
||||
|
||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: secretValue,
|
||||
key,
|
||||
key
|
||||
});
|
||||
|
||||
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: secretComment,
|
||||
key,
|
||||
key
|
||||
});
|
||||
|
||||
const secret = await SecretService.createSecret({
|
||||
@ -123,14 +168,15 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
||||
secretPath,
|
||||
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
|
||||
secretCommentIV: secretCommentEncrypted.iv,
|
||||
secretCommentTag: secretCommentEncrypted.tag,
|
||||
secretCommentTag: secretCommentEncrypted.tag
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
}),
|
||||
secretPath
|
||||
})
|
||||
});
|
||||
|
||||
const secretWithoutBlindIndex = secret.toObject();
|
||||
@ -139,10 +185,10 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
secret: repackageSecretToRaw({
|
||||
secret: secretWithoutBlindIndex,
|
||||
key,
|
||||
}),
|
||||
key
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update secret with name [secretName]
|
||||
@ -151,21 +197,15 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretValue,
|
||||
secretPath = "/",
|
||||
} = req.body;
|
||||
const { workspaceId, environment, type, secretValue, secretPath = "/" } = req.body;
|
||||
|
||||
const key = await BotService.getWorkspaceKeyWithBot({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: secretValue,
|
||||
key,
|
||||
key
|
||||
});
|
||||
|
||||
const secret = await SecretService.updateSecret({
|
||||
@ -177,21 +217,22 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext: secretValueEncrypted.ciphertext,
|
||||
secretValueIV: secretValueEncrypted.iv,
|
||||
secretValueTag: secretValueEncrypted.tag,
|
||||
secretPath,
|
||||
secretPath
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
}),
|
||||
secretPath
|
||||
})
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret: repackageSecretToRaw({
|
||||
secret,
|
||||
key,
|
||||
}),
|
||||
key
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
@ -202,12 +243,7 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretPath = "/",
|
||||
} = req.body;
|
||||
const { workspaceId, environment, type, secretPath = "/" } = req.body;
|
||||
|
||||
const { secret } = await SecretService.deleteSecret({
|
||||
secretName,
|
||||
@ -215,25 +251,26 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData,
|
||||
secretPath,
|
||||
secretPath
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
}),
|
||||
secretPath
|
||||
})
|
||||
});
|
||||
|
||||
const key = await BotService.getWorkspaceKeyWithBot({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret: repackageSecretToRaw({
|
||||
secret,
|
||||
key,
|
||||
}),
|
||||
key
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
@ -247,16 +284,35 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
const secretPath = req.query.secretPath as string;
|
||||
const includeImports = req.query.include_imports as string;
|
||||
|
||||
const secrets = await SecretService.getSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
});
|
||||
|
||||
if (includeImports === "true") {
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
let folderId = "root";
|
||||
// if folder exist get it and replace folderid with new one
|
||||
if (folders) {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath as string);
|
||||
if (!folder) {
|
||||
throw BadRequestError({ message: "Folder not found" });
|
||||
}
|
||||
folderId = folder.id;
|
||||
}
|
||||
const importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId);
|
||||
return res.status(200).send({
|
||||
secrets,
|
||||
imports: importedSecrets
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
secrets,
|
||||
secrets
|
||||
});
|
||||
};
|
||||
|
||||
@ -278,11 +334,11 @@ export const getSecretByName = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
type,
|
||||
secretPath,
|
||||
authData: req.authData,
|
||||
authData: req.authData
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret,
|
||||
secret
|
||||
});
|
||||
};
|
||||
|
||||
@ -306,7 +362,7 @@ export const createSecret = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretPath = "/",
|
||||
secretPath = "/"
|
||||
} = req.body;
|
||||
|
||||
const secret = await SecretService.createSecret({
|
||||
@ -324,25 +380,25 @@ export const createSecret = async (req: Request, res: Response) => {
|
||||
secretPath,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentTag
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
}),
|
||||
secretPath
|
||||
})
|
||||
});
|
||||
|
||||
const secretWithoutBlindIndex = secret.toObject();
|
||||
delete secretWithoutBlindIndex.secretBlindIndex;
|
||||
|
||||
return res.status(200).send({
|
||||
secret: secretWithoutBlindIndex,
|
||||
secret: secretWithoutBlindIndex
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Update secret with name [secretName]
|
||||
* @param req
|
||||
@ -357,7 +413,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretPath = "/",
|
||||
secretPath = "/"
|
||||
} = req.body;
|
||||
|
||||
const secret = await SecretService.updateSecret({
|
||||
@ -369,18 +425,19 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretPath,
|
||||
secretPath
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
}),
|
||||
secretPath
|
||||
})
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret,
|
||||
secret
|
||||
});
|
||||
};
|
||||
|
||||
@ -391,12 +448,7 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||
const { secretName } = req.params;
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
type,
|
||||
secretPath = "/",
|
||||
} = req.body;
|
||||
const { workspaceId, environment, type, secretPath = "/" } = req.body;
|
||||
|
||||
const { secret } = await SecretService.deleteSecret({
|
||||
secretName,
|
||||
@ -404,17 +456,18 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||
environment,
|
||||
type,
|
||||
authData: req.authData,
|
||||
secretPath,
|
||||
secretPath
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
event: eventPushSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
}),
|
||||
secretPath
|
||||
})
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret,
|
||||
secret
|
||||
});
|
||||
};
|
||||
|
@ -12,6 +12,7 @@ import { standardRequest } from "../../config/request";
|
||||
import { getHttpsEnabled, getJwtSignupSecret, getLoopsApiKey } from "../../config";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { TelemetryService } from "../../services";
|
||||
import { AuthProvider } from "../../models";
|
||||
|
||||
/**
|
||||
* Complete setting up user by adding their personal and auth information as part of the
|
||||
@ -116,11 +117,13 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
if (!user)
|
||||
throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
|
||||
|
||||
// initialize default organization and workspace
|
||||
await initializeDefaultOrg({
|
||||
organizationName,
|
||||
user,
|
||||
});
|
||||
if (user.authProvider !== AuthProvider.OKTA_SAML) {
|
||||
// initialize default organization and workspace
|
||||
await initializeDefaultOrg({
|
||||
organizationName,
|
||||
user,
|
||||
});
|
||||
}
|
||||
|
||||
// update organization membership statuses that are
|
||||
// invited to completed with user attached
|
||||
@ -174,7 +177,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
distinctId: email,
|
||||
properties: {
|
||||
email,
|
||||
attributionSource,
|
||||
...(attributionSource ? { attributionSource } : {})
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import * as secretController from "./secretController";
|
||||
import * as secretSnapshotController from "./secretSnapshotController";
|
||||
import * as organizationsController from "./organizationsController";
|
||||
import * as ssoController from "./ssoController";
|
||||
import * as usersController from "./usersController";
|
||||
import * as workspaceController from "./workspaceController";
|
||||
import * as actionController from "./actionController";
|
||||
import * as membershipController from "./membershipController";
|
||||
@ -10,6 +12,8 @@ export {
|
||||
secretController,
|
||||
secretSnapshotController,
|
||||
organizationsController,
|
||||
ssoController,
|
||||
usersController,
|
||||
workspaceController,
|
||||
actionController,
|
||||
membershipController,
|
||||
|
@ -27,6 +27,30 @@ export const getOrganizationPlan = async (req: Request, res: Response) => {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return checkout url for pro trial
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const startOrganizationTrial = async (req: Request, res: Response) => {
|
||||
const { organizationId } = req.params;
|
||||
const { success_url } = req.body;
|
||||
|
||||
const { data: { url } } = await licenseServerKeyRequest.post(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/session/trial`,
|
||||
{
|
||||
success_url
|
||||
}
|
||||
);
|
||||
|
||||
EELicenseService.delPlan(organizationId);
|
||||
|
||||
return res.status(200).send({
|
||||
url
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the organization's current plan's billing info
|
||||
* @param req
|
||||
@ -154,6 +178,12 @@ export const addOrganizationTaxId = async (req: Request, res: Response) => {
|
||||
return res.status(200).send(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tax id with id [taxId] from organization tax ids on file
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
|
||||
const { taxId } = req.params;
|
||||
|
||||
@ -164,6 +194,12 @@ export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
|
||||
return res.status(200).send(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return organization's invoices on file
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getOrganizationInvoices = async (req: Request, res: Response) => {
|
||||
const { data: { invoices } } = await licenseServerKeyRequest.get(
|
||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/invoices`
|
||||
|
@ -54,23 +54,20 @@ export const getSecretVersions = async (req: Request, res: Response) => {
|
||||
}
|
||||
}
|
||||
*/
|
||||
const { secretId, workspaceId, environment, folderId } = req.params;
|
||||
const { secretId } = req.params;
|
||||
|
||||
const offset: number = parseInt(req.query.offset as string);
|
||||
const limit: number = parseInt(req.query.limit as string);
|
||||
|
||||
const secretVersions = await SecretVersion.find({
|
||||
secret: secretId,
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
folder: folderId,
|
||||
secret: secretId
|
||||
})
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(offset)
|
||||
.limit(limit);
|
||||
|
||||
return res.status(200).send({
|
||||
secretVersions,
|
||||
secretVersions
|
||||
});
|
||||
};
|
||||
|
||||
@ -135,7 +132,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
// validate secret version
|
||||
const oldSecretVersion = await SecretVersion.findOne({
|
||||
secret: secretId,
|
||||
version,
|
||||
version
|
||||
}).select("+secretBlindIndex");
|
||||
|
||||
if (!oldSecretVersion) throw new Error("Failed to find secret version");
|
||||
@ -154,7 +151,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
secretValueTag,
|
||||
algorithm,
|
||||
folder,
|
||||
keyEncoding,
|
||||
keyEncoding
|
||||
} = oldSecretVersion;
|
||||
|
||||
// update secret
|
||||
@ -162,7 +159,7 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
secretId,
|
||||
{
|
||||
$inc: {
|
||||
version: 1,
|
||||
version: 1
|
||||
},
|
||||
workspace,
|
||||
type,
|
||||
@ -177,10 +174,10 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
secretValueTag,
|
||||
folderId: folder,
|
||||
algorithm,
|
||||
keyEncoding,
|
||||
keyEncoding
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
@ -204,17 +201,17 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
secretValueTag,
|
||||
folder,
|
||||
algorithm,
|
||||
keyEncoding,
|
||||
keyEncoding
|
||||
}).save();
|
||||
|
||||
// take secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId: secret.workspace,
|
||||
environment,
|
||||
folderId: folder,
|
||||
folderId: folder
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
secret,
|
||||
secret
|
||||
});
|
||||
};
|
||||
|
241
backend/src/ee/controllers/v1/ssoController.ts
Normal file
241
backend/src/ee/controllers/v1/ssoController.ts
Normal file
@ -0,0 +1,241 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { BotOrgService } from "../../../services";
|
||||
import { SSOConfig } from "../../models";
|
||||
import {
|
||||
MembershipOrg,
|
||||
User
|
||||
} from "../../../models";
|
||||
import { getSSOConfigHelper } from "../../helpers/organizations";
|
||||
import { client } from "../../../config";
|
||||
import { ResourceNotFoundError } from "../../../utils/errors";
|
||||
import { getSiteURL } from "../../../config";
|
||||
import { EELicenseService } from "../../services";
|
||||
|
||||
/**
|
||||
* Redirect user to appropriate SSO endpoint after successful authentication
|
||||
* to finish inputting their master key for logging in or signing up
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const redirectSSO = async (req: Request, res: Response) => {
|
||||
if (req.isUserCompleted) {
|
||||
return res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||
}
|
||||
|
||||
return res.redirect(`${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return organization SAML SSO configuration
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getSSOConfig = async (req: Request, res: Response) => {
|
||||
const organizationId = req.query.organizationId as string;
|
||||
|
||||
const data = await getSSOConfigHelper({
|
||||
organizationId: new Types.ObjectId(organizationId)
|
||||
});
|
||||
|
||||
return res.status(200).send(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update organization SAML SSO configuration
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const updateSSOConfig = async (req: Request, res: Response) => {
|
||||
const {
|
||||
organizationId,
|
||||
authProvider,
|
||||
isActive,
|
||||
entryPoint,
|
||||
issuer,
|
||||
cert,
|
||||
} = req.body;
|
||||
|
||||
const plan = await EELicenseService.getPlan(organizationId);
|
||||
|
||||
if (!plan.samlSSO) return res.status(400).send({
|
||||
message: "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
|
||||
});
|
||||
|
||||
interface PatchUpdate {
|
||||
authProvider?: string;
|
||||
isActive?: boolean;
|
||||
encryptedEntryPoint?: string;
|
||||
entryPointIV?: string;
|
||||
entryPointTag?: string;
|
||||
encryptedIssuer?: string;
|
||||
issuerIV?: string;
|
||||
issuerTag?: string;
|
||||
encryptedCert?: string;
|
||||
certIV?: string;
|
||||
certTag?: string;
|
||||
}
|
||||
|
||||
const update: PatchUpdate = {};
|
||||
|
||||
if (authProvider) {
|
||||
update.authProvider = authProvider;
|
||||
}
|
||||
|
||||
if (isActive !== undefined) {
|
||||
update.isActive = isActive;
|
||||
}
|
||||
|
||||
const key = await BotOrgService.getSymmetricKey(
|
||||
new Types.ObjectId(organizationId)
|
||||
);
|
||||
|
||||
if (entryPoint) {
|
||||
const {
|
||||
ciphertext: encryptedEntryPoint,
|
||||
iv: entryPointIV,
|
||||
tag: entryPointTag
|
||||
} = client.encryptSymmetric(entryPoint, key);
|
||||
|
||||
update.encryptedEntryPoint = encryptedEntryPoint;
|
||||
update.entryPointIV = entryPointIV;
|
||||
update.entryPointTag = entryPointTag;
|
||||
}
|
||||
|
||||
if (issuer) {
|
||||
const {
|
||||
ciphertext: encryptedIssuer,
|
||||
iv: issuerIV,
|
||||
tag: issuerTag
|
||||
} = client.encryptSymmetric(issuer, key);
|
||||
|
||||
update.encryptedIssuer = encryptedIssuer;
|
||||
update.issuerIV = issuerIV;
|
||||
update.issuerTag = issuerTag;
|
||||
}
|
||||
|
||||
if (cert) {
|
||||
const {
|
||||
ciphertext: encryptedCert,
|
||||
iv: certIV,
|
||||
tag: certTag
|
||||
} = client.encryptSymmetric(cert, key);
|
||||
|
||||
update.encryptedCert = encryptedCert;
|
||||
update.certIV = certIV;
|
||||
update.certTag = certTag;
|
||||
}
|
||||
|
||||
const ssoConfig = await SSOConfig.findOneAndUpdate(
|
||||
{
|
||||
organization: new Types.ObjectId(organizationId)
|
||||
},
|
||||
update,
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
if (!ssoConfig) throw ResourceNotFoundError({
|
||||
message: "Failed to find SSO config to update"
|
||||
});
|
||||
|
||||
if (update.isActive !== undefined) {
|
||||
const membershipOrgs = await MembershipOrg.find({
|
||||
organization: new Types.ObjectId(organizationId)
|
||||
}).select("user");
|
||||
|
||||
if (update.isActive) {
|
||||
await User.updateMany(
|
||||
{
|
||||
_id: {
|
||||
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
|
||||
}
|
||||
},
|
||||
{
|
||||
authProvider: ssoConfig.authProvider
|
||||
}
|
||||
);
|
||||
} else {
|
||||
await User.updateMany(
|
||||
{
|
||||
_id: {
|
||||
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
|
||||
}
|
||||
},
|
||||
{
|
||||
$unset: {
|
||||
authProvider: 1
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).send(ssoConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create organization SAML SSO configuration
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const createSSOConfig = async (req: Request, res: Response) => {
|
||||
const {
|
||||
organizationId,
|
||||
authProvider,
|
||||
isActive,
|
||||
entryPoint,
|
||||
issuer,
|
||||
cert
|
||||
} = req.body;
|
||||
|
||||
const plan = await EELicenseService.getPlan(organizationId);
|
||||
|
||||
if (!plan.samlSSO) return res.status(400).send({
|
||||
message: "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration."
|
||||
});
|
||||
|
||||
const key = await BotOrgService.getSymmetricKey(
|
||||
new Types.ObjectId(organizationId)
|
||||
);
|
||||
|
||||
const {
|
||||
ciphertext: encryptedEntryPoint,
|
||||
iv: entryPointIV,
|
||||
tag: entryPointTag
|
||||
} = client.encryptSymmetric(entryPoint, key);
|
||||
|
||||
const {
|
||||
ciphertext: encryptedIssuer,
|
||||
iv: issuerIV,
|
||||
tag: issuerTag
|
||||
} = client.encryptSymmetric(issuer, key);
|
||||
|
||||
const {
|
||||
ciphertext: encryptedCert,
|
||||
iv: certIV,
|
||||
tag: certTag
|
||||
} = client.encryptSymmetric(cert, key);
|
||||
|
||||
const ssoConfig = await new SSOConfig({
|
||||
organization: new Types.ObjectId(organizationId),
|
||||
authProvider,
|
||||
isActive,
|
||||
encryptedEntryPoint,
|
||||
entryPointIV,
|
||||
entryPointTag,
|
||||
encryptedIssuer,
|
||||
issuerIV,
|
||||
issuerTag,
|
||||
encryptedCert,
|
||||
certIV,
|
||||
certTag
|
||||
}).save();
|
||||
|
||||
return res.status(200).send(ssoConfig);
|
||||
}
|
13
backend/src/ee/controllers/v1/usersController.ts
Normal file
13
backend/src/ee/controllers/v1/usersController.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Request, Response } from "express";
|
||||
|
||||
/**
|
||||
* Return the ip address of the current user
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getMyIp = (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
ip: req.authData.authIP
|
||||
});
|
||||
}
|
@ -3,16 +3,20 @@ import { PipelineStage, Types } from "mongoose";
|
||||
import { Secret } from "../../../models";
|
||||
import {
|
||||
FolderVersion,
|
||||
IPType,
|
||||
ISecretVersion,
|
||||
Log,
|
||||
SecretSnapshot,
|
||||
SecretVersion,
|
||||
TFolderRootVersionSchema,
|
||||
TrustedIP
|
||||
} from "../../models";
|
||||
import { EESecretService } from "../../services";
|
||||
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
|
||||
import Folder, { TFolderSchema } from "../../../models/folder";
|
||||
import { searchByFolderId } from "../../../services/FolderService";
|
||||
import { EELicenseService } from "../../services";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
||||
|
||||
/**
|
||||
* Return secret snapshots for workspace with id [workspaceId]
|
||||
@ -588,3 +592,147 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
|
||||
logs,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return trusted ips for workspace with id [workspaceId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getWorkspaceTrustedIps = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
const trustedIps = await TrustedIP.find({
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
trustedIps
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a trusted ip to workspace with id [workspaceId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
const {
|
||||
ipAddress: ip,
|
||||
comment,
|
||||
isActive
|
||||
} = req.body;
|
||||
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
|
||||
|
||||
if (!plan.ipAllowlisting) return res.status(400).send({
|
||||
message: "Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range."
|
||||
});
|
||||
|
||||
const isValidIPOrCidr = isValidIpOrCidr(ip);
|
||||
|
||||
if (!isValidIPOrCidr) return res.status(400).send({
|
||||
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||
});
|
||||
|
||||
const { ipAddress, type, prefix } = extractIPDetails(ip);
|
||||
|
||||
const trustedIp = await new TrustedIP({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
ipAddress,
|
||||
type,
|
||||
prefix,
|
||||
isActive,
|
||||
comment,
|
||||
}).save();
|
||||
|
||||
return res.status(200).send({
|
||||
trustedIp
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update trusted ip with id [trustedIpId] workspace with id [workspaceId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
const { workspaceId, trustedIpId } = req.params;
|
||||
const {
|
||||
ipAddress: ip,
|
||||
comment
|
||||
} = req.body;
|
||||
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
|
||||
|
||||
if (!plan.ipAllowlisting) return res.status(400).send({
|
||||
message: "Failed to update IP access range due to plan restriction. Upgrade plan to update IP access range."
|
||||
});
|
||||
|
||||
const isValidIPOrCidr = isValidIpOrCidr(ip);
|
||||
|
||||
if (!isValidIPOrCidr) return res.status(400).send({
|
||||
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
||||
});
|
||||
|
||||
const { ipAddress, type, prefix } = extractIPDetails(ip);
|
||||
|
||||
const updateObject: {
|
||||
ipAddress: string;
|
||||
type: IPType;
|
||||
comment: string;
|
||||
prefix?: number;
|
||||
$unset?: {
|
||||
prefix: number;
|
||||
}
|
||||
} = {
|
||||
ipAddress,
|
||||
type,
|
||||
comment
|
||||
};
|
||||
|
||||
if (prefix !== undefined) {
|
||||
updateObject.prefix = prefix;
|
||||
} else {
|
||||
updateObject.$unset = { prefix: 1 };
|
||||
}
|
||||
|
||||
const trustedIp = await TrustedIP.findOneAndUpdate(
|
||||
{
|
||||
_id: new Types.ObjectId(trustedIpId),
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
},
|
||||
updateObject,
|
||||
{
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
trustedIp
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete IP access range from workspace with id [workspaceId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
const { workspaceId, trustedIpId } = req.params;
|
||||
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
|
||||
|
||||
if (!plan.ipAllowlisting) return res.status(400).send({
|
||||
message: "Failed to delete IP access range due to plan restriction. Upgrade plan to delete IP access range."
|
||||
});
|
||||
|
||||
const trustedIp = await TrustedIP.findOneAndDelete({
|
||||
_id: new Types.ObjectId(trustedIpId),
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
trustedIp
|
||||
});
|
||||
}
|
64
backend/src/ee/helpers/organizations.ts
Normal file
64
backend/src/ee/helpers/organizations.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
SSOConfig
|
||||
} from "../models";
|
||||
import {
|
||||
BotOrgService
|
||||
} from "../../services";
|
||||
import { client } from "../../config";
|
||||
import { ValidationError } from "../../utils/errors";
|
||||
|
||||
export const getSSOConfigHelper = async ({
|
||||
organizationId,
|
||||
ssoConfigId
|
||||
}: {
|
||||
organizationId?: Types.ObjectId;
|
||||
ssoConfigId?: Types.ObjectId;
|
||||
}) => {
|
||||
|
||||
if (!organizationId && !ssoConfigId) throw ValidationError({
|
||||
message: "Getting SSO data requires either id of organization or SSO data"
|
||||
});
|
||||
|
||||
const ssoConfig = await SSOConfig.findOne({
|
||||
...(organizationId ? { organization: organizationId } : {}),
|
||||
...(ssoConfigId ? { _id: ssoConfigId } : {})
|
||||
});
|
||||
|
||||
if (!ssoConfig) throw new Error("Failed to find organization SSO data");
|
||||
|
||||
const key = await BotOrgService.getSymmetricKey(
|
||||
ssoConfig.organization
|
||||
);
|
||||
|
||||
const entryPoint = client.decryptSymmetric(
|
||||
ssoConfig.encryptedEntryPoint,
|
||||
key,
|
||||
ssoConfig.entryPointIV,
|
||||
ssoConfig.entryPointTag
|
||||
);
|
||||
|
||||
const issuer = client.decryptSymmetric(
|
||||
ssoConfig.encryptedIssuer,
|
||||
key,
|
||||
ssoConfig.issuerIV,
|
||||
ssoConfig.issuerTag
|
||||
);
|
||||
|
||||
const cert = client.decryptSymmetric(
|
||||
ssoConfig.encryptedCert,
|
||||
key,
|
||||
ssoConfig.certIV,
|
||||
ssoConfig.certTag
|
||||
);
|
||||
|
||||
return ({
|
||||
_id: ssoConfig._id,
|
||||
organization: ssoConfig.organization,
|
||||
authProvider: ssoConfig.authProvider,
|
||||
isActive: ssoConfig.isActive,
|
||||
entryPoint,
|
||||
issuer,
|
||||
cert
|
||||
});
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
import requireLicenseAuth from "./requireLicenseAuth";
|
||||
import requireSecretSnapshotAuth from "./requireSecretSnapshotAuth";
|
||||
|
||||
export {
|
||||
requireLicenseAuth,
|
||||
requireSecretSnapshotAuth,
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
|
||||
/**
|
||||
* Validate if organization hosting meets license requirements to
|
||||
* access a license-specific route.
|
||||
* @param {Object} obj
|
||||
* @param {String[]} obj.acceptedTiers
|
||||
*/
|
||||
const requireLicenseAuth = ({
|
||||
acceptedTiers,
|
||||
}: {
|
||||
acceptedTiers: string[];
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default requireLicenseAuth;
|
@ -66,6 +66,4 @@ const actionSchema = new Schema<IAction>(
|
||||
}
|
||||
);
|
||||
|
||||
const Action = model<IAction>("Action", actionSchema);
|
||||
|
||||
export default Action;
|
||||
export const Action = model<IAction>("Action", actionSchema);
|
@ -52,9 +52,7 @@ const folderRootVersionSchema = new Schema<TFolderRootVersionSchema>(
|
||||
}
|
||||
);
|
||||
|
||||
const FolderVersion = model<TFolderRootVersionSchema>(
|
||||
export const FolderVersion = model<TFolderRootVersionSchema>(
|
||||
"FolderVersion",
|
||||
folderRootVersionSchema
|
||||
);
|
||||
|
||||
export default FolderVersion;
|
||||
);
|
@ -1,18 +1,7 @@
|
||||
import SecretSnapshot, { ISecretSnapshot } from "./secretSnapshot";
|
||||
import SecretVersion, { ISecretVersion } from "./secretVersion";
|
||||
import FolderVersion, { TFolderRootVersionSchema } from "./folderVersion";
|
||||
import Log, { ILog } from "./log";
|
||||
import Action, { IAction } from "./action";
|
||||
|
||||
export {
|
||||
SecretSnapshot,
|
||||
ISecretSnapshot,
|
||||
SecretVersion,
|
||||
ISecretVersion,
|
||||
FolderVersion,
|
||||
TFolderRootVersionSchema,
|
||||
Log,
|
||||
ILog,
|
||||
Action,
|
||||
IAction,
|
||||
};
|
||||
export * from "./secretSnapshot";
|
||||
export * from "./secretVersion";
|
||||
export * from "./folderVersion";
|
||||
export * from "./log";
|
||||
export * from "./action";
|
||||
export * from "./ssoConfig";
|
||||
export * from "./trustedIp";
|
@ -63,11 +63,10 @@ const logSchema = new Schema<ILog>(
|
||||
ipAddress: {
|
||||
type: String,
|
||||
},
|
||||
}, {
|
||||
timestamps: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
const Log = model<ILog>("Log", logSchema);
|
||||
|
||||
export default Log;
|
||||
export const Log = model<ILog>("Log", logSchema);
|
@ -46,9 +46,7 @@ const secretSnapshotSchema = new Schema<ISecretSnapshot>(
|
||||
}
|
||||
);
|
||||
|
||||
const SecretSnapshot = model<ISecretSnapshot>(
|
||||
export const SecretSnapshot = model<ISecretSnapshot>(
|
||||
"SecretSnapshot",
|
||||
secretSnapshotSchema
|
||||
);
|
||||
|
||||
export default SecretSnapshot;
|
||||
);
|
@ -124,9 +124,7 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
||||
}
|
||||
);
|
||||
|
||||
const SecretVersion = model<ISecretVersion>(
|
||||
export const SecretVersion = model<ISecretVersion>(
|
||||
"SecretVersion",
|
||||
secretVersionSchema
|
||||
);
|
||||
|
||||
export default SecretVersion;
|
||||
);
|
72
backend/src/ee/models/ssoConfig.ts
Normal file
72
backend/src/ee/models/ssoConfig.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
|
||||
export enum AuthProvider {
|
||||
OKTA_SAML = "okta-saml",
|
||||
AZURE_SAML = "azure-saml",
|
||||
JUMPCLOUD_SAML = "jumpcloud-saml"
|
||||
}
|
||||
|
||||
export interface ISSOConfig {
|
||||
organization: Types.ObjectId;
|
||||
authProvider: AuthProvider;
|
||||
isActive: boolean;
|
||||
encryptedEntryPoint: string;
|
||||
entryPointIV: string;
|
||||
entryPointTag: string;
|
||||
encryptedIssuer: string;
|
||||
issuerIV: string;
|
||||
issuerTag: string;
|
||||
encryptedCert: string;
|
||||
certIV: string;
|
||||
certTag: string;
|
||||
}
|
||||
|
||||
const ssoConfigSchema = new Schema<ISSOConfig>(
|
||||
{
|
||||
organization: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Organization"
|
||||
},
|
||||
authProvider: {
|
||||
type: String,
|
||||
enum: AuthProvider,
|
||||
required: true
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
encryptedEntryPoint: {
|
||||
type: String
|
||||
},
|
||||
entryPointIV: {
|
||||
type: String
|
||||
},
|
||||
entryPointTag: {
|
||||
type: String
|
||||
},
|
||||
encryptedIssuer: {
|
||||
type: String
|
||||
},
|
||||
issuerIV: {
|
||||
type: String
|
||||
},
|
||||
issuerTag: {
|
||||
type: String
|
||||
},
|
||||
encryptedCert: {
|
||||
type: String
|
||||
},
|
||||
certIV: {
|
||||
type: String
|
||||
},
|
||||
certTag: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
export const SSOConfig = model<ISSOConfig>("SSOConfig", ssoConfigSchema);
|
54
backend/src/ee/models/trustedIp.ts
Normal file
54
backend/src/ee/models/trustedIp.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
|
||||
export enum IPType {
|
||||
IPV4 = "ipv4",
|
||||
IPV6 = "ipv6"
|
||||
}
|
||||
|
||||
export interface ITrustedIP {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
ipAddress: string;
|
||||
type: "ipv4" | "ipv6", // either IPv4/IPv6 address or network IPv4/IPv6 address
|
||||
isActive: boolean;
|
||||
comment: string;
|
||||
prefix?: number; // CIDR
|
||||
}
|
||||
|
||||
const trustedIpSchema = new Schema<ITrustedIP>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true
|
||||
},
|
||||
ipAddress: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: [
|
||||
IPType.IPV4,
|
||||
IPType.IPV6
|
||||
],
|
||||
required: true
|
||||
},
|
||||
prefix: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
comment: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
export const TrustedIP = model<ITrustedIP>("TrustedIP", trustedIpSchema);
|
@ -1,6 +1,8 @@
|
||||
import secret from "./secret";
|
||||
import secretSnapshot from "./secretSnapshot";
|
||||
import organizations from "./organizations";
|
||||
import sso from "./sso";
|
||||
import users from "./users";
|
||||
import workspace from "./workspace";
|
||||
import action from "./action";
|
||||
import cloudProducts from "./cloudProducts";
|
||||
@ -9,6 +11,8 @@ export {
|
||||
secret,
|
||||
secretSnapshot,
|
||||
organizations,
|
||||
sso,
|
||||
users,
|
||||
workspace,
|
||||
action,
|
||||
cloudProducts,
|
||||
|
@ -41,6 +41,21 @@ router.get(
|
||||
organizationsController.getOrganizationPlan
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/:organizationId/session/trial",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
acceptedStatuses: [ACCEPTED],
|
||||
}),
|
||||
param("organizationId").exists().trim(),
|
||||
body("success_url").exists().trim(),
|
||||
validateRequest,
|
||||
organizationsController.startOrganizationTrial
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:organizationId/plan/billing",
|
||||
requireAuth({
|
||||
|
122
backend/src/ee/routes/v1/sso.ts
Normal file
122
backend/src/ee/routes/v1/sso.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
import passport from "passport";
|
||||
import {
|
||||
AuthProvider
|
||||
} from "../../models";
|
||||
import {
|
||||
requireAuth,
|
||||
requireOrganizationAuth,
|
||||
validateRequest,
|
||||
} from "../../../middleware";
|
||||
import { body, query } from "express-validator";
|
||||
import { ssoController } from "../../controllers/v1";
|
||||
import { authLimiter } from "../../../helpers/rateLimiter";
|
||||
import {
|
||||
ACCEPTED,
|
||||
ADMIN,
|
||||
OWNER
|
||||
} from "../../../variables";
|
||||
|
||||
router.get(
|
||||
"/redirect/google",
|
||||
authLimiter,
|
||||
(req, res, next) => {
|
||||
passport.authenticate("google", {
|
||||
scope: ["profile", "email"],
|
||||
session: false,
|
||||
...(req.query.callback_port ? {
|
||||
state: req.query.callback_port as string
|
||||
} : {})
|
||||
})(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/google",
|
||||
passport.authenticate("google", {
|
||||
failureRedirect: "/login/provider/error",
|
||||
session: false
|
||||
}),
|
||||
ssoController.redirectSSO
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/redirect/saml2/:ssoIdentifier",
|
||||
authLimiter,
|
||||
(req, res, next) => {
|
||||
const options = {
|
||||
failureRedirect: "/",
|
||||
additionalParams: {
|
||||
RelayState: req.query.callback_port ?? ""
|
||||
},
|
||||
};
|
||||
passport.authenticate("saml", options)(req, res, next);
|
||||
}
|
||||
);
|
||||
|
||||
router.post("/saml2/:ssoIdentifier",
|
||||
passport.authenticate("saml", {
|
||||
failureRedirect: "/login/provider/error",
|
||||
failureFlash: true,
|
||||
session: false
|
||||
}),
|
||||
ssoController.redirectSSO
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/config",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN],
|
||||
acceptedStatuses: [ACCEPTED],
|
||||
locationOrganizationId: "query"
|
||||
}),
|
||||
query("organizationId").exists().trim(),
|
||||
validateRequest,
|
||||
ssoController.getSSOConfig
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/config",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN],
|
||||
acceptedStatuses: [ACCEPTED],
|
||||
locationOrganizationId: "body"
|
||||
}),
|
||||
body("organizationId").exists().trim(),
|
||||
body("authProvider").exists().isString().isIn([AuthProvider.OKTA_SAML]),
|
||||
body("isActive").exists().isBoolean(),
|
||||
body("entryPoint").exists().isString(),
|
||||
body("issuer").exists().isString(),
|
||||
body("cert").exists().isString(),
|
||||
validateRequest,
|
||||
ssoController.createSSOConfig
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/config",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN],
|
||||
acceptedStatuses: [ACCEPTED],
|
||||
locationOrganizationId: "body"
|
||||
}),
|
||||
body("organizationId").exists().trim(),
|
||||
body("authProvider").optional().isString(),
|
||||
body("isActive").optional().isBoolean(),
|
||||
body("entryPoint").optional().isString(),
|
||||
body("issuer").optional().isString(),
|
||||
body("cert").optional().isString(),
|
||||
validateRequest,
|
||||
ssoController.updateSSOConfig
|
||||
);
|
||||
|
||||
export default router;
|
17
backend/src/ee/routes/v1/users.ts
Normal file
17
backend/src/ee/routes/v1/users.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
import {
|
||||
requireAuth
|
||||
} from "../../../middleware";
|
||||
import { AUTH_MODE_API_KEY, AUTH_MODE_JWT } from "../../../variables";
|
||||
import { usersController } from "../../controllers/v1";
|
||||
|
||||
router.get(
|
||||
"/me/ip",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||
}),
|
||||
usersController.getMyIp
|
||||
);
|
||||
|
||||
export default router;
|
@ -6,13 +6,18 @@ import {
|
||||
validateRequest,
|
||||
} from "../../../middleware";
|
||||
import { body, param, query } from "express-validator";
|
||||
import { ADMIN, MEMBER } from "../../../variables";
|
||||
import {
|
||||
ADMIN,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_JWT,
|
||||
MEMBER
|
||||
} from "../../../variables";
|
||||
import { workspaceController } from "../../controllers/v1";
|
||||
|
||||
router.get(
|
||||
"/:workspaceId/secret-snapshots",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt", "apiKey"],
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@ -30,7 +35,7 @@ router.get(
|
||||
router.get(
|
||||
"/:workspaceId/secret-snapshots/count",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@ -46,7 +51,7 @@ router.get(
|
||||
router.post(
|
||||
"/:workspaceId/secret-snapshots/rollback",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt", "apiKey"],
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@ -63,7 +68,7 @@ router.post(
|
||||
router.get(
|
||||
"/:workspaceId/logs",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt", "apiKey"],
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@ -79,4 +84,66 @@ router.get(
|
||||
workspaceController.getWorkspaceLogs
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:workspaceId/trusted-ips",
|
||||
param("workspaceId").exists().isString().trim(),
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "params",
|
||||
}),
|
||||
workspaceController.getWorkspaceTrustedIps
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/:workspaceId/trusted-ips",
|
||||
param("workspaceId").exists().isString().trim(),
|
||||
body("ipAddress").exists().isString().trim(),
|
||||
body("comment").default("").isString().trim(),
|
||||
body("isActive").exists().isBoolean(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN],
|
||||
locationWorkspaceId: "params",
|
||||
}),
|
||||
workspaceController.addWorkspaceTrustedIp
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:workspaceId/trusted-ips/:trustedIpId",
|
||||
param("workspaceId").exists().isString().trim(),
|
||||
param("trustedIpId").exists().isString().trim(),
|
||||
body("ipAddress").isString().trim().default(""),
|
||||
body("comment").default("").isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN],
|
||||
locationWorkspaceId: "params",
|
||||
}),
|
||||
workspaceController.updateWorkspaceTrustedIp
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:workspaceId/trusted-ips/:trustedIpId",
|
||||
param("workspaceId").exists().isString().trim(),
|
||||
param("trustedIpId").exists().isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN],
|
||||
locationWorkspaceId: "params",
|
||||
}),
|
||||
workspaceController.deleteWorkspaceTrustedIp
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -26,10 +26,15 @@ interface FeatureSet {
|
||||
environmentsUsed: number;
|
||||
secretVersioning: boolean;
|
||||
pitRecovery: boolean;
|
||||
ipAllowlisting: boolean;
|
||||
rbac: boolean;
|
||||
customRateLimits: boolean;
|
||||
customAlerts: boolean;
|
||||
auditLogs: boolean;
|
||||
samlSSO: boolean;
|
||||
status: "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | null;
|
||||
trial_end: number | null;
|
||||
has_used_trial: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,10 +61,15 @@ class EELicenseService {
|
||||
environmentsUsed: 0,
|
||||
secretVersioning: true,
|
||||
pitRecovery: false,
|
||||
ipAllowlisting: false,
|
||||
rbac: true,
|
||||
customRateLimits: true,
|
||||
customAlerts: true,
|
||||
auditLogs: false,
|
||||
samlSSO: false,
|
||||
status: null,
|
||||
trial_end: null,
|
||||
has_used_trial: true
|
||||
}
|
||||
|
||||
public localFeatureSet: NodeCache;
|
||||
@ -67,7 +77,7 @@ class EELicenseService {
|
||||
constructor() {
|
||||
this._isLicenseValid = true;
|
||||
this.localFeatureSet = new NodeCache({
|
||||
stdTTL: 300,
|
||||
stdTTL: 60,
|
||||
});
|
||||
}
|
||||
|
||||
@ -108,6 +118,12 @@ class EELicenseService {
|
||||
await this.getPlan(organizationId, workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
public async delPlan(organizationId: string) {
|
||||
if (this.instanceType === "cloud") {
|
||||
this.localFeatureSet.del(`${organizationId}-`);
|
||||
}
|
||||
}
|
||||
|
||||
public async initGlobalFeatureSet() {
|
||||
const licenseServerKey = await getLicenseServerKey();
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { eventPushSecrets } from "./secret"
|
||||
import { eventPushSecrets } from "./secret";
|
||||
import { eventStartIntegration } from "./integration";
|
||||
|
||||
export {
|
||||
eventPushSecrets,
|
||||
}
|
||||
export { eventPushSecrets, eventStartIntegration };
|
||||
|
23
backend/src/events/integration.ts
Normal file
23
backend/src/events/integration.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Types } from "mongoose";
|
||||
import { EVENT_START_INTEGRATION } from "../variables";
|
||||
|
||||
/*
|
||||
* Return event for starting integrations
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace to push secrets to
|
||||
* @returns
|
||||
*/
|
||||
export const eventStartIntegration = ({
|
||||
workspaceId,
|
||||
environment
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
}) => {
|
||||
return {
|
||||
name: EVENT_START_INTEGRATION,
|
||||
workspaceId,
|
||||
environment,
|
||||
payload: {}
|
||||
};
|
||||
};
|
@ -1,64 +1,54 @@
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
EVENT_PULL_SECRETS,
|
||||
EVENT_PUSH_SECRETS,
|
||||
} from "../variables";
|
||||
import { EVENT_PULL_SECRETS, EVENT_PUSH_SECRETS } from "../variables";
|
||||
|
||||
interface PushSecret {
|
||||
ciphertextKey: string;
|
||||
ivKey: string;
|
||||
tagKey: string;
|
||||
hashKey: string;
|
||||
ciphertextValue: string;
|
||||
ivValue: string;
|
||||
tagValue: string;
|
||||
hashValue: string;
|
||||
type: "shared" | "personal";
|
||||
ciphertextKey: string;
|
||||
ivKey: string;
|
||||
tagKey: string;
|
||||
hashKey: string;
|
||||
ciphertextValue: string;
|
||||
ivValue: string;
|
||||
tagValue: string;
|
||||
hashValue: string;
|
||||
type: "shared" | "personal";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return event for pushing secrets
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace to push secrets to
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
const eventPushSecrets = ({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}) => {
|
||||
return {
|
||||
name: EVENT_PUSH_SECRETS,
|
||||
workspaceId,
|
||||
environment,
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
}) => {
|
||||
return ({
|
||||
name: EVENT_PUSH_SECRETS,
|
||||
workspaceId,
|
||||
environment,
|
||||
payload: {
|
||||
|
||||
},
|
||||
});
|
||||
}
|
||||
secretPath,
|
||||
payload: {}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return event for pulling secrets
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace to pull secrets from
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
const eventPullSecrets = ({
|
||||
const eventPullSecrets = ({ workspaceId }: { workspaceId: string }) => {
|
||||
return {
|
||||
name: EVENT_PULL_SECRETS,
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
}) => {
|
||||
return ({
|
||||
name: EVENT_PULL_SECRETS,
|
||||
workspaceId,
|
||||
payload: {
|
||||
payload: {}
|
||||
};
|
||||
};
|
||||
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
eventPushSecrets,
|
||||
}
|
||||
export { eventPushSecrets };
|
||||
|
@ -122,11 +122,11 @@ export const getAuthUserPayload = async ({
|
||||
}, {
|
||||
lastUsed: new Date(),
|
||||
});
|
||||
|
||||
|
||||
if (!tokenVersion) throw UnauthorizedRequestError({
|
||||
message: "Failed to validate access token",
|
||||
});
|
||||
|
||||
|
||||
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({
|
||||
message: "Failed to validate access token",
|
||||
});
|
||||
@ -150,8 +150,8 @@ export const getAuthSTDPayload = async ({
|
||||
}) => {
|
||||
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
|
||||
|
||||
let serviceTokenData = await ServiceTokenData
|
||||
.findById(TOKEN_IDENTIFIER, "+secretHash +expiresAt");
|
||||
const serviceTokenData = await ServiceTokenData
|
||||
.findById(TOKEN_IDENTIFIER, "+secretHash +expiresAt")
|
||||
|
||||
if (!serviceTokenData) {
|
||||
throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" });
|
||||
@ -168,7 +168,7 @@ export const getAuthSTDPayload = async ({
|
||||
message: "Failed to authenticate service token",
|
||||
});
|
||||
|
||||
serviceTokenData = await ServiceTokenData
|
||||
const serviceTokenDataToReturn = await ServiceTokenData
|
||||
.findOneAndUpdate({
|
||||
_id: new Types.ObjectId(TOKEN_IDENTIFIER),
|
||||
}, {
|
||||
@ -176,11 +176,11 @@ export const getAuthSTDPayload = async ({
|
||||
}, {
|
||||
new: true,
|
||||
})
|
||||
.select("+encryptedKey +iv +tag");
|
||||
.select("+encryptedKey +iv +tag")
|
||||
|
||||
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" });
|
||||
if (!serviceTokenDataToReturn) throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" });
|
||||
|
||||
return serviceTokenData;
|
||||
return serviceTokenDataToReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -275,11 +275,11 @@ export const getAuthAPIKeyPayload = async ({
|
||||
* @return {String} obj.token - issued JWT token
|
||||
* @return {String} obj.refreshToken - issued refresh token
|
||||
*/
|
||||
export const issueAuthTokens = async ({
|
||||
export const issueAuthTokens = async ({
|
||||
userId,
|
||||
ip,
|
||||
userAgent,
|
||||
}: {
|
||||
}: {
|
||||
userId: Types.ObjectId;
|
||||
ip: string;
|
||||
userAgent: string;
|
||||
@ -292,7 +292,7 @@ export const issueAuthTokens = async ({
|
||||
ip,
|
||||
userAgent,
|
||||
});
|
||||
|
||||
|
||||
if (!tokenVersion) {
|
||||
// case: no existing ip and user agent exists
|
||||
// -> create new (session) token version for ip and user agent
|
||||
@ -389,7 +389,7 @@ export const validateProviderAuthToken = async ({
|
||||
const decodedToken = <jwt.ProviderAuthJwtPayload>(
|
||||
jwt.verify(providerAuthToken, await getJwtProviderAuthSecret())
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
decodedToken.authProvider !== user.authProvider ||
|
||||
decodedToken.email !== email
|
||||
|
@ -275,3 +275,70 @@ export const decryptSymmetricHelper = async ({
|
||||
|
||||
return plaintext;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return decrypted comments for workspace secrets with id [workspaceId]
|
||||
* and [envionment] using bot
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.workspaceId - id of workspace
|
||||
* @param {String} obj.environment - environment
|
||||
*/
|
||||
export const getSecretsCommentBotHelper = async ({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
} : {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}) => {
|
||||
const content = {} as any;
|
||||
const key = await getKey({ workspaceId: workspaceId });
|
||||
|
||||
let folderId = "root";
|
||||
const folders = await Folder.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
});
|
||||
|
||||
if (!folders && secretPath !== "/") {
|
||||
throw InternalServerError({ message: "Folder not found" });
|
||||
}
|
||||
|
||||
if (folders) {
|
||||
const folder = getFolderByPath(folders.nodes, secretPath);
|
||||
if (!folder) {
|
||||
throw InternalServerError({ message: "Folder not found" });
|
||||
}
|
||||
folderId = folder.id;
|
||||
}
|
||||
|
||||
const secrets = await Secret.find({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
type: SECRET_SHARED,
|
||||
folder: folderId,
|
||||
});
|
||||
|
||||
secrets.forEach((secret: ISecret) => {
|
||||
if(secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) {
|
||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretKeyCiphertext,
|
||||
iv: secret.secretKeyIV,
|
||||
tag: secret.secretKeyTag,
|
||||
key,
|
||||
});
|
||||
|
||||
const commentValue = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretCommentCiphertext,
|
||||
iv: secret.secretCommentIV,
|
||||
tag: secret.secretCommentTag,
|
||||
key,
|
||||
});
|
||||
|
||||
content[secretKey] = commentValue;
|
||||
}
|
||||
});
|
||||
|
||||
return content;
|
||||
}
|
134
backend/src/helpers/botOrg.ts
Normal file
134
backend/src/helpers/botOrg.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { Types } from "mongoose";
|
||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||
import { BotOrg } from "../models";
|
||||
import { decryptSymmetric128BitHexKeyUTF8 } from "../utils/crypto";
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8
|
||||
} from "../variables";
|
||||
import { InternalServerError } from "../utils/errors";
|
||||
import { encryptSymmetric128BitHexKeyUTF8, generateKeyPair } from "../utils/crypto";
|
||||
|
||||
/**
|
||||
* Create a bot with name [name] for organization with id [organizationId]
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.name - name of bot
|
||||
* @param {String} obj.organizationId - id of organization that bot belongs to
|
||||
*/
|
||||
export const createBotOrg = async ({
|
||||
name,
|
||||
organizationId,
|
||||
}: {
|
||||
name: string;
|
||||
organizationId: Types.ObjectId;
|
||||
}) => {
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const { publicKey, privateKey } = generateKeyPair();
|
||||
const key = client.createSymmetricKey();
|
||||
|
||||
if (rootEncryptionKey) {
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: privateKeyIV,
|
||||
tag: privateKeyTag
|
||||
} = client.encryptSymmetric(privateKey, rootEncryptionKey);
|
||||
|
||||
const {
|
||||
ciphertext: encryptedSymmetricKey,
|
||||
iv: symmetricKeyIV,
|
||||
tag: symmetricKeyTag
|
||||
} = client.encryptSymmetric(key, rootEncryptionKey);
|
||||
|
||||
return await new BotOrg({
|
||||
name,
|
||||
organization: organizationId,
|
||||
publicKey,
|
||||
encryptedSymmetricKey,
|
||||
symmetricKeyIV,
|
||||
symmetricKeyTag,
|
||||
symmetricKeyAlgorithm: ALGORITHM_AES_256_GCM,
|
||||
symmetricKeyKeyEncoding: ENCODING_SCHEME_BASE64,
|
||||
encryptedPrivateKey,
|
||||
privateKeyIV,
|
||||
privateKeyTag,
|
||||
privateKeyAlgorithm: ALGORITHM_AES_256_GCM,
|
||||
privateKeyKeyEncoding: ENCODING_SCHEME_BASE64
|
||||
}).save();
|
||||
} else if (encryptionKey) {
|
||||
const {
|
||||
ciphertext: encryptedPrivateKey,
|
||||
iv: privateKeyIV,
|
||||
tag: privateKeyTag
|
||||
} = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: privateKey,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
const {
|
||||
ciphertext: encryptedSymmetricKey,
|
||||
iv: symmetricKeyIV,
|
||||
tag: symmetricKeyTag
|
||||
} = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: key,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
return await new BotOrg({
|
||||
name,
|
||||
organization: organizationId,
|
||||
publicKey,
|
||||
encryptedSymmetricKey,
|
||||
symmetricKeyIV,
|
||||
symmetricKeyTag,
|
||||
symmetricKeyAlgorithm: ALGORITHM_AES_256_GCM,
|
||||
symmetricKeyKeyEncoding: ENCODING_SCHEME_UTF8,
|
||||
encryptedPrivateKey,
|
||||
privateKeyIV,
|
||||
privateKeyTag,
|
||||
privateKeyAlgorithm: ALGORITHM_AES_256_GCM,
|
||||
privateKeyKeyEncoding: ENCODING_SCHEME_UTF8
|
||||
}).save();
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: "Failed to create new organization bot due to missing encryption key",
|
||||
});
|
||||
};
|
||||
|
||||
export const getSymmetricKeyHelper = async (organizationId: Types.ObjectId) => {
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
const encryptionKey = await getEncryptionKey();
|
||||
|
||||
const botOrg = await BotOrg.findOne({
|
||||
organization: organizationId
|
||||
});
|
||||
|
||||
if (!botOrg) throw new Error("Failed to find organization bot");
|
||||
|
||||
if (rootEncryptionKey && botOrg.symmetricKeyKeyEncoding == ENCODING_SCHEME_BASE64) {
|
||||
const key = client.decryptSymmetric(
|
||||
botOrg.encryptedSymmetricKey,
|
||||
rootEncryptionKey,
|
||||
botOrg.symmetricKeyIV,
|
||||
botOrg.symmetricKeyTag
|
||||
);
|
||||
|
||||
return key;
|
||||
} else if (encryptionKey && botOrg.symmetricKeyKeyEncoding === ENCODING_SCHEME_UTF8) {
|
||||
const key = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: botOrg.encryptedSymmetricKey,
|
||||
iv: botOrg.symmetricKeyIV,
|
||||
tag: botOrg.symmetricKeyTag,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: "Failed to match encryption key with organization bot symmetric key encoding"
|
||||
});
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Bot } from "../models";
|
||||
import { EVENT_PUSH_SECRETS } from "../variables";
|
||||
import { EVENT_PUSH_SECRETS, EVENT_START_INTEGRATION } from "../variables";
|
||||
import { IntegrationService } from "../services";
|
||||
import { triggerWebhook } from "../services/WebhookService";
|
||||
|
||||
interface Event {
|
||||
name: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
secretPath?: string;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
@ -19,22 +21,31 @@ interface Event {
|
||||
* @param {Object} obj.event.payload - payload of event (depends on event)
|
||||
*/
|
||||
export const handleEventHelper = async ({ event }: { event: Event }) => {
|
||||
const { workspaceId, environment } = event;
|
||||
const { workspaceId, environment, secretPath } = event;
|
||||
|
||||
// TODO: moduralize bot check into separate function
|
||||
const bot = await Bot.findOne({
|
||||
workspace: workspaceId,
|
||||
isActive: true,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
if (!bot) return;
|
||||
|
||||
switch (event.name) {
|
||||
case EVENT_PUSH_SECRETS:
|
||||
IntegrationService.syncIntegrations({
|
||||
workspaceId,
|
||||
environment,
|
||||
});
|
||||
if (bot) {
|
||||
await IntegrationService.syncIntegrations({
|
||||
workspaceId,
|
||||
environment
|
||||
});
|
||||
}
|
||||
triggerWebhook(workspaceId.toString(), environment || "", secretPath || "");
|
||||
break;
|
||||
case EVENT_START_INTEGRATION:
|
||||
if (bot) {
|
||||
IntegrationService.syncIntegrations({
|
||||
workspaceId,
|
||||
environment
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
INTEGRATION_VERCEL,
|
||||
} from "../variables";
|
||||
import { UnauthorizedRequestError } from "../utils/errors";
|
||||
import * as Sentry from "@sentry/node";
|
||||
|
||||
interface Update {
|
||||
workspace: string;
|
||||
@ -115,46 +116,60 @@ export const syncIntegrationsHelper = async ({
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
}) => {
|
||||
const integrations = await Integration.find({
|
||||
workspace: workspaceId,
|
||||
...(environment
|
||||
? {
|
||||
try {
|
||||
const integrations = await Integration.find({
|
||||
workspace: workspaceId,
|
||||
...(environment
|
||||
? {
|
||||
environment,
|
||||
}
|
||||
: {}),
|
||||
isActive: true,
|
||||
app: { $ne: null },
|
||||
});
|
||||
|
||||
// for each workspace integration, sync/push secrets
|
||||
// to that integration
|
||||
for await (const integration of integrations) {
|
||||
// get workspace, environment (shared) secrets
|
||||
const secrets = await BotService.getSecrets({
|
||||
// issue here?
|
||||
workspaceId: integration.workspace,
|
||||
environment: integration.environment,
|
||||
secretPath: integration.secretPath,
|
||||
isActive: true,
|
||||
app: { $ne: null },
|
||||
});
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findById(
|
||||
integration.integrationAuth
|
||||
);
|
||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||
// for each workspace integration, sync/push secrets
|
||||
// to that integration
|
||||
for await (const integration of integrations) {
|
||||
// get workspace, environment (shared) secrets
|
||||
const secrets = await BotService.getSecrets({
|
||||
workspaceId: integration.workspace,
|
||||
environment: integration.environment,
|
||||
secretPath: integration.secretPath,
|
||||
});
|
||||
|
||||
// get integration auth access token
|
||||
const access = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integration.integrationAuth,
|
||||
});
|
||||
// get workspace, environment (shared) secrets comments
|
||||
const secretComments = await BotService.getSecretComments({
|
||||
workspaceId: integration.workspace,
|
||||
environment: integration.environment,
|
||||
secretPath: integration.secretPath,
|
||||
})
|
||||
|
||||
// sync secrets to integration
|
||||
await syncSecrets({
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets,
|
||||
accessId: access.accessId === undefined ? null : access.accessId,
|
||||
accessToken: access.accessToken,
|
||||
});
|
||||
const integrationAuth = await IntegrationAuth.findById(
|
||||
integration.integrationAuth
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||
|
||||
// get integration auth access token
|
||||
const access = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integration.integrationAuth,
|
||||
});
|
||||
|
||||
// sync secrets to integration
|
||||
await syncSecrets({
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets,
|
||||
accessId: access.accessId === undefined ? null : access.accessId,
|
||||
accessToken: access.accessToken,
|
||||
secretComments
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
console.log(`syncIntegrationsHelper: failed with [workspaceId=${workspaceId}] [environment=${environment}]`, err) // eslint-disable-line no-use-before-define
|
||||
throw err
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -14,6 +14,9 @@ import {
|
||||
licenseKeyRequest,
|
||||
licenseServerKeyRequest,
|
||||
} from "../config/request";
|
||||
import {
|
||||
createBotOrg
|
||||
} from "./botOrg";
|
||||
|
||||
/**
|
||||
* Create an organization with name [name]
|
||||
@ -29,6 +32,7 @@ export const createOrganization = async ({
|
||||
name: string;
|
||||
email: string;
|
||||
}) => {
|
||||
|
||||
const licenseServerKey = await getLicenseServerKey();
|
||||
let organization;
|
||||
|
||||
@ -45,12 +49,19 @@ export const createOrganization = async ({
|
||||
name,
|
||||
customerId
|
||||
}).save();
|
||||
|
||||
} else {
|
||||
organization = await new Organization({
|
||||
name,
|
||||
}).save();
|
||||
}
|
||||
|
||||
// initialize bot for organization
|
||||
await createBotOrg({
|
||||
name,
|
||||
organizationId: organization._id
|
||||
});
|
||||
|
||||
return organization;
|
||||
};
|
||||
|
||||
|
@ -109,9 +109,9 @@ export const v1PushSecrets = async ({
|
||||
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
|
||||
if (
|
||||
s.secretValueHash !==
|
||||
newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue ||
|
||||
newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue ||
|
||||
s.secretCommentHash !==
|
||||
newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashComment
|
||||
newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashComment
|
||||
) {
|
||||
// case: filter secrets where value or comment changed
|
||||
return true;
|
||||
@ -371,9 +371,9 @@ export const v2PushSecrets = async ({
|
||||
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
|
||||
if (
|
||||
s.secretValueHash !==
|
||||
newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretValueHash ||
|
||||
newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretValueHash ||
|
||||
s.secretCommentHash !==
|
||||
newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretCommentHash
|
||||
newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretCommentHash
|
||||
) {
|
||||
// case: filter secrets where value or comment changed
|
||||
return true;
|
||||
@ -484,7 +484,7 @@ export const v2PushSecrets = async ({
|
||||
|
||||
// (EE) add secret versions for new secrets
|
||||
EESecretService.addSecretVersions({
|
||||
secretVersions: newSecrets.map((secretDocument: ISecret) => {
|
||||
secretVersions: newSecrets.map((secretDocument) => {
|
||||
return new SecretVersion({
|
||||
...secretDocument,
|
||||
secret: secretDocument._id,
|
||||
|
@ -4,13 +4,14 @@ import {
|
||||
DeleteSecretParams,
|
||||
GetSecretParams,
|
||||
GetSecretsParams,
|
||||
UpdateSecretParams,
|
||||
UpdateSecretParams
|
||||
} from "../interfaces/services/SecretService";
|
||||
import {
|
||||
ISecret,
|
||||
IServiceTokenData,
|
||||
Secret,
|
||||
SecretBlindIndexData,
|
||||
ServiceTokenData,
|
||||
ServiceTokenData
|
||||
} from "../models";
|
||||
import { SecretVersion } from "../ee/models";
|
||||
import {
|
||||
@ -18,7 +19,7 @@ import {
|
||||
InternalServerError,
|
||||
SecretBlindIndexDataNotFoundError,
|
||||
SecretNotFoundError,
|
||||
UnauthorizedRequestError,
|
||||
UnauthorizedRequestError
|
||||
} from "../utils/errors";
|
||||
import {
|
||||
ACTION_ADD_SECRETS,
|
||||
@ -29,51 +30,65 @@ import {
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
SECRET_PERSONAL,
|
||||
SECRET_SHARED,
|
||||
SECRET_SHARED
|
||||
} from "../variables";
|
||||
import crypto from "crypto";
|
||||
import * as argon2 from "argon2";
|
||||
import {
|
||||
decryptSymmetric128BitHexKeyUTF8,
|
||||
encryptSymmetric128BitHexKeyUTF8,
|
||||
encryptSymmetric128BitHexKeyUTF8
|
||||
} from "../utils/crypto";
|
||||
import { TelemetryService } from "../services";
|
||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||
import { EELogService, EESecretService } from "../ee/services";
|
||||
import {
|
||||
getAuthDataPayloadIdObj,
|
||||
getAuthDataPayloadUserObj,
|
||||
} from "../utils/auth";
|
||||
import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/auth";
|
||||
import { getFolderIdFromServiceToken } from "../services/FolderService";
|
||||
import picomatch from "picomatch";
|
||||
import path from "path";
|
||||
|
||||
export const isValidScope = (
|
||||
authPayload: IServiceTokenData,
|
||||
environment: string,
|
||||
secretPath: string
|
||||
) => {
|
||||
const { scopes: tkScopes } = authPayload;
|
||||
const validScope = tkScopes.find(
|
||||
(scope) =>
|
||||
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
|
||||
scope.environment === environment
|
||||
);
|
||||
|
||||
return Boolean(validScope);
|
||||
};
|
||||
|
||||
export function containsGlobPatterns(secretPath: string) {
|
||||
const globChars = ["*", "?", "[", "]", "{", "}", "**"];
|
||||
const normalizedPath = path.normalize(secretPath);
|
||||
return globChars.some(char => normalizedPath.includes(char));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an object containing secret [secret] but with its value, key, comment decrypted.
|
||||
*
|
||||
*
|
||||
* Precondition: the workspace for secret [secret] must have E2EE disabled
|
||||
* @param {ISecret} secret - secret to repackage to raw
|
||||
* @param {String} key - symmetric key to use to decrypt secret
|
||||
* @returns
|
||||
* @returns
|
||||
*/
|
||||
export const repackageSecretToRaw = ({
|
||||
secret,
|
||||
key,
|
||||
}: {
|
||||
secret: ISecret;
|
||||
key: string;
|
||||
}) => {
|
||||
|
||||
export const repackageSecretToRaw = ({ secret, key }: { secret: ISecret; key: string }) => {
|
||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretKeyCiphertext,
|
||||
iv: secret.secretKeyIV,
|
||||
tag: secret.secretKeyTag,
|
||||
key,
|
||||
key
|
||||
});
|
||||
|
||||
const secretValue = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secret.secretValueCiphertext,
|
||||
iv: secret.secretValueIV,
|
||||
tag: secret.secretValueTag,
|
||||
key,
|
||||
key
|
||||
});
|
||||
|
||||
let secretComment = "";
|
||||
@ -83,11 +98,11 @@ export const repackageSecretToRaw = ({
|
||||
ciphertext: secret.secretCommentCiphertext,
|
||||
iv: secret.secretCommentIV,
|
||||
tag: secret.secretCommentTag,
|
||||
key,
|
||||
key
|
||||
});
|
||||
}
|
||||
|
||||
return ({
|
||||
return {
|
||||
_id: secret._id,
|
||||
version: secret.version,
|
||||
workspace: secret.workspace,
|
||||
@ -96,9 +111,9 @@ export const repackageSecretToRaw = ({
|
||||
user: secret.user,
|
||||
secretKey,
|
||||
secretValue,
|
||||
secretComment,
|
||||
});
|
||||
}
|
||||
secretComment
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create secret blind index data containing encrypted blind index [salt]
|
||||
@ -107,7 +122,7 @@ export const repackageSecretToRaw = ({
|
||||
* @param {Types.ObjectId} obj.workspaceId
|
||||
*/
|
||||
export const createSecretBlindIndexDataHelper = async ({
|
||||
workspaceId,
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
@ -121,7 +136,7 @@ export const createSecretBlindIndexDataHelper = async ({
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag,
|
||||
tag: saltTag
|
||||
} = client.encryptSymmetric(salt, rootEncryptionKey);
|
||||
|
||||
return await new SecretBlindIndexData({
|
||||
@ -130,16 +145,16 @@ export const createSecretBlindIndexDataHelper = async ({
|
||||
saltIV,
|
||||
saltTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_BASE64,
|
||||
keyEncoding: ENCODING_SCHEME_BASE64
|
||||
}).save();
|
||||
} else {
|
||||
const {
|
||||
ciphertext: encryptedSaltCiphertext,
|
||||
iv: saltIV,
|
||||
tag: saltTag,
|
||||
tag: saltTag
|
||||
} = encryptSymmetric128BitHexKeyUTF8({
|
||||
plaintext: salt,
|
||||
key: encryptionKey,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
return await new SecretBlindIndexData({
|
||||
@ -148,7 +163,7 @@ export const createSecretBlindIndexDataHelper = async ({
|
||||
saltIV,
|
||||
saltTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}).save();
|
||||
}
|
||||
};
|
||||
@ -160,7 +175,7 @@ export const createSecretBlindIndexDataHelper = async ({
|
||||
* @returns
|
||||
*/
|
||||
export const getSecretBlindIndexSaltHelper = async ({
|
||||
workspaceId,
|
||||
workspaceId
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
@ -168,36 +183,30 @@ export const getSecretBlindIndexSaltHelper = async ({
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||
workspace: workspaceId,
|
||||
workspace: workspaceId
|
||||
}).select("+algorithm +keyEncoding");
|
||||
|
||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||
|
||||
if (
|
||||
rootEncryptionKey &&
|
||||
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64
|
||||
) {
|
||||
if (rootEncryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||
return client.decryptSymmetric(
|
||||
secretBlindIndexData.encryptedSaltCiphertext,
|
||||
rootEncryptionKey,
|
||||
secretBlindIndexData.saltIV,
|
||||
secretBlindIndexData.saltTag
|
||||
);
|
||||
} else if (
|
||||
encryptionKey &&
|
||||
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8
|
||||
) {
|
||||
} else if (encryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||
// decrypt workspace salt
|
||||
return decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||
iv: secretBlindIndexData.saltIV,
|
||||
tag: secretBlindIndexData.saltTag,
|
||||
key: encryptionKey,
|
||||
key: encryptionKey
|
||||
});
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: "Failed to obtain workspace salt needed for secret blind indexing",
|
||||
message: "Failed to obtain workspace salt needed for secret blind indexing"
|
||||
});
|
||||
};
|
||||
|
||||
@ -210,7 +219,7 @@ export const getSecretBlindIndexSaltHelper = async ({
|
||||
*/
|
||||
export const generateSecretBlindIndexWithSaltHelper = async ({
|
||||
secretName,
|
||||
salt,
|
||||
salt
|
||||
}: {
|
||||
secretName: string;
|
||||
salt: string;
|
||||
@ -224,7 +233,7 @@ export const generateSecretBlindIndexWithSaltHelper = async ({
|
||||
memoryCost: 65536, // default pool of 64 MiB per thread.
|
||||
hashLength: 32,
|
||||
parallelism: 1,
|
||||
raw: true,
|
||||
raw: true
|
||||
})
|
||||
).toString("base64");
|
||||
|
||||
@ -240,7 +249,7 @@ export const generateSecretBlindIndexWithSaltHelper = async ({
|
||||
*/
|
||||
export const generateSecretBlindIndexHelper = async ({
|
||||
secretName,
|
||||
workspaceId,
|
||||
workspaceId
|
||||
}: {
|
||||
secretName: string;
|
||||
workspaceId: Types.ObjectId;
|
||||
@ -250,16 +259,13 @@ export const generateSecretBlindIndexHelper = async ({
|
||||
const rootEncryptionKey = await getRootEncryptionKey();
|
||||
|
||||
const secretBlindIndexData = await SecretBlindIndexData.findOne({
|
||||
workspace: workspaceId,
|
||||
workspace: workspaceId
|
||||
}).select("+algorithm +keyEncoding");
|
||||
|
||||
if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError();
|
||||
|
||||
let salt;
|
||||
if (
|
||||
rootEncryptionKey &&
|
||||
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64
|
||||
) {
|
||||
if (rootEncryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_BASE64) {
|
||||
salt = client.decryptSymmetric(
|
||||
secretBlindIndexData.encryptedSaltCiphertext,
|
||||
rootEncryptionKey,
|
||||
@ -269,32 +275,29 @@ export const generateSecretBlindIndexHelper = async ({
|
||||
|
||||
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||
secretName,
|
||||
salt,
|
||||
salt
|
||||
});
|
||||
|
||||
return secretBlindIndex;
|
||||
} else if (
|
||||
encryptionKey &&
|
||||
secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8
|
||||
) {
|
||||
} else if (encryptionKey && secretBlindIndexData.keyEncoding === ENCODING_SCHEME_UTF8) {
|
||||
// decrypt workspace salt
|
||||
salt = decryptSymmetric128BitHexKeyUTF8({
|
||||
ciphertext: secretBlindIndexData.encryptedSaltCiphertext,
|
||||
iv: secretBlindIndexData.saltIV,
|
||||
tag: secretBlindIndexData.saltTag,
|
||||
key: encryptionKey,
|
||||
key: encryptionKey
|
||||
});
|
||||
|
||||
const secretBlindIndex = await generateSecretBlindIndexWithSaltHelper({
|
||||
secretName,
|
||||
salt,
|
||||
salt
|
||||
});
|
||||
|
||||
return secretBlindIndex;
|
||||
}
|
||||
|
||||
throw InternalServerError({
|
||||
message: "Failed to generate secret blind index",
|
||||
message: "Failed to generate secret blind index"
|
||||
});
|
||||
};
|
||||
|
||||
@ -323,38 +326,33 @@ export const createSecretHelper = async ({
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretPath = "/",
|
||||
secretPath = "/"
|
||||
}: CreateSecretParams) => {
|
||||
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
if (!isValidScope(authData.authPayload, environment, secretPath)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
|
||||
const exists = await Secret.exists({
|
||||
secretBlindIndex,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
folder: folderId,
|
||||
type,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||
environment,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||
});
|
||||
|
||||
if (exists)
|
||||
throw BadRequestError({
|
||||
message: "Failed to create secret that already exists",
|
||||
message: "Failed to create secret that already exists"
|
||||
});
|
||||
|
||||
if (type === SECRET_PERSONAL) {
|
||||
@ -365,13 +363,13 @@ export const createSecretHelper = async ({
|
||||
secretBlindIndex,
|
||||
folder: folderId,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
type: SECRET_SHARED,
|
||||
environment,
|
||||
type: SECRET_SHARED
|
||||
});
|
||||
|
||||
if (!exists)
|
||||
throw BadRequestError({
|
||||
message:
|
||||
"Failed to create personal secret override for no corresponding shared secret",
|
||||
message: "Failed to create personal secret override for no corresponding shared secret"
|
||||
});
|
||||
}
|
||||
|
||||
@ -394,7 +392,7 @@ export const createSecretHelper = async ({
|
||||
secretCommentTag,
|
||||
folder: folderId,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
}).save();
|
||||
|
||||
const secretVersion = new SecretVersion({
|
||||
@ -414,12 +412,12 @@ export const createSecretHelper = async ({
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
|
||||
// (EE) add version for new secret
|
||||
await EESecretService.addSecretVersions({
|
||||
secretVersions: [secretVersion],
|
||||
secretVersions: [secretVersion]
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
@ -427,7 +425,7 @@ export const createSecretHelper = async ({
|
||||
name: ACTION_ADD_SECRETS,
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
secretIds: [secret._id],
|
||||
secretIds: [secret._id]
|
||||
});
|
||||
|
||||
action &&
|
||||
@ -436,14 +434,14 @@ export const createSecretHelper = async ({
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP,
|
||||
ipAddress: authData.authIP
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId,
|
||||
environment,
|
||||
folderId,
|
||||
folderId
|
||||
});
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
@ -452,7 +450,7 @@ export const createSecretHelper = async ({
|
||||
postHogClient.capture({
|
||||
event: "secrets added",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData,
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
@ -460,8 +458,8 @@ export const createSecretHelper = async ({
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent,
|
||||
},
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -480,21 +478,16 @@ export const getSecretsHelper = async ({
|
||||
workspaceId,
|
||||
environment,
|
||||
authData,
|
||||
secretPath = "/",
|
||||
secretPath = "/"
|
||||
}: GetSecretsParams) => {
|
||||
let secrets: ISecret[] = [];
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
if (!isValidScope(authData.authPayload, environment, secretPath)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
|
||||
// get personal secrets first
|
||||
secrets = await Secret.find({
|
||||
@ -502,8 +495,10 @@ export const getSecretsHelper = async ({
|
||||
environment,
|
||||
folder: folderId,
|
||||
type: SECRET_PERSONAL,
|
||||
...getAuthDataPayloadUserObj(authData),
|
||||
}).populate("tags").lean();
|
||||
...getAuthDataPayloadUserObj(authData)
|
||||
})
|
||||
.populate("tags")
|
||||
.lean();
|
||||
|
||||
// concat with shared secrets
|
||||
secrets = secrets.concat(
|
||||
@ -513,9 +508,11 @@ export const getSecretsHelper = async ({
|
||||
folder: folderId,
|
||||
type: SECRET_SHARED,
|
||||
secretBlindIndex: {
|
||||
$nin: secrets.map((secret) => secret.secretBlindIndex),
|
||||
},
|
||||
}).populate("tags").lean()
|
||||
$nin: secrets.map((secret) => secret.secretBlindIndex)
|
||||
}
|
||||
})
|
||||
.populate("tags")
|
||||
.lean()
|
||||
);
|
||||
|
||||
// (EE) create (audit) log
|
||||
@ -523,7 +520,7 @@ export const getSecretsHelper = async ({
|
||||
name: ACTION_READ_SECRETS,
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
secretIds: secrets.map((secret) => secret._id),
|
||||
secretIds: secrets.map((secret) => secret._id)
|
||||
});
|
||||
|
||||
action &&
|
||||
@ -532,7 +529,7 @@ export const getSecretsHelper = async ({
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP,
|
||||
ipAddress: authData.authIP
|
||||
}));
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
@ -541,7 +538,7 @@ export const getSecretsHelper = async ({
|
||||
postHogClient.capture({
|
||||
event: "secrets pulled",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData,
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
@ -549,8 +546,8 @@ export const getSecretsHelper = async ({
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent,
|
||||
},
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -573,25 +570,20 @@ export const getSecretHelper = async ({
|
||||
environment,
|
||||
type,
|
||||
authData,
|
||||
secretPath = "/",
|
||||
secretPath = "/"
|
||||
}: GetSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
let secret: ISecret | null = null;
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
if (!isValidScope(authData.authPayload, environment, secretPath)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
|
||||
// try getting personal secret first (if exists)
|
||||
secret = await Secret.findOne({
|
||||
@ -600,7 +592,7 @@ export const getSecretHelper = async ({
|
||||
environment,
|
||||
folder: folderId,
|
||||
type: type ?? SECRET_PERSONAL,
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
|
||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||
}).lean();
|
||||
|
||||
if (!secret) {
|
||||
@ -611,7 +603,7 @@ export const getSecretHelper = async ({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
type: SECRET_SHARED,
|
||||
type: SECRET_SHARED
|
||||
}).lean();
|
||||
}
|
||||
|
||||
@ -622,7 +614,7 @@ export const getSecretHelper = async ({
|
||||
name: ACTION_READ_SECRETS,
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
secretIds: [secret._id],
|
||||
secretIds: [secret._id]
|
||||
});
|
||||
|
||||
action &&
|
||||
@ -631,7 +623,7 @@ export const getSecretHelper = async ({
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP,
|
||||
ipAddress: authData.authIP
|
||||
}));
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
@ -640,7 +632,7 @@ export const getSecretHelper = async ({
|
||||
postHogClient.capture({
|
||||
event: "secrets pull",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData,
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
@ -648,8 +640,8 @@ export const getSecretHelper = async ({
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent,
|
||||
},
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -679,26 +671,21 @@ export const updateSecretHelper = async ({
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretPath,
|
||||
secretPath
|
||||
}: UpdateSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
let secret: ISecret | null = null;
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
if (!isValidScope(authData.authPayload, environment, secretPath)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
|
||||
if (type === SECRET_SHARED) {
|
||||
// case: update shared secret
|
||||
@ -708,16 +695,16 @@ export const updateSecretHelper = async ({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
type,
|
||||
type
|
||||
},
|
||||
{
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
$inc: { version: 1 },
|
||||
$inc: { version: 1 }
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
new: true
|
||||
}
|
||||
);
|
||||
} else {
|
||||
@ -730,16 +717,16 @@ export const updateSecretHelper = async ({
|
||||
environment,
|
||||
type,
|
||||
folder: folderId,
|
||||
...getAuthDataPayloadUserObj(authData),
|
||||
...getAuthDataPayloadUserObj(authData)
|
||||
},
|
||||
{
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
$inc: { version: 1 },
|
||||
$inc: { version: 1 }
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
new: true
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -763,12 +750,12 @@ export const updateSecretHelper = async ({
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
});
|
||||
|
||||
// (EE) add version for new secret
|
||||
await EESecretService.addSecretVersions({
|
||||
secretVersions: [secretVersion],
|
||||
secretVersions: [secretVersion]
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
@ -776,7 +763,7 @@ export const updateSecretHelper = async ({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
secretIds: [secret._id],
|
||||
secretIds: [secret._id]
|
||||
});
|
||||
|
||||
action &&
|
||||
@ -785,14 +772,14 @@ export const updateSecretHelper = async ({
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP,
|
||||
ipAddress: authData.authIP
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId,
|
||||
environment,
|
||||
folderId: secret?.folder,
|
||||
folderId: secret?.folder
|
||||
});
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
@ -801,7 +788,7 @@ export const updateSecretHelper = async ({
|
||||
postHogClient.capture({
|
||||
event: "secrets modified",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData,
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: 1,
|
||||
@ -809,8 +796,8 @@ export const updateSecretHelper = async ({
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent,
|
||||
},
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -833,26 +820,20 @@ export const deleteSecretHelper = async ({
|
||||
environment,
|
||||
type,
|
||||
authData,
|
||||
secretPath = "/",
|
||||
secretPath = "/"
|
||||
}: DeleteSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
// if using service token filter towards the folderId by secretpath
|
||||
if (authData.authPayload instanceof ServiceTokenData) {
|
||||
const { secretPath: serviceTkScopedSecretPath } = authData.authPayload;
|
||||
|
||||
if (secretPath !== serviceTkScopedSecretPath) {
|
||||
if (!isValidScope(authData.authPayload, environment, secretPath)) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath
|
||||
);
|
||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
|
||||
let secrets: ISecret[] = [];
|
||||
let secret: ISecret | null = null;
|
||||
@ -862,7 +843,7 @@ export const deleteSecretHelper = async ({
|
||||
secretBlindIndex,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
folder: folderId
|
||||
}).lean();
|
||||
|
||||
secret = await Secret.findOneAndDelete({
|
||||
@ -870,14 +851,14 @@ export const deleteSecretHelper = async ({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
folder: folderId,
|
||||
folder: folderId
|
||||
}).lean();
|
||||
|
||||
await Secret.deleteMany({
|
||||
secretBlindIndex,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId,
|
||||
folder: folderId
|
||||
});
|
||||
} else {
|
||||
secret = await Secret.findOneAndDelete({
|
||||
@ -886,7 +867,7 @@ export const deleteSecretHelper = async ({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
...getAuthDataPayloadUserObj(authData),
|
||||
...getAuthDataPayloadUserObj(authData)
|
||||
}).lean();
|
||||
|
||||
if (secret) {
|
||||
@ -897,7 +878,7 @@ export const deleteSecretHelper = async ({
|
||||
if (!secret) throw SecretNotFoundError();
|
||||
|
||||
await EESecretService.markDeletedSecretVersions({
|
||||
secretIds: secrets.map((secret) => secret._id),
|
||||
secretIds: secrets.map((secret) => secret._id)
|
||||
});
|
||||
|
||||
// (EE) create (audit) log
|
||||
@ -905,22 +886,23 @@ export const deleteSecretHelper = async ({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
secretIds: secrets.map((secret) => secret._id),
|
||||
secretIds: secrets.map((secret) => secret._id)
|
||||
});
|
||||
|
||||
action && (await EELogService.createLog({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP,
|
||||
}));
|
||||
action &&
|
||||
(await EELogService.createLog({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
}));
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId,
|
||||
environment,
|
||||
folderId: secret?.folder,
|
||||
folderId: secret?.folder
|
||||
});
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
@ -929,7 +911,7 @@ export const deleteSecretHelper = async ({
|
||||
postHogClient.capture({
|
||||
event: "secrets deleted",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData,
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
@ -937,13 +919,13 @@ export const deleteSecretHelper = async ({
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent,
|
||||
},
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ({
|
||||
return {
|
||||
secrets,
|
||||
secret,
|
||||
});
|
||||
secret
|
||||
};
|
||||
};
|
||||
|
@ -5,6 +5,10 @@ import {
|
||||
Secret,
|
||||
Workspace,
|
||||
} from "../models";
|
||||
import {
|
||||
IPType,
|
||||
TrustedIP
|
||||
} from "../ee/models";
|
||||
import { createBot } from "../helpers/bot";
|
||||
import { EELicenseService } from "../ee/services";
|
||||
import { SecretService } from "../services";
|
||||
@ -40,6 +44,26 @@ export const createWorkspace = async ({
|
||||
await SecretService.createSecretBlindIndexData({
|
||||
workspaceId: workspace._id,
|
||||
});
|
||||
|
||||
// initialize default trusted IPv4 CIDR - 0.0.0.0/0
|
||||
await new TrustedIP({
|
||||
workspace: workspace._id,
|
||||
ipAddress: "0.0.0.0",
|
||||
type: IPType.IPV4,
|
||||
prefix: 0,
|
||||
isActive: true,
|
||||
comment: ""
|
||||
}).save()
|
||||
|
||||
// initialize default trusted IPv6 CIDR - ::/0
|
||||
await new TrustedIP({
|
||||
workspace: workspace._id,
|
||||
ipAddress: "::",
|
||||
type: IPType.IPV6,
|
||||
prefix: 0,
|
||||
isActive: true,
|
||||
comment: ""
|
||||
});
|
||||
|
||||
await EELicenseService.refreshPlan(organizationId);
|
||||
|
||||
|
@ -5,11 +5,12 @@ import express from "express";
|
||||
require("express-async-errors");
|
||||
import helmet from "helmet";
|
||||
import cors from "cors";
|
||||
import { DatabaseService } from "./services";
|
||||
import { DatabaseService, GithubSecretScanningService } from "./services";
|
||||
import { EELicenseService } from "./ee/services";
|
||||
import { setUpHealthEndpoint } from "./services/health";
|
||||
import cookieParser from "cookie-parser";
|
||||
import swaggerUi = require("swagger-ui-express");
|
||||
import { Probot, createNodeMiddleware } from "probot";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const swaggerFile = require("../spec.json");
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
@ -18,8 +19,10 @@ import {
|
||||
action as eeActionRouter,
|
||||
cloudProducts as eeCloudProductsRouter,
|
||||
organizations as eeOrganizationsRouter,
|
||||
sso as eeSSORouter,
|
||||
secret as eeSecretRouter,
|
||||
secretSnapshot as eeSecretSnapshotRouter,
|
||||
users as eeUsersRouter,
|
||||
workspace as eeWorkspaceRouter,
|
||||
} from "./ee/routes/v1";
|
||||
import {
|
||||
@ -33,42 +36,46 @@ import {
|
||||
membership as v1MembershipRouter,
|
||||
organization as v1OrganizationRouter,
|
||||
password as v1PasswordRouter,
|
||||
secretImport as v1SecretImportRouter,
|
||||
secret as v1SecretRouter,
|
||||
secretScanning as v1SecretScanningRouter,
|
||||
secretsFolder as v1SecretsFolder,
|
||||
serviceToken as v1ServiceTokenRouter,
|
||||
signup as v1SignupRouter,
|
||||
userAction as v1UserActionRouter,
|
||||
user as v1UserRouter,
|
||||
workspace as v1WorkspaceRouter,
|
||||
webhooks as v1WebhooksRouter,
|
||||
workspace as v1WorkspaceRouter
|
||||
} from "./routes/v1";
|
||||
import {
|
||||
signup as v2SignupRouter,
|
||||
auth as v2AuthRouter,
|
||||
users as v2UsersRouter,
|
||||
environment as v2EnvironmentRouter,
|
||||
organizations as v2OrganizationsRouter,
|
||||
workspace as v2WorkspaceRouter,
|
||||
secret as v2SecretRouter, // begin to phase out
|
||||
secrets as v2SecretsRouter,
|
||||
serviceTokenData as v2ServiceTokenDataRouter,
|
||||
serviceAccounts as v2ServiceAccountsRouter,
|
||||
apiKeyData as v2APIKeyDataRouter,
|
||||
environment as v2EnvironmentRouter,
|
||||
serviceTokenData as v2ServiceTokenDataRouter,
|
||||
signup as v2SignupRouter,
|
||||
tags as v2TagsRouter,
|
||||
users as v2UsersRouter,
|
||||
workspace as v2WorkspaceRouter,
|
||||
} from "./routes/v2";
|
||||
import {
|
||||
auth as v3AuthRouter,
|
||||
secrets as v3SecretsRouter,
|
||||
signup as v3SignupRouter,
|
||||
workspaces as v3WorkspacesRouter,
|
||||
workspaces as v3WorkspacesRouter
|
||||
} from "./routes/v3";
|
||||
import { healthCheck } from "./routes/status";
|
||||
import { getLogger } from "./utils/logger";
|
||||
import { RouteNotFoundError } from "./utils/errors";
|
||||
import { requestErrorHandler } from "./middleware/requestErrorHandler";
|
||||
import { getNodeEnv, getPort, getSiteURL } from "./config";
|
||||
import { getNodeEnv, getPort, getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookProxy, getSecretScanningWebhookSecret, getSiteURL } from "./config";
|
||||
import { setup } from "./utils/setup";
|
||||
const SmeeClient = require('smee-client') // eslint-disable-line
|
||||
|
||||
const main = async () => {
|
||||
|
||||
await setup();
|
||||
|
||||
await EELicenseService.initGlobalFeatureSet();
|
||||
@ -76,14 +83,35 @@ const main = async () => {
|
||||
const app = express();
|
||||
app.enable("trust proxy");
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(
|
||||
cors({
|
||||
credentials: true,
|
||||
origin: await getSiteURL(),
|
||||
origin: await getSiteURL()
|
||||
})
|
||||
);
|
||||
|
||||
if (await getSecretScanningGitAppId() && await getSecretScanningWebhookSecret() && await getSecretScanningPrivateKey()) {
|
||||
const probot = new Probot({
|
||||
appId: await getSecretScanningGitAppId(),
|
||||
privateKey: await getSecretScanningPrivateKey(),
|
||||
secret: await getSecretScanningWebhookSecret(),
|
||||
});
|
||||
|
||||
if ((await getNodeEnv()) != "production") {
|
||||
const smee = new SmeeClient({
|
||||
source: await getSecretScanningWebhookProxy(),
|
||||
target: "http://backend:4000/ss-webhook",
|
||||
logger: console
|
||||
})
|
||||
|
||||
smee.start()
|
||||
}
|
||||
|
||||
app.use(createNodeMiddleware(GithubSecretScanningService, { probot, webhooksPath: "/ss-webhook" })); // secret scanning webhook
|
||||
}
|
||||
|
||||
if ((await getNodeEnv()) === "production") {
|
||||
// enable app-wide rate-limiting + helmet security
|
||||
// in production
|
||||
@ -102,9 +130,11 @@ const main = async () => {
|
||||
// (EE) routes
|
||||
app.use("/api/v1/secret", eeSecretRouter);
|
||||
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
||||
app.use("/api/v1/users", eeUsersRouter);
|
||||
app.use("/api/v1/workspace", eeWorkspaceRouter);
|
||||
app.use("/api/v1/action", eeActionRouter);
|
||||
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
||||
app.use("/api/v1/sso", eeSSORouter);
|
||||
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
||||
|
||||
// v1 routes (default)
|
||||
@ -125,6 +155,9 @@ const main = async () => {
|
||||
app.use("/api/v1/integration", v1IntegrationRouter);
|
||||
app.use("/api/v1/integration-auth", v1IntegrationAuthRouter);
|
||||
app.use("/api/v1/folders", v1SecretsFolder);
|
||||
app.use("/api/v1/secret-scanning", v1SecretScanningRouter);
|
||||
app.use("/api/v1/webhooks", v1WebhooksRouter);
|
||||
app.use("/api/v1/secret-imports", v1SecretImportRouter);
|
||||
|
||||
// v2 routes (improvements)
|
||||
app.use("/api/v2/signup", v2SignupRouter);
|
||||
@ -138,7 +171,6 @@ const main = async () => {
|
||||
app.use("/api/v2/secrets", v2SecretsRouter); // note: in the process of moving to v3/secrets
|
||||
app.use("/api/v2/service-token", v2ServiceTokenDataRouter);
|
||||
app.use("/api/v2/service-accounts", v2ServiceAccountsRouter); // new
|
||||
app.use("/api/v2/api-key", v2APIKeyDataRouter);
|
||||
|
||||
// v3 routes (experimental)
|
||||
app.use("/api/v3/auth", v3AuthRouter);
|
||||
@ -157,7 +189,7 @@ const main = async () => {
|
||||
if (res.headersSent) return next();
|
||||
next(
|
||||
RouteNotFoundError({
|
||||
message: `The requested source '(${req.method})${req.url}' was not found`,
|
||||
message: `The requested source '(${req.method})${req.url}' was not found`
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -165,9 +197,7 @@ const main = async () => {
|
||||
app.use(requestErrorHandler);
|
||||
|
||||
const server = app.listen(await getPort(), async () => {
|
||||
(await getLogger("backend-main")).info(
|
||||
`Server started listening at port ${await getPort()}`
|
||||
);
|
||||
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`);
|
||||
});
|
||||
|
||||
// await createTestUserForDevelopment();
|
||||
|
@ -1,36 +1,53 @@
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { IIntegrationAuth } from "../models";
|
||||
import { standardRequest } from "../config/request";
|
||||
import {
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_BITBUCKET_API_URL,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_CHECKLY_API_URL,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CIRCLECI_API_URL,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUDFLARE_PAGES_API_URL,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CLOUD_66_API_URL,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_CODEFRESH_API_URL,
|
||||
INTEGRATION_DIGITAL_OCEAN_API_URL,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_FLYIO_API_URL,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUDFLARE_PAGES_API_URL,
|
||||
INTEGRATION_GITLAB_API_URL,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_HEROKU_API_URL,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_LARAVELFORGE_API_URL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NETLIFY_API_URL,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_NORTHFLANK_API_URL,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_RENDER_API_URL,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_SUPABASE_API_URL,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TERRAFORM_CLOUD_API_URL,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TRAVISCI_API_URL,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_WINDMILL_API_URL,
|
||||
} from "../variables";
|
||||
import { IIntegrationAuth } from "../models";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { standardRequest } from "../config/request";
|
||||
|
||||
interface App {
|
||||
name: string;
|
||||
@ -52,11 +69,13 @@ const getApps = async ({
|
||||
accessToken,
|
||||
accessId,
|
||||
teamId,
|
||||
workspaceSlug,
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
accessToken: string;
|
||||
accessId?: string;
|
||||
teamId?: string;
|
||||
workspaceSlug?: string;
|
||||
}) => {
|
||||
let apps: App[] = [];
|
||||
switch (integrationAuth.integration) {
|
||||
@ -116,11 +135,29 @@ const getApps = async ({
|
||||
accessToken,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_LARAVELFORGE:
|
||||
apps = await getAppsLaravelForge({
|
||||
accessToken,
|
||||
serverId: accessId
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_TERRAFORM_CLOUD:
|
||||
apps = await getAppsTerraformCloud({
|
||||
accessToken,
|
||||
workspacesId: accessId,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_TRAVISCI:
|
||||
apps = await getAppsTravisCI({
|
||||
accessToken,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_TEAMCITY:
|
||||
apps = await getAppsTeamCity({
|
||||
integrationAuth,
|
||||
accessToken,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_SUPABASE:
|
||||
apps = await getAppsSupabase({
|
||||
accessToken,
|
||||
@ -135,7 +172,38 @@ const getApps = async ({
|
||||
apps = await getAppsCloudflarePages({
|
||||
accessToken,
|
||||
accountId: accessId
|
||||
})
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_NORTHFLANK:
|
||||
apps = await getAppsNorthflank({
|
||||
accessToken,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_BITBUCKET:
|
||||
apps = await getAppsBitBucket({
|
||||
accessToken,
|
||||
workspaceSlug
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_CODEFRESH:
|
||||
apps = await getAppsCodefresh({
|
||||
accessToken,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_WINDMILL:
|
||||
apps = await getAppsWindmill({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM:
|
||||
apps = await getAppsDigitalOceanAppPlatform({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_CLOUD_66:
|
||||
apps = await getAppsCloud66({
|
||||
accessToken,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@ -188,10 +256,10 @@ const getAppsVercel = async ({
|
||||
},
|
||||
...(integrationAuth?.teamId
|
||||
? {
|
||||
params: {
|
||||
teamId: integrationAuth.teamId,
|
||||
},
|
||||
}
|
||||
params: {
|
||||
teamId: integrationAuth.teamId,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
).data;
|
||||
@ -398,6 +466,40 @@ const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
|
||||
return apps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of sites for Laravel Forge integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - access token for Laravel Forge API
|
||||
* @param {String} obj.serverId - server id of Laravel Forge
|
||||
* @returns {Object[]} apps - names and ids of Laravel Forge sites
|
||||
* @returns {String} apps.name - name of Laravel Forge sites
|
||||
* @returns {String} apps.appId - id of Laravel Forge sites
|
||||
*/
|
||||
const getAppsLaravelForge = async ({
|
||||
accessToken,
|
||||
serverId
|
||||
}: {
|
||||
accessToken: string;
|
||||
serverId?: string;
|
||||
}) => {
|
||||
const res = (
|
||||
await standardRequest.get(`${INTEGRATION_LARAVELFORGE_API_URL}/api/v1/servers/${serverId}/sites`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
).data.sites;
|
||||
|
||||
const apps = res.map((a: any) => ({
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
}));
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of apps for Fly.io integration
|
||||
* @param {Object} obj
|
||||
@ -490,6 +592,43 @@ const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
|
||||
return apps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of projects for Terraform Cloud integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - access token for Terraform Cloud API
|
||||
* @param {String} obj.workspacesId - workspace id of Terraform Cloud projects
|
||||
* @returns {Object[]} apps - names and ids of Terraform Cloud projects
|
||||
* @returns {String} apps.name - name of Terraform Cloud projects
|
||||
*/
|
||||
const getAppsTerraformCloud = async ({
|
||||
accessToken,
|
||||
workspacesId
|
||||
}: {
|
||||
accessToken: string;
|
||||
workspacesId?: string;
|
||||
}) => {
|
||||
const res = (
|
||||
await standardRequest.get(`${INTEGRATION_TERRAFORM_CLOUD_API_URL}/api/v2/workspaces/${workspacesId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
).data.data;
|
||||
|
||||
const apps = []
|
||||
|
||||
const appsObj = {
|
||||
name: res?.attributes.name,
|
||||
appId: res?.id,
|
||||
};
|
||||
|
||||
apps.push(appsObj)
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return list of repositories for GitLab integration
|
||||
* @param {Object} obj
|
||||
@ -590,6 +729,39 @@ const getAppsGitlab = async ({
|
||||
return apps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of projects for TeamCity integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - access token for TeamCity API
|
||||
* @returns {Object[]} apps - names and ids of TeamCity projects
|
||||
* @returns {String} apps.name - name of TeamCity projects
|
||||
*/
|
||||
const getAppsTeamCity = async ({
|
||||
integrationAuth,
|
||||
accessToken,
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
const res = (
|
||||
await standardRequest.get(`${integrationAuth.url}/app/rest/projects`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
).data.project.slice(1);
|
||||
|
||||
const apps = res.map((a: any) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
};
|
||||
});
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of projects for Supabase integration
|
||||
* @param {Object} obj
|
||||
@ -653,15 +825,76 @@ const getAppsCheckly = async ({ accessToken }: { accessToken: string }) => {
|
||||
* @returns {Object[]} apps - Cloudflare Pages projects
|
||||
* @returns {String} apps.name - name of Cloudflare Pages project
|
||||
*/
|
||||
const getAppsCloudflarePages = async ({
|
||||
accessToken,
|
||||
accountId
|
||||
const getAppsCloudflarePages = async ({
|
||||
accessToken,
|
||||
accountId
|
||||
}: {
|
||||
accessToken: string;
|
||||
accountId?: string;
|
||||
accessToken: string;
|
||||
accountId?: string;
|
||||
}) => {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accountId}/pages/projects`,
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accountId}/pages/projects`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const apps = data.result.map((a: any) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
};
|
||||
});
|
||||
return apps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of repositories for the BitBucket integration based on provided BitBucket workspace
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - access token for BitBucket API
|
||||
* @param {String} obj.workspaceSlug - Workspace identifier for fetching BitBucket repositories
|
||||
* @returns {Object[]} apps - BitBucket repositories
|
||||
* @returns {String} apps.name - name of BitBucket repository
|
||||
*/
|
||||
const getAppsBitBucket = async ({
|
||||
accessToken,
|
||||
workspaceSlug,
|
||||
}: {
|
||||
accessToken: string;
|
||||
workspaceSlug?: string;
|
||||
}) => {
|
||||
interface RepositoriesResponse {
|
||||
size: number;
|
||||
page: number;
|
||||
pageLen: number;
|
||||
next: string;
|
||||
previous: string;
|
||||
values: Array<Repository>;
|
||||
}
|
||||
|
||||
interface Repository {
|
||||
type: string;
|
||||
uuid: string;
|
||||
name: string;
|
||||
is_private: boolean;
|
||||
created_on: string;
|
||||
updated_on: string;
|
||||
}
|
||||
|
||||
if (!workspaceSlug) {
|
||||
return []
|
||||
}
|
||||
|
||||
const repositories: Repository[] = [];
|
||||
let hasNextPage = true;
|
||||
let repositoriesUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/repositories/${workspaceSlug}`
|
||||
|
||||
while (hasNextPage) {
|
||||
const { data }: { data: RepositoriesResponse } = await standardRequest.get(
|
||||
repositoriesUrl,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
@ -670,13 +903,290 @@ const getAppsCloudflarePages = async ({
|
||||
}
|
||||
);
|
||||
|
||||
const apps = data.result.map((a: any) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
};
|
||||
});
|
||||
return apps;
|
||||
if (data?.values.length > 0) {
|
||||
data.values.forEach((repository) => {
|
||||
repositories.push(repository)
|
||||
})
|
||||
}
|
||||
|
||||
if (data.next) {
|
||||
repositoriesUrl = data.next
|
||||
} else {
|
||||
hasNextPage = false
|
||||
}
|
||||
}
|
||||
|
||||
const apps = repositories.map((repository) => {
|
||||
return {
|
||||
name: repository.name,
|
||||
appId: repository.uuid,
|
||||
};
|
||||
});
|
||||
return apps;
|
||||
}
|
||||
|
||||
/** Return list of projects for Northflank integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - access token for Northflank API
|
||||
* @returns {Object[]} apps - names of Northflank apps
|
||||
* @returns {String} apps.name - name of Northflank app
|
||||
*/
|
||||
const getAppsNorthflank = async ({ accessToken }: { accessToken: string }) => {
|
||||
const {
|
||||
data: {
|
||||
data: {
|
||||
projects
|
||||
}
|
||||
}
|
||||
} = await standardRequest.get(
|
||||
`${INTEGRATION_NORTHFLANK_API_URL}/v1/projects`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const apps = projects.map((a: any) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id
|
||||
};
|
||||
});
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of projects for Supabase integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - access token for Supabase API
|
||||
* @returns {Object[]} apps - names of Supabase apps
|
||||
* @returns {String} apps.name - name of Supabase app
|
||||
*/
|
||||
const getAppsCodefresh = async ({
|
||||
accessToken,
|
||||
}: {
|
||||
accessToken: string;
|
||||
}) => {
|
||||
const res = (
|
||||
await standardRequest.get(`${INTEGRATION_CODEFRESH_API_URL}/projects`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
})
|
||||
).data;
|
||||
|
||||
const apps = res.projects.map((a: any) => ({
|
||||
name: a.projectName,
|
||||
appId: a.id,
|
||||
}));
|
||||
|
||||
return apps;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of projects for Windmill integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - access token for Windmill API
|
||||
* @returns {Object[]} apps - names of Windmill workspaces
|
||||
* @returns {String} apps.name - name of Windmill workspace
|
||||
*/
|
||||
const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_WINDMILL_API_URL}/workspaces/list`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// check for write access of secrets in windmill workspaces
|
||||
const writeAccessCheck = data.map(async (app: any) => {
|
||||
try {
|
||||
const userPath = "u/user/variable";
|
||||
const folderPath = "f/folder/variable";
|
||||
|
||||
const { data: writeUser } = await standardRequest.post(
|
||||
`${INTEGRATION_WINDMILL_API_URL}/w/${app.id}/variables/create`,
|
||||
{
|
||||
path: userPath,
|
||||
value: "variable",
|
||||
is_secret: true,
|
||||
description: "variable description"
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { data: writeFolder } = await standardRequest.post(
|
||||
`${INTEGRATION_WINDMILL_API_URL}/w/${app.id}/variables/create`,
|
||||
{
|
||||
path: folderPath,
|
||||
value: "variable",
|
||||
is_secret: true,
|
||||
description: "variable description"
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// is write access is allowed then delete the created secrets from workspace
|
||||
if (writeUser && writeFolder) {
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_WINDMILL_API_URL}/w/${app.id}/variables/delete/${userPath}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_WINDMILL_API_URL}/w/${app.id}/variables/delete/${folderPath}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return app;
|
||||
} else {
|
||||
return { error: "cannot write secret" };
|
||||
}
|
||||
} catch (err: any) {
|
||||
return { error: err.message };
|
||||
}
|
||||
});
|
||||
|
||||
const appsWriteResponses = await Promise.all(writeAccessCheck);
|
||||
const appsWithWriteAccess = appsWriteResponses.filter((appRes: any) => !appRes.error);
|
||||
|
||||
const apps = appsWithWriteAccess.map((a: any) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
};
|
||||
});
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of applications for DigitalOcean App Platform integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - personal access token for DigitalOcean
|
||||
* @returns {Object[]} apps - names of DigitalOcean apps
|
||||
* @returns {String} apps.name - name of DigitalOcean app
|
||||
* @returns {String} apps.appId - id of DigitalOcean app
|
||||
*/
|
||||
const getAppsDigitalOceanAppPlatform = async ({ accessToken }: { accessToken: string }) => {
|
||||
interface DigitalOceanApp {
|
||||
id: string;
|
||||
owner_uuid: string;
|
||||
spec: Spec;
|
||||
}
|
||||
|
||||
interface Spec {
|
||||
name: string;
|
||||
region: string;
|
||||
envs: Env[];
|
||||
}
|
||||
|
||||
interface Env {
|
||||
key: string;
|
||||
value: string;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
const res = (
|
||||
await standardRequest.get(`${INTEGRATION_DIGITAL_OCEAN_API_URL}/v2/apps`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
})
|
||||
).data;
|
||||
|
||||
return (res.apps ?? []).map((a: DigitalOceanApp) => ({
|
||||
name: a.spec.name,
|
||||
appId: a.id
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of applications for Cloud66 integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - personal access token for Cloud66 API
|
||||
* @returns {Object[]} apps - Cloud66 apps
|
||||
* @returns {String} apps.name - name of Cloud66 app
|
||||
* @returns {String} apps.appId - uid of Cloud66 app
|
||||
*/
|
||||
const getAppsCloud66 = async ({ accessToken }: { accessToken: string }) => {
|
||||
interface Cloud66Apps {
|
||||
uid: string;
|
||||
name: string;
|
||||
account_id: number;
|
||||
git: string;
|
||||
git_branch: string;
|
||||
environment: string;
|
||||
cloud: string;
|
||||
fqdn: string;
|
||||
language: string;
|
||||
framework: string;
|
||||
status: number;
|
||||
health: number;
|
||||
last_activity: string;
|
||||
last_activity_iso: string;
|
||||
maintenance_mode: boolean;
|
||||
has_loadbalancer: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
deploy_directory: string;
|
||||
cloud_status: string;
|
||||
backend: string;
|
||||
version: string;
|
||||
revision: string;
|
||||
is_busy: boolean;
|
||||
account_name: string;
|
||||
is_cluster: boolean;
|
||||
is_inside_cluster: boolean;
|
||||
cluster_name: any;
|
||||
application_address: string;
|
||||
configstore_namespace: string;
|
||||
}
|
||||
|
||||
const stacks = (
|
||||
await standardRequest.get(`${INTEGRATION_CLOUD_66_API_URL}/3/stacks`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
})
|
||||
).data.response as Cloud66Apps[]
|
||||
|
||||
const apps = stacks.map((app) => ({
|
||||
name: app.name,
|
||||
appId: app.uid
|
||||
}));
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
||||
export { getApps };
|
||||
|
@ -2,6 +2,8 @@ import { standardRequest } from "../config/request";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITHUB_TOKEN_URL,
|
||||
INTEGRATION_GITLAB,
|
||||
@ -15,11 +17,13 @@ import {
|
||||
} from "../variables";
|
||||
import {
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGitHub,
|
||||
getClientIdGitLab,
|
||||
getClientIdNetlify,
|
||||
getClientIdVercel,
|
||||
getClientSecretAzure,
|
||||
getClientSecretBitBucket,
|
||||
getClientSecretGitHub,
|
||||
getClientSecretGitLab,
|
||||
getClientSecretHeroku,
|
||||
@ -78,6 +82,15 @@ interface ExchangeCodeGitlabResponse {
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
interface ExchangeCodeBitBucketResponse {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scopes: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for OAuth2
|
||||
* code-token exchange for integration named [integration]
|
||||
@ -129,6 +142,12 @@ const exchangeCode = async ({
|
||||
obj = await exchangeCodeGitlab({
|
||||
code,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_BITBUCKET:
|
||||
obj = await exchangeCodeBitBucket({
|
||||
code,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return obj;
|
||||
@ -347,4 +366,43 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return [accessToken], [accessExpiresAt], and [refreshToken] for BitBucket
|
||||
* code-token exchange
|
||||
* @param {Object} obj1
|
||||
* @param {Object} obj1.code - code for code-token exchange
|
||||
* @returns {Object} obj2
|
||||
* @returns {String} obj2.accessToken - access token for BitBucket API
|
||||
* @returns {String} obj2.refreshToken - refresh token for BitBucket API
|
||||
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||
*/
|
||||
const exchangeCodeBitBucket = async ({ code }: { code: string }) => {
|
||||
const accessExpiresAt = new Date();
|
||||
const res: ExchangeCodeBitBucketResponse = (
|
||||
await standardRequest.post(
|
||||
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code: code,
|
||||
client_id: await getClientIdBitBucket(),
|
||||
client_secret: await getClientSecretBitBucket(),
|
||||
redirect_uri: `${await getSiteURL()}/integrations/bitbucket/oauth2/callback`,
|
||||
} as any),
|
||||
{
|
||||
headers: {
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
)
|
||||
).data;
|
||||
|
||||
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
|
||||
|
||||
return {
|
||||
accessToken: res.access_token,
|
||||
refreshToken: res.refresh_token,
|
||||
accessExpiresAt,
|
||||
};
|
||||
};
|
||||
|
||||
export { exchangeCode };
|
||||
|
@ -2,6 +2,8 @@ import { standardRequest } from "../config/request";
|
||||
import { IIntegrationAuth } from "../models";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HEROKU,
|
||||
} from "../variables";
|
||||
@ -13,8 +15,10 @@ import {
|
||||
import { IntegrationService } from "../services";
|
||||
import {
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGitLab,
|
||||
getClientSecretAzure,
|
||||
getClientSecretBitBucket,
|
||||
getClientSecretGitLab,
|
||||
getClientSecretHeroku,
|
||||
getSiteURL,
|
||||
@ -46,6 +50,15 @@ interface RefreshTokenGitLabResponse {
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
interface RefreshTokenBitBucketResponse {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scopes: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return new access token by exchanging refresh token [refreshToken] for integration
|
||||
* named [integration]
|
||||
@ -83,6 +96,11 @@ const exchangeRefresh = async ({
|
||||
refreshToken,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_BITBUCKET:
|
||||
tokenDetails = await exchangeRefreshBitBucket({
|
||||
refreshToken,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error("Failed to exchange token for incompatible integration");
|
||||
}
|
||||
@ -218,4 +236,46 @@ const exchangeRefreshGitLab = async ({
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||
* BitBucket integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.refreshToken - refresh token to use to get new access token for BitBucket
|
||||
* @returns
|
||||
*/
|
||||
const exchangeRefreshBitBucket = async ({
|
||||
refreshToken,
|
||||
}: {
|
||||
refreshToken: string;
|
||||
}) => {
|
||||
const accessExpiresAt = new Date();
|
||||
const {
|
||||
data,
|
||||
}: {
|
||||
data: RefreshTokenBitBucketResponse;
|
||||
} = await standardRequest.post(
|
||||
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: refreshToken,
|
||||
client_id: await getClientIdBitBucket(),
|
||||
client_secret: await getClientSecretBitBucket(),
|
||||
redirect_uri: `${await getSiteURL()}/integrations/bitbucket/oauth2/callback`,
|
||||
} as any),
|
||||
{
|
||||
headers: {
|
||||
"Accept-Encoding": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
|
||||
|
||||
return {
|
||||
accessToken: data.access_token,
|
||||
refreshToken: data.refresh_token,
|
||||
accessExpiresAt,
|
||||
};
|
||||
};
|
||||
|
||||
export { exchangeRefresh };
|
||||
|
@ -18,7 +18,6 @@ const revokeAccess = async ({
|
||||
integrationAuth: IIntegrationAuth;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
let deletedIntegrationAuth;
|
||||
// add any integration-specific revocation logic
|
||||
switch (integrationAuth.integration) {
|
||||
case INTEGRATION_HEROKU:
|
||||
@ -33,7 +32,7 @@ const revokeAccess = async ({
|
||||
break;
|
||||
}
|
||||
|
||||
deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
|
||||
const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({
|
||||
_id: integrationAuth._id,
|
||||
});
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { ErrorRequestHandler } from "express";
|
||||
import { InternalServerError } from "../utils/errors";
|
||||
import { TokenExpiredError } from "jsonwebtoken";
|
||||
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { getLogger } from "../utils/logger";
|
||||
import RequestError, { LogLevel } from "../utils/requestError";
|
||||
import { getNodeEnv } from "../config";
|
||||
@ -19,7 +20,9 @@ export const requestErrorHandler: ErrorRequestHandler = async (
|
||||
}
|
||||
|
||||
//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)) {
|
||||
if (error instanceof TokenExpiredError) {
|
||||
error = UnauthorizedRequestError({ stack: error.stack, message: "Token expired" });
|
||||
} else if (!(error instanceof RequestError)) {
|
||||
error = InternalServerError({
|
||||
context: { exception: error.message },
|
||||
stack: error.stack,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import { Types } from "mongoose";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import {
|
||||
getAuthAPIKeyPayload,
|
||||
@ -51,6 +52,10 @@ const requireAuth = ({
|
||||
});
|
||||
|
||||
let authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
let authUserPayload: {
|
||||
user: IUser;
|
||||
tokenVersionId: Types.ObjectId;
|
||||
};
|
||||
switch (authMode) {
|
||||
case AUTH_MODE_SERVICE_ACCOUNT:
|
||||
authPayload = await getAuthSAAKPayload({
|
||||
@ -71,12 +76,12 @@ const requireAuth = ({
|
||||
req.user = authPayload;
|
||||
break;
|
||||
default:
|
||||
const { user, tokenVersionId } = await getAuthUserPayload({
|
||||
authUserPayload = await getAuthUserPayload({
|
||||
authTokenValue,
|
||||
});
|
||||
authPayload = user;
|
||||
req.user = user;
|
||||
req.tokenVersionId = tokenVersionId;
|
||||
authPayload = authUserPayload.user;
|
||||
req.user = authUserPayload.user;
|
||||
req.tokenVersionId = authUserPayload.tokenVersionId;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ const requireWorkspaceAuth = ({
|
||||
requiredPermissions = [],
|
||||
requireBlindIndicesEnabled = false,
|
||||
requireE2EEOff = false,
|
||||
checkIPAllowlist = false
|
||||
}: {
|
||||
acceptedRoles: Array<"admin" | "member">;
|
||||
locationWorkspaceId: req;
|
||||
@ -25,6 +26,7 @@ const requireWorkspaceAuth = ({
|
||||
requiredPermissions?: string[];
|
||||
requireBlindIndicesEnabled?: boolean;
|
||||
requireE2EEOff?: boolean;
|
||||
checkIPAllowlist?: boolean;
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const workspaceId = req[locationWorkspaceId]?.workspaceId;
|
||||
@ -39,6 +41,7 @@ const requireWorkspaceAuth = ({
|
||||
requiredPermissions,
|
||||
requireBlindIndicesEnabled,
|
||||
requireE2EEOff,
|
||||
checkIPAllowlist
|
||||
});
|
||||
|
||||
if (membership) {
|
||||
|
98
backend/src/models/botOrg.ts
Normal file
98
backend/src/models/botOrg.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
} from "../variables";
|
||||
|
||||
export interface IBotOrg {
|
||||
_id: Types.ObjectId;
|
||||
name: string;
|
||||
organization: Types.ObjectId;
|
||||
publicKey: string;
|
||||
encryptedSymmetricKey: string;
|
||||
symmetricKeyIV: string;
|
||||
symmetricKeyTag: string;
|
||||
symmetricKeyAlgorithm: "aes-256-gcm";
|
||||
symmetricKeyKeyEncoding: "base64" | "utf8";
|
||||
encryptedPrivateKey: string;
|
||||
privateKeyIV: string;
|
||||
privateKeyTag: string;
|
||||
privateKeyAlgorithm: "aes-256-gcm";
|
||||
privateKeyKeyEncoding: "base64" | "utf8";
|
||||
}
|
||||
|
||||
const botOrgSchema = new Schema<IBotOrg>(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
organization: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Organization",
|
||||
required: true,
|
||||
},
|
||||
publicKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
encryptedSymmetricKey: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
symmetricKeyIV: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
symmetricKeyTag: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
symmetricKeyAlgorithm: {
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true
|
||||
},
|
||||
symmetricKeyKeyEncoding: {
|
||||
type: String,
|
||||
enum: [
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
],
|
||||
required: true
|
||||
},
|
||||
encryptedPrivateKey: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
privateKeyIV: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
privateKeyTag: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
privateKeyAlgorithm: {
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true
|
||||
},
|
||||
privateKeyKeyEncoding: {
|
||||
type: String,
|
||||
enum: [
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
],
|
||||
required: true
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema);
|
||||
|
||||
export default BotOrg;
|
34
backend/src/models/gitAppInstallationSession.ts
Normal file
34
backend/src/models/gitAppInstallationSession.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
|
||||
type GitAppInstallationSession = {
|
||||
id: string;
|
||||
sessionId: string;
|
||||
organization: Types.ObjectId;
|
||||
user: Types.ObjectId;
|
||||
}
|
||||
|
||||
const gitAppInstallationSession = new Schema<GitAppInstallationSession>({
|
||||
id: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
sessionId: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
organization: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "User"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const GitAppInstallationSession = model<GitAppInstallationSession>("git_app_installation_session", gitAppInstallationSession);
|
||||
|
||||
export default GitAppInstallationSession;
|
31
backend/src/models/gitAppOrganizationInstallation.ts
Normal file
31
backend/src/models/gitAppOrganizationInstallation.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Schema, model } from "mongoose";
|
||||
|
||||
type Installation = {
|
||||
installationId: string
|
||||
organizationId: string
|
||||
user: Schema.Types.ObjectId
|
||||
};
|
||||
|
||||
|
||||
const gitAppOrganizationInstallation = new Schema<Installation>({
|
||||
installationId: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
organizationId: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const GitAppOrganizationInstallation = model<Installation>("git_app_organization_installation", gitAppOrganizationInstallation);
|
||||
|
||||
export default GitAppOrganizationInstallation;
|
152
backend/src/models/gitRisks.ts
Normal file
152
backend/src/models/gitRisks.ts
Normal file
@ -0,0 +1,152 @@
|
||||
import { Schema, model } from "mongoose";
|
||||
|
||||
export const STATUS_RESOLVED_FALSE_POSITIVE = "RESOLVED_FALSE_POSITIVE";
|
||||
export const STATUS_RESOLVED_REVOKED = "RESOLVED_REVOKED";
|
||||
export const STATUS_RESOLVED_NOT_REVOKED = "RESOLVED_NOT_REVOKED";
|
||||
export const STATUS_UNRESOLVED = "UNRESOLVED";
|
||||
|
||||
export type GitRisks = {
|
||||
id: string;
|
||||
description: string;
|
||||
startLine: string;
|
||||
endLine: string;
|
||||
startColumn: string;
|
||||
endColumn: string;
|
||||
match: string;
|
||||
secret: string;
|
||||
file: string;
|
||||
symlinkFile: string;
|
||||
commit: string;
|
||||
entropy: string;
|
||||
author: string;
|
||||
email: string;
|
||||
date: string;
|
||||
message: string;
|
||||
tags: string[];
|
||||
ruleID: string;
|
||||
fingerprint: string;
|
||||
fingerPrintWithoutCommitId: string
|
||||
|
||||
isFalsePositive: boolean; // New field for marking risks as false positives
|
||||
isResolved: boolean; // New field for marking risks as resolved
|
||||
riskOwner: string | null; // New field for setting a risk owner (nullable string)
|
||||
installationId: string,
|
||||
repositoryId: string,
|
||||
repositoryLink: string
|
||||
repositoryFullName: string
|
||||
status: string
|
||||
pusher: {
|
||||
name: string,
|
||||
email: string
|
||||
},
|
||||
organization: Schema.Types.ObjectId,
|
||||
}
|
||||
|
||||
const gitRisks = new Schema<GitRisks>({
|
||||
id: {
|
||||
type: String,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
},
|
||||
startLine: {
|
||||
type: String,
|
||||
},
|
||||
endLine: {
|
||||
type: String,
|
||||
},
|
||||
startColumn: {
|
||||
type: String,
|
||||
},
|
||||
endColumn: {
|
||||
type: String,
|
||||
},
|
||||
file: {
|
||||
type: String,
|
||||
},
|
||||
symlinkFile: {
|
||||
type: String,
|
||||
},
|
||||
commit: {
|
||||
type: String,
|
||||
},
|
||||
entropy: {
|
||||
type: String,
|
||||
},
|
||||
author: {
|
||||
type: String,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
},
|
||||
date: {
|
||||
type: String,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
},
|
||||
tags: {
|
||||
type: [String],
|
||||
},
|
||||
ruleID: {
|
||||
type: String,
|
||||
},
|
||||
fingerprint: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
fingerPrintWithoutCommitId: {
|
||||
type: String,
|
||||
},
|
||||
isFalsePositive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isResolved: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
riskOwner: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
installationId: {
|
||||
type: String,
|
||||
require: true
|
||||
},
|
||||
repositoryId: {
|
||||
type: String
|
||||
},
|
||||
repositoryLink: {
|
||||
type: String
|
||||
},
|
||||
repositoryFullName: {
|
||||
type: String
|
||||
},
|
||||
pusher: {
|
||||
name: {
|
||||
type: String
|
||||
},
|
||||
email: {
|
||||
type: String
|
||||
},
|
||||
},
|
||||
organization: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Organization",
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: [
|
||||
STATUS_RESOLVED_FALSE_POSITIVE,
|
||||
STATUS_RESOLVED_REVOKED,
|
||||
STATUS_RESOLVED_NOT_REVOKED,
|
||||
STATUS_UNRESOLVED
|
||||
],
|
||||
default: STATUS_UNRESOLVED
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
const GitRisks = model<GitRisks>("GitRisks", gitRisks);
|
||||
|
||||
export default GitRisks;
|
@ -1,5 +1,6 @@
|
||||
import BackupPrivateKey, { IBackupPrivateKey } from "./backupPrivateKey";
|
||||
import Bot, { IBot } from "./bot";
|
||||
import BotOrg, { IBotOrg } from "./botOrg";
|
||||
import BotKey, { IBotKey } from "./botKey";
|
||||
import IncidentContactOrg, { IIncidentContactOrg } from "./incidentContactOrg";
|
||||
import Integration, { IIntegration } from "./integration";
|
||||
@ -16,13 +17,14 @@ import ServiceAccountKey, { IServiceAccountKey } from "./serviceAccountKey"; //
|
||||
import ServiceAccountOrganizationPermission, { IServiceAccountOrganizationPermission } from "./serviceAccountOrganizationPermission"; // new
|
||||
import ServiceAccountWorkspacePermission, { IServiceAccountWorkspacePermission } from "./serviceAccountWorkspacePermission"; // new
|
||||
import TokenData, { ITokenData } from "./tokenData";
|
||||
import User,{ AuthProvider, IUser } from "./user";
|
||||
import User, { AuthProvider, IUser } from "./user";
|
||||
import UserAction, { IUserAction } from "./userAction";
|
||||
import Workspace, { IWorkspace } from "./workspace";
|
||||
import ServiceTokenData, { IServiceTokenData } from "./serviceTokenData";
|
||||
import APIKeyData, { IAPIKeyData } from "./apiKeyData";
|
||||
import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail";
|
||||
import TokenVersion, { ITokenVersion } from "./tokenVersion";
|
||||
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE } from "./gitRisks";
|
||||
|
||||
export {
|
||||
AuthProvider,
|
||||
@ -30,6 +32,8 @@ export {
|
||||
IBackupPrivateKey,
|
||||
Bot,
|
||||
IBot,
|
||||
BotOrg,
|
||||
IBotOrg,
|
||||
BotKey,
|
||||
IBotKey,
|
||||
IncidentContactOrg,
|
||||
@ -76,4 +80,6 @@ export {
|
||||
ILoginSRPDetail,
|
||||
TokenVersion,
|
||||
ITokenVersion,
|
||||
GitRisks,
|
||||
STATUS_RESOLVED_FALSE_POSITIVE
|
||||
};
|
||||
|
@ -1,23 +1,32 @@
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
import {
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_WINDMILL
|
||||
} from "../variables";
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
|
||||
export interface IIntegration {
|
||||
_id: Types.ObjectId;
|
||||
@ -48,11 +57,20 @@ export interface IIntegration {
|
||||
| "railway"
|
||||
| "flyio"
|
||||
| "circleci"
|
||||
| "laravel-forge"
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "checkly"
|
||||
| "terraform-cloud"
|
||||
| "teamcity"
|
||||
| "hashicorp-vault"
|
||||
| "cloudflare-pages";
|
||||
| "cloudflare-pages"
|
||||
| "bitbucket"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
| "cloud-66"
|
||||
| "northflank"
|
||||
| "windmill";
|
||||
integrationAuth: Types.ObjectId;
|
||||
}
|
||||
|
||||
@ -136,11 +154,20 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
@ -153,7 +180,7 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
type: String,
|
||||
required: true,
|
||||
default: "/",
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
@ -6,25 +5,60 @@ import {
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_WINDMILL
|
||||
} from "../variables";
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
|
||||
export interface IIntegrationAuth extends Document {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager' | 'checkly' | 'cloudflare-pages';
|
||||
integration:
|
||||
| "heroku"
|
||||
| "vercel"
|
||||
| "netlify"
|
||||
| "github"
|
||||
| "gitlab"
|
||||
| "render"
|
||||
| "railway"
|
||||
| "flyio"
|
||||
| "azure-key-vault"
|
||||
| "laravel-forge"
|
||||
| "circleci"
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "aws-parameter-store"
|
||||
| "aws-secret-manager"
|
||||
| "checkly"
|
||||
| "cloudflare-pages"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
| "bitbucket"
|
||||
| "cloud-66"
|
||||
| "terraform-cloud"
|
||||
| "teamcity"
|
||||
| "northflank"
|
||||
| "windmill";
|
||||
teamId: string;
|
||||
accountId: string;
|
||||
url: string;
|
||||
@ -65,10 +99,19 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
|
52
backend/src/models/secretImports.ts
Normal file
52
backend/src/models/secretImports.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
|
||||
export interface ISecretImports {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
environment: string;
|
||||
folderId: string;
|
||||
imports: Array<{
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
const secretImportSchema = new Schema<ISecretImports>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true
|
||||
},
|
||||
environment: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
folderId: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "root"
|
||||
},
|
||||
imports: {
|
||||
type: [
|
||||
{
|
||||
environment: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
secretPath: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
],
|
||||
default: []
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
const SecretImport = model<ISecretImports>("SecretImports", secretImportSchema);
|
||||
export default SecretImport;
|
@ -4,7 +4,10 @@ export interface IServiceTokenData extends Document {
|
||||
_id: Types.ObjectId;
|
||||
name: string;
|
||||
workspace: Types.ObjectId;
|
||||
environment: string;
|
||||
scopes: Array<{
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}>;
|
||||
user: Types.ObjectId;
|
||||
serviceAccount: Types.ObjectId;
|
||||
lastUsed: Date;
|
||||
@ -13,7 +16,6 @@ export interface IServiceTokenData extends Document {
|
||||
encryptedKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
secretPath: string;
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
@ -21,68 +23,72 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
environment: {
|
||||
type: String,
|
||||
required: true,
|
||||
scopes: {
|
||||
type: [
|
||||
{
|
||||
environment: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
secretPath: {
|
||||
type: String,
|
||||
default: "/",
|
||||
required: true
|
||||
}
|
||||
}
|
||||
],
|
||||
required: true
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
serviceAccount: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "ServiceAccount",
|
||||
ref: "ServiceAccount"
|
||||
},
|
||||
lastUsed: {
|
||||
type: Date,
|
||||
type: Date
|
||||
},
|
||||
expiresAt: {
|
||||
type: Date,
|
||||
type: Date
|
||||
},
|
||||
secretHash: {
|
||||
type: String,
|
||||
required: true,
|
||||
select: false,
|
||||
select: false
|
||||
},
|
||||
encryptedKey: {
|
||||
type: String,
|
||||
select: false,
|
||||
select: false
|
||||
},
|
||||
iv: {
|
||||
type: String,
|
||||
select: false,
|
||||
select: false
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
select: false,
|
||||
select: false
|
||||
},
|
||||
permissions: {
|
||||
type: [String],
|
||||
enum: ["read", "write"],
|
||||
default: ["read"],
|
||||
},
|
||||
secretPath: {
|
||||
type: String,
|
||||
default: "/",
|
||||
required: true,
|
||||
},
|
||||
default: ["read"]
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
const ServiceTokenData = model<IServiceTokenData>(
|
||||
"ServiceTokenData",
|
||||
serviceTokenDataSchema
|
||||
);
|
||||
const ServiceTokenData = model<IServiceTokenData>("ServiceTokenData", serviceTokenDataSchema);
|
||||
|
||||
export default ServiceTokenData;
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
|
||||
export enum AuthProvider {
|
||||
EMAIL = "email",
|
||||
GOOGLE = "google",
|
||||
OKTA_SAML = "okta-saml",
|
||||
AZURE_SAML = "azure-saml",
|
||||
JUMPCLOUD_SAML = "jumpcloud-saml",
|
||||
}
|
||||
|
||||
export interface IUser extends Document {
|
||||
|
85
backend/src/models/webhooks.ts
Normal file
85
backend/src/models/webhooks.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8 } from "../variables";
|
||||
|
||||
export interface IWebhook extends Document {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
url: string;
|
||||
lastStatus: "success" | "failed";
|
||||
lastRunErrorMessage?: string;
|
||||
isDisabled: boolean;
|
||||
encryptedSecretKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
algorithm: "aes-256-gcm";
|
||||
keyEncoding: "base64" | "utf8";
|
||||
}
|
||||
|
||||
const WebhookSchema = new Schema<IWebhook>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true
|
||||
},
|
||||
environment: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
secretPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "/"
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
lastStatus: {
|
||||
type: String,
|
||||
enum: ["success", "failed"]
|
||||
},
|
||||
lastRunErrorMessage: {
|
||||
type: String
|
||||
},
|
||||
isDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// used for webhook signature
|
||||
encryptedSecretKey: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
iv: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
select: false
|
||||
},
|
||||
algorithm: {
|
||||
// the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true,
|
||||
select: false
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
|
||||
required: true,
|
||||
select: false
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
const Webhook = model<IWebhook>("Webhook", WebhookSchema);
|
||||
|
||||
export default Webhook;
|
@ -1,5 +1,5 @@
|
||||
import express, { Request, Response } from "express";
|
||||
import { getSmtpConfigured } from "../../config";
|
||||
import { getInviteOnlySignup, getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookSecret, getSmtpConfigured } from "../../config";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -10,6 +10,8 @@ router.get(
|
||||
date: new Date(),
|
||||
message: "Ok",
|
||||
emailConfigured: await getSmtpConfigured(),
|
||||
secretScanningConfigured: await getSecretScanningGitAppId() && await getSecretScanningWebhookSecret() && await getSecretScanningPrivateKey(),
|
||||
inviteOnlySignup: await getInviteOnlySignup()
|
||||
})
|
||||
}
|
||||
);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
import { body } from "express-validator";
|
||||
import passport from "passport";
|
||||
import { requireAuth, validateRequest } from "../../middleware";
|
||||
import { authController } from "../../controllers/v1";
|
||||
import { authLimiter } from "../../helpers/rateLimiter";
|
||||
@ -44,21 +43,6 @@ router.post(
|
||||
authController.checkAuth
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/redirect/google",
|
||||
authLimiter,
|
||||
passport.authenticate("google", {
|
||||
scope: ["profile", "email"],
|
||||
session: false,
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/callback/google",
|
||||
passport.authenticate("google", { failureRedirect: "/login/provider/error", session: false }),
|
||||
authController.handleAuthProviderCallback,
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/common-passwords",
|
||||
authLimiter,
|
||||
|
@ -15,23 +15,29 @@ import password from "./password";
|
||||
import integration from "./integration";
|
||||
import integrationAuth from "./integrationAuth";
|
||||
import secretsFolder from "./secretsFolder";
|
||||
import secretScanning from "./secretScanning";
|
||||
import webhooks from "./webhook";
|
||||
import secretImport from "./secretImport";
|
||||
|
||||
export {
|
||||
signup,
|
||||
auth,
|
||||
bot,
|
||||
user,
|
||||
userAction,
|
||||
organization,
|
||||
workspace,
|
||||
membershipOrg,
|
||||
membership,
|
||||
key,
|
||||
inviteOrg,
|
||||
secret,
|
||||
serviceToken,
|
||||
password,
|
||||
integration,
|
||||
integrationAuth,
|
||||
secretsFolder,
|
||||
signup,
|
||||
auth,
|
||||
bot,
|
||||
user,
|
||||
userAction,
|
||||
organization,
|
||||
workspace,
|
||||
membershipOrg,
|
||||
membership,
|
||||
key,
|
||||
inviteOrg,
|
||||
secret,
|
||||
serviceToken,
|
||||
password,
|
||||
integration,
|
||||
integrationAuth,
|
||||
secretsFolder,
|
||||
secretScanning,
|
||||
webhooks,
|
||||
secretImport
|
||||
};
|
||||
|
@ -81,6 +81,7 @@ router.get(
|
||||
}),
|
||||
param("integrationAuthId"),
|
||||
query("teamId"),
|
||||
query("workspaceSlug"),
|
||||
validateRequest,
|
||||
integrationAuthController.getIntegrationAuthApps
|
||||
);
|
||||
@ -141,6 +142,33 @@ router.get(
|
||||
integrationAuthController.getIntegrationAuthRailwayServices
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/bitbucket/workspaces",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireIntegrationAuthorizationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
}),
|
||||
param("integrationAuthId").exists().isString(),
|
||||
validateRequest,
|
||||
integrationAuthController.getIntegrationAuthBitBucketWorkspaces
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/northflank/secret-groups",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
}),
|
||||
requireIntegrationAuthorizationAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
}),
|
||||
param("integrationAuthId").exists().isString(),
|
||||
query("appId").exists().isString(),
|
||||
validateRequest,
|
||||
integrationAuthController.getIntegrationAuthNorthflankSecretGroups
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:integrationAuthId",
|
||||
requireAuth({
|
||||
|
84
backend/src/routes/v1/secretImport.ts
Normal file
84
backend/src/routes/v1/secretImport.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
import { body, param, query } from "express-validator";
|
||||
import { secretImportController } from "../../controllers/v1";
|
||||
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
|
||||
import { ADMIN, AUTH_MODE_JWT, MEMBER } from "../../variables";
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "body"
|
||||
}),
|
||||
body("workspaceId").exists().isString().trim().notEmpty(),
|
||||
body("environment").exists().isString().trim().notEmpty(),
|
||||
body("folderId").default("root").isString().trim(),
|
||||
body("secretImport").exists().isObject(),
|
||||
body("secretImport.environment").isString().exists().trim(),
|
||||
body("secretImport.secretPath").isString().exists().trim(),
|
||||
validateRequest,
|
||||
secretImportController.createSecretImport
|
||||
);
|
||||
|
||||
router.put(
|
||||
"/:id",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
param("id").exists().isString().trim(),
|
||||
body("secretImports").exists().isArray(),
|
||||
body("secretImports.*.environment").isString().exists().trim(),
|
||||
body("secretImports.*.secretPath").isString().exists().trim(),
|
||||
validateRequest,
|
||||
secretImportController.updateSecretImport
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:id",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
param("id").exists().isString().trim(),
|
||||
body("secretImportPath").isString().exists().trim(),
|
||||
body("secretImportEnv").isString().exists().trim(),
|
||||
validateRequest,
|
||||
secretImportController.deleteSecretImport
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "query"
|
||||
}),
|
||||
query("workspaceId").exists().isString().trim().notEmpty(),
|
||||
query("environment").exists().isString().trim().notEmpty(),
|
||||
query("folderId").default("root").isString().trim(),
|
||||
validateRequest,
|
||||
secretImportController.getSecretImports
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/secrets",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "query"
|
||||
}),
|
||||
query("workspaceId").exists().isString().trim().notEmpty(),
|
||||
query("environment").exists().isString().trim().notEmpty(),
|
||||
query("folderId").default("root").isString().trim(),
|
||||
validateRequest,
|
||||
secretImportController.getAllSecretsFromImport
|
||||
);
|
||||
|
||||
export default router;
|
81
backend/src/routes/v1/secretScanning.ts
Normal file
81
backend/src/routes/v1/secretScanning.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
import {
|
||||
requireAuth,
|
||||
requireOrganizationAuth,
|
||||
validateRequest,
|
||||
} from "../../middleware";
|
||||
import { body, param } from "express-validator";
|
||||
import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../controllers/v1/secretScanningController";
|
||||
import { ACCEPTED, ADMIN, MEMBER, OWNER } from "../../variables";
|
||||
|
||||
router.post(
|
||||
"/create-installation-session/organization/:organizationId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
}),
|
||||
param("organizationId").exists().trim(),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
acceptedStatuses: [ACCEPTED],
|
||||
}),
|
||||
validateRequest,
|
||||
createInstallationSession
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/link-installation",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
}),
|
||||
body("installationId").exists().trim(),
|
||||
body("sessionId").exists().trim(),
|
||||
validateRequest,
|
||||
linkInstallationToOrganization
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/installation-status/organization/:organizationId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
}),
|
||||
param("organizationId").exists().trim(),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
acceptedStatuses: [ACCEPTED],
|
||||
}),
|
||||
validateRequest,
|
||||
getCurrentOrganizationInstallationStatus
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/organization/:organizationId/risks",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
}),
|
||||
param("organizationId").exists().trim(),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
acceptedStatuses: [ACCEPTED],
|
||||
}),
|
||||
validateRequest,
|
||||
getRisksForOrganization
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/organization/:organizationId/risks/:riskId/status",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
}),
|
||||
param("organizationId").exists().trim(),
|
||||
param("riskId").exists().trim(),
|
||||
body("status").exists(),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
acceptedStatuses: [ACCEPTED],
|
||||
}),
|
||||
validateRequest,
|
||||
updateRisksStatus
|
||||
);
|
||||
|
||||
export default router;
|
75
backend/src/routes/v1/webhook.ts
Normal file
75
backend/src/routes/v1/webhook.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import express from "express";
|
||||
const router = express.Router();
|
||||
import { requireAuth, requireWorkspaceAuth, validateRequest } from "../../middleware";
|
||||
import { body, param, query } from "express-validator";
|
||||
import { ADMIN, AUTH_MODE_JWT, MEMBER } from "../../variables";
|
||||
import { webhookController } from "../../controllers/v1";
|
||||
|
||||
router.post(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "body",
|
||||
locationEnvironment: "body"
|
||||
}),
|
||||
body("workspaceId").exists().isString().trim(),
|
||||
body("environment").exists().isString().trim(),
|
||||
body("webhookUrl").exists().isString().isURL().trim(),
|
||||
body("webhookSecretKey").isString().trim(),
|
||||
body("secretPath").default("/").isString().trim(),
|
||||
validateRequest,
|
||||
webhookController.createWebhook
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/:webhookId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
param("webhookId").exists().isString().trim(),
|
||||
body("isDisabled").default(false).isBoolean(),
|
||||
validateRequest,
|
||||
webhookController.updateWebhook
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/:webhookId/test",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
param("webhookId").exists().isString().trim(),
|
||||
validateRequest,
|
||||
webhookController.testWebhook
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:webhookId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
param("webhookId").exists().isString().trim(),
|
||||
validateRequest,
|
||||
webhookController.deleteWebhook
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "query",
|
||||
locationEnvironment: "query"
|
||||
}),
|
||||
query("workspaceId").exists().isString().trim(),
|
||||
query("environment").optional().isString().trim(),
|
||||
query("secretPath").optional().isString().trim(),
|
||||
validateRequest,
|
||||
webhookController.listWebhooks
|
||||
);
|
||||
|
||||
export default router;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user