mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 21:27:31 +00:00
Continue developing service token v2
This commit is contained in:
@@ -41,6 +41,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
|
||||||
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
|
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
|
||||||
|
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
- run: pip install --upgrade cloudsmith-cli
|
- run: pip install --upgrade cloudsmith-cli
|
||||||
- name: Publish to CloudSmith
|
- name: Publish to CloudSmith
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ node_modules
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
/dist
|
/dist
|
||||||
|
/completions/
|
||||||
|
/manpages/
|
||||||
|
|
||||||
# frontend
|
# frontend
|
||||||
|
|
||||||
|
|||||||
+53
-5
@@ -6,6 +6,11 @@
|
|||||||
# - cd cli && go mod tidy
|
# - cd cli && go mod tidy
|
||||||
# # you may remove this if you don't need go generate
|
# # you may remove this if you don't need go generate
|
||||||
# - cd cli && go generate ./...
|
# - cd cli && go generate ./...
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- ./cli/scripts/completions.sh
|
||||||
|
- ./cli/scripts/manpages.sh
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- id: darwin-build
|
- id: darwin-build
|
||||||
binary: infisical
|
binary: infisical
|
||||||
@@ -30,13 +35,13 @@ builds:
|
|||||||
- openbsd
|
- openbsd
|
||||||
- windows
|
- windows
|
||||||
goarch:
|
goarch:
|
||||||
- 386
|
- "386"
|
||||||
- amd64
|
- amd64
|
||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
goarm:
|
||||||
- 6
|
- "6"
|
||||||
- 7
|
- "7"
|
||||||
ignore:
|
ignore:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
goarch: "386"
|
goarch: "386"
|
||||||
@@ -44,6 +49,16 @@ builds:
|
|||||||
goarch: "386"
|
goarch: "386"
|
||||||
dir: ./cli
|
dir: ./cli
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
files:
|
||||||
|
- README*
|
||||||
|
- LICENSE*
|
||||||
|
- manpages/*
|
||||||
|
- completions/*
|
||||||
|
|
||||||
release:
|
release:
|
||||||
replace_existing_draft: true
|
replace_existing_draft: true
|
||||||
mode: 'replace'
|
mode: 'replace'
|
||||||
@@ -85,13 +100,22 @@ nfpms:
|
|||||||
homepage: https://infisical.com/
|
homepage: https://infisical.com/
|
||||||
maintainer: Infisical, Inc
|
maintainer: Infisical, Inc
|
||||||
description: The offical Infisical CLI
|
description: The offical Infisical CLI
|
||||||
license: Apache 2.0
|
license: MIT
|
||||||
formats:
|
formats:
|
||||||
- rpm
|
- rpm
|
||||||
- deb
|
- deb
|
||||||
- apk
|
- apk
|
||||||
- archlinux
|
- archlinux
|
||||||
bindir: /usr/bin
|
bindir: /usr/bin
|
||||||
|
contents:
|
||||||
|
- src: ./completions/infisical.bash
|
||||||
|
dst: /etc/bash_completion.d/infisical
|
||||||
|
- src: ./completions/infisical.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/infisical.fish
|
||||||
|
- src: ./completions/infisical.zsh
|
||||||
|
dst: /usr/share/zsh/site-functions/_infisical
|
||||||
|
- src: ./manpages/infisical.1.gz
|
||||||
|
dst: /usr/share/man/man1/infisical.1.gz
|
||||||
scoop:
|
scoop:
|
||||||
bucket:
|
bucket:
|
||||||
owner: Infisical
|
owner: Infisical
|
||||||
@@ -101,7 +125,31 @@ scoop:
|
|||||||
email: [email protected]
|
email: [email protected]
|
||||||
homepage: "https://infisical.com"
|
homepage: "https://infisical.com"
|
||||||
description: "The official Infisical CLI"
|
description: "The official Infisical CLI"
|
||||||
license: Apache-2.0
|
license: MIT
|
||||||
|
aurs:
|
||||||
|
-
|
||||||
|
name: infisical-bin
|
||||||
|
homepage: "https://infisical.com"
|
||||||
|
description: "The official Infisical CLI"
|
||||||
|
maintainers:
|
||||||
|
- Infisical, Inc <[email protected]>
|
||||||
|
license: MIT
|
||||||
|
private_key: '{{ .Env.AUR_KEY }}'
|
||||||
|
git_url: 'ssh://[email protected]/infisical-bin.git'
|
||||||
|
package: |-
|
||||||
|
# bin
|
||||||
|
install -Dm755 "./infisical" "${pkgdir}/usr/bin/infisical"
|
||||||
|
# license
|
||||||
|
install -Dm644 "./LICENSE" "${pkgdir}/usr/share/licenses/infisical/LICENSE"
|
||||||
|
# completions
|
||||||
|
mkdir -p "${pkgdir}/usr/share/bash-completion/completions/"
|
||||||
|
mkdir -p "${pkgdir}/usr/share/zsh/site-functions/"
|
||||||
|
mkdir -p "${pkgdir}/usr/share/fish/vendor_completions.d/"
|
||||||
|
install -Dm644 "./completions/infisical.bash" "${pkgdir}/usr/share/bash-completion/completions/infisical"
|
||||||
|
install -Dm644 "./completions/infisical.zsh" "${pkgdir}/usr/share/zsh/site-functions/infisical"
|
||||||
|
install -Dm644 "./completions/infisical.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/infisical.fish"
|
||||||
|
# man pages
|
||||||
|
install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz"
|
||||||
# dockers:
|
# dockers:
|
||||||
# - dockerfile: goreleaser.dockerfile
|
# - dockerfile: goreleaser.dockerfile
|
||||||
# goos: linux
|
# goos: linux
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
- **[Complete control over your data](https://infisical.com/docs/self-hosting/overview)** - host it yourself on any infrastructure
|
- **[Complete control over your data](https://infisical.com/docs/self-hosting/overview)** - host it yourself on any infrastructure
|
||||||
- **Navigate Multiple Environments** per project (e.g. development, staging, production, etc.)
|
- **Navigate Multiple Environments** per project (e.g. development, staging, production, etc.)
|
||||||
- **Personal/Shared** scoping for environment variables
|
- **Personal/Shared** scoping for environment variables
|
||||||
- **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure (Heroku available, more coming soon)
|
- **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure
|
||||||
- 🔜 **1-Click Deploy** to Digital Ocean and Heroku
|
- 🔜 **1-Click Deploy** to Digital Ocean and Heroku
|
||||||
- 🔜 **Authentication/Authorization** for projects (read/write controls soon)
|
- 🔜 **Authentication/Authorization** for projects (read/write controls soon)
|
||||||
- 🔜 **Automatic Secret Rotation**
|
- 🔜 **Automatic Secret Rotation**
|
||||||
@@ -270,13 +270,13 @@ We're currently setting the foundation and building [integrations](https://infis
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
<a href="https://infisical.com/docs/integrations/frameworks/rails?ref=github.com">
|
<a href="https://infisical.com/docs/integrations/frameworks/vue?ref=github.com">
|
||||||
✔️ Ruby on Rails
|
✔️ Vue
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="left" valign="middle">
|
<td align="left" valign="middle">
|
||||||
<a href="https://infisical.com/docs/integrations/frameworks/vue?ref=github.com">
|
<a href="https://infisical.com/docs/integrations/frameworks/rails?ref=github.com">
|
||||||
✔️ Vue
|
✔️ Ruby on Rails
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -292,6 +292,16 @@ We're currently setting the foundation and building [integrations](https://infis
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="left" valign="middle">
|
||||||
|
<a href="https://infisical.com/docs/integrations/frameworks/dotnet?ref=github.com">
|
||||||
|
✔️ .NET
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td align="left" valign="middle">
|
||||||
|
And more...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -321,4 +331,10 @@ Infisical officially launched as v.1.0 on November 21st, 2022. However, a lot of
|
|||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
<!-- markdownlint-disable -->
|
<!-- markdownlint-disable -->
|
||||||
|
|
||||||
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
|
<a href="https://github.com/dangtony98"><img src="https://avatars.githubusercontent.com/u/25857006?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/mv-turtle"><img src="https://avatars.githubusercontent.com/u/78047717?s=96&v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/maidul98"><img src="https://avatars.githubusercontent.com/u/9300960?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gangjun06"><img src="https://avatars.githubusercontent.com/u/50910815?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/reginaldbondoc"><img src="https://avatars.githubusercontent.com/u/7693108?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/SH5H"><img src="https://avatars.githubusercontent.com/u/25437192?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/gmgale"><img src="https://avatars.githubusercontent.com/u/62303146?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/asharonbaltazar"><img src="https://avatars.githubusercontent.com/u/58940073?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jon4hz"><img src="https://avatars.githubusercontent.com/u/26183582?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/edgarrmondragon"><img src="https://avatars.githubusercontent.com/u/16805946?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arjunyel"><img src="https://avatars.githubusercontent.com/u/11153289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/LemmyMwaura"><img src="https://avatars.githubusercontent.com/u/20738858?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/Zamion101"><img src="https://avatars.githubusercontent.com/u/8071263?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/akhilmhdh"><img src="https://avatars.githubusercontent.com/u/31166322?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/naorpeled"><img src="https://avatars.githubusercontent.com/u/6171622?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/jonerrr"><img src="https://avatars.githubusercontent.com/u/73760377?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/adrianmarinwork"><img src="https://avatars.githubusercontent.com/u/118568289?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/arthurzenika"><img src="https://avatars.githubusercontent.com/u/445200?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/hanywang2"><img src="https://avatars.githubusercontent.com/u/44352119?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/tobias-mintlify"><img src="https://avatars.githubusercontent.com/u/110702161?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wjhurley"><img src="https://avatars.githubusercontent.com/u/15939055?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/0xflotus"><img src="https://avatars.githubusercontent.com/u/26602940?v=4" width="50" height="50" alt=""/></a> <a href="https://github.com/wanjohiryan"><img src="https://avatars.githubusercontent.com/u/71614375?v=4" width="50" height="50" alt=""/></a>
|
||||||
|
|
||||||
|
## 🌎 Translations
|
||||||
|
|
||||||
|
Infisical is currently aviable in English and Korean. Help us translate Infisical to your language!
|
||||||
|
|
||||||
|
You can find all the info in [this issue](https://github.com/Infisical/infisical/issues/181).
|
||||||
Generated
+6
-6
@@ -6677,9 +6677,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/json5": {
|
"node_modules/json5": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz",
|
||||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
"integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"json5": "lib/cli.js"
|
"json5": "lib/cli.js"
|
||||||
@@ -17175,9 +17175,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz",
|
||||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
"integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"jsonwebtoken": {
|
"jsonwebtoken": {
|
||||||
|
|||||||
+48
-41
@@ -14,28 +14,31 @@ import { apiLimiter } from './helpers/rateLimiter';
|
|||||||
import {
|
import {
|
||||||
workspace as eeWorkspaceRouter,
|
workspace as eeWorkspaceRouter,
|
||||||
secret as eeSecretRouter
|
secret as eeSecretRouter
|
||||||
} from './ee/routes';
|
} from './ee/routes/v1';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
signup as signupRouter,
|
signup as v1SignupRouter,
|
||||||
auth as authRouter,
|
auth as v1AuthRouter,
|
||||||
bot as botRouter,
|
bot as v1BotRouter,
|
||||||
organization as organizationRouter,
|
organization as v1OrganizationRouter,
|
||||||
workspace as workspaceRouter,
|
workspace as v1WorkspaceRouter,
|
||||||
membershipOrg as membershipOrgRouter,
|
membershipOrg as v1MembershipOrgRouter,
|
||||||
membership as membershipRouter,
|
membership as v1MembershipRouter,
|
||||||
key as keyRouter,
|
key as v1KeyRouter,
|
||||||
inviteOrg as inviteOrgRouter,
|
inviteOrg as v1InviteOrgRouter,
|
||||||
user as userRouter,
|
user as v1UserRouter,
|
||||||
userAction as userActionRouter,
|
userAction as v1UserActionRouter,
|
||||||
secret as secretRouter,
|
secret as v1SecretRouter,
|
||||||
serviceToken as serviceTokenRouter,
|
serviceToken as v1ServiceTokenRouter,
|
||||||
password as passwordRouter,
|
serviceTokenData as v1ServiceTokenDataRouter,
|
||||||
stripe as stripeRouter,
|
password as v1PasswordRouter,
|
||||||
integration as integrationRouter,
|
stripe as v1StripeRouter,
|
||||||
integrationAuth as integrationAuthRouter,
|
integration as v1IntegrationRouter,
|
||||||
serviceTokenData as serviceTokenDataRouter
|
integrationAuth as v1IntegrationAuthRouter
|
||||||
} from './routes';
|
} from './routes/v1';
|
||||||
|
import {
|
||||||
|
secret as v2SecretRouter,
|
||||||
|
workspace as v2WorkspaceRouter
|
||||||
|
} from './routes/v2';
|
||||||
|
|
||||||
import { getLogger } from './utils/logger';
|
import { getLogger } from './utils/logger';
|
||||||
import { RouteNotFoundError } from './utils/errors';
|
import { RouteNotFoundError } from './utils/errors';
|
||||||
@@ -64,29 +67,33 @@ if (NODE_ENV === 'production') {
|
|||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
}
|
}
|
||||||
|
|
||||||
// /ee routers
|
// (EE) routes
|
||||||
app.use('/api/v1/secret', eeSecretRouter);
|
app.use('/api/v1/secret', eeSecretRouter);
|
||||||
app.use('/api/v1/workspace', eeWorkspaceRouter);
|
app.use('/api/v1/workspace', eeWorkspaceRouter);
|
||||||
|
|
||||||
// routers
|
// v1 routes
|
||||||
app.use('/api/v1/signup', signupRouter);
|
app.use('/api/v1/signup', v1SignupRouter);
|
||||||
app.use('/api/v1/auth', authRouter);
|
app.use('/api/v1/auth', v1AuthRouter);
|
||||||
app.use('/api/v1/bot', botRouter);
|
app.use('/api/v1/bot', v1BotRouter);
|
||||||
app.use('/api/v1/user', userRouter);
|
app.use('/api/v1/user', v1UserRouter);
|
||||||
app.use('/api/v1/user-action', userActionRouter);
|
app.use('/api/v1/user-action', v1UserActionRouter);
|
||||||
app.use('/api/v1/organization', organizationRouter);
|
app.use('/api/v1/organization', v1OrganizationRouter);
|
||||||
app.use('/api/v1/workspace', workspaceRouter);
|
app.use('/api/v1/workspace', v1WorkspaceRouter);
|
||||||
app.use('/api/v1/membership-org', membershipOrgRouter);
|
app.use('/api/v1/membership-org', v1MembershipOrgRouter);
|
||||||
app.use('/api/v1/membership', membershipRouter);
|
app.use('/api/v1/membership', v1MembershipRouter);
|
||||||
app.use('/api/v1/key', keyRouter);
|
app.use('/api/v1/key', v1KeyRouter);
|
||||||
app.use('/api/v1/invite-org', inviteOrgRouter);
|
app.use('/api/v1/invite-org', v1InviteOrgRouter);
|
||||||
app.use('/api/v1/secret', secretRouter);
|
app.use('/api/v1/secret', v1SecretRouter);
|
||||||
app.use('/api/v1/service-token', serviceTokenRouter);
|
app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecate
|
||||||
app.use('/api/v1/password', passwordRouter);
|
app.use('/api/v1/service-token-data', v1ServiceTokenDataRouter);
|
||||||
app.use('/api/v1/stripe', stripeRouter);
|
app.use('/api/v1/password', v1PasswordRouter);
|
||||||
app.use('/api/v1/integration', integrationRouter);
|
app.use('/api/v1/stripe', v1StripeRouter);
|
||||||
app.use('/api/v1/integration-auth', integrationAuthRouter);
|
app.use('/api/v1/integration', v1IntegrationRouter);
|
||||||
app.use('/api/v1/service-token-data', serviceTokenDataRouter);
|
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
||||||
|
|
||||||
|
// v2 routes
|
||||||
|
app.use('/api/v2/workspace', v2WorkspaceRouter);
|
||||||
|
app.use('/api/v2/secret', v2SecretRouter);
|
||||||
|
|
||||||
//* Handle unrouted requests and respond with proper error message as well as status code
|
//* Handle unrouted requests and respond with proper error message as well as status code
|
||||||
app.use((req, res, next)=>{
|
app.use((req, res, next)=>{
|
||||||
|
|||||||
+3
-3
@@ -4,14 +4,14 @@ import jwt from 'jsonwebtoken';
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import * as bigintConversion from 'bigint-conversion';
|
import * as bigintConversion from 'bigint-conversion';
|
||||||
const jsrp = require('jsrp');
|
const jsrp = require('jsrp');
|
||||||
import { User } from '../models';
|
import { User } from '../../models';
|
||||||
import { createToken, issueTokens, clearTokens } from '../helpers/auth';
|
import { createToken, issueTokens, clearTokens } from '../../helpers/auth';
|
||||||
import {
|
import {
|
||||||
NODE_ENV,
|
NODE_ENV,
|
||||||
JWT_AUTH_LIFETIME,
|
JWT_AUTH_LIFETIME,
|
||||||
JWT_AUTH_SECRET,
|
JWT_AUTH_SECRET,
|
||||||
JWT_REFRESH_SECRET
|
JWT_REFRESH_SECRET
|
||||||
} from '../config';
|
} from '../../config';
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Bot, BotKey } from '../models';
|
import { Bot, BotKey } from '../../models';
|
||||||
import { createBot } from '../helpers/bot';
|
import { createBot } from '../../helpers/bot';
|
||||||
|
|
||||||
interface BotKey {
|
interface BotKey {
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
@@ -14,6 +14,7 @@ import * as stripeController from './stripeController';
|
|||||||
import * as userActionController from './userActionController';
|
import * as userActionController from './userActionController';
|
||||||
import * as userController from './userController';
|
import * as userController from './userController';
|
||||||
import * as workspaceController from './workspaceController';
|
import * as workspaceController from './workspaceController';
|
||||||
|
import * as serviceTokenDataController from './serviceTokenDataController';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
authController,
|
authController,
|
||||||
@@ -31,5 +32,6 @@ export {
|
|||||||
stripeController,
|
stripeController,
|
||||||
userActionController,
|
userActionController,
|
||||||
userController,
|
userController,
|
||||||
workspaceController
|
workspaceController,
|
||||||
|
serviceTokenDataController
|
||||||
};
|
};
|
||||||
+4
-4
@@ -2,10 +2,10 @@ import { Request, Response } from 'express';
|
|||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { IntegrationAuth, Integration } from '../models';
|
import { IntegrationAuth, Integration } from '../../models';
|
||||||
import { INTEGRATION_SET, INTEGRATION_OPTIONS, ENV_DEV } from '../variables';
|
import { INTEGRATION_SET, INTEGRATION_OPTIONS, ENV_DEV } from '../../variables';
|
||||||
import { IntegrationService } from '../services';
|
import { IntegrationService } from '../../services';
|
||||||
import { getApps, revokeAccess } from '../integrations';
|
import { getApps, revokeAccess } from '../../integrations';
|
||||||
|
|
||||||
export const getIntegrationOptions = async (
|
export const getIntegrationOptions = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Integration, Bot, BotKey } from '../models';
|
import { Integration, Bot, BotKey } from '../../models';
|
||||||
import { EventService } from '../services';
|
import { EventService } from '../../services';
|
||||||
import { eventPushSecrets } from '../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
|
|
||||||
interface Key {
|
interface Key {
|
||||||
encryptedKey: string;
|
encryptedKey: string;
|
||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Key } from '../models';
|
import { Key } from '../../models';
|
||||||
import { findMembership } from '../helpers/membership';
|
import { findMembership } from '../../helpers/membership';
|
||||||
import { GRANTED } from '../variables';
|
import { GRANTED } from '../../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with
|
* Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with
|
||||||
+5
-5
@@ -1,13 +1,13 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Membership, MembershipOrg, User, Key } from '../models';
|
import { Membership, MembershipOrg, User, Key } from '../../models';
|
||||||
import {
|
import {
|
||||||
findMembership,
|
findMembership,
|
||||||
deleteMembership as deleteMember
|
deleteMembership as deleteMember
|
||||||
} from '../helpers/membership';
|
} from '../../helpers/membership';
|
||||||
import { sendMail } from '../helpers/nodemailer';
|
import { sendMail } from '../../helpers/nodemailer';
|
||||||
import { SITE_URL } from '../config';
|
import { SITE_URL } from '../../config';
|
||||||
import { ADMIN, MEMBER, GRANTED, ACCEPTED } from '../variables';
|
import { ADMIN, MEMBER, GRANTED, ACCEPTED } from '../../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that user is a member of workspace with id [workspaceId]
|
* Check that user is a member of workspace with id [workspaceId]
|
||||||
+28
-22
@@ -1,14 +1,14 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import { SITE_URL, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../config';
|
import { SITE_URL, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../../config';
|
||||||
import { MembershipOrg, Organization, User, Token } from '../models';
|
import { MembershipOrg, Organization, User, Token } from '../../models';
|
||||||
import { deleteMembershipOrg as deleteMemberFromOrg } from '../helpers/membershipOrg';
|
import { deleteMembershipOrg as deleteMemberFromOrg } from '../../helpers/membershipOrg';
|
||||||
import { checkEmailVerification } from '../helpers/signup';
|
import { checkEmailVerification } from '../../helpers/signup';
|
||||||
import { createToken } from '../helpers/auth';
|
import { createToken } from '../../helpers/auth';
|
||||||
import { updateSubscriptionOrgQuantity } from '../helpers/organization';
|
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
|
||||||
import { sendMail } from '../helpers/nodemailer';
|
import { sendMail } from '../../helpers/nodemailer';
|
||||||
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED } from '../variables';
|
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED } from '../../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete organization membership with id [membershipOrgId] from organization
|
* Delete organization membership with id [membershipOrgId] from organization
|
||||||
@@ -80,14 +80,14 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
|
|||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
let membershipToChangeRole;
|
let membershipToChangeRole;
|
||||||
try {
|
// try {
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
Sentry.setUser({ email: req.user.email });
|
// Sentry.setUser({ email: req.user.email });
|
||||||
Sentry.captureException(err);
|
// Sentry.captureException(err);
|
||||||
return res.status(400).send({
|
// return res.status(400).send({
|
||||||
message: 'Failed to change organization membership role'
|
// message: 'Failed to change organization membership role'
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
membershipOrg: membershipToChangeRole
|
membershipOrg: membershipToChangeRole
|
||||||
@@ -218,12 +218,6 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
|
|||||||
const { email, code } = req.body;
|
const { email, code } = req.body;
|
||||||
|
|
||||||
user = await User.findOne({ email }).select('+publicKey');
|
user = await User.findOne({ email }).select('+publicKey');
|
||||||
if (user && user?.publicKey) {
|
|
||||||
// case: user has already completed account
|
|
||||||
return res.status(403).send({
|
|
||||||
error: 'Failed email magic link verification for complete account'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
inviteEmail: email,
|
inviteEmail: email,
|
||||||
@@ -238,6 +232,18 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
|
|||||||
code
|
code
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (user && user?.publicKey) {
|
||||||
|
// case: user has already completed account
|
||||||
|
// membership can be approved and redirected to login/dashboard
|
||||||
|
membershipOrg.status = ACCEPTED;
|
||||||
|
await membershipOrg.save();
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'Successfully verified email',
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
// initialize user account
|
// initialize user account
|
||||||
user = await new User({
|
user = await new User({
|
||||||
+5
-5
@@ -6,7 +6,7 @@ import {
|
|||||||
STRIPE_PRODUCT_STARTER,
|
STRIPE_PRODUCT_STARTER,
|
||||||
STRIPE_PRODUCT_PRO,
|
STRIPE_PRODUCT_PRO,
|
||||||
STRIPE_PRODUCT_CARD_AUTH
|
STRIPE_PRODUCT_CARD_AUTH
|
||||||
} from '../config';
|
} from '../../config';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
|
|
||||||
const stripe = new Stripe(STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(STRIPE_SECRET_KEY, {
|
||||||
@@ -18,10 +18,10 @@ import {
|
|||||||
Organization,
|
Organization,
|
||||||
Workspace,
|
Workspace,
|
||||||
IncidentContactOrg
|
IncidentContactOrg
|
||||||
} from '../models';
|
} from '../../models';
|
||||||
import { createOrganization as create } from '../helpers/organization';
|
import { createOrganization as create } from '../../helpers/organization';
|
||||||
import { addMembershipsOrg } from '../helpers/membershipOrg';
|
import { addMembershipsOrg } from '../../helpers/membershipOrg';
|
||||||
import { OWNER, ACCEPTED } from '../variables';
|
import { OWNER, ACCEPTED } from '../../variables';
|
||||||
|
|
||||||
const productToPriceMap = {
|
const productToPriceMap = {
|
||||||
starter: STRIPE_PRODUCT_STARTER,
|
starter: STRIPE_PRODUCT_STARTER,
|
||||||
+6
-5
@@ -1,13 +1,14 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const jsrp = require('jsrp');
|
const jsrp = require('jsrp');
|
||||||
import * as bigintConversion from 'bigint-conversion';
|
import * as bigintConversion from 'bigint-conversion';
|
||||||
import { User, Token, BackupPrivateKey } from '../models';
|
import { User, Token, BackupPrivateKey } from '../../models';
|
||||||
import { checkEmailVerification } from '../helpers/signup';
|
import { checkEmailVerification } from '../../helpers/signup';
|
||||||
import { createToken } from '../helpers/auth';
|
import { createToken } from '../../helpers/auth';
|
||||||
import { sendMail } from '../helpers/nodemailer';
|
import { sendMail } from '../../helpers/nodemailer';
|
||||||
import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../config';
|
import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../../config';
|
||||||
|
|
||||||
const clientPublicKeys: any = {};
|
const clientPublicKeys: any = {};
|
||||||
|
|
||||||
+12
-8
@@ -1,16 +1,16 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { Key, Secret } from '../models';
|
import { Key, Secret } from '../../models';
|
||||||
import {
|
import {
|
||||||
pushSecrets as push,
|
v1PushSecrets as push,
|
||||||
pullSecrets as pull,
|
pullSecrets as pull,
|
||||||
reformatPullSecrets
|
reformatPullSecrets
|
||||||
} from '../helpers/secret';
|
} from '../../helpers/secret';
|
||||||
import { pushKeys } from '../helpers/key';
|
import { pushKeys } from '../../helpers/key';
|
||||||
import { eventPushSecrets } from '../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
import { EventService } from '../services';
|
import { EventService } from '../../services';
|
||||||
import { ENV_SET } from '../variables';
|
import { ENV_SET } from '../../variables';
|
||||||
import { postHogClient } from '../services';
|
import { postHogClient } from '../../services';
|
||||||
|
|
||||||
interface PushSecret {
|
interface PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
@@ -21,6 +21,10 @@ interface PushSecret {
|
|||||||
ivValue: string;
|
ivValue: string;
|
||||||
tagValue: string;
|
tagValue: string;
|
||||||
hashValue: string;
|
hashValue: string;
|
||||||
|
ciphertextComment: string;
|
||||||
|
ivComment: string;
|
||||||
|
tagComment: string;
|
||||||
|
hashComment: string;
|
||||||
type: 'shared' | 'personal';
|
type: 'shared' | 'personal';
|
||||||
}
|
}
|
||||||
|
|
||||||
+5
-15
@@ -1,8 +1,8 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { ServiceToken } from '../models';
|
import { ServiceToken } from '../../models';
|
||||||
import { createToken } from '../helpers/auth';
|
import { createToken } from '../../helpers/auth';
|
||||||
import { ENV_SET } from '../variables';
|
import { ENV_SET } from '../../variables';
|
||||||
import { JWT_SERVICE_SECRET } from '../config';
|
import { JWT_SERVICE_SECRET } from '../../config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return service token on request
|
* Return service token on request
|
||||||
@@ -11,7 +11,6 @@ import { JWT_SERVICE_SECRET } from '../config';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getServiceToken = async (req: Request, res: Response) => {
|
export const getServiceToken = async (req: Request, res: Response) => {
|
||||||
// get service token
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceToken: req.serviceToken
|
serviceToken: req.serviceToken
|
||||||
});
|
});
|
||||||
@@ -73,13 +72,4 @@ export const createServiceToken = async (req: Request, res: Response) => {
|
|||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
token
|
token
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* SERVICE_TOKEN: <JWT_SERVICE_TOKEN>,<PRIVATE_KEY_SERVICE_TOKEN>
|
|
||||||
* - <JWT_SERVICE_TOKEN> authorizes the service token for "service token"-only endpoints.
|
|
||||||
* - <PRIVATE_KEY_SERVICE_TOKEN> authorizes the service token to pull secrets via that endpoint.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import {
|
||||||
|
ServiceTokenData
|
||||||
|
} from '../../models';
|
||||||
|
import {
|
||||||
|
SALT_ROUNDS
|
||||||
|
} from '../../config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return service token data associated with service token on request
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getServiceTokenData = async (req: Request, res: Response) => ({
|
||||||
|
serviceTokenData: req.serviceTokenData
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new service token data for workspace with id [workspaceId] and
|
||||||
|
* environment [environment].
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||||
|
let serviceToken, serviceTokenData;
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
encryptedKey,
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
expiresIn
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
// create 41-char service token with first 9-char being the prefix
|
||||||
|
serviceToken = `st.${crypto.randomBytes(19).toString('hex')}`;
|
||||||
|
|
||||||
|
const serviceTokenHash = await bcrypt.hash(serviceToken, SALT_ROUNDS);
|
||||||
|
|
||||||
|
// compute access token expiration date
|
||||||
|
const expiresAt = new Date();
|
||||||
|
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||||
|
|
||||||
|
serviceTokenData = await new ServiceTokenData({
|
||||||
|
name,
|
||||||
|
workspace: workspaceId,
|
||||||
|
environment,
|
||||||
|
user: req.user._id,
|
||||||
|
expiresAt,
|
||||||
|
prefix: serviceToken.substring(0, 9),
|
||||||
|
serviceTokenHash,
|
||||||
|
encryptedKey,
|
||||||
|
iv,
|
||||||
|
tag
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to create service token data'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceToken,
|
||||||
|
serviceTokenData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete service token data with id [serviceTokenDataId].
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
||||||
|
let serviceTokenData;
|
||||||
|
try {
|
||||||
|
const { serviceTokenDataId } = req.params;
|
||||||
|
|
||||||
|
serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to delete service token data'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
serviceTokenData
|
||||||
|
});
|
||||||
|
}
|
||||||
+6
-6
@@ -1,15 +1,15 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { NODE_ENV, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../config';
|
import { NODE_ENV, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../../config';
|
||||||
import { User, MembershipOrg } from '../models';
|
import { User, MembershipOrg } from '../../models';
|
||||||
import { completeAccount } from '../helpers/user';
|
import { completeAccount } from '../../helpers/user';
|
||||||
import {
|
import {
|
||||||
sendEmailVerification,
|
sendEmailVerification,
|
||||||
checkEmailVerification,
|
checkEmailVerification,
|
||||||
initializeDefaultOrg
|
initializeDefaultOrg
|
||||||
} from '../helpers/signup';
|
} from '../../helpers/signup';
|
||||||
import { issueTokens, createToken } from '../helpers/auth';
|
import { issueTokens, createToken } from '../../helpers/auth';
|
||||||
import { INVITED, ACCEPTED } from '../variables';
|
import { INVITED, ACCEPTED } from '../../variables';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { UserAction } from '../models';
|
import { UserAction } from '../../models';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add user action [action]
|
* Add user action [action]
|
||||||
+5
-5
@@ -8,14 +8,14 @@ import {
|
|||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
IUser,
|
IUser,
|
||||||
ServiceToken,
|
ServiceToken,
|
||||||
ServiceTokenData,
|
ServiceTokenData
|
||||||
} from '../models';
|
} from '../../models';
|
||||||
import {
|
import {
|
||||||
createWorkspace as create,
|
createWorkspace as create,
|
||||||
deleteWorkspace as deleteWork
|
deleteWorkspace as deleteWork
|
||||||
} from '../helpers/workspace';
|
} from '../../helpers/workspace';
|
||||||
import { addMemberships } from '../helpers/membership';
|
import { addMemberships } from '../../helpers/membership';
|
||||||
import { ADMIN, COMPLETED, GRANTED } from '../variables';
|
import { ADMIN, COMPLETED, GRANTED } from '../../variables';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return public keys of members of workspace with id [workspaceId]
|
* Return public keys of members of workspace with id [workspaceId]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import * as workspaceController from './workspaceController';
|
||||||
|
|
||||||
|
export {
|
||||||
|
workspaceController
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import * as Sentry from '@sentry/node';
|
||||||
|
import {
|
||||||
|
Workspace,
|
||||||
|
Membership,
|
||||||
|
MembershipOrg,
|
||||||
|
Integration,
|
||||||
|
IntegrationAuth,
|
||||||
|
Key,
|
||||||
|
IUser,
|
||||||
|
ServiceToken,
|
||||||
|
} from '../../models';
|
||||||
|
import {
|
||||||
|
createWorkspace as create,
|
||||||
|
deleteWorkspace as deleteWork
|
||||||
|
} from '../../helpers/workspace';
|
||||||
|
import {
|
||||||
|
v2PushSecrets as push,
|
||||||
|
pullSecrets as pull,
|
||||||
|
reformatPullSecrets
|
||||||
|
} from '../../helpers/secret';
|
||||||
|
import { pushKeys } from '../../helpers/key';
|
||||||
|
import { addMemberships } from '../../helpers/membership';
|
||||||
|
import { postHogClient, EventService } from '../../services';
|
||||||
|
import { eventPushSecrets } from '../../events';
|
||||||
|
import { ADMIN, COMPLETED, GRANTED, ENV_SET } from '../../variables';
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
|
* for environment [environment]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||||
|
// upload (encrypted) secrets to workspace with id [workspaceId]
|
||||||
|
|
||||||
|
try {
|
||||||
|
let { secrets }: { secrets: V2PushSecret[] } = req.body;
|
||||||
|
const { keys, environment, channel } = req.body;
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
// validate environment
|
||||||
|
if (!ENV_SET.has(environment)) {
|
||||||
|
throw new Error('Failed to validate environment');
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitize secrets
|
||||||
|
secrets = secrets.filter(
|
||||||
|
(s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== ''
|
||||||
|
);
|
||||||
|
|
||||||
|
await push({
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secrets
|
||||||
|
});
|
||||||
|
|
||||||
|
await pushKeys({
|
||||||
|
userId: req.user._id,
|
||||||
|
workspaceId,
|
||||||
|
keys
|
||||||
|
});
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
postHogClient.capture({
|
||||||
|
event: 'secrets pushed',
|
||||||
|
distinctId: req.user.email,
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel: channel ? channel : 'cli'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// trigger event - push secrets
|
||||||
|
EventService.handleEvent({
|
||||||
|
event: eventPushSecrets({
|
||||||
|
workspaceId
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to upload workspace secrets'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'Successfully uploaded workspace secrets'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return (encrypted) secrets for workspace with id [workspaceId]
|
||||||
|
* for environment [environment]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const pullSecrets = async (req: Request, res: Response) => {
|
||||||
|
let secrets;
|
||||||
|
try {
|
||||||
|
const environment: string = req.query.environment as string;
|
||||||
|
const channel: string = req.query.channel as string;
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
let userId;
|
||||||
|
if (req.user) {
|
||||||
|
userId = req.user._id.toString();
|
||||||
|
} else if (req.serviceTokenData) {
|
||||||
|
userId = req.serviceTokenData.user._id
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets = await pull({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
if (channel !== 'cli') { // TODO: fix frontend to get rid of this reformat bs
|
||||||
|
secrets = reformatPullSecrets({ secrets });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postHogClient) {
|
||||||
|
// capture secrets pushed event in production
|
||||||
|
postHogClient.capture({
|
||||||
|
distinctId: req.user.email,
|
||||||
|
event: 'secrets pulled',
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
environment,
|
||||||
|
workspaceId,
|
||||||
|
channel: channel ? channel : 'cli'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to pull workspace secrets'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
secrets
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getWorkspaceKey = async (req: Request, res: Response) => {
|
||||||
|
let key;
|
||||||
|
try {
|
||||||
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
|
key = await Key.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
receiver: req.user._id
|
||||||
|
}).populate('sender', '+publicKey');
|
||||||
|
|
||||||
|
if (!key) throw new Error('Failed to find workspace key');
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: 'Failed to get workspace key'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
key
|
||||||
|
});
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { SecretVersion } from '../models';
|
import { SecretVersion } from '../../models';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return secret versions for secret with id [secretId]
|
* Return secret versions for secret with id [secretId]
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
import { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } from '../config';
|
import { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } from '../../../config';
|
||||||
const stripe = new Stripe(STRIPE_SECRET_KEY, {
|
const stripe = new Stripe(STRIPE_SECRET_KEY, {
|
||||||
apiVersion: '2022-08-01'
|
apiVersion: '2022-08-01'
|
||||||
});
|
});
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { SecretSnapshot } from '../models';
|
import { SecretSnapshot } from '../../models';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return secret snapshots for workspace with id [workspaceId]
|
* Return secret snapshots for workspace with id [workspaceId]
|
||||||
@@ -4,14 +4,16 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../../middleware';
|
||||||
import { body, query, param } from 'express-validator';
|
import { body, query, param } from 'express-validator';
|
||||||
import { secretController } from '../controllers';
|
import { secretController } from '../../controllers/v1';
|
||||||
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../../variables';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:secretId/secret-versions',
|
'/:secretId/secret-versions',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { stripeController } from '../controllers';
|
import { stripeController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post('/webhook', stripeController.handleWebhook);
|
router.post('/webhook', stripeController.handleWebhook);
|
||||||
|
|
||||||
@@ -4,14 +4,16 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../../middleware';
|
} from '../../../middleware';
|
||||||
import { param, query } from 'express-validator';
|
import { param, query } from 'express-validator';
|
||||||
import { ADMIN, MEMBER, GRANTED } from '../../variables';
|
import { ADMIN, MEMBER, GRANTED } from '../../../variables';
|
||||||
import { workspaceController } from '../controllers';
|
import { workspaceController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/secret-snapshots',
|
'/:workspaceId/secret-snapshots',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED]
|
acceptedStatuses: [GRANTED]
|
||||||
@@ -1,15 +1,69 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
import {
|
import {
|
||||||
User
|
User,
|
||||||
|
ServiceTokenData
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
JWT_AUTH_LIFETIME,
|
JWT_AUTH_LIFETIME,
|
||||||
JWT_AUTH_SECRET,
|
JWT_AUTH_SECRET,
|
||||||
JWT_REFRESH_LIFETIME,
|
JWT_REFRESH_LIFETIME,
|
||||||
JWT_REFRESH_SECRET
|
JWT_REFRESH_SECRET,
|
||||||
|
SALT_ROUNDS
|
||||||
} from '../config';
|
} from '../config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach auth payload
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.authTokenValue
|
||||||
|
*/
|
||||||
|
const attachAuthPayload = async ({
|
||||||
|
authTokenValue
|
||||||
|
}: {
|
||||||
|
authTokenValue: string;
|
||||||
|
}) => {
|
||||||
|
let serviceTokenHash, decodedToken; // intermediate variables
|
||||||
|
let serviceTokenData, user; // payloads
|
||||||
|
try {
|
||||||
|
switch (authTokenValue.split('.', 1)[0]) {
|
||||||
|
case 'st':
|
||||||
|
// case: service token auth mode
|
||||||
|
serviceTokenHash = await bcrypt.hash(authTokenValue, SALT_ROUNDS);
|
||||||
|
serviceTokenData = await ServiceTokenData
|
||||||
|
.findOne({
|
||||||
|
serviceTokenHash
|
||||||
|
})
|
||||||
|
.select('+encryptedKey +iv +tag');
|
||||||
|
|
||||||
|
if (!serviceTokenData) {
|
||||||
|
throw new Error('Account not found error');
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceTokenData;
|
||||||
|
default:
|
||||||
|
// case: JWT token auth mode
|
||||||
|
decodedToken = <jwt.UserIDJwtPayload>(
|
||||||
|
jwt.verify(authTokenValue, JWT_AUTH_SECRET)
|
||||||
|
);
|
||||||
|
|
||||||
|
user = await User.findOne({
|
||||||
|
_id: decodedToken.userId
|
||||||
|
}).select('+publicKey');
|
||||||
|
|
||||||
|
if (!user)
|
||||||
|
throw new Error('Account not found error');
|
||||||
|
|
||||||
|
if (!user?.publicKey)
|
||||||
|
throw new Error('Unable to authenticate due to partially set up account');
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error('Failed to attach auth payload');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return newly issued (JWT) auth and refresh tokens to user with id [userId]
|
* Return newly issued (JWT) auth and refresh tokens to user with id [userId]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
@@ -99,4 +153,9 @@ const createToken = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { createToken, issueTokens, clearTokens };
|
export {
|
||||||
|
attachAuthPayload,
|
||||||
|
createToken,
|
||||||
|
issueTokens,
|
||||||
|
clearTokens
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
decryptSymmetric,
|
decryptSymmetric,
|
||||||
decryptAsymmetric
|
decryptAsymmetric
|
||||||
} from '../utils/crypto';
|
} from '../utils/crypto';
|
||||||
import { decryptSecrets } from '../helpers/secret';
|
|
||||||
import { ENCRYPTION_KEY } from '../config';
|
import { ENCRYPTION_KEY } from '../config';
|
||||||
import { SECRET_SHARED } from '../variables';
|
import { SECRET_SHARED } from '../variables';
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const validateMembership = async ({
|
|||||||
membership = await Membership.findOne({
|
membership = await Membership.findOne({
|
||||||
user: userId,
|
user: userId,
|
||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
});
|
}).populate("workspace");
|
||||||
|
|
||||||
if (!membership) throw new Error('Failed to find membership');
|
if (!membership) throw new Error('Failed to find membership');
|
||||||
|
|
||||||
|
|||||||
+294
-76
@@ -14,9 +14,8 @@ import {
|
|||||||
} from '../ee/helpers/secret';
|
} from '../ee/helpers/secret';
|
||||||
import { decryptSymmetric } from '../utils/crypto';
|
import { decryptSymmetric } from '../utils/crypto';
|
||||||
import { SECRET_SHARED, SECRET_PERSONAL } from '../variables';
|
import { SECRET_SHARED, SECRET_PERSONAL } from '../variables';
|
||||||
import { LICENSE_KEY } from '../config';
|
|
||||||
|
|
||||||
interface PushSecret {
|
interface V1PushSecret {
|
||||||
ciphertextKey: string;
|
ciphertextKey: string;
|
||||||
ivKey: string;
|
ivKey: string;
|
||||||
tagKey: string;
|
tagKey: string;
|
||||||
@@ -25,9 +24,29 @@ interface PushSecret {
|
|||||||
ivValue: string;
|
ivValue: string;
|
||||||
tagValue: string;
|
tagValue: string;
|
||||||
hashValue: string;
|
hashValue: string;
|
||||||
|
ciphertextComment: string;
|
||||||
|
ivComment: string;
|
||||||
|
tagComment: string;
|
||||||
|
hashComment: string;
|
||||||
type: 'shared' | 'personal';
|
type: 'shared' | 'personal';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
interface Update {
|
interface Update {
|
||||||
[index: string]: any;
|
[index: string]: any;
|
||||||
}
|
}
|
||||||
@@ -45,7 +64,7 @@ type DecryptSecretType = 'text' | 'object' | 'expanded';
|
|||||||
* @param {String} obj.environment - environment for secrets
|
* @param {String} obj.environment - environment for secrets
|
||||||
* @param {Object[]} obj.secrets - secrets to push
|
* @param {Object[]} obj.secrets - secrets to push
|
||||||
*/
|
*/
|
||||||
const pushSecrets = async ({
|
const v1PushSecrets = async ({
|
||||||
userId,
|
userId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
@@ -54,7 +73,7 @@ const pushSecrets = async ({
|
|||||||
userId: string;
|
userId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
secrets: PushSecret[];
|
secrets: V1PushSecret[];
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
// TODO: clean up function and fix up types
|
// TODO: clean up function and fix up types
|
||||||
try {
|
try {
|
||||||
@@ -93,8 +112,9 @@ const pushSecrets = async ({
|
|||||||
const toUpdate = oldSecrets
|
const toUpdate = oldSecrets
|
||||||
.filter((s) => {
|
.filter((s) => {
|
||||||
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
|
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
|
||||||
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue) {
|
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashValue
|
||||||
// case: filter secrets where value changed
|
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].hashComment) {
|
||||||
|
// case: filter secrets where value or comment changed
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,14 +133,22 @@ const pushSecrets = async ({
|
|||||||
ciphertextValue,
|
ciphertextValue,
|
||||||
ivValue,
|
ivValue,
|
||||||
tagValue,
|
tagValue,
|
||||||
hashValue
|
hashValue,
|
||||||
|
ciphertextComment,
|
||||||
|
ivComment,
|
||||||
|
tagComment,
|
||||||
|
hashComment
|
||||||
} = newSecretsObj[`${s.type}-${s.secretKeyHash}`];
|
} = newSecretsObj[`${s.type}-${s.secretKeyHash}`];
|
||||||
|
|
||||||
const update: Update = {
|
const update: Update = {
|
||||||
secretValueCiphertext: ciphertextValue,
|
secretValueCiphertext: ciphertextValue,
|
||||||
secretValueIV: ivValue,
|
secretValueIV: ivValue,
|
||||||
secretValueTag: tagValue,
|
secretValueTag: tagValue,
|
||||||
secretValueHash: hashValue
|
secretValueHash: hashValue,
|
||||||
|
secretCommentCiphertext: ciphertextComment,
|
||||||
|
secretCommentIV: ivComment,
|
||||||
|
secretCommentTag: tagComment,
|
||||||
|
secretCommentHash: hashComment,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!s.version) {
|
if (!s.version) {
|
||||||
@@ -192,7 +220,254 @@ const pushSecrets = async ({
|
|||||||
secretValueCiphertext: s.ciphertextValue,
|
secretValueCiphertext: s.ciphertextValue,
|
||||||
secretValueIV: s.ivValue,
|
secretValueIV: s.ivValue,
|
||||||
secretValueTag: s.tagValue,
|
secretValueTag: s.tagValue,
|
||||||
secretValueHash: s.hashValue
|
secretValueHash: s.hashValue,
|
||||||
|
secretCommentCiphertext: s.ciphertextComment,
|
||||||
|
secretCommentIV: s.ivComment,
|
||||||
|
secretCommentTag: s.tagComment,
|
||||||
|
secretCommentHash: s.hashComment
|
||||||
|
};
|
||||||
|
|
||||||
|
if (toAdd[idx].type === 'personal') {
|
||||||
|
obj['user' as keyof typeof obj] = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// (EE) add secret versions for new secrets
|
||||||
|
EESecretService.addSecretVersions({
|
||||||
|
secretVersions: newSecrets.map(({
|
||||||
|
_id,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
}) => ({
|
||||||
|
secret: _id,
|
||||||
|
version: 1,
|
||||||
|
isDeleted: false,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// (EE) take a secret snapshot
|
||||||
|
await EESecretService.takeSecretSnapshot({
|
||||||
|
workspaceId
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to push shared and personal secrets');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push secrets for user with id [userId] to workspace
|
||||||
|
* with id [workspaceId] with environment [environment]. Follow steps:
|
||||||
|
* 1. Handle shared secrets (insert, delete)
|
||||||
|
* 2. handle personal secrets (insert, delete)
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.userId - id of user to push secrets for
|
||||||
|
* @param {String} obj.workspaceId - id of workspace to push to
|
||||||
|
* @param {String} obj.environment - environment for secrets
|
||||||
|
* @param {Object[]} obj.secrets - secrets to push
|
||||||
|
*/
|
||||||
|
const v2PushSecrets = async ({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
environment,
|
||||||
|
secrets
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
environment: string;
|
||||||
|
secrets: V2PushSecret[];
|
||||||
|
}): Promise<void> => {
|
||||||
|
// TODO: clean up function and fix up types
|
||||||
|
try {
|
||||||
|
// construct useful data structures
|
||||||
|
const oldSecrets = await pullSecrets({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
const oldSecretsObj: any = oldSecrets.reduce((accumulator, s: any) =>
|
||||||
|
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s })
|
||||||
|
, {});
|
||||||
|
const newSecretsObj: any = secrets.reduce((accumulator, s) =>
|
||||||
|
({ ...accumulator, [`${s.type}-${s.secretKeyHash}`]: s })
|
||||||
|
, {});
|
||||||
|
|
||||||
|
// handle deleting secrets
|
||||||
|
const toDelete = oldSecrets
|
||||||
|
.filter(
|
||||||
|
(s: ISecret) => !(`${s.type}-${s.secretKeyHash}` in newSecretsObj)
|
||||||
|
)
|
||||||
|
.map((s) => s._id);
|
||||||
|
if (toDelete.length > 0) {
|
||||||
|
await Secret.deleteMany({
|
||||||
|
_id: { $in: toDelete }
|
||||||
|
});
|
||||||
|
|
||||||
|
await SecretVersion.updateMany({
|
||||||
|
secret: { $in: toDelete }
|
||||||
|
}, {
|
||||||
|
isDeleted: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const toUpdate = oldSecrets
|
||||||
|
.filter((s) => {
|
||||||
|
if (`${s.type}-${s.secretKeyHash}` in newSecretsObj) {
|
||||||
|
if (s.secretValueHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretValueHash
|
||||||
|
|| s.secretCommentHash !== newSecretsObj[`${s.type}-${s.secretKeyHash}`].secretCommentHash) {
|
||||||
|
// case: filter secrets where value or comment changed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s.version) {
|
||||||
|
// case: filter (legacy) secrets that were not versioned
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const operations = toUpdate
|
||||||
|
.map((s) => {
|
||||||
|
const {
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
} = newSecretsObj[`${s.type}-${s.secretKeyHash}`];
|
||||||
|
|
||||||
|
const update: Update = {
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s.version) {
|
||||||
|
// case: (legacy) secret was not versioned
|
||||||
|
update.version = 1;
|
||||||
|
} else {
|
||||||
|
update['$inc'] = {
|
||||||
|
version: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.type === SECRET_PERSONAL) {
|
||||||
|
// attach user associated with the personal secret
|
||||||
|
update['user'] = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateOne: {
|
||||||
|
filter: {
|
||||||
|
_id: oldSecretsObj[`${s.type}-${s.secretKeyHash}`]._id
|
||||||
|
},
|
||||||
|
update
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await Secret.bulkWrite(operations as any);
|
||||||
|
|
||||||
|
// (EE) add secret versions for updated secrets
|
||||||
|
await EESecretService.addSecretVersions({
|
||||||
|
secretVersions: toUpdate.map((s) => {
|
||||||
|
const {
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
} = newSecretsObj[`${s.type}-${s.secretKeyHash}`];
|
||||||
|
|
||||||
|
return ({
|
||||||
|
secret: s._id,
|
||||||
|
version: s.version ? s.version + 1 : 1,
|
||||||
|
isDeleted: false,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle adding new secrets
|
||||||
|
const toAdd = secrets.filter((s) => !(`${s.type}-${s.secretKeyHash}` in oldSecretsObj));
|
||||||
|
|
||||||
|
if (toAdd.length > 0) {
|
||||||
|
// add secrets
|
||||||
|
const newSecrets = await Secret.insertMany(
|
||||||
|
toAdd.map(({
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash,
|
||||||
|
}, idx) => {
|
||||||
|
const obj: any = {
|
||||||
|
version: 1,
|
||||||
|
workspace: workspaceId,
|
||||||
|
type: toAdd[idx].type,
|
||||||
|
environment,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyHash,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueHash,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash
|
||||||
};
|
};
|
||||||
|
|
||||||
if (toAdd[idx].type === 'personal') {
|
if (toAdd[idx].type === 'personal') {
|
||||||
@@ -315,6 +590,13 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
|||||||
iv: s.secretValueIV,
|
iv: s.secretValueIV,
|
||||||
tag: s.secretValueTag,
|
tag: s.secretValueTag,
|
||||||
hash: s.secretValueHash
|
hash: s.secretValueHash
|
||||||
|
},
|
||||||
|
secretComment: {
|
||||||
|
workspace: s.workspace,
|
||||||
|
ciphertext: s.secretCommentCiphertext,
|
||||||
|
iv: s.secretCommentIV,
|
||||||
|
tag: s.secretCommentTag,
|
||||||
|
hash: s.secretCommentHash
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -326,73 +608,9 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
|
|||||||
return reformatedSecrets;
|
return reformatedSecrets;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Return decrypted secrets in format [format]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object[]} obj.secrets - array of (encrypted) secret key-value pair objects
|
|
||||||
* @param {String} obj.key - symmetric key to decrypt secret key-value pairs
|
|
||||||
* @param {String} obj.format - desired return format that is either "text," "object," or "expanded"
|
|
||||||
* @return {String|Object} (decrypted) secrets also called the content
|
|
||||||
*/
|
|
||||||
const decryptSecrets = ({
|
|
||||||
secrets,
|
|
||||||
key,
|
|
||||||
format
|
|
||||||
}: {
|
|
||||||
secrets: PushSecret[];
|
|
||||||
key: string;
|
|
||||||
format: DecryptSecretType;
|
|
||||||
}) => {
|
|
||||||
// init content
|
|
||||||
let content: any = format === 'text' ? '' : {};
|
|
||||||
|
|
||||||
// decrypt secrets
|
|
||||||
secrets.forEach((s, idx) => {
|
|
||||||
const secretKey = decryptSymmetric({
|
|
||||||
ciphertext: s.ciphertextKey,
|
|
||||||
iv: s.ivKey,
|
|
||||||
tag: s.tagKey,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretValue = decryptSymmetric({
|
|
||||||
ciphertext: s.ciphertextValue,
|
|
||||||
iv: s.ivValue,
|
|
||||||
tag: s.tagValue,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (format) {
|
|
||||||
case 'text':
|
|
||||||
content += secretKey;
|
|
||||||
content += '=';
|
|
||||||
content += secretValue;
|
|
||||||
|
|
||||||
if (idx < secrets.length) {
|
|
||||||
content += '\n';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'object':
|
|
||||||
content[secretKey] = secretValue;
|
|
||||||
break;
|
|
||||||
case 'expanded':
|
|
||||||
content[secretKey] = {
|
|
||||||
...s,
|
|
||||||
plaintextKey: secretKey,
|
|
||||||
plaintextValue: secretValue
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return content;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
pushSecrets,
|
v1PushSecrets,
|
||||||
|
v2PushSecrets,
|
||||||
pullSecrets,
|
pullSecrets,
|
||||||
reformatPullSecrets,
|
reformatPullSecrets
|
||||||
decryptSecrets
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
CLIENT_SECRET_NETLIFY,
|
CLIENT_SECRET_NETLIFY,
|
||||||
CLIENT_SECRET_GITHUB
|
CLIENT_SECRET_GITHUB
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import { user } from '../routes';
|
|
||||||
|
|
||||||
interface ExchangeCodeHerokuResponse {
|
interface ExchangeCodeHerokuResponse {
|
||||||
token_type: string;
|
token_type: string;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { User } from '../models';
|
import { User, ServiceTokenData } from '../models';
|
||||||
|
import {
|
||||||
|
attachAuthPayload
|
||||||
|
} from '../helpers/auth';
|
||||||
import { JWT_AUTH_SECRET } from '../config';
|
import { JWT_AUTH_SECRET } from '../config';
|
||||||
import { AccountNotFoundError, BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
import { AccountNotFoundError, BadRequestError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
@@ -11,34 +14,58 @@ declare module 'jsonwebtoken' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if JWT (auth) token on request is valid (e.g. not expired),
|
* Validate if token on request is valid (e.g. not expired) for various auth modes:
|
||||||
* if there is an associated user, and if that user is fully setup.
|
* - If token is a JWT token, then check if there is an associated user
|
||||||
* @param req - express request object
|
* and if user is fully setup.
|
||||||
* @param res - express response object
|
* - If token is a service token (st), then check if there is associated
|
||||||
* @param next - express next function
|
* service token data.
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String[]} obj.acceptedAuthModes - accepted modes of authentication (jwt/st)
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const requireAuth = async (req: Request, res: Response, next: NextFunction) => {
|
const requireAuth = ({
|
||||||
// JWT authentication middleware
|
acceptedAuthModes = ['jwt']
|
||||||
const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
|
}: {
|
||||||
if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`}))
|
acceptedAuthModes: string[];
|
||||||
if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(BadRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`}))
|
}) => {
|
||||||
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
|
||||||
|
if(AUTH_TOKEN_TYPE === null)
|
||||||
|
return next(BadRequestError({message: `Missing Authorization Header in the request header.`}))
|
||||||
|
if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer')
|
||||||
|
return next(BadRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`}))
|
||||||
|
if(AUTH_TOKEN_VALUE === null)
|
||||||
|
return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
|
||||||
|
|
||||||
|
// validate auth mode
|
||||||
|
let authMode;
|
||||||
|
switch (AUTH_TOKEN_VALUE.split('.', 1)[0]) {
|
||||||
|
case 'st':
|
||||||
|
authMode = 'st';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
authMode = 'jwt';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acceptedAuthModes.includes(authMode)) throw new Error('Failed to validate auth mode');
|
||||||
|
|
||||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
// attach auth request payload
|
||||||
jwt.verify(AUTH_TOKEN_VALUE, JWT_AUTH_SECRET)
|
const payload = await attachAuthPayload({
|
||||||
);
|
authTokenValue: AUTH_TOKEN_VALUE
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (authMode) {
|
||||||
|
case 'st':
|
||||||
|
req.serviceTokenData = payload;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
req.user = payload;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const user = await User.findOne({
|
return next();
|
||||||
_id: decodedToken.userId
|
}
|
||||||
}).select('+publicKey');
|
}
|
||||||
|
|
||||||
if (!user) return next(AccountNotFoundError({message: 'Failed to locate User account'}))
|
|
||||||
if (!user?.publicKey)
|
|
||||||
return next(UnauthorizedRequestError({message: 'Unable to authenticate due to partially set up account'}))
|
|
||||||
|
|
||||||
req.user = user;
|
|
||||||
return next();
|
|
||||||
};
|
|
||||||
|
|
||||||
export default requireAuth;
|
export default requireAuth;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { ServiceToken, ServiceTokenData } from '../models';
|
import { ServiceToken, ServiceTokenData } from '../models';
|
||||||
import { validateMembership } from '../helpers/membership';
|
import { validateMembership } from '../helpers/membership';
|
||||||
import { AccountNotFoundError } from '../utils/errors';
|
import { AccountNotFoundError, UnauthorizedRequestError } from '../utils/errors';
|
||||||
|
|
||||||
type req = 'params' | 'body' | 'query';
|
type req = 'params' | 'body' | 'query';
|
||||||
|
|
||||||
@@ -15,21 +15,25 @@ const requireServiceTokenDataAuth = ({
|
|||||||
location?: req;
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const { serviceTokenDataId } = req[location];
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenData
|
const serviceTokenData = await ServiceTokenData
|
||||||
.findById(req[location].serviceTokenDataId)
|
.findById(req[location].serviceTokenDataId)
|
||||||
.select('+encryptedKey +iv +tag');
|
.select('+encryptedKey +iv +tag');
|
||||||
|
|
||||||
if (!serviceTokenData) {
|
if (!serviceTokenData) {
|
||||||
return next(AccountNotFoundError({message: 'Failed to locate service token data'}));
|
return next(AccountNotFoundError({message: 'Failed to locate service token data'}));
|
||||||
}
|
}
|
||||||
|
|
||||||
await validateMembership({
|
if (req.user) {
|
||||||
userId: req.user._id.toString(),
|
// case: jwt auth
|
||||||
workspaceId: serviceTokenData.workspace.toString(),
|
await validateMembership({
|
||||||
acceptedRoles,
|
userId: req.user._id.toString(),
|
||||||
acceptedStatuses
|
workspaceId: serviceTokenData.workspace.toString(),
|
||||||
});
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
req.serviceTokenData = serviceTokenData;
|
req.serviceTokenData = serviceTokenData;
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ type req = 'params' | 'body' | 'query';
|
|||||||
* Validate if user on request is a member with proper roles for workspace
|
* Validate if user on request is a member with proper roles for workspace
|
||||||
* on request params.
|
* on request params.
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String[]} obj.acceptedRoles - accepted workspace roles
|
* @param {String[]} obj.acceptedRoles - accepted workspace roles for JWT auth
|
||||||
* @param {String[]} obj.acceptedStatuses - accepted workspace statuses
|
* @param {String[]} obj.acceptedStatuses - accepted workspace statuses for JWT auth
|
||||||
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
|
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
|
||||||
*/
|
*/
|
||||||
const requireWorkspaceAuth = ({
|
const requireWorkspaceAuth = ({
|
||||||
@@ -22,17 +22,28 @@ const requireWorkspaceAuth = ({
|
|||||||
location?: req;
|
location?: req;
|
||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
// workspace authorization middleware
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const membership = await validateMembership({
|
const { workspaceId } = req[location];
|
||||||
userId: req.user._id.toString(),
|
|
||||||
workspaceId: req[location].workspaceId,
|
|
||||||
acceptedRoles,
|
|
||||||
acceptedStatuses
|
|
||||||
});
|
|
||||||
|
|
||||||
req.membership = membership;
|
if (req.user) {
|
||||||
|
// case: jwt auth
|
||||||
|
const membership = await validateMembership({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId,
|
||||||
|
acceptedRoles,
|
||||||
|
acceptedStatuses
|
||||||
|
});
|
||||||
|
|
||||||
|
req.membership = membership;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
req.serviceTokenData
|
||||||
|
&& req.serviceTokenData.workspace !== workspaceId
|
||||||
|
&& req.serviceTokenData.environment !== req.body.environment
|
||||||
|
)
|
||||||
|
// case: st auth
|
||||||
|
next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ export interface ISecret {
|
|||||||
secretValueIV: string;
|
secretValueIV: string;
|
||||||
secretValueTag: string;
|
secretValueTag: string;
|
||||||
secretValueHash: string;
|
secretValueHash: string;
|
||||||
|
secretCommentCiphertext?: string;
|
||||||
|
secretCommentIV?: string;
|
||||||
|
secretCommentTag?: string;
|
||||||
|
secretCommentHash?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const secretSchema = new Schema<ISecret>(
|
const secretSchema = new Schema<ISecret>(
|
||||||
@@ -82,6 +86,22 @@ const secretSchema = new Schema<ISecret>(
|
|||||||
secretValueHash: {
|
secretValueHash: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
secretCommentCiphertext: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
secretCommentIV: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
secretCommentTag: {
|
||||||
|
type: String, // symmetric
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
secretCommentHash: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export interface IServiceTokenData {
|
|||||||
name: string;
|
name: string;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
environment: string; // TODO: adapt to upcoming environment id
|
environment: string; // TODO: adapt to upcoming environment id
|
||||||
|
user: Types.ObjectId;
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
serviceTokenHash: string;
|
serviceTokenHash: string;
|
||||||
@@ -28,6 +29,11 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
user: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'User',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
expiresAt: {
|
expiresAt: {
|
||||||
type: Date
|
type: Date
|
||||||
},
|
},
|
||||||
@@ -38,7 +44,8 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
|
|||||||
serviceTokenHash: {
|
serviceTokenHash: {
|
||||||
type: String,
|
type: String,
|
||||||
unique: true,
|
unique: true,
|
||||||
required: true
|
required: true,
|
||||||
|
select: true
|
||||||
},
|
},
|
||||||
encryptedKey: {
|
encryptedKey: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
import express from 'express';
|
|
||||||
const router = express.Router();
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import bcrypt from 'bcrypt';
|
|
||||||
import * as Sentry from '@sentry/node';
|
|
||||||
import {
|
|
||||||
requireAuth,
|
|
||||||
requireWorkspaceAuth,
|
|
||||||
requireServiceTokenDataAuth,
|
|
||||||
validateRequest
|
|
||||||
} from '../middleware';
|
|
||||||
import {
|
|
||||||
ServiceTokenData
|
|
||||||
} from '../models';
|
|
||||||
import { param, body, query } from 'express-validator';
|
|
||||||
import {
|
|
||||||
SALT_ROUNDS
|
|
||||||
} from '../config';
|
|
||||||
import {
|
|
||||||
ADMIN,
|
|
||||||
MEMBER,
|
|
||||||
COMPLETED,
|
|
||||||
GRANTED
|
|
||||||
} from '../variables';
|
|
||||||
|
|
||||||
// TODO: move logic into separate controller (probably after pull with latest routing)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 2 different concepts that we should distinguish between:
|
|
||||||
* - API key (user) - allows user to perform queries and mutations on whatever
|
|
||||||
* their account could access (better than JWT because it has ACL and scoping).
|
|
||||||
* - Service token (bound to a workspace and environment).
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service token flow?
|
|
||||||
* 1. Post service token data details including project key encrypted under <symmetric_key> on cient-side.
|
|
||||||
* 2. Construct <infisical_token> on client-side as <infisical_token>=<service_token>.<symmetric_key>
|
|
||||||
* 3. Need for CLI to be able to get back service token details
|
|
||||||
*/
|
|
||||||
|
|
||||||
router.post(
|
|
||||||
'/',
|
|
||||||
requireWorkspaceAuth({
|
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [COMPLETED, GRANTED],
|
|
||||||
location: 'body'
|
|
||||||
}),
|
|
||||||
requireAuth,
|
|
||||||
body('name').exists().trim(),
|
|
||||||
body('workspace'),
|
|
||||||
body('environment'),
|
|
||||||
body('encryptedKey'),
|
|
||||||
body('iv'),
|
|
||||||
body('tag'),
|
|
||||||
body('expiresAt'),
|
|
||||||
validateRequest,
|
|
||||||
async (req, res) => {
|
|
||||||
let serviceToken, serviceTokenData;
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
workspace,
|
|
||||||
environment,
|
|
||||||
encryptedKey,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
expiresAt
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
// create 38-char service token with first 6-char being the prefix
|
|
||||||
serviceToken = crypto.randomBytes(19).toString('hex');
|
|
||||||
|
|
||||||
const serviceTokenHash = await bcrypt.hash(serviceToken, SALT_ROUNDS);
|
|
||||||
|
|
||||||
serviceTokenData = await new ServiceTokenData({
|
|
||||||
name,
|
|
||||||
workspace,
|
|
||||||
environment,
|
|
||||||
expiresAt,
|
|
||||||
prefix: serviceToken.substring(0, 6),
|
|
||||||
serviceTokenHash,
|
|
||||||
encryptedKey,
|
|
||||||
iv,
|
|
||||||
tag
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to create service token data'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
serviceToken,
|
|
||||||
serviceTokenData
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: CLI has to get service token details without needing a JWT
|
|
||||||
router.get(
|
|
||||||
'/:serviceTokenDataId',
|
|
||||||
requireAuth,
|
|
||||||
requireServiceTokenDataAuth,
|
|
||||||
param('serviceTokenDataId').exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
async (req, res) => {
|
|
||||||
return ({
|
|
||||||
serviceTokenData: req.serviceTokenData
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.delete(
|
|
||||||
'/:serviceTokenDataId',
|
|
||||||
requireAuth,
|
|
||||||
requireServiceTokenDataAuth,
|
|
||||||
param('serviceTokenDataId').exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
async (req, res) => {
|
|
||||||
let serviceTokenData;
|
|
||||||
try {
|
|
||||||
const { serviceTokenDataId } = req.params;
|
|
||||||
|
|
||||||
serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Sentry.setUser({ email: req.user.email });
|
|
||||||
Sentry.captureException(err);
|
|
||||||
return res.status(400).send({
|
|
||||||
message: 'Failed to delete service token data'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
serviceTokenData
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import express from 'express';
|
|
||||||
const router = express.Router();
|
|
||||||
import { requireAuth } from '../middleware';
|
|
||||||
import { userController } from '../controllers';
|
|
||||||
|
|
||||||
router.get('/', requireAuth, userController.getUser);
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { requireAuth, validateRequest } from '../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { authController } from '../controllers';
|
import { authController } from '../../controllers/v1';
|
||||||
import { loginLimiter } from '../helpers/rateLimiter';
|
import { loginLimiter } from '../../helpers/rateLimiter';
|
||||||
|
|
||||||
router.post('/token', validateRequest, authController.getNewToken);
|
router.post('/token', validateRequest, authController.getNewToken);
|
||||||
|
|
||||||
@@ -25,7 +25,19 @@ router.post(
|
|||||||
authController.login2
|
authController.login2
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post('/logout', requireAuth, authController.logout);
|
router.post(
|
||||||
router.post('/checkAuth', requireAuth, authController.checkAuth);
|
'/logout',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
authController.logout
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
'/checkAuth',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
authController.checkAuth
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -6,13 +6,15 @@ import {
|
|||||||
requireBotAuth,
|
requireBotAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { botController } from '../controllers';
|
import { botController } from '../../controllers/v1';
|
||||||
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -24,7 +26,9 @@ router.get(
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
'/:botId/active',
|
'/:botId/active',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireBotAuth({
|
requireBotAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -4,14 +4,16 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireIntegrationAuth,
|
requireIntegrationAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, GRANTED } from '../../variables';
|
||||||
import { body, param } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { integrationController } from '../controllers';
|
import { integrationController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
'/:integrationId',
|
'/:integrationId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireIntegrationAuth({
|
requireIntegrationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED]
|
acceptedStatuses: [GRANTED]
|
||||||
@@ -29,7 +31,9 @@ router.patch(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:integrationId',
|
'/:integrationId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireIntegrationAuth({
|
requireIntegrationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED]
|
acceptedStatuses: [GRANTED]
|
||||||
@@ -6,19 +6,23 @@ import {
|
|||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
requireIntegrationAuthorizationAuth,
|
requireIntegrationAuthorizationAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, GRANTED } from '../../variables';
|
||||||
import { integrationAuthController } from '../controllers';
|
import { integrationAuthController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/integration-options',
|
'/integration-options',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
integrationAuthController.getIntegrationOptions
|
integrationAuthController.getIntegrationOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/oauth-token',
|
'/oauth-token',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED],
|
acceptedStatuses: [GRANTED],
|
||||||
@@ -33,7 +37,9 @@ router.post(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:integrationAuthId/apps',
|
'/:integrationAuthId/apps',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireIntegrationAuthorizationAuth({
|
requireIntegrationAuthorizationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED]
|
acceptedStatuses: [GRANTED]
|
||||||
@@ -45,7 +51,9 @@ router.get(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:integrationAuthId',
|
'/:integrationAuthId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireIntegrationAuthorizationAuth({
|
requireIntegrationAuthorizationAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED],
|
acceptedStatuses: [GRANTED],
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { requireAuth, validateRequest } from '../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { membershipOrgController } from '../controllers';
|
import { membershipOrgController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/signup',
|
'/signup',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
body('inviteeEmail').exists().trim().notEmpty().isEmail(),
|
body('inviteeEmail').exists().trim().notEmpty().isEmail(),
|
||||||
body('organizationId').exists().trim().notEmpty(),
|
body('organizationId').exists().trim().notEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -4,14 +4,16 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { body, param } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
import { keyController } from '../controllers';
|
import { keyController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -24,7 +26,9 @@ router.post(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/latest',
|
'/:workspaceId/latest',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body, param } from 'express-validator';
|
import { body, param } from 'express-validator';
|
||||||
import { requireAuth, validateRequest } from '../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { membershipController } from '../controllers';
|
import { membershipController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get( // used for CLI (deprecate)
|
router.get( // used for CLI (deprecate)
|
||||||
'/:workspaceId/connect',
|
'/:workspaceId/connect',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
param('workspaceId').exists().trim(),
|
param('workspaceId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
membershipController.validateMembership
|
membershipController.validateMembership
|
||||||
@@ -14,7 +16,9 @@ router.get( // used for CLI (deprecate)
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:membershipId',
|
'/:membershipId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
param('membershipId').exists().trim(),
|
param('membershipId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
membershipController.deleteMembership
|
membershipController.deleteMembership
|
||||||
@@ -22,7 +26,9 @@ router.delete(
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:membershipId/change-role',
|
'/:membershipId/change-role',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
body('role').exists().trim(),
|
body('role').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
membershipController.changeMembershipRole
|
membershipController.changeMembershipRole
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { param } from 'express-validator';
|
import { param } from 'express-validator';
|
||||||
import { requireAuth, validateRequest } from '../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { membershipOrgController } from '../controllers';
|
import { membershipOrgController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
// TODO
|
// TODO
|
||||||
'/membershipOrg/:membershipOrgId/change-role',
|
'/membershipOrg/:membershipOrgId/change-role',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
param('membershipOrgId'),
|
param('membershipOrgId'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
membershipOrgController.changeMembershipOrgRole
|
membershipOrgController.changeMembershipOrgRole
|
||||||
@@ -15,7 +17,9 @@ router.post(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:membershipOrgId',
|
'/:membershipOrgId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
param('membershipOrgId').exists().trim(),
|
param('membershipOrgId').exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
membershipOrgController.deleteMembershipOrg
|
membershipOrgController.deleteMembershipOrg
|
||||||
@@ -5,19 +5,23 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireOrganizationAuth,
|
requireOrganizationAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { OWNER, ADMIN, MEMBER, ACCEPTED } from '../variables';
|
import { OWNER, ADMIN, MEMBER, ACCEPTED } from '../../variables';
|
||||||
import { organizationController } from '../controllers';
|
import { organizationController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
organizationController.getOrganizations
|
organizationController.getOrganizations
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post( // not used on frontend
|
router.post( // not used on frontend
|
||||||
'/',
|
'/',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
body('organizationName').exists().trim().notEmpty(),
|
body('organizationName').exists().trim().notEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
organizationController.createOrganization
|
organizationController.createOrganization
|
||||||
@@ -25,7 +29,9 @@ router.post( // not used on frontend
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:organizationId',
|
'/:organizationId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
@@ -37,7 +43,9 @@ router.get(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:organizationId/users',
|
'/:organizationId/users',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
@@ -49,7 +57,9 @@ router.get(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:organizationId/my-workspaces',
|
'/:organizationId/my-workspaces',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
@@ -61,7 +71,9 @@ router.get(
|
|||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
'/:organizationId/name',
|
'/:organizationId/name',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
@@ -74,7 +86,9 @@ router.patch(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:organizationId/incidentContactOrg',
|
'/:organizationId/incidentContactOrg',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
@@ -86,7 +100,9 @@ router.get(
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:organizationId/incidentContactOrg',
|
'/:organizationId/incidentContactOrg',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
@@ -99,7 +115,9 @@ router.post(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:organizationId/incidentContactOrg',
|
'/:organizationId/incidentContactOrg',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
@@ -112,7 +130,9 @@ router.delete(
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:organizationId/customer-portal-session',
|
'/:organizationId/customer-portal-session',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
@@ -124,7 +144,9 @@ router.post(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:organizationId/subscriptions',
|
'/:organizationId/subscriptions',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { requireAuth, requireSignupAuth, validateRequest } from '../middleware';
|
import { requireAuth, requireSignupAuth, validateRequest } from '../../middleware';
|
||||||
import { passwordController } from '../controllers';
|
import { passwordController } from '../../controllers/v1';
|
||||||
import { passwordLimiter } from '../helpers/rateLimiter';
|
import { passwordLimiter } from '../../helpers/rateLimiter';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/srp1',
|
'/srp1',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
body('clientPublicKey').exists().trim().notEmpty(),
|
body('clientPublicKey').exists().trim().notEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
passwordController.srp1
|
passwordController.srp1
|
||||||
@@ -16,7 +18,9 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
'/change-password',
|
'/change-password',
|
||||||
passwordLimiter,
|
passwordLimiter,
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
body('clientProof').exists().trim().notEmpty(),
|
body('clientProof').exists().trim().notEmpty(),
|
||||||
body('encryptedPrivateKey').exists().trim().notEmpty().notEmpty(), // private key encrypted under new pwd
|
body('encryptedPrivateKey').exists().trim().notEmpty().notEmpty(), // private key encrypted under new pwd
|
||||||
body('iv').exists().trim().notEmpty(), // new iv for private key
|
body('iv').exists().trim().notEmpty(), // new iv for private key
|
||||||
@@ -54,7 +58,9 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
'/backup-private-key',
|
'/backup-private-key',
|
||||||
passwordLimiter,
|
passwordLimiter,
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
body('clientProof').exists().trim().notEmpty(),
|
body('clientProof').exists().trim().notEmpty(),
|
||||||
body('encryptedPrivateKey').exists().trim().notEmpty(), // (backup) private key encrypted under a strong key
|
body('encryptedPrivateKey').exists().trim().notEmpty(), // (backup) private key encrypted under a strong key
|
||||||
body('iv').exists().trim().notEmpty(), // new iv for (backup) private key
|
body('iv').exists().trim().notEmpty(), // new iv for (backup) private key
|
||||||
@@ -5,14 +5,16 @@ import {
|
|||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
requireServiceTokenAuth,
|
requireServiceTokenAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { body, query, param } from 'express-validator';
|
import { body, query, param } from 'express-validator';
|
||||||
import { secretController } from '../controllers';
|
import { secretController } from '../../controllers/v1';
|
||||||
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -28,7 +30,9 @@ router.post(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -5,12 +5,12 @@ import {
|
|||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
requireServiceTokenAuth,
|
requireServiceTokenAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { ADMIN, MEMBER, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, GRANTED } from '../../variables';
|
||||||
import { serviceTokenController } from '../controllers';
|
import { serviceTokenController } from '../../controllers/v1';
|
||||||
|
|
||||||
// Note to devs: service-token to be deprecated in favor of api-key
|
// note: deprecate service-token routes in favor of service-token data routes/structure
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
@@ -20,7 +20,9 @@ router.get(
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED],
|
acceptedStatuses: [GRANTED],
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import express, { Request, Response } from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth,
|
||||||
|
requireServiceTokenDataAuth,
|
||||||
|
validateRequest
|
||||||
|
} from '../../middleware';
|
||||||
|
import { param, body } from 'express-validator';
|
||||||
|
import {
|
||||||
|
ADMIN,
|
||||||
|
MEMBER,
|
||||||
|
COMPLETED,
|
||||||
|
GRANTED
|
||||||
|
} from '../../variables';
|
||||||
|
import { serviceTokenDataController } from '../../controllers/v1';
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['st']
|
||||||
|
}),
|
||||||
|
param('serviceTokenDataId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
serviceTokenDataController.getServiceTokenData
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED],
|
||||||
|
location: 'body'
|
||||||
|
}),
|
||||||
|
body('name').exists().trim(),
|
||||||
|
body('workspaceId'),
|
||||||
|
body('environment'),
|
||||||
|
body('encryptedKey'),
|
||||||
|
body('iv'),
|
||||||
|
body('tag'),
|
||||||
|
body('expiresIn'), // measured in ms
|
||||||
|
validateRequest,
|
||||||
|
serviceTokenDataController.createServiceTokenData
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/:serviceTokenDataId',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireServiceTokenDataAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED],
|
||||||
|
}),
|
||||||
|
param('serviceTokenDataId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
serviceTokenDataController.deleteServiceTokenData
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body } from 'express-validator';
|
import { body } from 'express-validator';
|
||||||
import { requireSignupAuth, validateRequest } from '../middleware';
|
import { requireSignupAuth, validateRequest } from '../../middleware';
|
||||||
import { signupController } from '../controllers';
|
import { signupController } from '../../controllers/v1';
|
||||||
import { signupLimiter } from '../helpers/rateLimiter';
|
import { signupLimiter } from '../../helpers/rateLimiter';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/email/signup',
|
'/email/signup',
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { stripeController } from '../controllers';
|
import { stripeController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post('/webhook', stripeController.handleWebhook);
|
router.post('/webhook', stripeController.handleWebhook);
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import { requireAuth } from '../../middleware';
|
||||||
|
import { userController } from '../../controllers/v1';
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
userController.getUser
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { requireAuth, validateRequest } from '../middleware';
|
import { requireAuth, validateRequest } from '../../middleware';
|
||||||
import { body, query } from 'express-validator';
|
import { body, query } from 'express-validator';
|
||||||
import { userActionController } from '../controllers';
|
import { userActionController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
body('action'),
|
body('action'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
userActionController.addUserAction
|
userActionController.addUserAction
|
||||||
@@ -14,7 +16,9 @@ router.post(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/',
|
'/',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
query('action'),
|
query('action'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
userActionController.getUserAction
|
userActionController.getUserAction
|
||||||
@@ -5,13 +5,15 @@ import {
|
|||||||
requireAuth,
|
requireAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
validateRequest
|
validateRequest
|
||||||
} from '../middleware';
|
} from '../../middleware';
|
||||||
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../variables';
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
import { workspaceController, membershipController } from '../controllers';
|
import { workspaceController, membershipController } from '../../controllers/v1';
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/keys',
|
'/:workspaceId/keys',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -23,7 +25,9 @@ router.get(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/users',
|
'/:workspaceId/users',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -33,11 +37,19 @@ router.get(
|
|||||||
workspaceController.getWorkspaceMemberships
|
workspaceController.getWorkspaceMemberships
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get('/', requireAuth, workspaceController.getWorkspaces);
|
router.get(
|
||||||
|
'/',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
workspaceController.getWorkspaces
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -49,7 +61,9 @@ router.get(
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
body('workspaceName').exists().trim().notEmpty(),
|
body('workspaceName').exists().trim().notEmpty(),
|
||||||
body('organizationId').exists().trim().notEmpty(),
|
body('organizationId').exists().trim().notEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
@@ -58,7 +72,9 @@ router.post(
|
|||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:workspaceId',
|
'/:workspaceId',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN],
|
acceptedRoles: [ADMIN],
|
||||||
acceptedStatuses: [GRANTED]
|
acceptedStatuses: [GRANTED]
|
||||||
@@ -70,7 +86,9 @@ router.delete(
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId/name',
|
'/:workspaceId/name',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [COMPLETED, GRANTED]
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
@@ -83,7 +101,9 @@ router.post(
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/:workspaceId/invite-signup',
|
'/:workspaceId/invite-signup',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED]
|
acceptedStatuses: [GRANTED]
|
||||||
@@ -96,7 +116,9 @@ router.post(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/integrations',
|
'/:workspaceId/integrations',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED]
|
acceptedStatuses: [GRANTED]
|
||||||
@@ -108,7 +130,9 @@ router.get(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/authorizations',
|
'/:workspaceId/authorizations',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED]
|
acceptedStatuses: [GRANTED]
|
||||||
@@ -120,7 +144,9 @@ router.get(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/service-tokens', // deprecate
|
'/:workspaceId/service-tokens', // deprecate
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED]
|
acceptedStatuses: [GRANTED]
|
||||||
@@ -132,7 +158,9 @@ router.get(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/:workspaceId/service-token-data',
|
'/:workspaceId/service-token-data',
|
||||||
requireAuth,
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
requireWorkspaceAuth({
|
requireWorkspaceAuth({
|
||||||
acceptedRoles: [ADMIN, MEMBER],
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
acceptedStatuses: [GRANTED]
|
acceptedStatuses: [GRANTED]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import secret from './secret';
|
||||||
|
import workspace from './workspace';
|
||||||
|
|
||||||
|
export {
|
||||||
|
secret,
|
||||||
|
workspace
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import express from 'express';
|
||||||
|
const router = express.Router();
|
||||||
|
import { body, param, query } from 'express-validator';
|
||||||
|
import {
|
||||||
|
requireAuth,
|
||||||
|
requireWorkspaceAuth,
|
||||||
|
requireServiceTokenAuth,
|
||||||
|
validateRequest
|
||||||
|
} from '../../middleware';
|
||||||
|
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../variables';
|
||||||
|
import { membershipController } from '../../controllers/v1';
|
||||||
|
import { workspaceController } from '../../controllers/v2';
|
||||||
|
|
||||||
|
router.post( // unfinished
|
||||||
|
'/:workspaceId/secrets',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
body('secrets').exists(),
|
||||||
|
body('keys').exists(),
|
||||||
|
body('environment').exists().trim().notEmpty(),
|
||||||
|
body('channel'),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.pushWorkspaceSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get( // unfinished, check that it works with st
|
||||||
|
'/:workspaceId/secrets',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt', 'st']
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
query('environment').exists().trim(),
|
||||||
|
query('channel'),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.pullSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:workspaceId/key',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireWorkspaceAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER],
|
||||||
|
acceptedStatuses: [COMPLETED, GRANTED]
|
||||||
|
}),
|
||||||
|
param('workspaceId').exists().trim(),
|
||||||
|
validateRequest,
|
||||||
|
workspaceController.getWorkspaceKey
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -4,6 +4,8 @@ go 1.19
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/99designs/keyring v1.2.2
|
github.com/99designs/keyring v1.2.2
|
||||||
|
github.com/muesli/mango-cobra v1.2.0
|
||||||
|
github.com/muesli/roff v0.1.0
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
golang.org/x/crypto v0.3.0
|
golang.org/x/crypto v0.3.0
|
||||||
golang.org/x/term v0.3.0
|
golang.org/x/term v0.3.0
|
||||||
@@ -22,6 +24,8 @@ require (
|
|||||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||||
github.com/mtibben/percent v0.2.1 // indirect
|
github.com/mtibben/percent v0.2.1 // indirect
|
||||||
|
github.com/muesli/mango v0.1.0 // indirect
|
||||||
|
github.com/muesli/mango-pflag v0.1.0 // indirect
|
||||||
github.com/oklog/ulid v1.3.1 // indirect
|
github.com/oklog/ulid v1.3.1 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
|||||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
|
||||||
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
|
||||||
|
github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI=
|
||||||
|
github.com/muesli/mango v0.1.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=
|
||||||
|
github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbYvWg=
|
||||||
|
github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA=
|
||||||
|
github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg=
|
||||||
|
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
|
||||||
|
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
|
||||||
|
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||||
|
*/
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
mcobra "github.com/muesli/mango-cobra"
|
||||||
|
"github.com/muesli/roff"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var manCmd = &cobra.Command{
|
||||||
|
Use: "man",
|
||||||
|
Short: "generates the manpages",
|
||||||
|
SilenceUsage: true,
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
Hidden: true,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
manPage, err := mcobra.NewManPage(1, rootCmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprint(os.Stdout, manPage.Build(roff.NewDocument()))
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(manCmd)
|
||||||
|
}
|
||||||
@@ -14,8 +14,8 @@ var rootCmd = &cobra.Command{
|
|||||||
Use: "infisical",
|
Use: "infisical",
|
||||||
Short: "Infisical CLI is used to inject environment variables into any process",
|
Short: "Infisical CLI is used to inject environment variables into any process",
|
||||||
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
|
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
|
||||||
CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
|
CompletionOptions: cobra.CompletionOptions{HiddenDefaultCmd: true},
|
||||||
Version: "0.1.15",
|
Version: "0.1.16",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
|
|||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
rm -rf completions
|
||||||
|
mkdir completions
|
||||||
|
cd cli
|
||||||
|
for sh in bash zsh fish; do
|
||||||
|
go run . completion "$sh" > "../completions/infisical.$sh"
|
||||||
|
done
|
||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
rm -rf manpages
|
||||||
|
mkdir manpages
|
||||||
|
cd cli
|
||||||
|
go run . man | gzip -c > "../manpages/infisical.1.gz"
|
||||||
@@ -48,6 +48,8 @@ services:
|
|||||||
- ./frontend/public:/app/public
|
- ./frontend/public:/app/public
|
||||||
- ./frontend/styles:/app/styles
|
- ./frontend/styles:/app/styles
|
||||||
- ./frontend/components:/app/components
|
- ./frontend/components:/app/components
|
||||||
|
- ./frontend/locales:/app/locales
|
||||||
|
- ./frontend/next-i18next.config.js:/app/next-i18next.config.js
|
||||||
env_file: .env
|
env_file: .env
|
||||||
environment:
|
environment:
|
||||||
- NEXT_PUBLIC_ENV=development
|
- NEXT_PUBLIC_ENV=development
|
||||||
|
|||||||
@@ -88,6 +88,14 @@ The Infisical CLI provides a way to inject environment variables from the platfo
|
|||||||
sudo apt-get update && sudo apt-get install -y infisical
|
sudo apt-get update && sudo apt-get install -y infisical
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Arch Linux">
|
||||||
|
Use the `yay` package manager to install from the [Arch User Repository](https://aur.archlinux.org/packages/infisical-bin)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yay -S infisical-bin
|
||||||
|
```
|
||||||
|
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: ".NET"
|
||||||
|
---
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- [Install the CLI](/cli/overview)
|
||||||
|
|
||||||
|
## Initialize Infisical for your [.NET](https://dotnet.microsoft.com) app
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# navigate to the root of your of your project
|
||||||
|
cd /path/to/project
|
||||||
|
|
||||||
|
# then initialize infisical
|
||||||
|
infisical init
|
||||||
|
```
|
||||||
|
|
||||||
|
## Start your application as usual but with Infisical
|
||||||
|
|
||||||
|
```bash
|
||||||
|
infisical run -- <your application start command>
|
||||||
|
|
||||||
|
# Example
|
||||||
|
infisical run -- dotnet run
|
||||||
|
```
|
||||||
+2
-1
@@ -166,7 +166,8 @@
|
|||||||
"integrations/frameworks/django",
|
"integrations/frameworks/django",
|
||||||
"integrations/frameworks/flask",
|
"integrations/frameworks/flask",
|
||||||
"integrations/frameworks/laravel",
|
"integrations/frameworks/laravel",
|
||||||
"integrations/frameworks/rails"
|
"integrations/frameworks/rails",
|
||||||
|
"integrations/frameworks/dotnet"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,3 +32,5 @@ yarn-error.log*
|
|||||||
.env.production.local
|
.env.production.local
|
||||||
.vercel
|
.vercel
|
||||||
.env.infisical
|
.env.infisical
|
||||||
|
|
||||||
|
.vscode
|
||||||
@@ -48,6 +48,7 @@ export default function RouteGuard({ children }) {
|
|||||||
// Check if the user is authenticated
|
// Check if the user is authenticated
|
||||||
const response = await checkAuth();
|
const response = await checkAuth();
|
||||||
// #TODO: figure our why sometimes it doesn't output a response
|
// #TODO: figure our why sometimes it doesn't output a response
|
||||||
|
// ANS(akhilmhdh): Because inside the security client the await token() doesn't have try/catch
|
||||||
if (!publicPaths.includes(path)) {
|
if (!publicPaths.includes(path)) {
|
||||||
try {
|
try {
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
|
|||||||
@@ -1,38 +1,39 @@
|
|||||||
/* eslint-disable no-unexpected-multiline */
|
/* eslint-disable no-unexpected-multiline */
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import Link from 'next/link';
|
import Link from "next/link";
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from "next/router";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
import {
|
import {
|
||||||
faBookOpen,
|
faBookOpen,
|
||||||
faGear,
|
faGear,
|
||||||
faKey,
|
faKey,
|
||||||
faMobile,
|
faMobile,
|
||||||
faPlug,
|
faPlug,
|
||||||
faUser
|
faUser,
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import getOrganizations from '~/pages/api/organization/getOrgs';
|
import getOrganizations from "~/pages/api/organization/getOrgs";
|
||||||
import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects';
|
import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
|
||||||
import getOrganizationUsers from '~/pages/api/organization/GetOrgUsers';
|
import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers";
|
||||||
import checkUserAction from '~/pages/api/userActions/checkUserAction';
|
import checkUserAction from "~/pages/api/userActions/checkUserAction";
|
||||||
import addUserToWorkspace from '~/pages/api/workspace/addUserToWorkspace';
|
import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace";
|
||||||
import createWorkspace from '~/pages/api/workspace/createWorkspace';
|
import createWorkspace from "~/pages/api/workspace/createWorkspace";
|
||||||
import getWorkspaces from '~/pages/api/workspace/getWorkspaces';
|
import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
|
||||||
import uploadKeys from '~/pages/api/workspace/uploadKeys';
|
import uploadKeys from "~/pages/api/workspace/uploadKeys";
|
||||||
|
|
||||||
import NavBarDashboard from '../navigation/NavBarDashboard';
|
import NavBarDashboard from "../navigation/NavBarDashboard";
|
||||||
import onboardingCheck from '../utilities/checks/OnboardingCheck';
|
import onboardingCheck from "../utilities/checks/OnboardingCheck";
|
||||||
import { tempLocalStorage } from '../utilities/checks/tempLocalStorage';
|
import { tempLocalStorage } from "../utilities/checks/tempLocalStorage";
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptAssymmetric
|
encryptAssymmetric,
|
||||||
} from '../utilities/cryptography/crypto';
|
} from "../utilities/cryptography/crypto";
|
||||||
import Button from './buttons/Button';
|
import Button from "./buttons/Button";
|
||||||
import AddWorkspaceDialog from './dialog/AddWorkspaceDialog';
|
import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
|
||||||
import Listbox from './Listbox';
|
import Listbox from "./Listbox";
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -41,15 +42,17 @@ interface LayoutProps {
|
|||||||
export default function Layout({ children }: LayoutProps) {
|
export default function Layout({ children }: LayoutProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [workspaceList, setWorkspaceList] = useState([]);
|
const [workspaceList, setWorkspaceList] = useState([]);
|
||||||
const [workspaceMapping, setWorkspaceMapping] = useState([{ '1': '2' }]);
|
const [workspaceMapping, setWorkspaceMapping] = useState([{ "1": "2" }]);
|
||||||
const [workspaceSelected, setWorkspaceSelected] = useState('∞');
|
const [workspaceSelected, setWorkspaceSelected] = useState("∞");
|
||||||
const [newWorkspaceName, setNewWorkspaceName] = useState('');
|
const [newWorkspaceName, setNewWorkspaceName] = useState("");
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] =
|
const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] =
|
||||||
useState(0);
|
useState(0);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
function closeModal() {
|
function closeModal() {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
@@ -75,35 +78,35 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
if (!currentWorkspaces.includes(workspaceName)) {
|
if (!currentWorkspaces.includes(workspaceName)) {
|
||||||
const newWorkspace = await createWorkspace({
|
const newWorkspace = await createWorkspace({
|
||||||
workspaceName,
|
workspaceName,
|
||||||
organizationId: tempLocalStorage('orgData.id')
|
organizationId: tempLocalStorage("orgData.id"),
|
||||||
});
|
});
|
||||||
const newWorkspaceId = newWorkspace._id;
|
const newWorkspaceId = newWorkspace._id;
|
||||||
|
|
||||||
if (addAllUsers) {
|
if (addAllUsers) {
|
||||||
const orgUsers = await getOrganizationUsers({
|
const orgUsers = await getOrganizationUsers({
|
||||||
orgId: tempLocalStorage('orgData.id')
|
orgId: tempLocalStorage("orgData.id"),
|
||||||
});
|
});
|
||||||
orgUsers.map(async (user: any) => {
|
orgUsers.map(async (user: any) => {
|
||||||
if (user.status == 'accepted') {
|
if (user.status == "accepted") {
|
||||||
const result = await addUserToWorkspace(
|
const result = await addUserToWorkspace(
|
||||||
user.user.email,
|
user.user.email,
|
||||||
newWorkspaceId
|
newWorkspaceId
|
||||||
);
|
);
|
||||||
if (result?.invitee && result?.latestKey) {
|
if (result?.invitee && result?.latestKey) {
|
||||||
const PRIVATE_KEY = tempLocalStorage('PRIVATE_KEY');
|
const PRIVATE_KEY = tempLocalStorage("PRIVATE_KEY");
|
||||||
|
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
// assymmetrically decrypt symmetric key with local private key
|
||||||
const key = decryptAssymmetric({
|
const key = decryptAssymmetric({
|
||||||
ciphertext: result.latestKey.encryptedKey,
|
ciphertext: result.latestKey.encryptedKey,
|
||||||
nonce: result.latestKey.nonce,
|
nonce: result.latestKey.nonce,
|
||||||
publicKey: result.latestKey.sender.publicKey,
|
publicKey: result.latestKey.sender.publicKey,
|
||||||
privateKey: PRIVATE_KEY
|
privateKey: PRIVATE_KEY,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { ciphertext, nonce } = encryptAssymmetric({
|
const { ciphertext, nonce } = encryptAssymmetric({
|
||||||
plaintext: key,
|
plaintext: key,
|
||||||
publicKey: result.invitee.publicKey,
|
publicKey: result.invitee.publicKey,
|
||||||
privateKey: PRIVATE_KEY
|
privateKey: PRIVATE_KEY,
|
||||||
}) as { ciphertext: string; nonce: string };
|
}) as { ciphertext: string; nonce: string };
|
||||||
|
|
||||||
uploadKeys(
|
uploadKeys(
|
||||||
@@ -116,11 +119,11 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
router.push('/dashboard/' + newWorkspaceId + '?Development');
|
router.push("/dashboard/" + newWorkspaceId + "?Development");
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
setNewWorkspaceName('');
|
setNewWorkspaceName("");
|
||||||
} else {
|
} else {
|
||||||
console.error('A project with this name already exists.');
|
console.error("A project with this name already exists.");
|
||||||
setError(true);
|
setError(true);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -131,62 +134,65 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = useMemo(
|
||||||
{
|
() => [
|
||||||
href:
|
{
|
||||||
'/dashboard/' +
|
href:
|
||||||
workspaceMapping[workspaceSelected as any] +
|
"/dashboard/" +
|
||||||
'?Development',
|
workspaceMapping[workspaceSelected as any] +
|
||||||
title: 'Secrets',
|
"?Development",
|
||||||
emoji: <FontAwesomeIcon icon={faKey} />
|
title: t("nav:menu.secrets"),
|
||||||
},
|
emoji: <FontAwesomeIcon icon={faKey} />,
|
||||||
{
|
},
|
||||||
href: '/users/' + workspaceMapping[workspaceSelected as any],
|
{
|
||||||
title: 'Members',
|
href: "/users/" + workspaceMapping[workspaceSelected as any],
|
||||||
emoji: <FontAwesomeIcon icon={faUser} />
|
title: t("nav:menu.members"),
|
||||||
},
|
emoji: <FontAwesomeIcon icon={faUser} />,
|
||||||
{
|
},
|
||||||
href: '/integrations/' + workspaceMapping[workspaceSelected as any],
|
{
|
||||||
title: 'Integrations',
|
href: "/integrations/" + workspaceMapping[workspaceSelected as any],
|
||||||
emoji: <FontAwesomeIcon icon={faPlug} />
|
title: t("nav:menu.integrations"),
|
||||||
},
|
emoji: <FontAwesomeIcon icon={faPlug} />,
|
||||||
{
|
},
|
||||||
href: '/settings/project/' + workspaceMapping[workspaceSelected as any],
|
{
|
||||||
title: 'Project Settings',
|
href: "/settings/project/" + workspaceMapping[workspaceSelected as any],
|
||||||
emoji: <FontAwesomeIcon icon={faGear} />
|
title: t("nav:menu.project-settings"),
|
||||||
}
|
emoji: <FontAwesomeIcon icon={faGear} />,
|
||||||
];
|
},
|
||||||
|
],
|
||||||
|
[t, workspaceMapping, workspaceSelected]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Put a user in a workspace if they're not in one yet
|
// Put a user in a workspace if they're not in one yet
|
||||||
const putUserInWorkSpace = async () => {
|
const putUserInWorkSpace = async () => {
|
||||||
if (tempLocalStorage('orgData.id') === '') {
|
if (tempLocalStorage("orgData.id") === "") {
|
||||||
const userOrgs = await getOrganizations();
|
const userOrgs = await getOrganizations();
|
||||||
localStorage.setItem('orgData.id', userOrgs[0]._id);
|
localStorage.setItem("orgData.id", userOrgs[0]._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const orgUserProjects = await getOrganizationUserProjects({
|
const orgUserProjects = await getOrganizationUserProjects({
|
||||||
orgId: tempLocalStorage('orgData.id')
|
orgId: tempLocalStorage("orgData.id"),
|
||||||
});
|
});
|
||||||
const userWorkspaces = orgUserProjects;
|
const userWorkspaces = orgUserProjects;
|
||||||
if (
|
if (
|
||||||
userWorkspaces.length == 0 &&
|
userWorkspaces.length == 0 &&
|
||||||
router.asPath != '/noprojects' &&
|
router.asPath != "/noprojects" &&
|
||||||
!router.asPath.includes('settings')
|
!router.asPath.includes("settings")
|
||||||
) {
|
) {
|
||||||
router.push('/noprojects');
|
router.push("/noprojects");
|
||||||
} else if (router.asPath != '/noprojects') {
|
} else if (router.asPath != "/noprojects") {
|
||||||
const intendedWorkspaceId = router.asPath
|
const intendedWorkspaceId = router.asPath
|
||||||
.split('/')
|
.split("/")
|
||||||
[router.asPath.split('/').length - 1].split('?')[0];
|
[router.asPath.split("/").length - 1].split("?")[0];
|
||||||
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
|
||||||
if (
|
if (
|
||||||
intendedWorkspaceId != 'heroku' &&
|
intendedWorkspaceId != "heroku" &&
|
||||||
!userWorkspaces
|
!userWorkspaces
|
||||||
.map((workspace: { _id: string }) => workspace._id)
|
.map((workspace: { _id: string }) => workspace._id)
|
||||||
.includes(intendedWorkspaceId)
|
.includes(intendedWorkspaceId)
|
||||||
) {
|
) {
|
||||||
router.push('/dashboard/' + userWorkspaces[0]._id + '?Development');
|
router.push("/dashboard/" + userWorkspaces[0]._id + "?Development");
|
||||||
} else {
|
} else {
|
||||||
setWorkspaceList(
|
setWorkspaceList(
|
||||||
userWorkspaces.map((workspace: any) => workspace.name)
|
userWorkspaces.map((workspace: any) => workspace.name)
|
||||||
@@ -195,7 +201,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
userWorkspaces.map((workspace: any) => [
|
userWorkspaces.map((workspace: any) => [
|
||||||
workspace.name,
|
workspace.name,
|
||||||
workspace._id
|
workspace._id,
|
||||||
])
|
])
|
||||||
) as any
|
) as any
|
||||||
);
|
);
|
||||||
@@ -203,12 +209,12 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
userWorkspaces.map((workspace: any) => [
|
userWorkspaces.map((workspace: any) => [
|
||||||
workspace._id,
|
workspace._id,
|
||||||
workspace.name
|
workspace.name,
|
||||||
])
|
])
|
||||||
)[
|
)[
|
||||||
router.asPath
|
router.asPath
|
||||||
.split('/')
|
.split("/")
|
||||||
[router.asPath.split('/').length - 1].split('?')[0]
|
[router.asPath.split("/").length - 1].split("?")[0]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -224,16 +230,16 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
workspaceMapping[workspaceSelected as any] &&
|
workspaceMapping[workspaceSelected as any] &&
|
||||||
`${workspaceMapping[workspaceSelected as any]}` !==
|
`${workspaceMapping[workspaceSelected as any]}` !==
|
||||||
router.asPath
|
router.asPath
|
||||||
.split('/')
|
.split("/")
|
||||||
[router.asPath.split('/').length - 1].split('?')[0]
|
[router.asPath.split("/").length - 1].split("?")[0]
|
||||||
) {
|
) {
|
||||||
router.push(
|
router.push(
|
||||||
'/dashboard/' +
|
"/dashboard/" +
|
||||||
workspaceMapping[workspaceSelected as any] +
|
workspaceMapping[workspaceSelected as any] +
|
||||||
'?Development'
|
"?Development"
|
||||||
);
|
);
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'projectData.id',
|
"projectData.id",
|
||||||
`${workspaceMapping[workspaceSelected as any]}`
|
`${workspaceMapping[workspaceSelected as any]}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -257,7 +263,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex justify-center w-full mt-[4.5rem] mb-6 bg-bunker-600 h-20 flex-col items-center px-4">
|
<div className="flex justify-center w-full mt-[4.5rem] mb-6 bg-bunker-600 h-20 flex-col items-center px-4">
|
||||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||||
PROJECT
|
{t("nav:menu.project")}
|
||||||
</div>
|
</div>
|
||||||
{workspaceList.length > 0 ? (
|
{workspaceList.length > 0 ? (
|
||||||
<Listbox
|
<Listbox
|
||||||
@@ -282,11 +288,11 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
{workspaceList.length > 0 &&
|
{workspaceList.length > 0 &&
|
||||||
menuItems.map(({ href, title, emoji }) => (
|
menuItems.map(({ href, title, emoji }) => (
|
||||||
<li className="mt-0.5 mx-2" key={title}>
|
<li className="mt-0.5 mx-2" key={title}>
|
||||||
{router.asPath.split('/')[1] === href.split('/')[1] &&
|
{router.asPath.split("/")[1] === href.split("/")[1] &&
|
||||||
(['project', 'billing', 'org', 'personal'].includes(
|
(["project", "billing", "org", "personal"].includes(
|
||||||
router.asPath.split('/')[2]
|
router.asPath.split("/")[2]
|
||||||
)
|
)
|
||||||
? router.asPath.split('/')[2] === href.split('/')[2]
|
? router.asPath.split("/")[2] === href.split("/")[2]
|
||||||
: true) ? (
|
: true) ? (
|
||||||
<div
|
<div
|
||||||
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
|
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
|
||||||
@@ -297,7 +303,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
</p>
|
</p>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
) : router.asPath == '/noprojects' ? (
|
) : router.asPath == "/noprojects" ? (
|
||||||
<div
|
<div
|
||||||
className={`flex p-2.5 text-white text-sm rounded`}
|
className={`flex p-2.5 text-white text-sm rounded`}
|
||||||
>
|
>
|
||||||
@@ -323,7 +329,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full mt-40 mb-4 px-2">
|
<div className="w-full mt-40 mb-4 px-2">
|
||||||
{router.asPath.split('/')[1] === 'home' ? (
|
{router.asPath.split("/")[1] === "home" ? (
|
||||||
<div
|
<div
|
||||||
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
|
className={`flex relative px-0.5 py-2.5 text-white text-sm rounded cursor-pointer bg-primary-50/10`}
|
||||||
>
|
>
|
||||||
@@ -334,12 +340,12 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
Infisical Guide
|
Infisical Guide
|
||||||
<img
|
<img
|
||||||
src={`/images/progress-${
|
src={`/images/progress-${
|
||||||
totalOnboardingActionsDone == 0 ? '0' : ''
|
totalOnboardingActionsDone == 0 ? "0" : ""
|
||||||
}${totalOnboardingActionsDone == 1 ? '14' : ''}${
|
}${totalOnboardingActionsDone == 1 ? "14" : ""}${
|
||||||
totalOnboardingActionsDone == 2 ? '28' : ''
|
totalOnboardingActionsDone == 2 ? "28" : ""
|
||||||
}${totalOnboardingActionsDone == 3 ? '43' : ''}${
|
}${totalOnboardingActionsDone == 3 ? "43" : ""}${
|
||||||
totalOnboardingActionsDone == 4 ? '57' : ''
|
totalOnboardingActionsDone == 4 ? "57" : ""
|
||||||
}${totalOnboardingActionsDone == 5 ? '71' : ''}.svg`}
|
}${totalOnboardingActionsDone == 5 ? "71" : ""}.svg`}
|
||||||
height={58}
|
height={58}
|
||||||
width={58}
|
width={58}
|
||||||
alt="progress bar"
|
alt="progress bar"
|
||||||
@@ -359,12 +365,12 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
Infisical Guide
|
Infisical Guide
|
||||||
<img
|
<img
|
||||||
src={`/images/progress-${
|
src={`/images/progress-${
|
||||||
totalOnboardingActionsDone == 0 ? '0' : ''
|
totalOnboardingActionsDone == 0 ? "0" : ""
|
||||||
}${totalOnboardingActionsDone == 1 ? '14' : ''}${
|
}${totalOnboardingActionsDone == 1 ? "14" : ""}${
|
||||||
totalOnboardingActionsDone == 2 ? '28' : ''
|
totalOnboardingActionsDone == 2 ? "28" : ""
|
||||||
}${totalOnboardingActionsDone == 3 ? '43' : ''}${
|
}${totalOnboardingActionsDone == 3 ? "43" : ""}${
|
||||||
totalOnboardingActionsDone == 4 ? '57' : ''
|
totalOnboardingActionsDone == 4 ? "57" : ""
|
||||||
}${totalOnboardingActionsDone == 5 ? '71' : ''}.svg`}
|
}${totalOnboardingActionsDone == 5 ? "71" : ""}.svg`}
|
||||||
height={58}
|
height={58}
|
||||||
width={58}
|
width={58}
|
||||||
alt="progress bar"
|
alt="progress bar"
|
||||||
@@ -394,9 +400,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
className="text-gray-300 text-7xl mb-8"
|
className="text-gray-300 text-7xl mb-8"
|
||||||
/>
|
/>
|
||||||
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
|
<p className="text-gray-200 px-6 text-center text-lg max-w-sm">
|
||||||
{' '}
|
{` ${t("common:no-mobile")} `}
|
||||||
To use Infisical, please log in through a device with larger
|
|
||||||
dimensions.{' '}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ interface OverrideProps {
|
|||||||
keyName: string;
|
keyName: string;
|
||||||
value: string;
|
value: string;
|
||||||
pos: number;
|
pos: number;
|
||||||
|
comment: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ToggleProps {
|
interface ToggleProps {
|
||||||
@@ -17,6 +18,7 @@ interface ToggleProps {
|
|||||||
value: string;
|
value: string;
|
||||||
pos: number;
|
pos: number;
|
||||||
id: string;
|
id: string;
|
||||||
|
comment: string;
|
||||||
deleteOverride: (id: string) => void;
|
deleteOverride: (id: string) => void;
|
||||||
sharedToHide: string[];
|
sharedToHide: string[];
|
||||||
setSharedToHide: (values: string[]) => void;
|
setSharedToHide: (values: string[]) => void;
|
||||||
@@ -46,6 +48,7 @@ export default function Toggle ({
|
|||||||
value,
|
value,
|
||||||
pos,
|
pos,
|
||||||
id,
|
id,
|
||||||
|
comment,
|
||||||
deleteOverride,
|
deleteOverride,
|
||||||
sharedToHide,
|
sharedToHide,
|
||||||
setSharedToHide
|
setSharedToHide
|
||||||
@@ -55,13 +58,12 @@ export default function Toggle ({
|
|||||||
checked={enabled}
|
checked={enabled}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
if (enabled == false) {
|
if (enabled == false) {
|
||||||
addOverride({ id, keyName, value, pos });
|
addOverride({ id, keyName, value, pos, comment });
|
||||||
setSharedToHide([
|
setSharedToHide([
|
||||||
...sharedToHide!,
|
...sharedToHide!,
|
||||||
id
|
id
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
setSharedToHide(sharedToHide!.filter(tempId => tempId != id))
|
|
||||||
deleteOverride(id);
|
deleteOverride(id);
|
||||||
}
|
}
|
||||||
setEnabled(!enabled);
|
setEnabled(!enabled);
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus";
|
|
||||||
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
|
|
||||||
import {
|
|
||||||
decryptAssymmetric,
|
|
||||||
encryptAssymmetric
|
|
||||||
} from "../../utilities/cryptography/crypto";
|
|
||||||
import Button from "../buttons/Button";
|
import Button from "../buttons/Button";
|
||||||
|
|
||||||
const ActivateBotDialog = ({
|
const ActivateBotDialog = ({
|
||||||
@@ -16,6 +11,7 @@ const ActivateBotDialog = ({
|
|||||||
handleBotActivate,
|
handleBotActivate,
|
||||||
handleIntegrationOption
|
handleIntegrationOption
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -64,18 +60,18 @@ const ActivateBotDialog = ({
|
|||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400"
|
className="text-lg font-medium leading-6 text-gray-400"
|
||||||
>
|
>
|
||||||
Grant Infisical access to your secrets
|
{t("integrations:grant-access-to-secrets")}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2 mb-2">
|
<div className="mt-2 mb-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Most cloud integrations require Infisical to be able to decrypt your secrets so they can be forwarded over.
|
{t("integrations:why-infisical-needs-access")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6 max-w-max">
|
<div className="mt-6 max-w-max">
|
||||||
<Button
|
<Button
|
||||||
onButtonPressed={submit}
|
onButtonPressed={submit}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
text="Grant access"
|
text={t("integrations:grant-access-button")}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Fragment, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
import addIncidentContact from "~/pages/api/organization/addIncidentContact";
|
import addIncidentContact from "~/pages/api/organization/addIncidentContact";
|
||||||
@@ -14,6 +15,7 @@ const AddIncidentContactDialog = ({
|
|||||||
setIncidentContacts,
|
setIncidentContacts,
|
||||||
}) => {
|
}) => {
|
||||||
let [incidentContactEmail, setIncidentContactEmail] = useState("");
|
let [incidentContactEmail, setIncidentContactEmail] = useState("");
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
setIncidentContacts(
|
setIncidentContacts(
|
||||||
@@ -59,17 +61,16 @@ const AddIncidentContactDialog = ({
|
|||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400"
|
className="text-lg font-medium leading-6 text-gray-400"
|
||||||
>
|
>
|
||||||
Add an Incident Contact
|
{t("section-incident:add-dialog.title")}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2 mb-2">
|
<div className="mt-2 mb-2">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
This contact will be notified in the unlikely event of a
|
{t("section-incident:add-dialog.description")}
|
||||||
severe incident.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-28">
|
<div className="max-h-28">
|
||||||
<InputField
|
<InputField
|
||||||
label="Email"
|
label={t("common:email")}
|
||||||
onChangeHandler={setIncidentContactEmail}
|
onChangeHandler={setIncidentContactEmail}
|
||||||
type="varName"
|
type="varName"
|
||||||
value={incidentContactEmail}
|
value={incidentContactEmail}
|
||||||
@@ -81,7 +82,7 @@ const AddIncidentContactDialog = ({
|
|||||||
<Button
|
<Button
|
||||||
onButtonPressed={submit}
|
onButtonPressed={submit}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
text="Add Incident Contact"
|
text={t("section-incident:add-dialog.add-incident")}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Fragment, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { Trans, useTranslation } from "next-i18next";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
import Button from "../buttons/Button";
|
import Button from "../buttons/Button";
|
||||||
@@ -15,6 +16,7 @@ const AddProjectMemberDialog = ({
|
|||||||
setEmail,
|
setEmail,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-50">
|
<div className="z-50">
|
||||||
@@ -49,48 +51,55 @@ const AddProjectMemberDialog = ({
|
|||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
>
|
>
|
||||||
Add a member to your project
|
{t("section-members:add-dialog.add-member-to-project")}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
) : (
|
) : (
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
>
|
>
|
||||||
All the users in your organization are already invited.
|
{t("section-members:add-dialog.already-all-invited")}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
)}
|
)}
|
||||||
<div className="mt-2 mb-4">
|
<div className="mt-2 mb-4">
|
||||||
{data?.length > 0 ? (
|
{data?.length > 0 ? (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
The user will receive an email with the instructions.
|
{t("section-members:add-dialog.user-will-email")}
|
||||||
</p>
|
</p>
|
||||||
<div className="">
|
<div className="">
|
||||||
<button
|
<Trans
|
||||||
type="button"
|
i18nKey="section-members:add-dialog.looking-add"
|
||||||
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
components={[
|
||||||
onClick={() =>
|
// eslint-disable-next-line react/jsx-key
|
||||||
router.push("/settings/org/" + router.query.id)
|
<button
|
||||||
}
|
type="button"
|
||||||
>
|
className="inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
If you are looking to add users to your org,
|
onClick={() =>
|
||||||
</button>
|
router.push(
|
||||||
<button
|
"/settings/org/" + router.query.id
|
||||||
type="button"
|
)
|
||||||
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
}
|
||||||
onClick={() =>
|
/>,
|
||||||
router.push(
|
// eslint-disable-next-line react/jsx-key
|
||||||
"/settings/org/" + router.query.id + "?invite"
|
<button
|
||||||
)
|
type="button"
|
||||||
}
|
className="ml-1 inline-flex justify-center rounded-md py-1 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
>
|
onClick={() =>
|
||||||
click here.
|
router.push(
|
||||||
</button>
|
"/settings/org/" +
|
||||||
|
router.query.id +
|
||||||
|
"?invite"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Add more users to the organization first.
|
{t("section-members:add-dialog.add-user-org-first")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -110,7 +119,7 @@ const AddProjectMemberDialog = ({
|
|||||||
<Button
|
<Button
|
||||||
onButtonPressed={submitModal}
|
onButtonPressed={submitModal}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
text="Add Member"
|
text={t("section-members:add-member")}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,7 +129,7 @@ const AddProjectMemberDialog = ({
|
|||||||
router.push("/settings/org/" + router.query.id)
|
router.push("/settings/org/" + router.query.id)
|
||||||
}
|
}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
text="Add Users to Organization"
|
text={t("section-members:add-dialog.add-user-to-org")}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,40 +1,42 @@
|
|||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useState } from "react";
|
||||||
import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons';
|
import { useTranslation } from "next-i18next";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { Dialog, Transition } from '@headlessui/react';
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import nacl from 'tweetnacl';
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import nacl from "tweetnacl";
|
||||||
|
|
||||||
import addServiceToken from '~/pages/api/serviceToken/addServiceToken';
|
import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
|
||||||
import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
|
import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
|
||||||
|
|
||||||
import { envMapping } from '../../../public/data/frequentConstants';
|
import { envMapping } from "../../../public/data/frequentConstants";
|
||||||
import {
|
import {
|
||||||
decryptAssymmetric,
|
decryptAssymmetric,
|
||||||
encryptAssymmetric
|
encryptAssymmetric,
|
||||||
} from '../../utilities/cryptography/crypto';
|
} from "../../utilities/cryptography/crypto";
|
||||||
import Button from '../buttons/Button';
|
import Button from "../buttons/Button";
|
||||||
import InputField from '../InputField';
|
import InputField from "../InputField";
|
||||||
import ListBox from '../Listbox';
|
import ListBox from "../Listbox";
|
||||||
|
|
||||||
const expiryMapping = {
|
const expiryMapping = {
|
||||||
'1 day': 86400,
|
"1 day": 86400,
|
||||||
'7 days': 604800,
|
"7 days": 604800,
|
||||||
'1 month': 2592000,
|
"1 month": 2592000,
|
||||||
'6 months': 15552000,
|
"6 months": 15552000,
|
||||||
'12 months': 31104000
|
"12 months": 31104000,
|
||||||
};
|
};
|
||||||
|
|
||||||
const AddServiceTokenDialog = ({
|
const AddServiceTokenDialog = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
closeModal,
|
closeModal,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
workspaceName
|
workspaceName,
|
||||||
}) => {
|
}) => {
|
||||||
const [serviceToken, setServiceToken] = useState('');
|
const [serviceToken, setServiceToken] = useState("");
|
||||||
const [serviceTokenName, setServiceTokenName] = useState('');
|
const [serviceTokenName, setServiceTokenName] = useState("");
|
||||||
const [serviceTokenEnv, setServiceTokenEnv] = useState('Development');
|
const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
|
||||||
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState('1 day');
|
const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
|
||||||
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const generateServiceToken = async () => {
|
const generateServiceToken = async () => {
|
||||||
const latestFileKey = await getLatestFileKey({ workspaceId });
|
const latestFileKey = await getLatestFileKey({ workspaceId });
|
||||||
@@ -43,7 +45,7 @@ const AddServiceTokenDialog = ({
|
|||||||
ciphertext: latestFileKey.latestKey.encryptedKey,
|
ciphertext: latestFileKey.latestKey.encryptedKey,
|
||||||
nonce: latestFileKey.latestKey.nonce,
|
nonce: latestFileKey.latestKey.nonce,
|
||||||
publicKey: latestFileKey.latestKey.sender.publicKey,
|
publicKey: latestFileKey.latestKey.sender.publicKey,
|
||||||
privateKey: localStorage.getItem('PRIVATE_KEY')
|
privateKey: localStorage.getItem("PRIVATE_KEY"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// generate new public/private key pair
|
// generate new public/private key pair
|
||||||
@@ -55,7 +57,7 @@ const AddServiceTokenDialog = ({
|
|||||||
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
|
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
|
||||||
plaintext: key,
|
plaintext: key,
|
||||||
publicKey,
|
publicKey,
|
||||||
privateKey
|
privateKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
let newServiceToken = await addServiceToken({
|
let newServiceToken = await addServiceToken({
|
||||||
@@ -65,16 +67,16 @@ const AddServiceTokenDialog = ({
|
|||||||
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
expiresIn: expiryMapping[serviceTokenExpiresIn],
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptedKey,
|
encryptedKey,
|
||||||
nonce
|
nonce,
|
||||||
});
|
});
|
||||||
|
|
||||||
const serviceToken = newServiceToken + ',' + privateKey;
|
const serviceToken = newServiceToken + "," + privateKey;
|
||||||
setServiceToken(serviceToken);
|
setServiceToken(serviceToken);
|
||||||
};
|
};
|
||||||
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
// Get the text field
|
// Get the text field
|
||||||
var copyText = document.getElementById('serviceToken');
|
var copyText = document.getElementById("serviceToken");
|
||||||
|
|
||||||
// Select the text field
|
// Select the text field
|
||||||
copyText.select();
|
copyText.select();
|
||||||
@@ -91,8 +93,8 @@ const AddServiceTokenDialog = ({
|
|||||||
|
|
||||||
const closeAddServiceTokenModal = () => {
|
const closeAddServiceTokenModal = () => {
|
||||||
closeModal();
|
closeModal();
|
||||||
setServiceTokenName('');
|
setServiceTokenName("");
|
||||||
setServiceToken('');
|
setServiceToken("");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -122,27 +124,26 @@ const AddServiceTokenDialog = ({
|
|||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
{serviceToken == '' ? (
|
{serviceToken == "" ? (
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
<Dialog.Title
|
<Dialog.Title
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
>
|
>
|
||||||
Add a service token for {workspaceName}
|
{t("section-token:add-dialog.title", {
|
||||||
|
target: workspaceName,
|
||||||
|
})}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2 mb-4">
|
<div className="mt-2 mb-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Specify the name, environment, and expiry period. When
|
{t("section-token:add-dialog.description")}
|
||||||
a token is generated, you will only be able to see it
|
|
||||||
once before it disappears. Make sure to save it
|
|
||||||
somewhere.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-28 mb-2">
|
<div className="max-h-28 mb-2">
|
||||||
<InputField
|
<InputField
|
||||||
label="Service Token Name"
|
label={t("section-token:add-dialog.name")}
|
||||||
onChangeHandler={setServiceTokenName}
|
onChangeHandler={setServiceTokenName}
|
||||||
type="varName"
|
type="varName"
|
||||||
value={serviceTokenName}
|
value={serviceTokenName}
|
||||||
@@ -155,13 +156,13 @@ const AddServiceTokenDialog = ({
|
|||||||
selected={serviceTokenEnv}
|
selected={serviceTokenEnv}
|
||||||
onChange={setServiceTokenEnv}
|
onChange={setServiceTokenEnv}
|
||||||
data={[
|
data={[
|
||||||
'Development',
|
"Development",
|
||||||
'Staging',
|
"Staging",
|
||||||
'Production',
|
"Production",
|
||||||
'Testing'
|
"Testing",
|
||||||
]}
|
]}
|
||||||
isFull={true}
|
width="full"
|
||||||
text="Environment: "
|
text={`${t("common:environment")}: `}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-28">
|
<div className="max-h-28">
|
||||||
@@ -169,14 +170,14 @@ const AddServiceTokenDialog = ({
|
|||||||
selected={serviceTokenExpiresIn}
|
selected={serviceTokenExpiresIn}
|
||||||
onChange={setServiceTokenExpiresIn}
|
onChange={setServiceTokenExpiresIn}
|
||||||
data={[
|
data={[
|
||||||
'1 day',
|
"1 day",
|
||||||
'7 days',
|
"7 days",
|
||||||
'1 month',
|
"1 month",
|
||||||
'6 months',
|
"6 months",
|
||||||
'12 months'
|
"12 months",
|
||||||
]}
|
]}
|
||||||
isFull={true}
|
width="full"
|
||||||
text="Expires in: "
|
text={`${t("common:expired-in")}: `}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-max">
|
<div className="max-w-max">
|
||||||
@@ -184,10 +185,10 @@ const AddServiceTokenDialog = ({
|
|||||||
<Button
|
<Button
|
||||||
onButtonPressed={() => generateServiceToken()}
|
onButtonPressed={() => generateServiceToken()}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
text="Add Service Token"
|
text={t("section-token:add-dialog.add")}
|
||||||
textDisabled="Add Service Token"
|
textDisabled={t("section-token:add-dialog.add")}
|
||||||
size="md"
|
size="md"
|
||||||
active={serviceTokenName == '' ? false : true}
|
active={serviceTokenName == "" ? false : true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -198,13 +199,14 @@ const AddServiceTokenDialog = ({
|
|||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
className="text-lg font-medium leading-6 text-gray-400 z-50"
|
||||||
>
|
>
|
||||||
Copy your service token
|
{t("section-token:add-dialog.copy-service-token")}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2 mb-4">
|
<div className="mt-2 mb-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-sm text-gray-500">
|
<p className="text-sm text-gray-500">
|
||||||
Once you close this popup, you will never see your
|
{t(
|
||||||
service token again
|
"section-token:add-dialog.copy-service-token-description"
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -234,7 +236,7 @@ const AddServiceTokenDialog = ({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
<span className="absolute hidden group-hover:flex group-hover:animate-popup duration-300 w-28 -left-8 -top-20 translate-y-full px-3 py-2 bg-chicago-900 rounded-md text-center text-gray-400 text-sm">
|
||||||
Click to Copy
|
{t("common.click-to-copy")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the text field where people can add comments to particular secrets.
|
||||||
|
*/
|
||||||
|
const CommentField = ({ comment, modifyComment, position }: { comment: string; modifyComment: (value: string, posistion: number) => void; position: number;}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return <div className={`relative mt-4 px-4 pt-4`}>
|
||||||
|
<p className='text-sm text-bunker-300'>{t("dashboard:sidebar.comments")}</p>
|
||||||
|
<textarea
|
||||||
|
className="bg-bunker-800 h-32 w-full bg-bunker-800 p-2 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
|
||||||
|
value={comment}
|
||||||
|
onChange={(e) => modifyComment(e.target.value, position)}
|
||||||
|
placeholder="Leave any comments here..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommentField;
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { type ChangeEvent, type DragEvent, useState } from 'react';
|
import { type ChangeEvent, type DragEvent, useState } from "react";
|
||||||
import Image from 'next/image';
|
import Image from "next/image";
|
||||||
import { faUpload } from '@fortawesome/free-solid-svg-icons';
|
import { useTranslation } from "next-i18next";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import Button from '../basic/buttons/Button';
|
import Button from "../basic/buttons/Button";
|
||||||
import Error from '../basic/Error';
|
import Error from "../basic/Error";
|
||||||
import parse from '../utilities/file';
|
import parse from "../utilities/file";
|
||||||
import guidGenerator from '../utilities/randomId';
|
import guidGenerator from "../utilities/randomId";
|
||||||
|
|
||||||
interface DropZoneProps {
|
interface DropZoneProps {
|
||||||
// TODO: change Data type from any
|
// TODO: change Data type from any
|
||||||
@@ -26,8 +27,10 @@ const DropZone = ({
|
|||||||
errorDragAndDrop,
|
errorDragAndDrop,
|
||||||
setButtonReady,
|
setButtonReady,
|
||||||
keysExist,
|
keysExist,
|
||||||
numCurrentRows
|
numCurrentRows,
|
||||||
}: DropZoneProps) => {
|
}: DropZoneProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleDragEnter = (e: DragEvent) => {
|
const handleDragEnter = (e: DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -43,7 +46,7 @@ const DropZone = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
// set dropEffect to copy i.e copy of the source item
|
// set dropEffect to copy i.e copy of the source item
|
||||||
e.dataTransfer.dropEffect = 'copy';
|
e.dataTransfer.dropEffect = "copy";
|
||||||
};
|
};
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -54,7 +57,7 @@ const DropZone = ({
|
|||||||
setTimeout(() => setLoading(false), 5000);
|
setTimeout(() => setLoading(false), 5000);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.dataTransfer.dropEffect = 'copy';
|
e.dataTransfer.dropEffect = "copy";
|
||||||
|
|
||||||
const file = e.dataTransfer.files[0];
|
const file = e.dataTransfer.files[0];
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
@@ -69,7 +72,7 @@ const DropZone = ({
|
|||||||
pos: numCurrentRows + index,
|
pos: numCurrentRows + index,
|
||||||
key: key,
|
key: key,
|
||||||
value: keyPairs[key as keyof typeof keyPairs],
|
value: keyPairs[key as keyof typeof keyPairs],
|
||||||
type: 'shared'
|
type: "shared",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setData(newData);
|
setData(newData);
|
||||||
@@ -96,16 +99,16 @@ const DropZone = ({
|
|||||||
reader.onload = (event) => {
|
reader.onload = (event) => {
|
||||||
if (event.target === null || event.target.result === null) return;
|
if (event.target === null || event.target.result === null) return;
|
||||||
const { result } = event.target;
|
const { result } = event.target;
|
||||||
if (typeof result === 'string') {
|
if (typeof result === "string") {
|
||||||
const newData = result
|
const newData = result
|
||||||
.split('\n')
|
.split("\n")
|
||||||
.map((line: string, index: number) => {
|
.map((line: string, index: number) => {
|
||||||
return {
|
return {
|
||||||
id: guidGenerator(),
|
id: guidGenerator(),
|
||||||
pos: numCurrentRows + index,
|
pos: numCurrentRows + index,
|
||||||
key: line.split('=')[0],
|
key: line.split("=")[0],
|
||||||
value: line.split('=').slice(1, line.split('=').length).join('='),
|
value: line.split("=").slice(1, line.split("=").length).join("="),
|
||||||
type: 'shared'
|
type: "shared",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setData(newData);
|
setData(newData);
|
||||||
@@ -126,7 +129,7 @@ const DropZone = ({
|
|||||||
</div>
|
</div>
|
||||||
) : keysExist ? (
|
) : keysExist ? (
|
||||||
<div
|
<div
|
||||||
className="opacity-60 hover:opacity-100 duration-200 relative bg-bunker outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-gray-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
|
className="opacity-60 hover:opacity-100 duration-200 relative bg-mineshaft-900 outline max-w-[calc(100%-1rem)] w-full outline-dashed outline-chicago-600 rounded-md outline-2 flex flex-col items-center justify-center mb-16 mx-auto mt-1 py-8 px-2"
|
||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
@@ -147,11 +150,9 @@ const DropZone = ({
|
|||||||
<div className="flex flex-row">
|
<div className="flex flex-row">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faUpload}
|
icon={faUpload}
|
||||||
className="text-gray-300 text-3xl mr-6"
|
className="text-bunker-300 text-3xl mr-6"
|
||||||
/>
|
/>
|
||||||
<p className="text-gray-300 mt-1">
|
<p className="text-bunker-300 mt-1">{t("common:drop-zone-keys")}</p>
|
||||||
Drag and drop your .env file here to add more keys.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{errorDragAndDrop ? (
|
{errorDragAndDrop ? (
|
||||||
<div className="mt-8 max-w-xl opacity-80">
|
<div className="mt-8 max-w-xl opacity-80">
|
||||||
@@ -170,7 +171,7 @@ const DropZone = ({
|
|||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
|
<FontAwesomeIcon icon={faUpload} className="text-7xl mb-8" />
|
||||||
<p className="">Drag and drop your .env file here.</p>
|
<p className="">{t("common:drop-zone")}</p>
|
||||||
<input
|
<input
|
||||||
id="fileSelect"
|
id="fileSelect"
|
||||||
type="file"
|
type="file"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Fragment,useState } from 'react';
|
import { Fragment,useState } from 'react';
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
import { faShuffle } from '@fortawesome/free-solid-svg-icons';
|
import { faShuffle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Menu, Transition } from '@headlessui/react';
|
import { Menu, Transition } from '@headlessui/react';
|
||||||
@@ -10,6 +11,7 @@ import { Menu, Transition } from '@headlessui/react';
|
|||||||
*/
|
*/
|
||||||
const GenerateSecretMenu = ({ modifyValue, position }: { modifyValue: (value: string, position: number) => void; position: number; }) => {
|
const GenerateSecretMenu = ({ modifyValue, position }: { modifyValue: (value: string, position: number) => void; position: number; }) => {
|
||||||
const [randomStringLength, setRandomStringLength] = useState(32);
|
const [randomStringLength, setRandomStringLength] = useState(32);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return <Menu as="div" className="relative inline-block text-left">
|
return <Menu as="div" className="relative inline-block text-left">
|
||||||
<div>
|
<div>
|
||||||
@@ -51,8 +53,8 @@ const GenerateSecretMenu = ({ modifyValue, position }: { modifyValue: (value: st
|
|||||||
icon={faShuffle}
|
icon={faShuffle}
|
||||||
/>
|
/>
|
||||||
<div className="text-sm justify-between flex flex-row w-full">
|
<div className="text-sm justify-between flex flex-row w-full">
|
||||||
<p>Generate Random Hex</p>
|
<p>{t("dashboard:sidebar.generate-random-hex")}</p>
|
||||||
<p>digits</p>
|
<p>{t("dashboard:sidebar.digits")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row absolute bottom-[0.4rem] right-[3.3rem] w-16 bg-bunker-800 border border-chicago-700 rounded-md text-bunker-200 ">
|
<div className="flex flex-row absolute bottom-[0.4rem] right-[3.3rem] w-16 bg-bunker-800 border border-chicago-700 rounded-md text-bunker-200 ">
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
import { faX } from '@fortawesome/free-solid-svg-icons';
|
import { faX } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import SecretVersionList from 'ee/components/SecretVersionList';
|
import SecretVersionList from 'ee/components/SecretVersionList';
|
||||||
|
|
||||||
import Button from '../basic/buttons/Button';
|
import Button from '../basic/buttons/Button';
|
||||||
import Toggle from '../basic/Toggle';
|
import Toggle from '../basic/Toggle';
|
||||||
|
import CommentField from './CommentField';
|
||||||
import DashboardInputField from './DashboardInputField';
|
import DashboardInputField from './DashboardInputField';
|
||||||
import GenerateSecretMenu from './GenerateSecretMenu';
|
import GenerateSecretMenu from './GenerateSecretMenu';
|
||||||
|
|
||||||
@@ -15,6 +17,7 @@ interface SecretProps {
|
|||||||
pos: number;
|
pos: number;
|
||||||
type: string;
|
type: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
comment: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OverrideProps {
|
interface OverrideProps {
|
||||||
@@ -22,6 +25,7 @@ interface OverrideProps {
|
|||||||
keyName: string;
|
keyName: string;
|
||||||
value: string;
|
value: string;
|
||||||
pos: number;
|
pos: number;
|
||||||
|
comment: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SideBarProps {
|
interface SideBarProps {
|
||||||
@@ -29,6 +33,7 @@ interface SideBarProps {
|
|||||||
data: SecretProps[];
|
data: SecretProps[];
|
||||||
modifyKey: (value: string, position: number) => void;
|
modifyKey: (value: string, position: number) => void;
|
||||||
modifyValue: (value: string, position: number) => void;
|
modifyValue: (value: string, position: number) => void;
|
||||||
|
modifyComment: (value: string, position: number) => void;
|
||||||
addOverride: (value: OverrideProps) => void;
|
addOverride: (value: OverrideProps) => void;
|
||||||
deleteOverride: (id: string) => void;
|
deleteOverride: (id: string) => void;
|
||||||
buttonReady: boolean;
|
buttonReady: boolean;
|
||||||
@@ -56,6 +61,7 @@ const SideBar = ({
|
|||||||
data,
|
data,
|
||||||
modifyKey,
|
modifyKey,
|
||||||
modifyValue,
|
modifyValue,
|
||||||
|
modifyComment,
|
||||||
addOverride,
|
addOverride,
|
||||||
deleteOverride,
|
deleteOverride,
|
||||||
buttonReady,
|
buttonReady,
|
||||||
@@ -64,29 +70,30 @@ const SideBar = ({
|
|||||||
setSharedToHide
|
setSharedToHide
|
||||||
}: SideBarProps) => {
|
}: SideBarProps) => {
|
||||||
const [overrideEnabled, setOverrideEnabled] = useState(data.map(secret => secret.type).includes("personal"));
|
const [overrideEnabled, setOverrideEnabled] = useState(data.map(secret => secret.type).includes("personal"));
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return <div className='absolute border-l border-mineshaft-500 bg-bunker fixed h-full w-96 top-14 right-0 z-50 shadow-xl flex flex-col justify-between'>
|
return <div className='absolute border-l border-mineshaft-500 bg-bunker fixed h-full w-96 top-14 right-0 z-50 shadow-xl flex flex-col justify-between'>
|
||||||
<div className='h-min overflow-y-auto'>
|
<div className='h-min overflow-y-auto'>
|
||||||
<div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center">
|
<div className="flex flex-row px-4 py-3 border-b border-mineshaft-500 justify-between items-center">
|
||||||
<p className="font-semibold text-lg text-bunker-200">Secret</p>
|
<p className="font-semibold text-lg text-bunker-200">{t("dashboard:sidebar.secret")}</p>
|
||||||
<div className='p-1' onClick={() => toggleSidebar("None")}>
|
<div className='p-1' onClick={() => toggleSidebar("None")}>
|
||||||
<FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/>
|
<FontAwesomeIcon icon={faX} className='w-4 h-4 text-bunker-300 cursor-pointer'/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-4 px-4 pointer-events-none'>
|
<div className='mt-4 px-4 pointer-events-none'>
|
||||||
<p className='text-sm text-bunker-300'>Key</p>
|
<p className='text-sm text-bunker-300'>{t("dashboard:sidebar.key")}</p>
|
||||||
<DashboardInputField
|
<DashboardInputField
|
||||||
onChangeHandler={modifyKey}
|
onChangeHandler={modifyKey}
|
||||||
type="varName"
|
type="varName"
|
||||||
position={data[0].pos}
|
position={data[0]?.pos}
|
||||||
value={data[0].key}
|
value={data[0]?.key}
|
||||||
isDuplicate={false}
|
isDuplicate={false}
|
||||||
blurred={false}
|
blurred={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{data.filter(secret => secret.type == "shared")[0]?.value
|
{data.filter(secret => secret.type == "shared")[0]?.value
|
||||||
? <div className={`relative mt-2 px-4 ${overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}>
|
? <div className={`relative mt-2 px-4 ${overrideEnabled && "opacity-40 pointer-events-none"} duration-200`}>
|
||||||
<p className='text-sm text-bunker-300'>Value</p>
|
<p className='text-sm text-bunker-300'>{t("dashboard:sidebar.value")}</p>
|
||||||
<DashboardInputField
|
<DashboardInputField
|
||||||
onChangeHandler={modifyValue}
|
onChangeHandler={modifyValue}
|
||||||
type="value"
|
type="value"
|
||||||
@@ -100,21 +107,22 @@ const SideBar = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: <div className='px-4 text-sm text-bunker-300 pt-4'>
|
: <div className='px-4 text-sm text-bunker-300 pt-4'>
|
||||||
<span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>Note:</span>
|
<span className='py-0.5 px-1 rounded-md bg-primary-200/10 mr-1'>{t("common:note")}:</span>
|
||||||
This secret is personal. It is not shared with any of your teammates.
|
{t("dashboard:sidebar.personal-explanation")}
|
||||||
</div>}
|
</div>}
|
||||||
<div className='mt-4 px-4'>
|
<div className='mt-4 px-4'>
|
||||||
{data.filter(secret => secret.type == "shared")[0]?.value &&
|
{data.filter(secret => secret.type == "shared")[0]?.value &&
|
||||||
<div className='flex flex-row items-center justify-between my-2 pl-1 pr-2'>
|
<div className='flex flex-row items-center justify-between my-2 pl-1 pr-2'>
|
||||||
<p className='text-sm text-bunker-300'>Override value with a personal value</p>
|
<p className='text-sm text-bunker-300'>{t("dashboard:sidebar.override")}</p>
|
||||||
<Toggle
|
<Toggle
|
||||||
enabled={overrideEnabled}
|
enabled={overrideEnabled}
|
||||||
setEnabled={setOverrideEnabled}
|
setEnabled={setOverrideEnabled}
|
||||||
addOverride={addOverride}
|
addOverride={addOverride}
|
||||||
keyName={data[0].key}
|
keyName={data[0]?.key}
|
||||||
value={data[0].value}
|
value={data[0]?.value}
|
||||||
pos={data[0].pos}
|
pos={data[0]?.pos}
|
||||||
id={data[0].id}
|
id={data[0]?.id}
|
||||||
|
comment={data[0]?.comment}
|
||||||
deleteOverride={deleteOverride}
|
deleteOverride={deleteOverride}
|
||||||
sharedToHide={sharedToHide}
|
sharedToHide={sharedToHide}
|
||||||
setSharedToHide={setSharedToHide}
|
setSharedToHide={setSharedToHide}
|
||||||
@@ -124,13 +132,13 @@ const SideBar = ({
|
|||||||
<DashboardInputField
|
<DashboardInputField
|
||||||
onChangeHandler={modifyValue}
|
onChangeHandler={modifyValue}
|
||||||
type="value"
|
type="value"
|
||||||
position={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].pos : data[0].pos}
|
position={overrideEnabled ? data.filter(secret => secret.type == "personal")[0]?.pos : data[0]?.pos}
|
||||||
value={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].value : data[0].value}
|
value={overrideEnabled ? data.filter(secret => secret.type == "personal")[0]?.value : data[0]?.value}
|
||||||
isDuplicate={false}
|
isDuplicate={false}
|
||||||
blurred={true}
|
blurred={true}
|
||||||
/>
|
/>
|
||||||
<div className='absolute right-[0.57rem] top-[0.3rem] z-50'>
|
<div className='absolute right-[0.57rem] top-[0.3rem] z-50'>
|
||||||
<GenerateSecretMenu modifyValue={modifyValue} position={overrideEnabled ? data.filter(secret => secret.type == "personal")[0].pos : data[0].pos} />
|
<GenerateSecretMenu modifyValue={modifyValue} position={overrideEnabled ? data.filter(secret => secret.type == "personal")[0]?.pos : data[0]?.pos} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -143,21 +151,12 @@ const SideBar = ({
|
|||||||
isFull={true}
|
isFull={true}
|
||||||
/>
|
/>
|
||||||
</div> */}
|
</div> */}
|
||||||
<div className={`relative mt-4 px-4 pt-4`}>
|
<SecretVersionList secretId={data[0]?.id} />
|
||||||
<div className='flex flex-row justify-between'>
|
<CommentField comment={data.filter(secret => secret.type == "shared")[0]?.comment} modifyComment={modifyComment} position={data[0]?.pos} />
|
||||||
<p className='text-sm text-bunker-300'>Comments & notes</p>
|
|
||||||
<div className="bg-yellow rounded-md h-min">
|
|
||||||
<p className="relative text-black text-xs px-1.5 h-min">Coming soon!</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='h-32 opacity-50 w-full bg-bunker-800 p-2 rounded-md border border-mineshaft-500 rounded-md text-sm text-bunker-300'>
|
|
||||||
Leave your comment here...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={`flex justify-start max-w-sm mt-4 px-4 mt-full mb-[4.7rem]`}>
|
<div className={`flex justify-start max-w-sm mt-4 px-4 mt-full mb-[4.7rem]`}>
|
||||||
<Button
|
<Button
|
||||||
text="Save Changes"
|
text={String(t("common:save-changes"))}
|
||||||
onButtonPressed={savePush}
|
onButtonPressed={savePush}
|
||||||
color="primary"
|
color="primary"
|
||||||
size="md"
|
size="md"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
|
||||||
import CloudIntegration from "./CloudIntegration";
|
import CloudIntegration from "./CloudIntegration";
|
||||||
|
|
||||||
@@ -24,12 +25,14 @@ const CloudIntegrationSection = ({
|
|||||||
integrationOptionPress,
|
integrationOptionPress,
|
||||||
integrationAuths
|
integrationAuths
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`flex flex-col justify-between items-start m-4 mt-7 text-xl max-w-5xl px-2`}>
|
<div className={`flex flex-col justify-between items-start m-4 mt-7 text-xl max-w-5xl px-2`}>
|
||||||
<h1 className="font-semibold text-3xl">Cloud Integrations</h1>
|
<h1 className="font-semibold text-3xl">{t("integrations:cloud-integrations")}</h1>
|
||||||
<p className="text-base text-gray-400">
|
<p className="text-base text-gray-400">
|
||||||
Click on an integration to begin syncing secrets to it.
|
{t("integrations:click-to-start")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 grid-cols-4 grid-rows-2 mx-6 max-w-5xl">
|
<div className="grid gap-4 grid-cols-4 grid-rows-2 mx-6 max-w-5xl">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
|
||||||
import FrameworkIntegration from "./FrameworkIntegration";
|
import FrameworkIntegration from "./FrameworkIntegration";
|
||||||
|
|
||||||
@@ -15,24 +16,26 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FrameworkIntegrationSection = ({ frameworks }: Props) => {
|
const FrameworkIntegrationSection = ({ frameworks }: Props) => {
|
||||||
return (
|
const { t } = useTranslation();
|
||||||
<>
|
|
||||||
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
|
return (
|
||||||
<h1 className="font-semibold text-3xl">Framework Integrations</h1>
|
<>
|
||||||
<p className="text-base text-gray-400">
|
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
|
||||||
Click on a framework to get the setup instructions.
|
<h1 className="font-semibold text-3xl">{t("integrations:framework-integrations")}</h1>
|
||||||
</p>
|
<p className="text-base text-gray-400">
|
||||||
</div>
|
{t("integrations:click-to-setup")}
|
||||||
<div className="grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl">
|
</p>
|
||||||
{frameworks.map((framework) => (
|
</div>
|
||||||
<FrameworkIntegration
|
<div className="grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl">
|
||||||
framework={framework}
|
{frameworks.map((framework) => (
|
||||||
key={`framework-integration-${framework.slug}`}
|
<FrameworkIntegration
|
||||||
/>
|
framework={framework}
|
||||||
))}
|
key={`framework-integration-${framework.slug}`}
|
||||||
</div>
|
/>
|
||||||
</>
|
))}
|
||||||
);
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FrameworkIntegrationSection;
|
export default FrameworkIntegrationSection;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
/* eslint-disable react/jsx-key */
|
/* eslint-disable react/jsx-key */
|
||||||
import { Fragment, useEffect, useState } from 'react';
|
import { Fragment, useEffect, useMemo, useState } from "react";
|
||||||
import Image from 'next/image';
|
import Image from "next/image";
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from "next/router";
|
||||||
import { faGithub, faSlack } from '@fortawesome/free-brands-svg-icons';
|
import { TFunction, useTranslation } from "next-i18next";
|
||||||
import { faCircleQuestion } from '@fortawesome/free-regular-svg-icons';
|
import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons";
|
||||||
|
import { faCircleQuestion } from "@fortawesome/free-regular-svg-icons";
|
||||||
import {
|
import {
|
||||||
faAngleDown,
|
faAngleDown,
|
||||||
faBook,
|
faBook,
|
||||||
@@ -12,39 +12,39 @@ import {
|
|||||||
faEnvelope,
|
faEnvelope,
|
||||||
faGear,
|
faGear,
|
||||||
faPlus,
|
faPlus,
|
||||||
faRightFromBracket
|
faRightFromBracket,
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { Menu, Transition } from '@headlessui/react';
|
import { Menu, Transition } from "@headlessui/react";
|
||||||
|
|
||||||
import logout from '~/pages/api/auth/Logout';
|
import logout from "~/pages/api/auth/Logout";
|
||||||
import getOrganization from '~/pages/api/organization/GetOrg';
|
|
||||||
import getOrganizations from '~/pages/api/organization/getOrgs';
|
|
||||||
import getUser from '~/pages/api/user/getUser';
|
|
||||||
|
|
||||||
import guidGenerator from '../utilities/randomId';
|
import getOrganization from "../../pages/api/organization/GetOrg";
|
||||||
|
import getOrganizations from "../../pages/api/organization/getOrgs";
|
||||||
|
import getUser from "../../pages/api/user/getUser";
|
||||||
|
import guidGenerator from "../utilities/randomId";
|
||||||
|
|
||||||
const supportOptions = [
|
const supportOptions = (t: TFunction) => [
|
||||||
[
|
[
|
||||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
|
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faSlack} />,
|
||||||
'Join Slack Forum',
|
t("nav:support.slack"),
|
||||||
'https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g'
|
"https://join.slack.com/t/infisical/shared_invite/zt-1dgg63ln8-G7PCNJdCymAT9YF3j1ewVA",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
|
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faBook} />,
|
||||||
'Read Docs',
|
t("nav:support.docs"),
|
||||||
'https://infisical.com/docs/getting-started/introduction'
|
"https://infisical.com/docs/getting-started/introduction",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
|
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faGithub} />,
|
||||||
'Open a GitHub Issue',
|
t("nav:support.issue"),
|
||||||
'https://github.com/Infisical/infisical-cli/issues'
|
"https://github.com/Infisical/infisical-cli/issues",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
|
<FontAwesomeIcon className="text-lg pl-1.5 pr-3" icon={faEnvelope} />,
|
||||||
'Send us an Email',
|
t("nav:support.email"),
|
||||||
'mailto:[email protected]'
|
"mailto:[email protected]",
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface ICurrentOrg {
|
export interface ICurrentOrg {
|
||||||
@@ -68,6 +68,10 @@ export default function Navbar() {
|
|||||||
const [orgs, setOrgs] = useState([]);
|
const [orgs, setOrgs] = useState([]);
|
||||||
const [currentOrg, setCurrentOrg] = useState<ICurrentOrg | undefined>();
|
const [currentOrg, setCurrentOrg] = useState<ICurrentOrg | undefined>();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const supportOptionsList = useMemo(() => supportOptions(t), [t]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const userData = await getUser();
|
const userData = await getUser();
|
||||||
@@ -75,16 +79,16 @@ export default function Navbar() {
|
|||||||
const orgsData = await getOrganizations();
|
const orgsData = await getOrganizations();
|
||||||
setOrgs(orgsData);
|
setOrgs(orgsData);
|
||||||
const currentOrg = await getOrganization({
|
const currentOrg = await getOrganization({
|
||||||
orgId: String(localStorage.getItem('orgData.id'))
|
orgId: String(localStorage.getItem("orgData.id")),
|
||||||
});
|
});
|
||||||
setCurrentOrg(currentOrg);
|
setCurrentOrg(currentOrg);
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeApp = async () => {
|
const closeApp = async () => {
|
||||||
console.log('Logging out...');
|
console.log("Logging out...");
|
||||||
await logout();
|
await logout();
|
||||||
router.push('/login');
|
router.push("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -119,18 +123,17 @@ export default function Navbar() {
|
|||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5">
|
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none px-2 py-1.5">
|
||||||
{supportOptions.map((option) => (
|
{supportOptionsList.map(([icon, text, url]) => (
|
||||||
// eslint-disable-next-line react/jsx-no-target-blank
|
|
||||||
<a
|
<a
|
||||||
key={guidGenerator()}
|
key={guidGenerator()}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener noreferrer"
|
||||||
href={String(option[2])}
|
href={String(url)}
|
||||||
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
|
className="font-normal text-gray-300 duration-200 rounded-md w-full flex items-center py-0.5"
|
||||||
>
|
>
|
||||||
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
|
<div className="relative flex justify-start items-center cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/10 duration-200 hover:text-gray-200 w-full">
|
||||||
{option[0]}
|
{icon}
|
||||||
<div className="text-sm">{option[1]}</div>
|
<div className="text-sm">{text}</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
@@ -159,11 +162,11 @@ export default function Navbar() {
|
|||||||
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none">
|
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none">
|
||||||
<div className="px-1 py-1 ">
|
<div className="px-1 py-1 ">
|
||||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||||
SIGNED IN AS
|
{t("nav:user.signed-in-as")}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push('/settings/personal/' + router.query.id)
|
router.push("/settings/personal/" + router.query.id)
|
||||||
}
|
}
|
||||||
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
|
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
|
||||||
>
|
>
|
||||||
@@ -173,11 +176,11 @@ export default function Navbar() {
|
|||||||
<div className="flex items-center justify-between w-full">
|
<div className="flex items-center justify-between w-full">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-300 px-2 pt-1 text-sm">
|
<p className="text-gray-300 px-2 pt-1 text-sm">
|
||||||
{' '}
|
{" "}
|
||||||
{user?.firstName} {user?.lastName}
|
{user?.firstName} {user?.lastName}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-400 px-2 pb-1 text-xs">
|
<p className="text-gray-400 px-2 pb-1 text-xs">
|
||||||
{' '}
|
{" "}
|
||||||
{user?.email}
|
{user?.email}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,11 +193,11 @@ export default function Navbar() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="px-2 pt-2">
|
<div className="px-2 pt-2">
|
||||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||||
CURRENT ORGANIZATION
|
{t("nav:user.current-organization")}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push('/settings/org/' + router.query.id)
|
router.push("/settings/org/" + router.query.id)
|
||||||
}
|
}
|
||||||
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
|
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
|
||||||
>
|
>
|
||||||
@@ -217,7 +220,7 @@ export default function Navbar() {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push('/settings/billing/' + router.query.id)
|
router.push("/settings/billing/" + router.query.id)
|
||||||
}
|
}
|
||||||
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
|
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
|
||||||
>
|
>
|
||||||
@@ -225,7 +228,7 @@ export default function Navbar() {
|
|||||||
className="text-lg pl-1.5 pr-3"
|
className="text-lg pl-1.5 pr-3"
|
||||||
icon={faCoins}
|
icon={faCoins}
|
||||||
/>
|
/>
|
||||||
<div className="text-sm">Usage & Billing</div>
|
<div className="text-sm">{t("nav:user.usage-billing")}</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -235,7 +238,7 @@ export default function Navbar() {
|
|||||||
<div
|
<div
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(
|
router.push(
|
||||||
'/settings/org/' + router.query.id + '?invite'
|
"/settings/org/" + router.query.id + "?invite"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
|
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
|
||||||
@@ -243,26 +246,26 @@ export default function Navbar() {
|
|||||||
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
<span className="rounded-lg absolute inset-y-0 left-0 flex items-center pl-3 pr-4">
|
||||||
<FontAwesomeIcon icon={faPlus} className="ml-1" />
|
<FontAwesomeIcon icon={faPlus} className="ml-1" />
|
||||||
</span>
|
</span>
|
||||||
<div className="text-sm ml-1">Invite Members</div>
|
<div className="text-sm ml-1">{t("nav:user.invite")}</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{orgs?.length > 1 && (
|
{orgs?.length > 1 && (
|
||||||
<div className="px-1 pt-1">
|
<div className="px-1 pt-1">
|
||||||
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
|
||||||
OTHER ORGANIZATIONS
|
{t("nav:user.other-organizations")}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-start px-1 mt-3 mb-2">
|
<div className="flex flex-col items-start px-1 mt-3 mb-2">
|
||||||
{orgs
|
{orgs
|
||||||
.filter(
|
.filter(
|
||||||
(org: { _id: string }) =>
|
(org: { _id: string }) =>
|
||||||
org._id != localStorage.getItem('orgData.id')
|
org._id != localStorage.getItem("orgData.id")
|
||||||
)
|
)
|
||||||
.map((org: { _id: string; name: string }) => (
|
.map((org: { _id: string; name: string }) => (
|
||||||
<div
|
<div
|
||||||
key={guidGenerator()}
|
key={guidGenerator()}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
localStorage.setItem('orgData.id', org._id);
|
localStorage.setItem("orgData.id", org._id);
|
||||||
router.reload();
|
router.reload();
|
||||||
}}
|
}}
|
||||||
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
|
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
|
||||||
@@ -287,8 +290,8 @@ export default function Navbar() {
|
|||||||
onClick={closeApp}
|
onClick={closeApp}
|
||||||
className={`${
|
className={`${
|
||||||
active
|
active
|
||||||
? 'bg-red font-semibold text-white'
|
? "bg-red font-semibold text-white"
|
||||||
: 'text-gray-400'
|
: "text-gray-400"
|
||||||
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
|
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
|
||||||
>
|
>
|
||||||
<div className="relative flex justify-start items-center cursor-pointer select-none">
|
<div className="relative flex justify-start items-center cursor-pointer select-none">
|
||||||
@@ -296,7 +299,7 @@ export default function Navbar() {
|
|||||||
className="text-lg ml-1.5 mr-3"
|
className="text-lg ml-1.5 mr-3"
|
||||||
icon={faRightFromBracket}
|
icon={faRightFromBracket}
|
||||||
/>
|
/>
|
||||||
Log Out
|
{t("common:logout")}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -16,12 +16,19 @@ export default class SecurityClient {
|
|||||||
const req = new Request(resource, options);
|
const req = new Request(resource, options);
|
||||||
|
|
||||||
if (this.#token == '') {
|
if (this.#token == '') {
|
||||||
this.setToken(await token());
|
try {
|
||||||
|
// TODO: This should be moved to a context to do it only once when app loads
|
||||||
|
// this try catch saves route guard from a stuck state
|
||||||
|
this.setToken(await token());
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Unauthorized access");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#token) {
|
if (this.#token) {
|
||||||
req.headers.set('Authorization', 'Bearer ' + this.#token);
|
req.headers.set('Authorization', 'Bearer ' + this.#token);
|
||||||
return fetch(req);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return fetch(req);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const attemptLogin = async (
|
|||||||
// if everything works, go the main dashboard page.
|
// if everything works, go the main dashboard page.
|
||||||
const { token, publicKey, encryptedPrivateKey, iv, tag } =
|
const { token, publicKey, encryptedPrivateKey, iv, tag } =
|
||||||
await login2(email, clientProof);
|
await login2(email, clientProof);
|
||||||
|
|
||||||
SecurityClient.setToken(token);
|
SecurityClient.setToken(token);
|
||||||
|
|
||||||
const privateKey = Aes256Gcm.decrypt({
|
const privateKey = Aes256Gcm.decrypt({
|
||||||
@@ -108,15 +109,16 @@ const attemptLogin = async (
|
|||||||
if (isSignUp) {
|
if (isSignUp) {
|
||||||
await pushKeys({
|
await pushKeys({
|
||||||
obj: {
|
obj: {
|
||||||
DATABASE_URL: [
|
sDATABASE_URL: [
|
||||||
'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
|
'mongodb+srv://${DB_USERNAME}:${DB_PASSWORD}@mongodb.net',
|
||||||
'personal'
|
'This is an example of secret referencing.'
|
||||||
],
|
],
|
||||||
DB_USERNAME: ['user1234', 'personal'],
|
sDB_USERNAME: ['OVERRIDE_THIS', ''],
|
||||||
DB_PASSWORD: ['example_password', 'personal'],
|
sDB_PASSWORD: ['OVERRIDE_THIS', ''],
|
||||||
TWILIO_AUTH_TOKEN: ['example_twillion_token', 'shared'],
|
pDB_USERNAME: ['user1234', 'This is an example of secret overriding. Your team can have a shared value of a secret, while you can override it to whatever value you need.'],
|
||||||
WEBSITE_URL: ['http://localhost:3000', 'shared'],
|
pDB_PASSWORD: ['example_password', 'This is an example of secret overriding. Your team can have a shared value of a secret, while you can override it to whatever value you need.'],
|
||||||
STRIPE_SECRET_KEY: ['sk_test_7348oyho4hfq398HIUOH78', 'shared']
|
sTWILIO_AUTH_TOKEN: ['example_twillio_token', ''],
|
||||||
|
sWEBSITE_URL: ['http://localhost:3000', ''],
|
||||||
},
|
},
|
||||||
workspaceId: projectToLogin,
|
workspaceId: projectToLogin,
|
||||||
env: 'Development'
|
env: 'Development'
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ const {
|
|||||||
const nacl = require('tweetnacl');
|
const nacl = require('tweetnacl');
|
||||||
nacl.util = require('tweetnacl-util');
|
nacl.util = require('tweetnacl-util');
|
||||||
|
|
||||||
|
interface SecretProps {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
type: 'personal' | 'shared';
|
||||||
|
comment: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
env: keyof typeof envMapping;
|
env: keyof typeof envMapping;
|
||||||
setFileState: any;
|
setFileState: any;
|
||||||
@@ -34,12 +42,12 @@ const getSecretsForProject = async ({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('ERROR: Not able to access the latest file');
|
console.log('ERROR: Not able to access the latest file');
|
||||||
}
|
}
|
||||||
// This is called isKeyAvilable but what it really means is if a person is able to create new key pairs
|
// This is called isKeyAvailable but what it really means is if a person is able to create new key pairs
|
||||||
setIsKeyAvailable(!file.key ? file.secrets.length == 0 : true);
|
setIsKeyAvailable(!file.key ? file.secrets.length == 0 : true);
|
||||||
|
|
||||||
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY');
|
||||||
|
|
||||||
const tempFileState: { key: string; value: string; type: 'personal' | 'shared'; }[] = [];
|
const tempFileState: SecretProps[] = [];
|
||||||
if (file.key) {
|
if (file.key) {
|
||||||
// assymmetrically decrypt symmetric key with local private key
|
// assymmetrically decrypt symmetric key with local private key
|
||||||
const key = decryptAssymmetric({
|
const key = decryptAssymmetric({
|
||||||
@@ -64,10 +72,25 @@ const getSecretsForProject = async ({
|
|||||||
tag: secretPair.secretValue.tag,
|
tag: secretPair.secretValue.tag,
|
||||||
key
|
key
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let plainTextComment;
|
||||||
|
if (secretPair.secretComment.ciphertext) {
|
||||||
|
plainTextComment = decryptSymmetric({
|
||||||
|
ciphertext: secretPair.secretComment.ciphertext,
|
||||||
|
iv: secretPair.secretComment.iv,
|
||||||
|
tag: secretPair.secretComment.tag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
plainTextComment = "";
|
||||||
|
}
|
||||||
|
|
||||||
tempFileState.push({
|
tempFileState.push({
|
||||||
|
id: secretPair._id,
|
||||||
key: plainTextKey,
|
key: plainTextKey,
|
||||||
value: plainTextValue,
|
value: plainTextValue,
|
||||||
type: secretPair.type
|
type: secretPair.type,
|
||||||
|
comment: plainTextComment
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -76,22 +99,24 @@ const getSecretsForProject = async ({
|
|||||||
setData(
|
setData(
|
||||||
tempFileState.map((line, index) => {
|
tempFileState.map((line, index) => {
|
||||||
return {
|
return {
|
||||||
id: guidGenerator(),
|
id: line['id'],
|
||||||
pos: index,
|
pos: index,
|
||||||
key: line['key'],
|
key: line['key'],
|
||||||
value: line['value'],
|
value: line['value'],
|
||||||
type: line['type']
|
type: line['type'],
|
||||||
|
comment: line['comment']
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return tempFileState.map((line, index) => {
|
return tempFileState.map((line, index) => {
|
||||||
return {
|
return {
|
||||||
id: guidGenerator(),
|
id: line['id'],
|
||||||
pos: index,
|
pos: index,
|
||||||
key: line['key'],
|
key: line['key'],
|
||||||
value: line['value'],
|
value: line['value'],
|
||||||
type: line['type']
|
type: line['type'],
|
||||||
|
comment: line['comment']
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: st
|
|||||||
const secrets = Object.keys(obj).map((key) => {
|
const secrets = Object.keys(obj).map((key) => {
|
||||||
// encrypt key
|
// encrypt key
|
||||||
const {
|
const {
|
||||||
ciphertext: ciphertextKey,
|
ciphertext: secretKeyCiphertext,
|
||||||
iv: ivKey,
|
iv: secretKeyIV,
|
||||||
tag: tagKey,
|
tag: secretKeyTag,
|
||||||
} = encryptSymmetric({
|
} = encryptSymmetric({
|
||||||
plaintext: key.slice(1),
|
plaintext: key.slice(1),
|
||||||
key: randomBytes,
|
key: randomBytes,
|
||||||
@@ -57,25 +57,39 @@ const pushKeys = async({ obj, workspaceId, env }: { obj: object; workspaceId: st
|
|||||||
|
|
||||||
// encrypt value
|
// encrypt value
|
||||||
const {
|
const {
|
||||||
ciphertext: ciphertextValue,
|
ciphertext: secretValueCiphertext,
|
||||||
iv: ivValue,
|
iv: secretValueIV,
|
||||||
tag: tagValue,
|
tag: secretValueTag,
|
||||||
} = encryptSymmetric({
|
} = encryptSymmetric({
|
||||||
plaintext: obj[key as keyof typeof obj][0],
|
plaintext: obj[key as keyof typeof obj][0],
|
||||||
key: randomBytes,
|
key: randomBytes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// encrypt comment
|
||||||
|
const {
|
||||||
|
ciphertext: secretCommentCiphertext,
|
||||||
|
iv: secretCommentIV,
|
||||||
|
tag: secretCommentTag,
|
||||||
|
} = encryptSymmetric({
|
||||||
|
plaintext: obj[key as keyof typeof obj][1],
|
||||||
|
key: randomBytes,
|
||||||
|
});
|
||||||
|
|
||||||
const visibility = key.charAt(0) == "p" ? "personal" : "shared";
|
const visibility = key.charAt(0) == "p" ? "personal" : "shared";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ciphertextKey,
|
secretKeyCiphertext,
|
||||||
ivKey,
|
secretKeyIV,
|
||||||
tagKey,
|
secretKeyTag,
|
||||||
hashKey: crypto.createHash("sha256").update(key.slice(1)).digest("hex"),
|
secretKeyHash: crypto.createHash("sha256").update(key.slice(1)).digest("hex"),
|
||||||
ciphertextValue,
|
secretValueCiphertext,
|
||||||
ivValue,
|
secretValueIV,
|
||||||
tagValue,
|
secretValueTag,
|
||||||
hashValue: crypto.createHash("sha256").update(obj[key as keyof typeof obj][0]).digest("hex"),
|
secretValueHash: crypto.createHash("sha256").update(obj[key as keyof typeof obj][0]).digest("hex"),
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentHash: crypto.createHash("sha256").update(obj[key as keyof typeof obj][1]).digest("hex"),
|
||||||
type: visibility,
|
type: visibility,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { GetServerSideProps, GetStaticProps } from "next";
|
||||||
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||||
|
|
||||||
|
const DefaultNamespaces = ["common", "nav"];
|
||||||
|
|
||||||
|
type GetTranslatedStaticProps = (
|
||||||
|
namespaces: string[],
|
||||||
|
getStaticProps?: GetStaticProps<any>
|
||||||
|
) => GetStaticProps;
|
||||||
|
|
||||||
|
type GetTranslatedServerSideProps = (
|
||||||
|
namespaces: string[],
|
||||||
|
getStaticProps?: GetServerSideProps<any>
|
||||||
|
) => GetServerSideProps;
|
||||||
|
|
||||||
|
export const getTranslatedStaticProps: GetTranslatedStaticProps =
|
||||||
|
(namespaces, getStaticProps) =>
|
||||||
|
async ({ locale, ...context }) => {
|
||||||
|
let staticProps = { props: {} };
|
||||||
|
if (typeof getStaticProps === "function") {
|
||||||
|
staticProps = (await getStaticProps(context)) as any;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...staticProps,
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(locale ?? "en", [
|
||||||
|
...DefaultNamespaces,
|
||||||
|
...namespaces,
|
||||||
|
])),
|
||||||
|
...staticProps.props,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTranslatedServerSideProps: GetTranslatedServerSideProps =
|
||||||
|
(namespaces, getServerSideProps) =>
|
||||||
|
async ({ locale, ...context }) => {
|
||||||
|
let staticProps = { props: {} };
|
||||||
|
if (typeof getServerSideProps === "function") {
|
||||||
|
staticProps = (await getServerSideProps(context)) as any;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...staticProps,
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(locale ?? "en", [
|
||||||
|
...DefaultNamespaces,
|
||||||
|
...namespaces,
|
||||||
|
])),
|
||||||
|
...staticProps.props,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -16,3 +16,8 @@ export const publicPaths = [
|
|||||||
`/terms`,
|
`/terms`,
|
||||||
`/subprocessors`,
|
`/subprocessors`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const languageMap = {
|
||||||
|
en: "English",
|
||||||
|
ko: "한국어",
|
||||||
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user