mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-02 16:55:02 +00:00
Compare commits
207 Commits
patch-k8s-
...
docs/ansib
Author | SHA1 | Date | |
---|---|---|---|
6a001a214b | |||
9fe2021d9f | |||
9e2bd31833 | |||
e88b0ad3c4 | |||
74644fd8bb | |||
2069ac1554 | |||
5a2516e0a7 | |||
b52bc3bed7 | |||
4a153e5658 | |||
7324822be5 | |||
766f301aea | |||
b815e3eb56 | |||
31231cfcca | |||
ee772e4a77 | |||
7bc29c5981 | |||
e9a89930da | |||
b85499859c | |||
7f17194c0f | |||
1e1ad450d2 | |||
5287b322d8 | |||
45d96be1ff | |||
12840bfdbd | |||
fef5369738 | |||
c94b7d63f6 | |||
485ddc5c50 | |||
edd9c66e49 | |||
0a3b85534b | |||
ec2cc5162e | |||
7ce472957c | |||
8529e0da3d | |||
e5a5433f10 | |||
ee6e518ff8 | |||
15a7222505 | |||
25d482cc62 | |||
785a2bec6a | |||
449466f326 | |||
4131e9c3f1 | |||
310595256f | |||
1737880e58 | |||
b72483f5f2 | |||
ee14bda706 | |||
e56463d52b | |||
ebd3d7c7c4 | |||
9ecbfe201b | |||
ba2a03897f | |||
304f14c0ed | |||
51e5c25e16 | |||
0f6490b1e7 | |||
f894e48fcb | |||
37cfa22619 | |||
94557344b7 | |||
d5063018eb | |||
51d68505d3 | |||
ade27ad072 | |||
683c512bce | |||
43ff28b5fb | |||
ce41855e84 | |||
d24461b17c | |||
1797e56f9f | |||
74f3ca5356 | |||
db27beaf0b | |||
d6e55f51f2 | |||
e9b5996567 | |||
094fe73917 | |||
dc3f85e92e | |||
c463256058 | |||
8df22302fd | |||
f37fa2bbf5 | |||
597c9d6f2a | |||
24d2eea930 | |||
382cb910af | |||
6725475575 | |||
026864951b | |||
287ed05ab7 | |||
37b036e614 | |||
024914c168 | |||
19e8b6d37b | |||
b6d648f1f3 | |||
a514a62a29 | |||
2f24956651 | |||
13d058025c | |||
8ccaa7f29b | |||
b83964051c | |||
0a2b078bdc | |||
40d16fa996 | |||
a3739cfe50 | |||
a73623258e | |||
6da39f41a6 | |||
69bbbfcfd8 | |||
c9d58ec77d | |||
cb364186d8 | |||
918afe05b6 | |||
e822820151 | |||
b5ac49eefe | |||
b21d1a0ed2 | |||
70f1122362 | |||
ea03db8a2c | |||
38d9abca17 | |||
5bed2580c3 | |||
d0b899897b | |||
1861dc85de | |||
bc6bf33674 | |||
44fd35baf5 | |||
8ddfee4c36 | |||
4d0bff4377 | |||
c7b2489d0b | |||
68eb0f8dd9 | |||
5941e8e836 | |||
80e50d13ec | |||
99c8dda4e1 | |||
14c8e3fa3b | |||
7aa3cb53a2 | |||
567309e848 | |||
f264340903 | |||
51b788cc5b | |||
8e0f424249 | |||
f3767d3963 | |||
51cbfdbc46 | |||
f5a580eb72 | |||
460ebf3296 | |||
7f7f11c970 | |||
f799e224a0 | |||
8a87277fe6 | |||
32805c726a | |||
6c4a6d31e4 | |||
e7b89b645f | |||
b60cf2eb07 | |||
cf5a79995f | |||
c51f09fd3a | |||
f9444c5205 | |||
7dd0943b2d | |||
31a9f032b3 | |||
9c55d1906d | |||
ff54a20ace | |||
8bf7eba07b | |||
bb75ea550a | |||
344f7276d2 | |||
c375662411 | |||
cc4ad1df4b | |||
c92c0f7288 | |||
fbe0cf006f | |||
d2f959558e | |||
e50c89e326 | |||
6cda14328b | |||
b551ee50e7 | |||
93aeacc6b6 | |||
f940f8b79d | |||
72ac2c04b8 | |||
bb3d591f21 | |||
763ce1b206 | |||
1f97ac5192 | |||
5f29562fad | |||
f3e8ef1537 | |||
544d37bbc4 | |||
4f6adb50d1 | |||
444ce9508d | |||
aabd896c37 | |||
50ef23e8a0 | |||
b87f51a044 | |||
1233d9c1a0 | |||
ff0b4d7f2b | |||
ef61bc6a40 | |||
13ee8c4e13 | |||
6ea9fc7134 | |||
89d0c0e3c3 | |||
a4f6b828ad | |||
0fb2056b8b | |||
ec5cf97f18 | |||
69b57817d6 | |||
aafbe40c02 | |||
9d9b83f909 | |||
ea1f144b54 | |||
591f33ffbe | |||
855158d0bb | |||
87e997e7a0 | |||
3c449214d1 | |||
d813f0716f | |||
6787c0eaaa | |||
c91f6521c1 | |||
0ebd1d3d81 | |||
d257a449bb | |||
6a744c96e5 | |||
28b617fd89 | |||
8b1eaad7b5 | |||
c917cf8a18 | |||
282830e7a2 | |||
3d6f04b94e | |||
60a5092947 | |||
69dae1f0b2 | |||
6557d7668e | |||
77e3d10a64 | |||
814b71052d | |||
6579b3c93f | |||
99c41bb63b | |||
63df0dba64 | |||
4e050cfe7a | |||
32f5c96dd2 | |||
5b923c25b5 | |||
29016fbb23 | |||
0c0139ac8f | |||
180274be34 | |||
595a26a366 | |||
41c41a647f | |||
c3d2b7d3fc | |||
87984a704a | |||
33e4104e98 | |||
597e1e1ca8 |
@ -108,7 +108,7 @@ brews:
|
|||||||
zsh_completion.install "completions/infisical.zsh" => "_infisical"
|
zsh_completion.install "completions/infisical.zsh" => "_infisical"
|
||||||
fish_completion.install "completions/infisical.fish"
|
fish_completion.install "completions/infisical.fish"
|
||||||
man1.install "manpages/infisical.1.gz"
|
man1.install "manpages/infisical.1.gz"
|
||||||
- name: 'infisical@{{.Version}}'
|
- name: "infisical@{{.Version}}"
|
||||||
tap:
|
tap:
|
||||||
owner: Infisical
|
owner: Infisical
|
||||||
name: homebrew-get-cli
|
name: homebrew-get-cli
|
||||||
@ -186,12 +186,14 @@ aurs:
|
|||||||
# man pages
|
# man pages
|
||||||
install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz"
|
install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz"
|
||||||
|
|
||||||
# dockers:
|
dockers:
|
||||||
# - dockerfile: cli/docker/Dockerfile
|
- dockerfile: docker/alpine
|
||||||
# goos: linux
|
goos: linux
|
||||||
# goarch: amd64
|
goarch: amd64
|
||||||
# ids:
|
ids:
|
||||||
# - infisical
|
- all-other-builds
|
||||||
# image_templates:
|
image_templates:
|
||||||
# - "infisical/cli:{{ .Version }}"
|
- "infisical/cli:{{ .Version }}"
|
||||||
# - "infisical/cli:latest"
|
- "infisical/cli:{{ .Major }}.{{ .Minor }}"
|
||||||
|
- "infisical/cli:{{ .Major }}"
|
||||||
|
- "infisical/cli:latest"
|
||||||
|
@ -129,7 +129,7 @@ Note that this security address should be used only for undisclosed vulnerabilit
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Whether it's big or small, we love contributions. Check out our guide to see how to [get started](https://infisical.com/docs/contributing/overview).
|
Whether it's big or small, we love contributions. Check out our guide to see how to [get started](https://infisical.com/docs/contributing/getting-started).
|
||||||
|
|
||||||
Not sure where to get started? You can:
|
Not sure where to get started? You can:
|
||||||
|
|
||||||
|
44
backend/package-lock.json
generated
44
backend/package-lock.json
generated
@ -24,7 +24,7 @@
|
|||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"argon2": "^0.30.3",
|
"argon2": "^0.30.3",
|
||||||
"aws-sdk": "^2.1364.0",
|
"aws-sdk": "^2.1364.0",
|
||||||
"axios": "^1.3.5",
|
"axios": "^1.6.0",
|
||||||
"axios-retry": "^3.4.0",
|
"axios-retry": "^3.4.0",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
"bigint-conversion": "^2.4.0",
|
"bigint-conversion": "^2.4.0",
|
||||||
@ -60,7 +60,7 @@
|
|||||||
"pino": "^8.16.1",
|
"pino": "^8.16.1",
|
||||||
"pino-http": "^8.5.1",
|
"pino-http": "^8.5.1",
|
||||||
"posthog-node": "^2.6.0",
|
"posthog-node": "^2.6.0",
|
||||||
"probot": "^12.3.1",
|
"probot": "^12.3.3",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
"rate-limit-mongo": "^2.3.2",
|
"rate-limit-mongo": "^2.3.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
@ -5991,9 +5991,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/webhooks": {
|
"node_modules/@octokit/webhooks": {
|
||||||
"version": "9.26.0",
|
"version": "9.26.3",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.3.tgz",
|
||||||
"integrity": "sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA==",
|
"integrity": "sha512-DLGk+gzeVq5oK89Bo601txYmyrelMQ7Fi5EnjHE0Xs8CWicy2xkmnJMKptKJrBJpstqbd/9oeDFi/Zj2pudBDQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/request-error": "^2.0.2",
|
"@octokit/request-error": "^2.0.2",
|
||||||
"@octokit/webhooks-methods": "^2.0.0",
|
"@octokit/webhooks-methods": "^2.0.0",
|
||||||
@ -8325,9 +8325,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.6.2",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
|
||||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.0",
|
"follow-redirects": "^1.15.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
@ -16306,9 +16306,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/probot": {
|
"node_modules/probot": {
|
||||||
"version": "12.3.1",
|
"version": "12.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.3.tgz",
|
||||||
"integrity": "sha512-ECSgycmAC0ILEK6cOa+x3QPufP5JybsuohOFCYr3glQU5SkbmypZJE/Sfio9mxAFHK5LCXveIDsfZCxf6ck4JA==",
|
"integrity": "sha512-cdtKd+xISzi8sw6++BYBXleRknCA6hqUMoHj/sJqQBrjbNxQLhfeFCq9O2d0Z4eShsy5YFRR3MWwDKJ9uAE0CA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/core": "^3.2.4",
|
"@octokit/core": "^3.2.4",
|
||||||
"@octokit/plugin-enterprise-compatibility": "^1.2.8",
|
"@octokit/plugin-enterprise-compatibility": "^1.2.8",
|
||||||
@ -16317,7 +16317,7 @@
|
|||||||
"@octokit/plugin-retry": "^3.0.6",
|
"@octokit/plugin-retry": "^3.0.6",
|
||||||
"@octokit/plugin-throttling": "^3.3.4",
|
"@octokit/plugin-throttling": "^3.3.4",
|
||||||
"@octokit/types": "^8.0.0",
|
"@octokit/types": "^8.0.0",
|
||||||
"@octokit/webhooks": "^9.8.4",
|
"@octokit/webhooks": "^9.26.3",
|
||||||
"@probot/get-private-key": "^1.1.0",
|
"@probot/get-private-key": "^1.1.0",
|
||||||
"@probot/octokit-plugin-config": "^1.0.0",
|
"@probot/octokit-plugin-config": "^1.0.0",
|
||||||
"@probot/pino": "^2.2.0",
|
"@probot/pino": "^2.2.0",
|
||||||
@ -23392,9 +23392,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@octokit/webhooks": {
|
"@octokit/webhooks": {
|
||||||
"version": "9.26.0",
|
"version": "9.26.3",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.3.tgz",
|
||||||
"integrity": "sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA==",
|
"integrity": "sha512-DLGk+gzeVq5oK89Bo601txYmyrelMQ7Fi5EnjHE0Xs8CWicy2xkmnJMKptKJrBJpstqbd/9oeDFi/Zj2pudBDQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@octokit/request-error": "^2.0.2",
|
"@octokit/request-error": "^2.0.2",
|
||||||
"@octokit/webhooks-methods": "^2.0.0",
|
"@octokit/webhooks-methods": "^2.0.0",
|
||||||
@ -25250,9 +25250,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "1.6.2",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
|
||||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"follow-redirects": "^1.15.0",
|
"follow-redirects": "^1.15.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
@ -31039,9 +31039,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"probot": {
|
"probot": {
|
||||||
"version": "12.3.1",
|
"version": "12.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.3.tgz",
|
||||||
"integrity": "sha512-ECSgycmAC0ILEK6cOa+x3QPufP5JybsuohOFCYr3glQU5SkbmypZJE/Sfio9mxAFHK5LCXveIDsfZCxf6ck4JA==",
|
"integrity": "sha512-cdtKd+xISzi8sw6++BYBXleRknCA6hqUMoHj/sJqQBrjbNxQLhfeFCq9O2d0Z4eShsy5YFRR3MWwDKJ9uAE0CA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@octokit/core": "^3.2.4",
|
"@octokit/core": "^3.2.4",
|
||||||
"@octokit/plugin-enterprise-compatibility": "^1.2.8",
|
"@octokit/plugin-enterprise-compatibility": "^1.2.8",
|
||||||
@ -31050,7 +31050,7 @@
|
|||||||
"@octokit/plugin-retry": "^3.0.6",
|
"@octokit/plugin-retry": "^3.0.6",
|
||||||
"@octokit/plugin-throttling": "^3.3.4",
|
"@octokit/plugin-throttling": "^3.3.4",
|
||||||
"@octokit/types": "^8.0.0",
|
"@octokit/types": "^8.0.0",
|
||||||
"@octokit/webhooks": "^9.8.4",
|
"@octokit/webhooks": "^9.26.3",
|
||||||
"@probot/get-private-key": "^1.1.0",
|
"@probot/get-private-key": "^1.1.0",
|
||||||
"@probot/octokit-plugin-config": "^1.0.0",
|
"@probot/octokit-plugin-config": "^1.0.0",
|
||||||
"@probot/pino": "^2.2.0",
|
"@probot/pino": "^2.2.0",
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
"argon2": "^0.30.3",
|
"argon2": "^0.30.3",
|
||||||
"aws-sdk": "^2.1364.0",
|
"aws-sdk": "^2.1364.0",
|
||||||
"axios": "^1.3.5",
|
"axios": "^1.6.0",
|
||||||
"axios-retry": "^3.4.0",
|
"axios-retry": "^3.4.0",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
"bigint-conversion": "^2.4.0",
|
"bigint-conversion": "^2.4.0",
|
||||||
@ -51,7 +51,7 @@
|
|||||||
"pino": "^8.16.1",
|
"pino": "^8.16.1",
|
||||||
"pino-http": "^8.5.1",
|
"pino-http": "^8.5.1",
|
||||||
"posthog-node": "^2.6.0",
|
"posthog-node": "^2.6.0",
|
||||||
"probot": "^12.3.1",
|
"probot": "^12.3.3",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^7.1.3",
|
||||||
"rate-limit-mongo": "^2.3.2",
|
"rate-limit-mongo": "^2.3.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
|
1282
backend/spec.json
1282
backend/spec.json
File diff suppressed because it is too large
Load Diff
@ -3,15 +3,22 @@ import jwt from "jsonwebtoken";
|
|||||||
import * as bigintConversion from "bigint-conversion";
|
import * as bigintConversion from "bigint-conversion";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const jsrp = require("jsrp");
|
const jsrp = require("jsrp");
|
||||||
import { LoginSRPDetail, TokenVersion, User } from "../../models";
|
import {
|
||||||
|
LoginSRPDetail,
|
||||||
|
TokenVersion,
|
||||||
|
User
|
||||||
|
} from "../../models";
|
||||||
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
|
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
|
||||||
import { checkUserDevice } from "../../helpers/user";
|
import { checkUserDevice } from "../../helpers/user";
|
||||||
import { AuthTokenType } from "../../variables";
|
import { AuthTokenType } from "../../variables";
|
||||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
UnauthorizedRequestError
|
||||||
|
} from "../../utils/errors";
|
||||||
import {
|
import {
|
||||||
getAuthSecret,
|
getAuthSecret,
|
||||||
getHttpsEnabled,
|
getHttpsEnabled,
|
||||||
getJwtAuthLifetime
|
getJwtAuthLifetime,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { ActorType } from "../../ee/models";
|
import { ActorType } from "../../ee/models";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
@ -25,10 +32,11 @@ declare module "jsonwebtoken" {
|
|||||||
userId: string;
|
userId: string;
|
||||||
refreshVersion?: number;
|
refreshVersion?: number;
|
||||||
}
|
}
|
||||||
export interface ServiceRefreshTokenJwtPayload extends jwt.JwtPayload {
|
export interface IdentityAccessTokenJwtPayload extends jwt.JwtPayload {
|
||||||
serviceTokenDataId: string;
|
_id: string;
|
||||||
|
clientSecretId: string;
|
||||||
|
identityAccessTokenId: string;
|
||||||
authTokenType: string;
|
authTokenType: string;
|
||||||
tokenVersion: number;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,4 +274,4 @@ export const getNewToken = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
export const handleAuthProviderCallback = (req: Request, res: Response) => {
|
export const handleAuthProviderCallback = (req: Request, res: Response) => {
|
||||||
res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`);
|
res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||||
};
|
};
|
@ -1,4 +1,5 @@
|
|||||||
import * as authController from "./authController";
|
import * as authController from "./authController";
|
||||||
|
import * as universalAuthController from "./universalAuthController";
|
||||||
import * as botController from "./botController";
|
import * as botController from "./botController";
|
||||||
import * as integrationAuthController from "./integrationAuthController";
|
import * as integrationAuthController from "./integrationAuthController";
|
||||||
import * as integrationController from "./integrationController";
|
import * as integrationController from "./integrationController";
|
||||||
@ -20,6 +21,7 @@ import * as adminController from "./adminController";
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
authController,
|
authController,
|
||||||
|
universalAuthController,
|
||||||
botController,
|
botController,
|
||||||
integrationAuthController,
|
integrationAuthController,
|
||||||
integrationController,
|
integrationController,
|
||||||
|
@ -2,7 +2,7 @@ import { Request, Response } from "express";
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { standardRequest } from "../../config/request";
|
import { standardRequest } from "../../config/request";
|
||||||
import { getApps, getTeams, revokeAccess } from "../../integrations";
|
import { getApps, getTeams, revokeAccess } from "../../integrations";
|
||||||
import { Bot, IntegrationAuth, Workspace } from "../../models";
|
import { Bot, IIntegrationAuth, Integration, IntegrationAuth, Workspace } from "../../models";
|
||||||
import { EventType } from "../../ee/models";
|
import { EventType } from "../../ee/models";
|
||||||
import { IntegrationService } from "../../services";
|
import { IntegrationService } from "../../services";
|
||||||
import { EEAuditLogService } from "../../ee/services";
|
import { EEAuditLogService } from "../../ee/services";
|
||||||
@ -130,7 +130,6 @@ export const oAuthExchange = async (req: Request, res: Response) => {
|
|||||||
export const saveIntegrationToken = async (req: Request, res: Response) => {
|
export const saveIntegrationToken = async (req: Request, res: Response) => {
|
||||||
// TODO: refactor
|
// TODO: refactor
|
||||||
// TODO: check if access token is valid for each integration
|
// TODO: check if access token is valid for each integration
|
||||||
let integrationAuth;
|
|
||||||
const {
|
const {
|
||||||
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
|
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
|
||||||
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
|
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
|
||||||
@ -152,31 +151,21 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
if (!bot) throw new Error("Bot must be enabled to save integration access token");
|
if (!bot) throw new Error("Bot must be enabled to save integration access token");
|
||||||
|
|
||||||
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
let integrationAuth = await new IntegrationAuth({
|
||||||
{
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
integration,
|
||||||
integration
|
url,
|
||||||
},
|
namespace,
|
||||||
{
|
algorithm: ALGORITHM_AES_256_GCM,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||||
integration,
|
...(integration === INTEGRATION_GCP_SECRET_MANAGER
|
||||||
url,
|
? {
|
||||||
namespace,
|
metadata: {
|
||||||
algorithm: ALGORITHM_AES_256_GCM,
|
authMethod: "serviceAccount"
|
||||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
|
||||||
...(integration === INTEGRATION_GCP_SECRET_MANAGER
|
|
||||||
? {
|
|
||||||
metadata: {
|
|
||||||
authMethod: "serviceAccount"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
: {})
|
}
|
||||||
},
|
: {})
|
||||||
{
|
}).save();
|
||||||
new: true,
|
|
||||||
upsert: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// encrypt and save integration access details
|
// encrypt and save integration access details
|
||||||
if (refreshToken) {
|
if (refreshToken) {
|
||||||
@ -188,12 +177,12 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// encrypt and save integration access details
|
// encrypt and save integration access details
|
||||||
if (accessId || accessToken) {
|
if (accessId || accessToken) {
|
||||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
integrationAuth = (await IntegrationService.setIntegrationAuthAccess({
|
||||||
integrationAuthId: integrationAuth._id.toString(),
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
accessId,
|
accessId,
|
||||||
accessToken,
|
accessToken,
|
||||||
accessExpiresAt: undefined
|
accessExpiresAt: undefined
|
||||||
});
|
})) as IIntegrationAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error("Failed to save integration access token");
|
if (!integrationAuth) throw new Error("Failed to save integration access token");
|
||||||
@ -1208,13 +1197,64 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all integration authorizations and integrations for workspace with id [workspaceId]
|
||||||
|
* with integration name [integration]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteIntegrationAuths = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
query: { integration, workspaceId }
|
||||||
|
} = await validateRequest(reqValidator.DeleteIntegrationAuthsV1, req);
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataProjectPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Delete,
|
||||||
|
ProjectPermissionSub.Integrations
|
||||||
|
);
|
||||||
|
|
||||||
|
const integrationAuths = await IntegrationAuth.deleteMany({
|
||||||
|
integration,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
const integrations = await Integration.deleteMany({
|
||||||
|
integration,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
await EEAuditLogService.createAuditLog(
|
||||||
|
req.authData,
|
||||||
|
{
|
||||||
|
type: EventType.UNAUTHORIZE_INTEGRATION,
|
||||||
|
metadata: {
|
||||||
|
integration
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
integrationAuths,
|
||||||
|
integrations
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete integration authorization with id [integrationAuthId]
|
* Delete integration authorization with id [integrationAuthId]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
export const deleteIntegrationAuthById = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
params: { integrationAuthId }
|
params: { integrationAuthId }
|
||||||
} = await validateRequest(reqValidator.DeleteIntegrationAuthV1, req);
|
} = await validateRequest(reqValidator.DeleteIntegrationAuthV1, req);
|
||||||
|
@ -251,6 +251,21 @@ export const deleteIntegration = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!deletedIntegration) throw new Error("Failed to find integration");
|
if (!deletedIntegration) throw new Error("Failed to find integration");
|
||||||
|
|
||||||
|
const numOtherIntegrationsUsingSameAuth = await Integration.countDocuments({
|
||||||
|
integrationAuth: deletedIntegration.integrationAuth,
|
||||||
|
_id: {
|
||||||
|
$nin: [deletedIntegration._id]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (numOtherIntegrationsUsingSameAuth === 0) {
|
||||||
|
// no other integrations are using the same integration auth
|
||||||
|
// -> delete integration auth associated with the integration being deleted
|
||||||
|
await IntegrationAuth.deleteOne({
|
||||||
|
_id: deletedIntegration.integrationAuth
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await EEAuditLogService.createAuditLog(
|
await EEAuditLogService.createAuditLog(
|
||||||
req.authData,
|
req.authData,
|
||||||
|
@ -4,9 +4,9 @@ import { IUser, Key, Membership, MembershipOrg, User, Workspace } from "../../mo
|
|||||||
import { EventType, Role } from "../../ee/models";
|
import { EventType, Role } from "../../ee/models";
|
||||||
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
|
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
|
||||||
import { sendMail } from "../../helpers/nodemailer";
|
import { sendMail } from "../../helpers/nodemailer";
|
||||||
import { ACCEPTED, ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables";
|
import { ACCEPTED, ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../../variables";
|
||||||
import { getSiteURL } from "../../config";
|
import { getSiteURL } from "../../config";
|
||||||
import { EEAuditLogService } from "../../ee/services";
|
import { EEAuditLogService, EELicenseService } from "../../ee/services";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
import * as reqValidator from "../../validation/membership";
|
import * as reqValidator from "../../validation/membership";
|
||||||
import {
|
import {
|
||||||
@ -129,7 +129,7 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
|
|||||||
ProjectPermissionSub.Member
|
ProjectPermissionSub.Member
|
||||||
);
|
);
|
||||||
|
|
||||||
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
|
const isCustomRole = ![ADMIN, MEMBER, VIEWER, NO_ACCESS].includes(role);
|
||||||
if (isCustomRole) {
|
if (isCustomRole) {
|
||||||
const wsRole = await Role.findOne({
|
const wsRole = await Role.findOne({
|
||||||
slug: role,
|
slug: role,
|
||||||
@ -137,6 +137,13 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
|
|||||||
workspace: membershipToChangeRole.workspace
|
workspace: membershipToChangeRole.workspace
|
||||||
});
|
});
|
||||||
if (!wsRole) throw BadRequestError({ message: "Role not found" });
|
if (!wsRole) throw BadRequestError({ message: "Role not found" });
|
||||||
|
|
||||||
|
const plan = await EELicenseService.getPlan(wsRole.organization);
|
||||||
|
|
||||||
|
if (!plan.rbac) return res.status(400).send({
|
||||||
|
message: "Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
|
||||||
|
});
|
||||||
|
|
||||||
const membership = await Membership.findByIdAndUpdate(membershipId, {
|
const membership = await Membership.findByIdAndUpdate(membershipId, {
|
||||||
role: CUSTOM,
|
role: CUSTOM,
|
||||||
customRole: wsRole
|
customRole: wsRole
|
||||||
|
@ -21,7 +21,7 @@ import { validateRequest } from "../../helpers/validation";
|
|||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
getUserOrgPermissions
|
getAuthDataOrgPermissions
|
||||||
} from "../../ee/services/RoleService";
|
} from "../../ee/services/RoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
@ -44,11 +44,12 @@ export const deleteMembershipOrg = async (req: Request, _res: Response) => {
|
|||||||
if (!membershipOrgToDelete) {
|
if (!membershipOrgToDelete) {
|
||||||
throw new Error("Failed to delete organization membership that doesn't exist");
|
throw new Error("Failed to delete organization membership that doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: membershipOrgToDelete.organization
|
||||||
|
});
|
||||||
|
|
||||||
const { permission, membership: membershipOrg } = await getUserOrgPermissions(
|
|
||||||
req.user._id,
|
|
||||||
membershipOrgToDelete.organization.toString()
|
|
||||||
);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Delete,
|
OrgPermissionActions.Delete,
|
||||||
OrgPermissionSubjects.Member
|
OrgPermissionSubjects.Member
|
||||||
@ -60,7 +61,7 @@ export const deleteMembershipOrg = async (req: Request, _res: Response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await updateSubscriptionOrgQuantity({
|
await updateSubscriptionOrgQuantity({
|
||||||
organizationId: membershipOrg.organization.toString()
|
organizationId: membershipOrgToDelete.organization.toString()
|
||||||
});
|
});
|
||||||
|
|
||||||
return membershipOrgToDelete;
|
return membershipOrgToDelete;
|
||||||
@ -96,7 +97,11 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
|||||||
body: { inviteeEmail, organizationId }
|
body: { inviteeEmail, organizationId }
|
||||||
} = await validateRequest(reqValidator.InviteUserToOrgv1, req);
|
} = await validateRequest(reqValidator.InviteUserToOrgv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Create,
|
OrgPermissionActions.Create,
|
||||||
OrgPermissionSubjects.Member
|
OrgPermissionSubjects.Member
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
IncidentContactOrg,
|
IncidentContactOrg,
|
||||||
Membership,
|
Membership,
|
||||||
@ -14,7 +15,7 @@ import { ACCEPTED } from "../../variables";
|
|||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
getUserOrgPermissions
|
getAuthDataOrgPermissions
|
||||||
} from "../../ee/services/RoleService";
|
} from "../../ee/services/RoleService";
|
||||||
import { OrganizationNotFoundError } from "../../utils/errors";
|
import { OrganizationNotFoundError } from "../../utils/errors";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
@ -44,7 +45,10 @@ export const getOrganization = async (req: Request, res: Response) => {
|
|||||||
} = await validateRequest(reqValidator.GetOrgv1, req);
|
} = await validateRequest(reqValidator.GetOrgv1, req);
|
||||||
|
|
||||||
// ensure user has membership
|
// ensure user has membership
|
||||||
await getUserOrgPermissions(req.user._id, organizationId);
|
await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
})
|
||||||
|
|
||||||
const organization = await Organization.findById(organizationId);
|
const organization = await Organization.findById(organizationId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -68,8 +72,12 @@ export const getOrganizationMembers = async (req: Request, res: Response) => {
|
|||||||
const {
|
const {
|
||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgMembersv1, req);
|
} = await validateRequest(reqValidator.GetOrgMembersv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Member
|
OrgPermissionSubjects.Member
|
||||||
@ -95,7 +103,10 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgWorkspacesv1, req);
|
} = await validateRequest(reqValidator.GetOrgWorkspacesv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
})
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Workspace
|
OrgPermissionSubjects.Workspace
|
||||||
@ -137,7 +148,10 @@ export const changeOrganizationName = async (req: Request, res: Response) => {
|
|||||||
body: { name }
|
body: { name }
|
||||||
} = await validateRequest(reqValidator.ChangeOrgNamev1, req);
|
} = await validateRequest(reqValidator.ChangeOrgNamev1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Edit,
|
OrgPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.Settings
|
OrgPermissionSubjects.Settings
|
||||||
@ -172,7 +186,10 @@ export const getOrganizationIncidentContacts = async (req: Request, res: Respons
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgIncidentContactv1, req);
|
} = await validateRequest(reqValidator.GetOrgIncidentContactv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.IncidentAccount
|
OrgPermissionSubjects.IncidentAccount
|
||||||
@ -199,7 +216,10 @@ export const addOrganizationIncidentContact = async (req: Request, res: Response
|
|||||||
body: { email }
|
body: { email }
|
||||||
} = await validateRequest(reqValidator.CreateOrgIncideContact, req);
|
} = await validateRequest(reqValidator.CreateOrgIncideContact, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Create,
|
OrgPermissionActions.Create,
|
||||||
OrgPermissionSubjects.IncidentAccount
|
OrgPermissionSubjects.IncidentAccount
|
||||||
@ -228,7 +248,10 @@ export const deleteOrganizationIncidentContact = async (req: Request, res: Respo
|
|||||||
body: { email }
|
body: { email }
|
||||||
} = await validateRequest(reqValidator.DelOrgIncideContact, req);
|
} = await validateRequest(reqValidator.DelOrgIncideContact, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Delete,
|
OrgPermissionActions.Delete,
|
||||||
OrgPermissionSubjects.IncidentAccount
|
OrgPermissionSubjects.IncidentAccount
|
||||||
@ -257,7 +280,10 @@ export const createOrganizationPortalSession = async (req: Request, res: Respons
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req);
|
} = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Edit,
|
OrgPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -321,7 +347,10 @@ export const getOrganizationMembersAndTheirWorkspaces = async (req: Request, res
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgMembersv1, req);
|
} = await validateRequest(reqValidator.GetOrgMembersv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Member
|
OrgPermissionSubjects.Member
|
||||||
|
@ -111,11 +111,17 @@ export const createSecretImp = async (req: Request, res: Response) => {
|
|||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: secretImport.environment, secretPath: secretImport.secretPath })
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await Folder.findOne({
|
const folders = await Folder.findOne({
|
||||||
@ -323,7 +329,7 @@ export const updateSecretImport = async (req: Request, res: Response) => {
|
|||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
workspaceId: importSecDoc.workspace
|
workspaceId: importSecDoc.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
@ -331,6 +337,13 @@ export const updateSecretImport = async (req: Request, res: Response) => {
|
|||||||
secretPath
|
secretPath
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
secretImports.forEach(({ environment, secretPath }) => {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
|
);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderBefore = importSecDoc.imports;
|
const orderBefore = importSecDoc.imports;
|
||||||
@ -453,7 +466,7 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
|
|||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
workspaceId: importSecDoc.workspace
|
workspaceId: importSecDoc.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
@ -620,7 +633,7 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
|||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
@ -677,7 +690,7 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
|||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
workspaceId: importSecDoc.workspace
|
workspaceId: importSecDoc.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
@ -21,7 +21,7 @@ import * as reqValidator from "../../validation/secretScanning";
|
|||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
getUserOrgPermissions
|
getAuthDataOrgPermissions
|
||||||
} from "../../ee/services/RoleService";
|
} from "../../ee/services/RoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
@ -37,8 +37,11 @@ export const createInstallationSession = async (req: Request, res: Response) =>
|
|||||||
message: "Failed to find organization"
|
message: "Failed to find organization"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Create,
|
OrgPermissionActions.Create,
|
||||||
OrgPermissionSubjects.SecretScanning
|
OrgPermissionSubjects.SecretScanning
|
||||||
@ -70,11 +73,12 @@ export const linkInstallationToOrganization = async (req: Request, res: Response
|
|||||||
if (!installationSession) {
|
if (!installationSession) {
|
||||||
throw UnauthorizedRequestError();
|
throw UnauthorizedRequestError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: installationSession.organization
|
||||||
|
});
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(
|
|
||||||
req.user._id,
|
|
||||||
installationSession.organization.toString()
|
|
||||||
);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Edit,
|
OrgPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.SecretScanning
|
OrgPermissionSubjects.SecretScanning
|
||||||
@ -142,7 +146,10 @@ export const getRisksForOrganization = async (req: Request, res: Response) => {
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgRisksv1, req);
|
} = await validateRequest(reqValidator.GetOrgRisksv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.SecretScanning
|
OrgPermissionSubjects.SecretScanning
|
||||||
@ -162,7 +169,10 @@ export const updateRisksStatus = async (req: Request, res: Response) => {
|
|||||||
body: { status }
|
body: { status }
|
||||||
} = await validateRequest(reqValidator.UpdateRiskStatusv1, req);
|
} = await validateRequest(reqValidator.UpdateRiskStatusv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Edit,
|
OrgPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.SecretScanning
|
OrgPermissionSubjects.SecretScanning
|
||||||
|
1269
backend/src/controllers/v1/universalAuthController.ts
Normal file
1269
backend/src/controllers/v1/universalAuthController.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@ import { OrganizationNotFoundError } from "../../utils/errors";
|
|||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
getUserOrgPermissions
|
getAuthDataOrgPermissions
|
||||||
} from "../../ee/services/RoleService";
|
} from "../../ee/services/RoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
@ -152,7 +152,10 @@ export const createWorkspace = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Create,
|
OrgPermissionActions.Create,
|
||||||
OrgPermissionSubjects.Workspace
|
OrgPermissionSubjects.Workspace
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Membership, MembershipOrg, Workspace } from "../../models";
|
import {
|
||||||
|
IWorkspace,
|
||||||
|
Identity,
|
||||||
|
IdentityMembership,
|
||||||
|
IdentityMembershipOrg,
|
||||||
|
Membership,
|
||||||
|
MembershipOrg,
|
||||||
|
User,
|
||||||
|
Workspace
|
||||||
|
} from "../../models";
|
||||||
import { Role } from "../../ee/models";
|
import { Role } from "../../ee/models";
|
||||||
import { deleteMembershipOrg } from "../../helpers/membershipOrg";
|
import { deleteMembershipOrg } from "../../helpers/membershipOrg";
|
||||||
import {
|
import {
|
||||||
@ -9,15 +18,16 @@ import {
|
|||||||
updateSubscriptionOrgQuantity
|
updateSubscriptionOrgQuantity
|
||||||
} from "../../helpers/organization";
|
} from "../../helpers/organization";
|
||||||
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
||||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../utils/errors";
|
||||||
import { ACCEPTED, ADMIN, CUSTOM } from "../../variables";
|
import { ACCEPTED, ADMIN, CUSTOM, MEMBER, NO_ACCESS } from "../../variables";
|
||||||
import * as reqValidator from "../../validation/organization";
|
import * as reqValidator from "../../validation/organization";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
getUserOrgPermissions
|
getAuthDataOrgPermissions
|
||||||
} from "../../ee/services/RoleService";
|
} from "../../ee/services/RoleService";
|
||||||
|
import { EELicenseService } from "../../ee/services";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,11 +37,12 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
*/
|
*/
|
||||||
export const getOrganizationMemberships = async (req: Request, res: Response) => {
|
export const getOrganizationMemberships = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Return organization memberships'
|
#swagger.summary = 'Return organization user memberships'
|
||||||
#swagger.description = 'Return organization memberships'
|
#swagger.description = 'Return organization user memberships'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": [],
|
||||||
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['organizationId'] = {
|
#swagger.parameters['organizationId'] = {
|
||||||
@ -63,7 +74,10 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgMembersv2, req);
|
} = await validateRequest(reqValidator.GetOrgMembersv2, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Member
|
OrgPermissionSubjects.Member
|
||||||
@ -85,11 +99,12 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
|
|||||||
*/
|
*/
|
||||||
export const updateOrganizationMembership = async (req: Request, res: Response) => {
|
export const updateOrganizationMembership = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Update organization membership'
|
#swagger.summary = 'Update organization user membership'
|
||||||
#swagger.description = 'Update organization membership'
|
#swagger.description = 'Update organization user membership'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": [],
|
||||||
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['organizationId'] = {
|
#swagger.parameters['organizationId'] = {
|
||||||
@ -141,16 +156,32 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
|
|||||||
params: { organizationId, membershipId },
|
params: { organizationId, membershipId },
|
||||||
body: { role }
|
body: { role }
|
||||||
} = await validateRequest(reqValidator.UpdateOrgMemberv2, req);
|
} = await validateRequest(reqValidator.UpdateOrgMemberv2, req);
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
|
||||||
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Edit,
|
OrgPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.Member
|
OrgPermissionSubjects.Member
|
||||||
);
|
);
|
||||||
|
|
||||||
const isCustomRole = !["admin", "member"].includes(role);
|
const isCustomRole = ![ADMIN, MEMBER, NO_ACCESS].includes(role);
|
||||||
if (isCustomRole) {
|
if (isCustomRole) {
|
||||||
const orgRole = await Role.findOne({ slug: role, isOrgRole: true });
|
const orgRole = await Role.findOne({
|
||||||
|
slug: role,
|
||||||
|
isOrgRole: true,
|
||||||
|
organization: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
if (!orgRole) throw BadRequestError({ message: "Role not found" });
|
if (!orgRole) throw BadRequestError({ message: "Role not found" });
|
||||||
|
|
||||||
|
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
|
||||||
|
|
||||||
|
if (!plan.rbac) return res.status(400).send({
|
||||||
|
message:
|
||||||
|
"Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
|
||||||
|
});
|
||||||
|
|
||||||
const membership = await MembershipOrg.findByIdAndUpdate(membershipId, {
|
const membership = await MembershipOrg.findByIdAndUpdate(membershipId, {
|
||||||
role: CUSTOM,
|
role: CUSTOM,
|
||||||
@ -189,11 +220,12 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
|
|||||||
*/
|
*/
|
||||||
export const deleteOrganizationMembership = async (req: Request, res: Response) => {
|
export const deleteOrganizationMembership = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Delete organization membership'
|
#swagger.summary = 'Delete organization user membership'
|
||||||
#swagger.description = 'Delete organization membership'
|
#swagger.description = 'Delete organization user membership'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": [],
|
||||||
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['organizationId'] = {
|
#swagger.parameters['organizationId'] = {
|
||||||
@ -227,7 +259,18 @@ export const deleteOrganizationMembership = async (req: Request, res: Response)
|
|||||||
const {
|
const {
|
||||||
params: { organizationId, membershipId }
|
params: { organizationId, membershipId }
|
||||||
} = await validateRequest(reqValidator.DeleteOrgMemberv2, req);
|
} = await validateRequest(reqValidator.DeleteOrgMemberv2, req);
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
|
||||||
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
|
_id: new Types.ObjectId(membershipId),
|
||||||
|
organization: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!membershipOrg) throw ResourceNotFoundError();
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: membershipOrg.organization
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Delete,
|
OrgPermissionActions.Delete,
|
||||||
OrgPermissionSubjects.Member
|
OrgPermissionSubjects.Member
|
||||||
@ -259,7 +302,8 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
|||||||
#swagger.description = 'Return projects in organization that user is part of'
|
#swagger.description = 'Return projects in organization that user is part of'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": [],
|
||||||
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['organizationId'] = {
|
#swagger.parameters['organizationId'] = {
|
||||||
@ -287,11 +331,16 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const {
|
const {
|
||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgWorkspacesv2, req);
|
} = await validateRequest(reqValidator.GetOrgWorkspacesv2, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Workspace
|
OrgPermissionSubjects.Workspace
|
||||||
@ -308,13 +357,27 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
|||||||
).map((w) => w._id.toString())
|
).map((w) => w._id.toString())
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaces = (
|
let workspaces: IWorkspace[] = [];
|
||||||
await Membership.find({
|
|
||||||
user: req.user._id
|
if (req.authData.authPayload instanceof Identity) {
|
||||||
}).populate("workspace")
|
workspaces = (
|
||||||
)
|
await IdentityMembership.find({
|
||||||
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
identity: req.authData.authPayload._id
|
||||||
.map((m) => m.workspace);
|
}).populate<{ workspace: IWorkspace }>("workspace")
|
||||||
|
)
|
||||||
|
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
||||||
|
.map((m) => m.workspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.authData.authPayload instanceof User) {
|
||||||
|
workspaces = (
|
||||||
|
await Membership.find({
|
||||||
|
user: req.authData.authPayload._id
|
||||||
|
}).populate<{ workspace: IWorkspace }>("workspace")
|
||||||
|
)
|
||||||
|
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
||||||
|
.map((m) => m.workspace);
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
workspaces
|
workspaces
|
||||||
@ -377,3 +440,66 @@ export const deleteOrganizationById = async (req: Request, res: Response) => {
|
|||||||
organization
|
organization
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of identity memberships for organization with id [organizationId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getOrganizationIdentityMemberships = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Return organization identity memberships'
|
||||||
|
#swagger.description = 'Return organization identity memberships'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"bearerAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['organizationId'] = {
|
||||||
|
"description": "ID of organization",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"identityMemberships": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/IdentityMembershipOrg"
|
||||||
|
},
|
||||||
|
"description": "Identity memberships of organization"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
params: { organizationId }
|
||||||
|
} = await validateRequest(reqValidator.GetOrgIdentityMembershipsV2, req);
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Read,
|
||||||
|
OrgPermissionSubjects.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
const identityMemberships = await IdentityMembershipOrg.find({
|
||||||
|
organization: new Types.ObjectId(organizationId)
|
||||||
|
}).populate("identity customRole");
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
identityMemberships
|
||||||
|
});
|
||||||
|
}
|
@ -13,7 +13,7 @@ import {
|
|||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
getAuthDataProjectPermissions
|
getAuthDataProjectPermissions
|
||||||
} from "../../ee/services/ProjectRoleService";
|
} from "../../ee/services/ProjectRoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,6 +86,14 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
|||||||
ProjectPermissionSub.ServiceTokens
|
ProjectPermissionSub.ServiceTokens
|
||||||
);
|
);
|
||||||
|
|
||||||
|
scopes.forEach(({ environment, secretPath }) => {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: secretPath })
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
const secret = crypto.randomBytes(16).toString("hex");
|
const secret = crypto.randomBytes(16).toString("hex");
|
||||||
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||||
|
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Key, Membership, ServiceTokenData, Workspace } from "../../models";
|
import {
|
||||||
|
IIdentity,
|
||||||
|
IdentityMembership,
|
||||||
|
IdentityMembershipOrg,
|
||||||
|
Key,
|
||||||
|
Membership,
|
||||||
|
ServiceTokenData,
|
||||||
|
Workspace
|
||||||
|
} from "../../models";
|
||||||
|
import { IRole, Role } from "../../ee/models";
|
||||||
import {
|
import {
|
||||||
pullSecrets as pull,
|
pullSecrets as pull,
|
||||||
v2PushSecrets as push,
|
v2PushSecrets as push,
|
||||||
@ -16,9 +25,13 @@ import * as reqValidator from "../../validation";
|
|||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub,
|
ProjectPermissionSub,
|
||||||
getAuthDataProjectPermissions
|
getAuthDataProjectPermissions,
|
||||||
|
getWorkspaceRolePermissions,
|
||||||
|
isAtLeastAsPrivilegedWorkspace
|
||||||
} from "../../ee/services/ProjectRoleService";
|
} from "../../ee/services/ProjectRoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { BadRequestError, ForbiddenRequestError, ResourceNotFoundError } from "../../utils/errors";
|
||||||
|
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../../variables";
|
||||||
|
|
||||||
interface V2PushSecret {
|
interface V2PushSecret {
|
||||||
type: string; // personal or shared
|
type: string; // personal or shared
|
||||||
@ -169,11 +182,11 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
|||||||
"apiKeyAuth": []
|
"apiKeyAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['workspaceId'] = {
|
#swagger.parameters['workspaceId'] = {
|
||||||
"description": "ID of project",
|
"description": "ID of project",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.responses[200] = {
|
#swagger.responses[200] = {
|
||||||
content: {
|
content: {
|
||||||
@ -198,7 +211,7 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
|||||||
receiver: req.user._id
|
receiver: req.user._id
|
||||||
}).populate("sender", "+publicKey");
|
}).populate("sender", "+publicKey");
|
||||||
|
|
||||||
if (!key) throw new Error("Failed to find workspace key");
|
if (!key) throw new Error(`getWorkspaceKey: Failed to find workspace key [workspaceId=${workspaceId}] [receiver=${req.user._id}]`);
|
||||||
|
|
||||||
await EEAuditLogService.createAuditLog(
|
await EEAuditLogService.createAuditLog(
|
||||||
req.authData,
|
req.authData,
|
||||||
@ -236,33 +249,34 @@ export const getWorkspaceServiceTokenData = async (req: Request, res: Response)
|
|||||||
*/
|
*/
|
||||||
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Return project memberships'
|
#swagger.summary = 'Return project user memberships'
|
||||||
#swagger.description = 'Return project memberships'
|
#swagger.description = 'Return project user memberships'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": [],
|
||||||
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['workspaceId'] = {
|
#swagger.parameters['workspaceId'] = {
|
||||||
"description": "ID of project",
|
"description": "ID of project",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.responses[200] = {
|
#swagger.responses[200] = {
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"memberships": {
|
"memberships": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
$ref: "#/components/schemas/Membership"
|
$ref: "#/components/schemas/Membership"
|
||||||
},
|
},
|
||||||
"description": "Memberships of project"
|
"description": "Memberships of project"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,26 +313,27 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const updateWorkspaceMembership = async (req: Request, res: Response) => {
|
export const updateWorkspaceMembership = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Update project membership'
|
#swagger.summary = 'Update project user membership'
|
||||||
#swagger.description = 'Update project membership'
|
#swagger.description = 'Update project user membership'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": [],
|
||||||
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['workspaceId'] = {
|
#swagger.parameters['workspaceId'] = {
|
||||||
"description": "ID of project",
|
"description": "ID of project",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.parameters['membershipId'] = {
|
#swagger.parameters['membershipId'] = {
|
||||||
"description": "ID of project membership to update",
|
"description": "ID of project membership to update",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.requestBody = {
|
#swagger.requestBody = {
|
||||||
"required": true,
|
"required": true,
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
@ -327,7 +342,7 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
"properties": {
|
"properties": {
|
||||||
"role": {
|
"role": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Role of membership - either admin or member",
|
"description": "Role to update to for project membership",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,13 +354,13 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"membership": {
|
"membership": {
|
||||||
$ref: "#/components/schemas/Membership",
|
$ref: "#/components/schemas/Membership",
|
||||||
"description": "Updated membership"
|
"description": "Updated membership"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -389,36 +404,37 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
|||||||
*/
|
*/
|
||||||
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
|
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Delete project membership'
|
#swagger.summary = 'Delete project user membership'
|
||||||
#swagger.description = 'Delete project membership'
|
#swagger.description = 'Delete project user membership'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": [],
|
||||||
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['workspaceId'] = {
|
#swagger.parameters['workspaceId'] = {
|
||||||
"description": "ID of project",
|
"description": "ID of project",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.parameters['membershipId'] = {
|
#swagger.parameters['membershipId'] = {
|
||||||
"description": "ID of project membership to delete",
|
"description": "ID of project membership to delete",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
#swagger.responses[200] = {
|
#swagger.responses[200] = {
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"membership": {
|
"membership": {
|
||||||
$ref: "#/components/schemas/Membership",
|
$ref: "#/components/schemas/Membership",
|
||||||
"description": "Deleted membership"
|
"description": "Deleted membership"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -491,3 +507,377 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
|||||||
workspace
|
workspace
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add identity with id [identityId] to workspace
|
||||||
|
* with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const addIdentityToWorkspace = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
params: { workspaceId, identityId },
|
||||||
|
body: {
|
||||||
|
role
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.AddIdentityToWorkspaceV2, req);
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataProjectPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
let identityMembership = await IdentityMembership.findOne({
|
||||||
|
identity: new Types.ObjectId(identityId),
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (identityMembership) throw BadRequestError({
|
||||||
|
message: `Identity with id ${identityId} already exists in project with id ${workspaceId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const workspace = await Workspace.findById(workspaceId);
|
||||||
|
if (!workspace) throw ResourceNotFoundError();
|
||||||
|
|
||||||
|
const identityMembershipOrg = await IdentityMembershipOrg.findOne({
|
||||||
|
identity: new Types.ObjectId(identityId),
|
||||||
|
organization: workspace.organization
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!identityMembershipOrg) throw ResourceNotFoundError({
|
||||||
|
message: `Failed to find identity with id ${identityId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!identityMembershipOrg.organization.equals(workspace.organization)) throw BadRequestError({
|
||||||
|
message: "Failed to add identity to project in another organization"
|
||||||
|
});
|
||||||
|
|
||||||
|
const rolePermission = await getWorkspaceRolePermissions(role, workspaceId);
|
||||||
|
const isAsPrivilegedAsIntendedRole = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
||||||
|
|
||||||
|
if (!isAsPrivilegedAsIntendedRole) throw ForbiddenRequestError({
|
||||||
|
message: "Failed to add identity to project with more privileged role"
|
||||||
|
});
|
||||||
|
|
||||||
|
let customRole;
|
||||||
|
if (role) {
|
||||||
|
const isCustomRole = ![ADMIN, MEMBER, VIEWER, NO_ACCESS].includes(role);
|
||||||
|
if (isCustomRole) {
|
||||||
|
customRole = await Role.findOne({
|
||||||
|
slug: role,
|
||||||
|
isOrgRole: false,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
identityMembership = await new IdentityMembership({
|
||||||
|
identity: identityMembershipOrg.identity,
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
role: customRole ? CUSTOM : role,
|
||||||
|
customRole
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
identityMembership
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update role of identity with id [identityId] in workspace
|
||||||
|
* with id [workspaceId] to [role]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const updateIdentityWorkspaceRole = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Update project identity membership'
|
||||||
|
#swagger.description = 'Update project identity membership'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"bearerAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['identityId'] = {
|
||||||
|
"description": "ID of identity whose membership to update in project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.requestBody = {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"role": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Role to update to for identity project membership",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"identityMembership": {
|
||||||
|
$ref: "#/components/schemas/IdentityMembership",
|
||||||
|
"description": "Updated identity membership"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
params: { workspaceId, identityId },
|
||||||
|
body: {
|
||||||
|
role
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.UpdateIdentityWorkspaceRoleV2, req);
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataProjectPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
let identityMembership = await IdentityMembership
|
||||||
|
.findOne({
|
||||||
|
identity: new Types.ObjectId(identityId),
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
})
|
||||||
|
.populate<{
|
||||||
|
identity: IIdentity,
|
||||||
|
customRole: IRole
|
||||||
|
}>("identity customRole");
|
||||||
|
|
||||||
|
if (!identityMembership) throw BadRequestError({
|
||||||
|
message: `Identity with id ${identityId} does not exist in project with id ${workspaceId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const identityRolePermission = await getWorkspaceRolePermissions(
|
||||||
|
identityMembership?.customRole?.slug ?? identityMembership.role,
|
||||||
|
identityMembership.workspace.toString()
|
||||||
|
);
|
||||||
|
const isAsPrivilegedAsIdentity = isAtLeastAsPrivilegedWorkspace(permission, identityRolePermission);
|
||||||
|
if (!isAsPrivilegedAsIdentity) throw ForbiddenRequestError({
|
||||||
|
message: "Failed to update role of more privileged identity"
|
||||||
|
});
|
||||||
|
|
||||||
|
const rolePermission = await getWorkspaceRolePermissions(role, workspaceId);
|
||||||
|
const isAsPrivilegedAsIntendedRole = isAtLeastAsPrivilegedWorkspace(permission, rolePermission);
|
||||||
|
|
||||||
|
if (!isAsPrivilegedAsIntendedRole) throw ForbiddenRequestError({
|
||||||
|
message: "Failed to update identity to a more privileged role"
|
||||||
|
});
|
||||||
|
|
||||||
|
let customRole;
|
||||||
|
if (role) {
|
||||||
|
const isCustomRole = ![ADMIN, MEMBER, VIEWER, NO_ACCESS].includes(role);
|
||||||
|
if (isCustomRole) {
|
||||||
|
customRole = await Role.findOne({
|
||||||
|
slug: role,
|
||||||
|
isOrgRole: false,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
identityMembership = await IdentityMembership.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
identity: identityMembership.identity._id,
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: customRole ? CUSTOM : role,
|
||||||
|
customRole
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
identityMembership
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete identity with id [identityId] from workspace
|
||||||
|
* with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
*/
|
||||||
|
export const deleteIdentityFromWorkspace = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Delete project identity membership'
|
||||||
|
#swagger.description = 'Delete project identity membership'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"bearerAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['identityId'] = {
|
||||||
|
"description": "ID of identity whose membership to delete in project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"identityMembership": {
|
||||||
|
$ref: "#/components/schemas/IdentityMembership",
|
||||||
|
"description": "Deleted identity membership"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
params: { workspaceId, identityId }
|
||||||
|
} = await validateRequest(reqValidator.DeleteIdentityFromWorkspaceV2, req);
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataProjectPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Delete,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
const identityMembership = await IdentityMembership
|
||||||
|
.findOne({
|
||||||
|
identity: new Types.ObjectId(identityId),
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
})
|
||||||
|
.populate<{
|
||||||
|
identity: IIdentity,
|
||||||
|
customRole: IRole
|
||||||
|
}>("identity customRole");
|
||||||
|
|
||||||
|
if (!identityMembership) throw ResourceNotFoundError({
|
||||||
|
message: `Identity with id ${identityId} does not exist in project with id ${workspaceId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const identityRolePermission = await getWorkspaceRolePermissions(
|
||||||
|
identityMembership?.customRole?.slug ?? identityMembership.role,
|
||||||
|
identityMembership.workspace.toString()
|
||||||
|
);
|
||||||
|
const isAsPrivilegedAsIdentity = isAtLeastAsPrivilegedWorkspace(permission, identityRolePermission);
|
||||||
|
if (!isAsPrivilegedAsIdentity) throw ForbiddenRequestError({
|
||||||
|
message: "Failed to remove more privileged identity from project"
|
||||||
|
});
|
||||||
|
|
||||||
|
await IdentityMembership.findByIdAndDelete(identityMembership._id);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
identityMembership
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of identity memberships for workspace with id [workspaceId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWorkspaceIdentityMemberships = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Return project identity memberships'
|
||||||
|
#swagger.description = 'Return project identity memberships'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"bearerAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['workspaceId'] = {
|
||||||
|
"description": "ID of project",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"identityMemberships": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
$ref: "#/components/schemas/IdentityMembership"
|
||||||
|
},
|
||||||
|
"description": "Identity memberships of project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
params: { workspaceId }
|
||||||
|
} = await validateRequest(reqValidator.GetWorkspaceIdentityMembersV2, req);
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataProjectPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
const identityMemberships = await IdentityMembership.find({
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
}).populate("identity customRole");
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
identityMemberships
|
||||||
|
});
|
||||||
|
}
|
@ -94,7 +94,7 @@ const checkSecretsPermission = async ({
|
|||||||
});
|
});
|
||||||
return { authVerifier: () => true };
|
return { authVerifier: () => true };
|
||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.IDENTITY: {
|
||||||
const { permission } = await getAuthDataProjectPermissions({
|
const { permission } = await getAuthDataProjectPermissions({
|
||||||
authData,
|
authData,
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
@ -348,7 +348,7 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const {
|
const {
|
||||||
query: { secretPath, environment, workspaceId, type, include_imports },
|
query: { secretPath, environment, workspaceId, type, include_imports, version },
|
||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
|
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
|
||||||
|
|
||||||
@ -371,7 +371,8 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
|||||||
type,
|
type,
|
||||||
secretPath,
|
secretPath,
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
include_imports
|
include_imports,
|
||||||
|
version
|
||||||
});
|
});
|
||||||
|
|
||||||
const key = await BotService.getWorkspaceKeyWithBot({
|
const key = await BotService.getWorkspaceKeyWithBot({
|
||||||
@ -865,7 +866,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const getSecretByName = async (req: Request, res: Response) => {
|
export const getSecretByName = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
query: { secretPath, environment, workspaceId, type, include_imports },
|
query: { secretPath, environment, workspaceId, type, include_imports, version },
|
||||||
params: { secretName }
|
params: { secretName }
|
||||||
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
|
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
|
||||||
|
|
||||||
@ -888,7 +889,8 @@ export const getSecretByName = async (req: Request, res: Response) => {
|
|||||||
type,
|
type,
|
||||||
secretPath,
|
secretPath,
|
||||||
authData: req.authData,
|
authData: req.authData,
|
||||||
include_imports
|
include_imports,
|
||||||
|
version
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { validateRequest } from "../../helpers/validation";
|
import { validateRequest } from "../../helpers/validation";
|
||||||
import { Membership, Secret, ServiceTokenDataV3, User } from "../../models";
|
import { Membership, Secret, User } from "../../models";
|
||||||
import { SecretService } from "../../services";
|
import { SecretService } from "../../services";
|
||||||
import { getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService";
|
import { getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService";
|
||||||
import { UnauthorizedRequestError } from "../../utils/errors";
|
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||||
@ -140,17 +140,3 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
|
|||||||
message: "Successfully named workspace secrets"
|
message: "Successfully named workspace secrets"
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWorkspaceServiceTokenData = async (req: Request, res: Response) => {
|
|
||||||
const {
|
|
||||||
params: { workspaceId }
|
|
||||||
} = await validateRequest(reqValidator.GetWorkspaceServiceTokenDataV3, req);
|
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenDataV3.find({
|
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
|
||||||
}).populate("customRole");
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
serviceTokenData
|
|
||||||
});
|
|
||||||
}
|
|
460
backend/src/ee/controllers/v1/identitiesController.ts
Normal file
460
backend/src/ee/controllers/v1/identitiesController.ts
Normal file
@ -0,0 +1,460 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import {
|
||||||
|
IIdentity,
|
||||||
|
Identity,
|
||||||
|
IdentityAccessToken,
|
||||||
|
IdentityMembership,
|
||||||
|
IdentityMembershipOrg,
|
||||||
|
IdentityUniversalAuth,
|
||||||
|
IdentityUniversalAuthClientSecret,
|
||||||
|
Organization
|
||||||
|
} from "../../../models";
|
||||||
|
import {
|
||||||
|
EventType,
|
||||||
|
IRole,
|
||||||
|
Role
|
||||||
|
} from "../../models";
|
||||||
|
import { validateRequest } from "../../../helpers/validation";
|
||||||
|
import * as reqValidator from "../../../validation/identities";
|
||||||
|
import {
|
||||||
|
getAuthDataOrgPermissions,
|
||||||
|
getOrgRolePermissions,
|
||||||
|
isAtLeastAsPrivilegedOrg
|
||||||
|
} from "../../services/RoleService";
|
||||||
|
import {
|
||||||
|
BadRequestError,
|
||||||
|
ForbiddenRequestError,
|
||||||
|
ResourceNotFoundError,
|
||||||
|
} from "../../../utils/errors";
|
||||||
|
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS } from "../../../variables";
|
||||||
|
import {
|
||||||
|
OrgPermissionActions,
|
||||||
|
OrgPermissionSubjects
|
||||||
|
} from "../../services/RoleService";
|
||||||
|
import { EEAuditLogService } from "../../services";
|
||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create identity
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const createIdentity = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Create identity'
|
||||||
|
#swagger.description = 'Create identity'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"bearerAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.requestBody = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of entity to create",
|
||||||
|
"example": "development"
|
||||||
|
},
|
||||||
|
"organizationId": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "ID of organization where to create identity",
|
||||||
|
"example": "dev-environment"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Role to assume for organization membership",
|
||||||
|
"example": "no-access"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name", "organizationId", "role"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"identity": {
|
||||||
|
$ref: '#/definitions/Identity'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Details of the created identity"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
body: {
|
||||||
|
name,
|
||||||
|
organizationId,
|
||||||
|
role
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.CreateIdentityV1, req);
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Create,
|
||||||
|
OrgPermissionSubjects.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
const rolePermission = await getOrgRolePermissions(role, organizationId);
|
||||||
|
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, rolePermission);
|
||||||
|
|
||||||
|
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||||
|
message: "Failed to create a more privileged identity"
|
||||||
|
});
|
||||||
|
|
||||||
|
const organization = await Organization.findById(organizationId);
|
||||||
|
if (!organization) throw BadRequestError({ message: `Organization with id ${organizationId} not found` });
|
||||||
|
|
||||||
|
const isCustomRole = ![ADMIN, MEMBER, NO_ACCESS].includes(role);
|
||||||
|
|
||||||
|
let customRole;
|
||||||
|
if (isCustomRole) {
|
||||||
|
customRole = await Role.findOne({
|
||||||
|
slug: role,
|
||||||
|
isOrgRole: true,
|
||||||
|
organization: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const identity = await new Identity({
|
||||||
|
name
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
await new IdentityMembershipOrg({
|
||||||
|
identity: identity._id,
|
||||||
|
organization: new Types.ObjectId(organizationId),
|
||||||
|
role: isCustomRole ? CUSTOM : role,
|
||||||
|
customRole
|
||||||
|
}).save();
|
||||||
|
|
||||||
|
await EEAuditLogService.createAuditLog(
|
||||||
|
req.authData,
|
||||||
|
{
|
||||||
|
type: EventType.CREATE_IDENTITY,
|
||||||
|
metadata: {
|
||||||
|
identityId: identity._id.toString(),
|
||||||
|
name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
identity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update identity with id [identityId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const updateIdentity = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Update identity'
|
||||||
|
#swagger.description = 'Update identity'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"bearerAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['identityId'] = {
|
||||||
|
"description": "ID of identity to update",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.requestBody = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of entity to update to",
|
||||||
|
"example": "development"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Role to update to for organization membership",
|
||||||
|
"example": "no-access"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"identity": {
|
||||||
|
$ref: '#/definitions/Identity'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Details of the updated identity"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
params: { identityId },
|
||||||
|
body: {
|
||||||
|
name,
|
||||||
|
role
|
||||||
|
}
|
||||||
|
} = await validateRequest(reqValidator.UpdateIdentityV1, req);
|
||||||
|
|
||||||
|
const identityMembershipOrg = await IdentityMembershipOrg
|
||||||
|
.findOne({
|
||||||
|
identity: new Types.ObjectId(identityId)
|
||||||
|
})
|
||||||
|
.populate<{
|
||||||
|
identity: IIdentity,
|
||||||
|
customRole: IRole
|
||||||
|
}>("identity customRole");
|
||||||
|
|
||||||
|
if (!identityMembershipOrg) throw ResourceNotFoundError({
|
||||||
|
message: `Failed to find identity with id ${identityId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: identityMembershipOrg.organization
|
||||||
|
});
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Edit,
|
||||||
|
OrgPermissionSubjects.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
const identityRolePermission = await getOrgRolePermissions(
|
||||||
|
identityMembershipOrg?.customRole?.slug ?? identityMembershipOrg.role,
|
||||||
|
identityMembershipOrg.organization.toString()
|
||||||
|
);
|
||||||
|
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, identityRolePermission);
|
||||||
|
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||||
|
message: "Failed to update more privileged identity"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (role) {
|
||||||
|
const rolePermission = await getOrgRolePermissions(role, identityMembershipOrg.organization.toString());
|
||||||
|
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, rolePermission);
|
||||||
|
|
||||||
|
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||||
|
message: "Failed to update identity to a more privileged role"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let customRole;
|
||||||
|
if (role) {
|
||||||
|
const isCustomRole = ![ADMIN, MEMBER, NO_ACCESS].includes(role);
|
||||||
|
if (isCustomRole) {
|
||||||
|
customRole = await Role.findOne({
|
||||||
|
slug: role,
|
||||||
|
isOrgRole: true,
|
||||||
|
organization: identityMembershipOrg.organization
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const identity = await Identity.findByIdAndUpdate(
|
||||||
|
identityId,
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!identity) throw BadRequestError({
|
||||||
|
message: `Failed to update identity with id ${identityId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
await IdentityMembershipOrg.findOneAndUpdate(
|
||||||
|
{
|
||||||
|
identity: identity._id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: customRole ? CUSTOM : role,
|
||||||
|
...(customRole ? {
|
||||||
|
customRole
|
||||||
|
} : {}),
|
||||||
|
...(role && !customRole ? { // non-custom role
|
||||||
|
$unset: {
|
||||||
|
customRole: 1
|
||||||
|
}
|
||||||
|
} : {})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await EEAuditLogService.createAuditLog(
|
||||||
|
req.authData,
|
||||||
|
{
|
||||||
|
type: EventType.UPDATE_IDENTITY,
|
||||||
|
metadata: {
|
||||||
|
identityId: identity._id.toString(),
|
||||||
|
name: identity.name,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
organizationId: identityMembershipOrg.organization
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
identity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete identity with id [identityId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const deleteIdentity = async (req: Request, res: Response) => {
|
||||||
|
/*
|
||||||
|
#swagger.summary = 'Delete identity'
|
||||||
|
#swagger.description = 'Delete identity'
|
||||||
|
|
||||||
|
#swagger.security = [{
|
||||||
|
"bearerAuth": []
|
||||||
|
}]
|
||||||
|
|
||||||
|
#swagger.parameters['identityId'] = {
|
||||||
|
"description": "ID of identity",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.responses[200] = {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"identity": {
|
||||||
|
$ref: '#/definitions/Identity'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Details of the deleted identity"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
params: { identityId }
|
||||||
|
} = await validateRequest(reqValidator.DeleteIdentityV1, req);
|
||||||
|
|
||||||
|
const identityMembershipOrg = await IdentityMembershipOrg
|
||||||
|
.findOne({
|
||||||
|
identity: new Types.ObjectId(identityId)
|
||||||
|
})
|
||||||
|
.populate<{
|
||||||
|
identity: IIdentity,
|
||||||
|
customRole: IRole
|
||||||
|
}>("identity customRole");
|
||||||
|
|
||||||
|
if (!identityMembershipOrg) throw ResourceNotFoundError({
|
||||||
|
message: `Failed to find identity with id ${identityId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: identityMembershipOrg.organization
|
||||||
|
});
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionActions.Delete,
|
||||||
|
OrgPermissionSubjects.Identity
|
||||||
|
);
|
||||||
|
|
||||||
|
const identityRolePermission = await getOrgRolePermissions(
|
||||||
|
identityMembershipOrg?.customRole?.slug ?? identityMembershipOrg.role,
|
||||||
|
identityMembershipOrg.organization.toString()
|
||||||
|
);
|
||||||
|
const hasRequiredPrivileges = isAtLeastAsPrivilegedOrg(permission, identityRolePermission);
|
||||||
|
if (!hasRequiredPrivileges) throw ForbiddenRequestError({
|
||||||
|
message: "Failed to delete more privileged identity"
|
||||||
|
});
|
||||||
|
|
||||||
|
const identity = await Identity.findByIdAndDelete(identityMembershipOrg.identity);
|
||||||
|
if (!identity) throw ResourceNotFoundError({
|
||||||
|
message: `Identity with id ${identityId} not found`
|
||||||
|
});
|
||||||
|
|
||||||
|
await IdentityMembershipOrg.findByIdAndDelete(identityMembershipOrg._id);
|
||||||
|
|
||||||
|
await IdentityMembership.deleteMany({
|
||||||
|
identity: identityMembershipOrg.identity
|
||||||
|
});
|
||||||
|
|
||||||
|
await IdentityUniversalAuth.deleteMany({
|
||||||
|
identity: identityMembershipOrg.identity
|
||||||
|
});
|
||||||
|
|
||||||
|
await IdentityUniversalAuthClientSecret.deleteMany({
|
||||||
|
identity: identityMembershipOrg.identity
|
||||||
|
});
|
||||||
|
|
||||||
|
await IdentityAccessToken.deleteMany({
|
||||||
|
identity: identityMembershipOrg.identity
|
||||||
|
});
|
||||||
|
|
||||||
|
await EEAuditLogService.createAuditLog(
|
||||||
|
req.authData,
|
||||||
|
{
|
||||||
|
type: EventType.DELETE_IDENTITY,
|
||||||
|
metadata: {
|
||||||
|
identityId: identity._id.toString()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
organizationId: identityMembershipOrg.organization
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
identity
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
|||||||
|
import * as identitiesController from "./identitiesController";
|
||||||
import * as secretController from "./secretController";
|
import * as secretController from "./secretController";
|
||||||
import * as secretSnapshotController from "./secretSnapshotController";
|
import * as secretSnapshotController from "./secretSnapshotController";
|
||||||
import * as organizationsController from "./organizationsController";
|
import * as organizationsController from "./organizationsController";
|
||||||
@ -13,6 +14,7 @@ import * as secretRotationProviderController from "./secretRotationProviderContr
|
|||||||
import * as secretRotationController from "./secretRotationController";
|
import * as secretRotationController from "./secretRotationController";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
identitiesController,
|
||||||
secretController,
|
secretController,
|
||||||
secretSnapshotController,
|
secretSnapshotController,
|
||||||
organizationsController,
|
organizationsController,
|
||||||
|
@ -8,7 +8,7 @@ import * as reqValidator from "../../../validation/organization";
|
|||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
getUserOrgPermissions
|
getAuthDataOrgPermissions,
|
||||||
} from "../../services/RoleService";
|
} from "../../services/RoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { Organization } from "../../../models";
|
import { Organization } from "../../../models";
|
||||||
@ -20,7 +20,10 @@ export const getOrganizationPlansTable = async (req: Request, res: Response) =>
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgPlansTablev1, req);
|
} = await validateRequest(reqValidator.GetOrgPlansTablev1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -42,7 +45,10 @@ export const getOrganizationPlan = async (req: Request, res: Response) => {
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgPlanv1, req);
|
} = await validateRequest(reqValidator.GetOrgPlanv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -70,7 +76,10 @@ export const startOrganizationTrial = async (req: Request, res: Response) => {
|
|||||||
body: { success_url }
|
body: { success_url }
|
||||||
} = await validateRequest(reqValidator.StartOrgTrailv1, req);
|
} = await validateRequest(reqValidator.StartOrgTrailv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Create,
|
OrgPermissionActions.Create,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -116,7 +125,10 @@ export const getOrganizationPlanBillingInfo = async (req: Request, res: Response
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req);
|
} = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -149,7 +161,10 @@ export const getOrganizationPlanTable = async (req: Request, res: Response) => {
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgPlanTablev1, req);
|
} = await validateRequest(reqValidator.GetOrgPlanTablev1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -176,7 +191,10 @@ export const getOrganizationBillingDetails = async (req: Request, res: Response)
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgBillingDetailsv1, req);
|
} = await validateRequest(reqValidator.GetOrgBillingDetailsv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -204,7 +222,10 @@ export const updateOrganizationBillingDetails = async (req: Request, res: Respon
|
|||||||
body: { name, email }
|
body: { name, email }
|
||||||
} = await validateRequest(reqValidator.UpdateOrgBillingDetailsv1, req);
|
} = await validateRequest(reqValidator.UpdateOrgBillingDetailsv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Edit,
|
OrgPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -238,7 +259,10 @@ export const getOrganizationPmtMethods = async (req: Request, res: Response) =>
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgPmtMethodsv1, req);
|
} = await validateRequest(reqValidator.GetOrgPmtMethodsv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -271,7 +295,10 @@ export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
|
|||||||
body: { success_url, cancel_url }
|
body: { success_url, cancel_url }
|
||||||
} = await validateRequest(reqValidator.CreateOrgPmtMethodv1, req);
|
} = await validateRequest(reqValidator.CreateOrgPmtMethodv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Create,
|
OrgPermissionActions.Create,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -312,7 +339,10 @@ export const deleteOrganizationPmtMethod = async (req: Request, res: Response) =
|
|||||||
params: { organizationId, pmtMethodId }
|
params: { organizationId, pmtMethodId }
|
||||||
} = await validateRequest(reqValidator.DelOrgPmtMethodv1, req);
|
} = await validateRequest(reqValidator.DelOrgPmtMethodv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Delete,
|
OrgPermissionActions.Delete,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -342,7 +372,10 @@ export const getOrganizationTaxIds = async (req: Request, res: Response) => {
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgTaxIdsv1, req);
|
} = await validateRequest(reqValidator.GetOrgTaxIdsv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -375,7 +408,10 @@ export const addOrganizationTaxId = async (req: Request, res: Response) => {
|
|||||||
body: { type, value }
|
body: { type, value }
|
||||||
} = await validateRequest(reqValidator.CreateOrgTaxId, req);
|
} = await validateRequest(reqValidator.CreateOrgTaxId, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Create,
|
OrgPermissionActions.Create,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -412,7 +448,10 @@ export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
|
|||||||
params: { organizationId, taxId }
|
params: { organizationId, taxId }
|
||||||
} = await validateRequest(reqValidator.DelOrgTaxIdv1, req);
|
} = await validateRequest(reqValidator.DelOrgTaxIdv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Delete,
|
OrgPermissionActions.Delete,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -445,7 +484,10 @@ export const getOrganizationInvoices = async (req: Request, res: Response) => {
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgInvoicesv1, req);
|
} = await validateRequest(reqValidator.GetOrgInvoicesv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
@ -480,7 +522,10 @@ export const getOrganizationLicenses = async (req: Request, res: Response) => {
|
|||||||
params: { organizationId }
|
params: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetOrgLicencesv1, req);
|
} = await validateRequest(reqValidator.GetOrgLicencesv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Billing
|
OrgPermissionSubjects.Billing
|
||||||
|
@ -15,14 +15,17 @@ import {
|
|||||||
adminProjectPermissions,
|
adminProjectPermissions,
|
||||||
getAuthDataProjectPermissions,
|
getAuthDataProjectPermissions,
|
||||||
memberProjectPermissions,
|
memberProjectPermissions,
|
||||||
|
noAccessProjectPermissions,
|
||||||
viewerProjectPermission
|
viewerProjectPermission
|
||||||
} from "../../services/ProjectRoleService";
|
} from "../../services/ProjectRoleService";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
adminPermissions,
|
adminPermissions,
|
||||||
|
getAuthDataOrgPermissions,
|
||||||
getUserOrgPermissions,
|
getUserOrgPermissions,
|
||||||
memberPermissions
|
memberPermissions,
|
||||||
|
noAccessPermissions
|
||||||
} from "../../services/RoleService";
|
} from "../../services/RoleService";
|
||||||
import { BadRequestError } from "../../../utils/errors";
|
import { BadRequestError } from "../../../utils/errors";
|
||||||
import { Role } from "../../models";
|
import { Role } from "../../models";
|
||||||
@ -36,7 +39,11 @@ export const createRole = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
|
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
|
||||||
if (isOrgRole) {
|
if (isOrgRole) {
|
||||||
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(orgId)
|
||||||
|
});
|
||||||
|
|
||||||
if (permission.cannot(OrgPermissionActions.Create, OrgPermissionSubjects.Role)) {
|
if (permission.cannot(OrgPermissionActions.Create, OrgPermissionSubjects.Role)) {
|
||||||
throw BadRequestError({ message: "user doesn't have the permission." });
|
throw BadRequestError({ message: "user doesn't have the permission." });
|
||||||
}
|
}
|
||||||
@ -80,9 +87,12 @@ export const updateRole = async (req: Request, res: Response) => {
|
|||||||
body: { name, description, slug, permissions, workspaceId, orgId }
|
body: { name, description, slug, permissions, workspaceId, orgId }
|
||||||
} = await validateRequest(UpdateRoleSchema, req);
|
} = await validateRequest(UpdateRoleSchema, req);
|
||||||
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
|
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
|
||||||
|
|
||||||
if (isOrgRole) {
|
if (isOrgRole) {
|
||||||
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(orgId)
|
||||||
|
});
|
||||||
if (permission.cannot(OrgPermissionActions.Edit, OrgPermissionSubjects.Role)) {
|
if (permission.cannot(OrgPermissionActions.Edit, OrgPermissionSubjects.Role)) {
|
||||||
throw BadRequestError({ message: "User doesn't have the org permission." });
|
throw BadRequestError({ message: "User doesn't have the org permission." });
|
||||||
}
|
}
|
||||||
@ -138,7 +148,10 @@ export const deleteRole = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const isOrgRole = !role.workspace;
|
const isOrgRole = !role.workspace;
|
||||||
if (isOrgRole) {
|
if (isOrgRole) {
|
||||||
const { permission } = await getUserOrgPermissions(req.user.id, role.organization.toString());
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: role.organization
|
||||||
|
});
|
||||||
if (permission.cannot(OrgPermissionActions.Delete, OrgPermissionSubjects.Role)) {
|
if (permission.cannot(OrgPermissionActions.Delete, OrgPermissionSubjects.Role)) {
|
||||||
throw BadRequestError({ message: "User doesn't have the org permission." });
|
throw BadRequestError({ message: "User doesn't have the org permission." });
|
||||||
}
|
}
|
||||||
@ -170,7 +183,10 @@ export const getRoles = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const isOrgRole = !workspaceId;
|
const isOrgRole = !workspaceId;
|
||||||
if (isOrgRole) {
|
if (isOrgRole) {
|
||||||
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(orgId)
|
||||||
|
});
|
||||||
if (permission.cannot(OrgPermissionActions.Read, OrgPermissionSubjects.Role)) {
|
if (permission.cannot(OrgPermissionActions.Read, OrgPermissionSubjects.Role)) {
|
||||||
throw BadRequestError({ message: "User doesn't have the org permission." });
|
throw BadRequestError({ message: "User doesn't have the org permission." });
|
||||||
}
|
}
|
||||||
@ -195,6 +211,13 @@ export const getRoles = async (req: Request, res: Response) => {
|
|||||||
description: "Complete administration access over the organization",
|
description: "Complete administration access over the organization",
|
||||||
permissions: isOrgRole ? adminPermissions.rules : adminProjectPermissions.rules
|
permissions: isOrgRole ? adminPermissions.rules : adminProjectPermissions.rules
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
_id: "no-access",
|
||||||
|
name: "No Access",
|
||||||
|
slug: "no-access",
|
||||||
|
description: "No access to any resources in the organization",
|
||||||
|
permissions: isOrgRole ? noAccessPermissions.rules : noAccessProjectPermissions.rules
|
||||||
|
},
|
||||||
{
|
{
|
||||||
_id: "member",
|
_id: "member",
|
||||||
name: isOrgRole ? "Member" : "Developer",
|
name: isOrgRole ? "Member" : "Developer",
|
||||||
@ -229,7 +252,7 @@ export const getUserPermissions = async (req: Request, res: Response) => {
|
|||||||
const {
|
const {
|
||||||
params: { orgId }
|
params: { orgId }
|
||||||
} = await validateRequest(GetUserPermission, req);
|
} = await validateRequest(GetUserPermission, req);
|
||||||
|
|
||||||
const { permission, membership } = await getUserOrgPermissions(req.user._id, orgId);
|
const { permission, membership } = await getUserOrgPermissions(req.user._id, orgId);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
|
@ -17,12 +17,12 @@ export const getSecretApprovalRequestCount = async (req: Request, res: Response)
|
|||||||
} = await validateRequest(reqValidator.getSecretApprovalRequestCount, req);
|
} = await validateRequest(reqValidator.getSecretApprovalRequestCount, req);
|
||||||
|
|
||||||
if (!(req.authData.authPayload instanceof User)) return;
|
if (!(req.authData.authPayload instanceof User)) return;
|
||||||
|
|
||||||
const membership = await Membership.findOne({
|
const membership = await Membership.findOne({
|
||||||
user: req.authData.authPayload._id,
|
user: req.authData.authPayload._id,
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership) throw UnauthorizedRequestError();
|
if (!membership) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
const approvalRequestCount = await SecretApprovalRequest.aggregate([
|
const approvalRequestCount = await SecretApprovalRequest.aggregate([
|
||||||
@ -73,12 +73,12 @@ export const getSecretApprovalRequests = async (req: Request, res: Response) =>
|
|||||||
} = await validateRequest(reqValidator.getSecretApprovalRequests, req);
|
} = await validateRequest(reqValidator.getSecretApprovalRequests, req);
|
||||||
|
|
||||||
if (!(req.authData.authPayload instanceof User)) return;
|
if (!(req.authData.authPayload instanceof User)) return;
|
||||||
|
|
||||||
const membership = await Membership.findOne({
|
const membership = await Membership.findOne({
|
||||||
user: req.authData.authPayload._id,
|
user: req.authData.authPayload._id,
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership) throw UnauthorizedRequestError();
|
if (!membership) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
@ -168,13 +168,13 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons
|
|||||||
user: req.authData.authPayload._id,
|
user: req.authData.authPayload._id,
|
||||||
workspace: secretApprovalRequest.workspace
|
workspace: secretApprovalRequest.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership) throw UnauthorizedRequestError();
|
if (!membership) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
// allow to fetch only if its admin or is the committer or approver
|
// allow to fetch only if its admin or is the committer or approver
|
||||||
if (
|
if (
|
||||||
membership.role !== "admin" &&
|
membership.role !== "admin" &&
|
||||||
secretApprovalRequest.committer !== membership.id &&
|
!secretApprovalRequest.committer.equals(membership.id) &&
|
||||||
!secretApprovalRequest.policy.approvers.find(
|
!secretApprovalRequest.policy.approvers.find(
|
||||||
(approverId) => approverId.toString() === membership._id.toString()
|
(approverId) => approverId.toString() === membership._id.toString()
|
||||||
)
|
)
|
||||||
@ -215,7 +215,7 @@ export const updateSecretApprovalReviewStatus = async (req: Request, res: Respon
|
|||||||
user: req.authData.authPayload._id,
|
user: req.authData.authPayload._id,
|
||||||
workspace: secretApprovalRequest.workspace
|
workspace: secretApprovalRequest.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership) throw UnauthorizedRequestError();
|
if (!membership) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -257,7 +257,7 @@ export const mergeSecretApprovalRequest = async (req: Request, res: Response) =>
|
|||||||
user: req.authData.authPayload._id,
|
user: req.authData.authPayload._id,
|
||||||
workspace: secretApprovalRequest.workspace
|
workspace: secretApprovalRequest.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership) throw UnauthorizedRequestError();
|
if (!membership) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -307,7 +307,7 @@ export const updateSecretApprovalRequestStatus = async (req: Request, res: Respo
|
|||||||
user: req.authData.authPayload._id,
|
user: req.authData.authPayload._id,
|
||||||
workspace: secretApprovalRequest.workspace
|
workspace: secretApprovalRequest.workspace
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membership) throw UnauthorizedRequestError();
|
if (!membership) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -13,7 +13,7 @@ import { validateRequest } from "../../../helpers/validation";
|
|||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
getUserOrgPermissions
|
getAuthDataOrgPermissions
|
||||||
} from "../../services/RoleService";
|
} from "../../services/RoleService";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
@ -47,7 +47,10 @@ export const getSSOConfig = async (req: Request, res: Response) => {
|
|||||||
query: { organizationId }
|
query: { organizationId }
|
||||||
} = await validateRequest(reqValidator.GetSsoConfigv1, req);
|
} = await validateRequest(reqValidator.GetSsoConfigv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Read,
|
OrgPermissionActions.Read,
|
||||||
OrgPermissionSubjects.Sso
|
OrgPermissionSubjects.Sso
|
||||||
@ -71,7 +74,10 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
|
|||||||
body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
|
body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
|
||||||
} = await validateRequest(reqValidator.UpdateSsoConfigv1, req);
|
} = await validateRequest(reqValidator.UpdateSsoConfigv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Edit,
|
OrgPermissionActions.Edit,
|
||||||
OrgPermissionSubjects.Sso
|
OrgPermissionSubjects.Sso
|
||||||
@ -206,7 +212,10 @@ export const createSSOConfig = async (req: Request, res: Response) => {
|
|||||||
body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
|
body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
|
||||||
} = await validateRequest(reqValidator.CreateSsoConfigv1, req);
|
} = await validateRequest(reqValidator.CreateSsoConfigv1, req);
|
||||||
|
|
||||||
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
const { permission } = await getAuthDataOrgPermissions({
|
||||||
|
authData: req.authData,
|
||||||
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
OrgPermissionActions.Create,
|
OrgPermissionActions.Create,
|
||||||
OrgPermissionSubjects.Sso
|
OrgPermissionSubjects.Sso
|
||||||
|
@ -2,10 +2,11 @@ import { Request, Response } from "express";
|
|||||||
import { PipelineStage, Types } from "mongoose";
|
import { PipelineStage, Types } from "mongoose";
|
||||||
import {
|
import {
|
||||||
Folder,
|
Folder,
|
||||||
|
Identity,
|
||||||
|
IdentityMembership,
|
||||||
Membership,
|
Membership,
|
||||||
Secret,
|
Secret,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
ServiceTokenDataV3,
|
|
||||||
TFolderSchema,
|
TFolderSchema,
|
||||||
User,
|
User,
|
||||||
Workspace
|
Workspace
|
||||||
@ -17,10 +18,10 @@ import {
|
|||||||
FolderVersion,
|
FolderVersion,
|
||||||
IPType,
|
IPType,
|
||||||
ISecretVersion,
|
ISecretVersion,
|
||||||
|
IdentityActor,
|
||||||
SecretSnapshot,
|
SecretSnapshot,
|
||||||
SecretVersion,
|
SecretVersion,
|
||||||
ServiceActor,
|
ServiceActor,
|
||||||
ServiceActorV3,
|
|
||||||
TFolderRootVersionSchema,
|
TFolderRootVersionSchema,
|
||||||
TrustedIP,
|
TrustedIP,
|
||||||
UserActor
|
UserActor
|
||||||
@ -61,15 +62,30 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) =
|
|||||||
#swagger.description = 'Return project secret snapshots ids'
|
#swagger.description = 'Return project secret snapshots ids'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": [],
|
||||||
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['workspaceId'] = {
|
#swagger.parameters['workspaceId'] = {
|
||||||
"description": "ID of project",
|
"description": "ID of project where to get secret snapshots for",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['environment'] = {
|
||||||
|
"description": "Slug of environment where to get secret snapshots for",
|
||||||
|
"required": true,
|
||||||
|
"type": "string",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
|
||||||
|
#swagger.parameters['directory'] = {
|
||||||
|
"description": "Path where to get secret snapshots for like / or /foo/bar. Default is /",
|
||||||
|
"required": false,
|
||||||
|
"type": "string",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
|
||||||
#swagger.parameters['offset'] = {
|
#swagger.parameters['offset'] = {
|
||||||
"description": "Number of secret snapshots to skip",
|
"description": "Number of secret snapshots to skip",
|
||||||
"required": false,
|
"required": false,
|
||||||
@ -194,11 +210,12 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
|||||||
#swagger.description = 'Roll back project secrets to those captured in a secret snapshot version.'
|
#swagger.description = 'Roll back project secrets to those captured in a secret snapshot version.'
|
||||||
|
|
||||||
#swagger.security = [{
|
#swagger.security = [{
|
||||||
"apiKeyAuth": []
|
"apiKeyAuth": [],
|
||||||
|
"bearerAuth": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
#swagger.parameters['workspaceId'] = {
|
#swagger.parameters['workspaceId'] = {
|
||||||
"description": "ID of project",
|
"description": "ID of project where to roll back",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -210,6 +227,14 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"environment": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Slug of environment where to roll back"
|
||||||
|
},
|
||||||
|
"directory": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path where to roll back for like / or /foo/bar. Default is /"
|
||||||
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Version of secret snapshot to roll back to",
|
"description": "Version of secret snapshot to roll back to",
|
||||||
@ -669,6 +694,21 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
|||||||
ProjectPermissionSub.AuditLogs
|
ProjectPermissionSub.AuditLogs
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let actorMetadataQuery = "";
|
||||||
|
if (actor) {
|
||||||
|
switch (actor?.split("-", 2)[0]) {
|
||||||
|
case ActorType.USER:
|
||||||
|
actorMetadataQuery = "actor.metadata.userId";
|
||||||
|
break;
|
||||||
|
case ActorType.SERVICE:
|
||||||
|
actorMetadataQuery = "actor.metadata.serviceId";
|
||||||
|
break;
|
||||||
|
case ActorType.IDENTITY:
|
||||||
|
actorMetadataQuery = "actor.metadata.identityId";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
...(eventType
|
...(eventType
|
||||||
@ -684,13 +724,9 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
|||||||
...(actor
|
...(actor
|
||||||
? {
|
? {
|
||||||
"actor.type": actor.substring(0, actor.lastIndexOf("-")),
|
"actor.type": actor.substring(0, actor.lastIndexOf("-")),
|
||||||
...(actor.split("-", 2)[0] === ActorType.USER
|
...({
|
||||||
? {
|
[actorMetadataQuery]: actor.substring(actor.lastIndexOf("-") + 1)
|
||||||
"actor.metadata.userId": actor.substring(actor.lastIndexOf("-") + 1)
|
})
|
||||||
}
|
|
||||||
: {
|
|
||||||
"actor.metadata.serviceId": actor.substring(actor.lastIndexOf("-") + 1)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(startDate || endDate
|
...(startDate || endDate
|
||||||
@ -702,7 +738,9 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
};
|
};
|
||||||
|
|
||||||
const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit);
|
const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
auditLogs
|
auditLogs
|
||||||
});
|
});
|
||||||
@ -731,6 +769,7 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
|
|||||||
const userIds = await Membership.distinct("user", {
|
const userIds = await Membership.distinct("user", {
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
|
|
||||||
const userActors: UserActor[] = (
|
const userActors: UserActor[] = (
|
||||||
await User.find({
|
await User.find({
|
||||||
_id: {
|
_id: {
|
||||||
@ -757,19 +796,25 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const serviceV3Actors: ServiceActorV3[] = (
|
const identityIds = await IdentityMembership.distinct("identity", {
|
||||||
await ServiceTokenDataV3.find({
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
});
|
||||||
|
|
||||||
|
const identityActors: IdentityActor[] = (
|
||||||
|
await Identity.find({
|
||||||
|
_id: {
|
||||||
|
$in: identityIds
|
||||||
|
}
|
||||||
})
|
})
|
||||||
).map((serviceTokenData) => ({
|
).map((identity) => ({
|
||||||
type: ActorType.SERVICE_V3,
|
type: ActorType.IDENTITY,
|
||||||
metadata: {
|
metadata: {
|
||||||
serviceId: serviceTokenData._id.toString(),
|
identityId: identity._id.toString(),
|
||||||
name: serviceTokenData.name
|
name: identity.name
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const actors = [...userActors, ...serviceActors, ...serviceV3Actors];
|
const actors = [...userActors, ...serviceActors, ...identityActors];
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
actors
|
actors
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import * as serviceTokenDataController from "./serviceTokenDataController";
|
|
||||||
import * as apiKeyDataController from "./apiKeyDataController";
|
import * as apiKeyDataController from "./apiKeyDataController";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
serviceTokenDataController,
|
|
||||||
apiKeyDataController
|
apiKeyDataController
|
||||||
}
|
}
|
@ -1,469 +0,0 @@
|
|||||||
import jwt from "jsonwebtoken";
|
|
||||||
import { Request, Response } from "express";
|
|
||||||
import { Types } from "mongoose";
|
|
||||||
import {
|
|
||||||
IServiceTokenDataV3,
|
|
||||||
IUser,
|
|
||||||
ServiceTokenDataV3,
|
|
||||||
ServiceTokenDataV3Key,
|
|
||||||
Workspace
|
|
||||||
} from "../../../models";
|
|
||||||
import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3";
|
|
||||||
import {
|
|
||||||
ActorType,
|
|
||||||
EventType,
|
|
||||||
Role
|
|
||||||
} from "../../models";
|
|
||||||
import { validateRequest } from "../../../helpers/validation";
|
|
||||||
import * as reqValidator from "../../../validation/serviceTokenDataV3";
|
|
||||||
import { createToken } from "../../../helpers/auth";
|
|
||||||
import {
|
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSub,
|
|
||||||
getAuthDataProjectPermissions
|
|
||||||
} from "../../services/ProjectRoleService";
|
|
||||||
import { ForbiddenError } from "@casl/ability";
|
|
||||||
import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../../utils/errors";
|
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
|
||||||
import { EEAuditLogService, EELicenseService } from "../../services";
|
|
||||||
import { getAuthSecret } from "../../../config";
|
|
||||||
import { ADMIN, AuthTokenType, CUSTOM, MEMBER, VIEWER } from "../../../variables";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return project key for service token V3
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
*/
|
|
||||||
export const getServiceTokenDataKey = async (req: Request, res: Response) => {
|
|
||||||
const key = await ServiceTokenDataV3Key.findOne({
|
|
||||||
serviceTokenData: (req.authData.authPayload as IServiceTokenDataV3)._id
|
|
||||||
}).populate<{ sender: IUser }>("sender", "publicKey");
|
|
||||||
|
|
||||||
if (!key) throw ResourceNotFoundError({
|
|
||||||
message: "Failed to find project key for service token"
|
|
||||||
});
|
|
||||||
|
|
||||||
const { _id, workspace, encryptedKey, nonce, sender: { publicKey } } = key;
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
key: {
|
|
||||||
_id,
|
|
||||||
workspace,
|
|
||||||
encryptedKey,
|
|
||||||
publicKey,
|
|
||||||
nonce
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return access and refresh token as per refresh operation
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
*/
|
|
||||||
export const refreshToken = async (req: Request, res: Response) => {
|
|
||||||
const {
|
|
||||||
body: {
|
|
||||||
refresh_token
|
|
||||||
}
|
|
||||||
} = await validateRequest(reqValidator.RefreshTokenV3, req);
|
|
||||||
|
|
||||||
const decodedToken = <jwt.ServiceRefreshTokenJwtPayload>(
|
|
||||||
jwt.verify(refresh_token, await getAuthSecret())
|
|
||||||
);
|
|
||||||
|
|
||||||
if (decodedToken.authTokenType !== AuthTokenType.SERVICE_REFRESH_TOKEN) throw UnauthorizedRequestError();
|
|
||||||
|
|
||||||
let serviceTokenData = await ServiceTokenDataV3.findOne({
|
|
||||||
_id: new Types.ObjectId(decodedToken.serviceTokenDataId),
|
|
||||||
isActive: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!serviceTokenData) throw UnauthorizedRequestError();
|
|
||||||
|
|
||||||
if (decodedToken.tokenVersion !== serviceTokenData.tokenVersion) {
|
|
||||||
// raise alarm
|
|
||||||
throw UnauthorizedRequestError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const response: {
|
|
||||||
refresh_token?: string;
|
|
||||||
access_token: string;
|
|
||||||
expires_in: number;
|
|
||||||
token_type: string;
|
|
||||||
} = {
|
|
||||||
refresh_token,
|
|
||||||
access_token: "",
|
|
||||||
expires_in: 0,
|
|
||||||
token_type: "Bearer"
|
|
||||||
};
|
|
||||||
|
|
||||||
if (serviceTokenData.isRefreshTokenRotationEnabled) {
|
|
||||||
serviceTokenData = await ServiceTokenDataV3.findByIdAndUpdate(
|
|
||||||
serviceTokenData._id,
|
|
||||||
{
|
|
||||||
$inc: {
|
|
||||||
tokenVersion: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!serviceTokenData) throw BadRequestError();
|
|
||||||
|
|
||||||
response.refresh_token = createToken({
|
|
||||||
payload: {
|
|
||||||
serviceTokenDataId: serviceTokenData._id.toString(),
|
|
||||||
authTokenType: AuthTokenType.SERVICE_REFRESH_TOKEN,
|
|
||||||
tokenVersion: serviceTokenData.tokenVersion
|
|
||||||
},
|
|
||||||
secret: await getAuthSecret()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
response.access_token = createToken({
|
|
||||||
payload: {
|
|
||||||
serviceTokenDataId: serviceTokenData._id.toString(),
|
|
||||||
authTokenType: AuthTokenType.SERVICE_ACCESS_TOKEN,
|
|
||||||
tokenVersion: serviceTokenData.tokenVersion
|
|
||||||
},
|
|
||||||
expiresIn: serviceTokenData.accessTokenTTL,
|
|
||||||
secret: await getAuthSecret()
|
|
||||||
});
|
|
||||||
|
|
||||||
response.expires_in = serviceTokenData.accessTokenTTL;
|
|
||||||
|
|
||||||
await ServiceTokenDataV3.findByIdAndUpdate(
|
|
||||||
serviceTokenData._id,
|
|
||||||
{
|
|
||||||
refreshTokenLastUsed: new Date(),
|
|
||||||
$inc: { refreshTokenUsageCount: 1 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).send(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create service token data V3
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const createServiceTokenData = async (req: Request, res: Response) => {
|
|
||||||
const {
|
|
||||||
body: {
|
|
||||||
name,
|
|
||||||
workspaceId,
|
|
||||||
publicKey,
|
|
||||||
role,
|
|
||||||
trustedIps,
|
|
||||||
expiresIn,
|
|
||||||
accessTokenTTL,
|
|
||||||
isRefreshTokenRotationEnabled,
|
|
||||||
encryptedKey, // for ServiceTokenDataV3Key
|
|
||||||
nonce, // for ServiceTokenDataV3Key
|
|
||||||
}
|
|
||||||
} = await validateRequest(reqValidator.CreateServiceTokenV3, req);
|
|
||||||
const { permission } = await getAuthDataProjectPermissions({
|
|
||||||
authData: req.authData,
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
|
||||||
});
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
ProjectPermissionSub.ServiceTokens
|
|
||||||
);
|
|
||||||
|
|
||||||
const workspace = await Workspace.findById(workspaceId);
|
|
||||||
if (!workspace) throw BadRequestError({ message: "Workspace not found" });
|
|
||||||
|
|
||||||
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
|
|
||||||
|
|
||||||
let customRole;
|
|
||||||
if (isCustomRole) {
|
|
||||||
customRole = await Role.findOne({
|
|
||||||
slug: role,
|
|
||||||
isOrgRole: false,
|
|
||||||
workspace: workspace._id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const plan = await EELicenseService.getPlan(workspace.organization);
|
|
||||||
|
|
||||||
// validate trusted ips
|
|
||||||
const reformattedTrustedIps = trustedIps.map((trustedIp) => {
|
|
||||||
if (!plan.ipAllowlisting && trustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({
|
|
||||||
message: "Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range."
|
|
||||||
});
|
|
||||||
|
|
||||||
const isValidIPOrCidr = isValidIpOrCidr(trustedIp.ipAddress);
|
|
||||||
|
|
||||||
if (!isValidIPOrCidr) return res.status(400).send({
|
|
||||||
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
|
||||||
});
|
|
||||||
|
|
||||||
return extractIPDetails(trustedIp.ipAddress);
|
|
||||||
});
|
|
||||||
|
|
||||||
let expiresAt;
|
|
||||||
if (expiresIn) {
|
|
||||||
expiresAt = new Date();
|
|
||||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
let user;
|
|
||||||
if (req.authData.actor.type === ActorType.USER) {
|
|
||||||
user = req.authData.authPayload._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isActive = true;
|
|
||||||
const serviceTokenData = await new ServiceTokenDataV3({
|
|
||||||
name,
|
|
||||||
user,
|
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
|
||||||
publicKey,
|
|
||||||
refreshTokenUsageCount: 0,
|
|
||||||
accessTokenUsageCount: 0,
|
|
||||||
tokenVersion: 1,
|
|
||||||
trustedIps: reformattedTrustedIps,
|
|
||||||
role: isCustomRole ? CUSTOM : role,
|
|
||||||
customRole,
|
|
||||||
isActive,
|
|
||||||
expiresAt,
|
|
||||||
accessTokenTTL,
|
|
||||||
isRefreshTokenRotationEnabled
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
await new ServiceTokenDataV3Key({
|
|
||||||
encryptedKey,
|
|
||||||
nonce,
|
|
||||||
sender: req.user._id,
|
|
||||||
serviceTokenData: serviceTokenData._id,
|
|
||||||
workspace: new Types.ObjectId(workspaceId)
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
const refreshToken = createToken({
|
|
||||||
payload: {
|
|
||||||
serviceTokenDataId: serviceTokenData._id.toString(),
|
|
||||||
authTokenType: AuthTokenType.SERVICE_REFRESH_TOKEN,
|
|
||||||
tokenVersion: serviceTokenData.tokenVersion
|
|
||||||
},
|
|
||||||
secret: await getAuthSecret()
|
|
||||||
});
|
|
||||||
|
|
||||||
await EEAuditLogService.createAuditLog(
|
|
||||||
req.authData,
|
|
||||||
{
|
|
||||||
type: EventType.CREATE_SERVICE_TOKEN_V3, // TODO: update
|
|
||||||
metadata: {
|
|
||||||
name,
|
|
||||||
isActive,
|
|
||||||
role,
|
|
||||||
trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>,
|
|
||||||
expiresAt
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
serviceTokenData,
|
|
||||||
refreshToken
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update service token V3 data with id [serviceTokenDataId]
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const updateServiceTokenData = async (req: Request, res: Response) => {
|
|
||||||
const {
|
|
||||||
params: { serviceTokenDataId },
|
|
||||||
body: {
|
|
||||||
name,
|
|
||||||
isActive,
|
|
||||||
role,
|
|
||||||
trustedIps,
|
|
||||||
expiresIn,
|
|
||||||
accessTokenTTL,
|
|
||||||
isRefreshTokenRotationEnabled
|
|
||||||
}
|
|
||||||
} = await validateRequest(reqValidator.UpdateServiceTokenV3, req);
|
|
||||||
|
|
||||||
let serviceTokenData = await ServiceTokenDataV3.findById(serviceTokenDataId);
|
|
||||||
if (!serviceTokenData) throw ResourceNotFoundError({
|
|
||||||
message: "Service token not found"
|
|
||||||
});
|
|
||||||
|
|
||||||
const { permission } = await getAuthDataProjectPermissions({
|
|
||||||
authData: req.authData,
|
|
||||||
workspaceId: serviceTokenData.workspace
|
|
||||||
});
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Edit,
|
|
||||||
ProjectPermissionSub.ServiceTokens
|
|
||||||
);
|
|
||||||
|
|
||||||
const workspace = await Workspace.findById(serviceTokenData.workspace);
|
|
||||||
if (!workspace) throw BadRequestError({ message: "Workspace not found" });
|
|
||||||
|
|
||||||
let customRole;
|
|
||||||
if (role) {
|
|
||||||
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
|
|
||||||
if (isCustomRole) {
|
|
||||||
customRole = await Role.findOne({
|
|
||||||
slug: role,
|
|
||||||
isOrgRole: false,
|
|
||||||
workspace: workspace._id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!customRole) throw BadRequestError({ message: "Role not found" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const plan = await EELicenseService.getPlan(workspace.organization);
|
|
||||||
|
|
||||||
// validate trusted ips
|
|
||||||
let reformattedTrustedIps;
|
|
||||||
if (trustedIps) {
|
|
||||||
reformattedTrustedIps = trustedIps.map((trustedIp) => {
|
|
||||||
if (!plan.ipAllowlisting && trustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({
|
|
||||||
message: "Failed to update IP access range to service token due to plan restriction. Upgrade plan to update IP access range."
|
|
||||||
});
|
|
||||||
|
|
||||||
const isValidIPOrCidr = isValidIpOrCidr(trustedIp.ipAddress);
|
|
||||||
|
|
||||||
if (!isValidIPOrCidr) return res.status(400).send({
|
|
||||||
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
|
|
||||||
});
|
|
||||||
|
|
||||||
return extractIPDetails(trustedIp.ipAddress);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let expiresAt;
|
|
||||||
if (expiresIn) {
|
|
||||||
expiresAt = new Date();
|
|
||||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceTokenData = await ServiceTokenDataV3.findByIdAndUpdate(
|
|
||||||
serviceTokenDataId,
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
isActive,
|
|
||||||
role: customRole ? CUSTOM : role,
|
|
||||||
...(customRole ? {
|
|
||||||
customRole
|
|
||||||
} : {}),
|
|
||||||
...(role && !customRole ? { // non-custom role
|
|
||||||
$unset: {
|
|
||||||
customRole: 1
|
|
||||||
}
|
|
||||||
} : {}),
|
|
||||||
trustedIps: reformattedTrustedIps,
|
|
||||||
expiresAt,
|
|
||||||
accessTokenTTL,
|
|
||||||
isRefreshTokenRotationEnabled
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!serviceTokenData) throw BadRequestError({
|
|
||||||
message: "Failed to update service token"
|
|
||||||
});
|
|
||||||
|
|
||||||
await EEAuditLogService.createAuditLog(
|
|
||||||
req.authData,
|
|
||||||
{
|
|
||||||
type: EventType.UPDATE_SERVICE_TOKEN_V3,
|
|
||||||
metadata: {
|
|
||||||
name: serviceTokenData.name,
|
|
||||||
isActive,
|
|
||||||
role,
|
|
||||||
trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>,
|
|
||||||
expiresAt
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
workspaceId: serviceTokenData.workspace
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
serviceTokenData
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete service token data with id [serviceTokenDataId]
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
|
||||||
const {
|
|
||||||
params: { serviceTokenDataId }
|
|
||||||
} = await validateRequest(reqValidator.DeleteServiceTokenV3, req);
|
|
||||||
|
|
||||||
let serviceTokenData = await ServiceTokenDataV3.findById(serviceTokenDataId);
|
|
||||||
if (!serviceTokenData) throw ResourceNotFoundError({
|
|
||||||
message: "Service token not found"
|
|
||||||
});
|
|
||||||
|
|
||||||
const { permission } = await getAuthDataProjectPermissions({
|
|
||||||
authData: req.authData,
|
|
||||||
workspaceId: serviceTokenData.workspace
|
|
||||||
});
|
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Delete,
|
|
||||||
ProjectPermissionSub.ServiceTokens
|
|
||||||
);
|
|
||||||
|
|
||||||
serviceTokenData = await ServiceTokenDataV3.findByIdAndDelete(serviceTokenDataId);
|
|
||||||
|
|
||||||
if (!serviceTokenData) throw BadRequestError({
|
|
||||||
message: "Failed to delete service token"
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceTokenDataV3Key.findOneAndDelete({
|
|
||||||
serviceTokenData: serviceTokenData._id
|
|
||||||
});
|
|
||||||
|
|
||||||
await EEAuditLogService.createAuditLog(
|
|
||||||
req.authData,
|
|
||||||
{
|
|
||||||
type: EventType.DELETE_SERVICE_TOKEN_V3,
|
|
||||||
metadata: {
|
|
||||||
name: serviceTokenData.name,
|
|
||||||
isActive: serviceTokenData.isActive,
|
|
||||||
role: serviceTokenData.role,
|
|
||||||
trustedIps: serviceTokenData.trustedIps as Array<IServiceTokenV3TrustedIp>,
|
|
||||||
expiresAt: serviceTokenData.expiresAt
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
workspaceId: serviceTokenData.workspace
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
serviceTokenData
|
|
||||||
});
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ export interface IAuditLog {
|
|||||||
event: Event;
|
event: Event;
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
userAgentType: UserAgentType;
|
userAgentType: UserAgentType;
|
||||||
expiresAt: Date;
|
expiresAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auditLogSchema = new Schema<IAuditLog>(
|
const auditLogSchema = new Schema<IAuditLog>(
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
export enum ActorType {
|
export enum ActorType { // would extend to AWS, Azure, ...
|
||||||
USER = "user",
|
USER = "user", // userIdentity
|
||||||
SERVICE = "service",
|
SERVICE = "service",
|
||||||
SERVICE_V3 = "service-v3",
|
IDENTITY = "identity"
|
||||||
// Machine = "machine"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum UserAgentType {
|
export enum UserAgentType {
|
||||||
WEB = "web",
|
WEB = "web",
|
||||||
CLI = "cli",
|
CLI = "cli",
|
||||||
K8_OPERATOR = "k8-operator",
|
K8_OPERATOR = "k8-operator",
|
||||||
OTHER = "other"
|
TERRAFORM = "terraform",
|
||||||
|
OTHER = "other",
|
||||||
|
PYTHON_SDK = "InfisicalPythonSDK",
|
||||||
|
NODE_SDK = "InfisicalNodeSDK"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EventType {
|
export enum EventType {
|
||||||
@ -32,9 +34,16 @@ export enum EventType {
|
|||||||
DELETE_TRUSTED_IP = "delete-trusted-ip",
|
DELETE_TRUSTED_IP = "delete-trusted-ip",
|
||||||
CREATE_SERVICE_TOKEN = "create-service-token", // v2
|
CREATE_SERVICE_TOKEN = "create-service-token", // v2
|
||||||
DELETE_SERVICE_TOKEN = "delete-service-token", // v2
|
DELETE_SERVICE_TOKEN = "delete-service-token", // v2
|
||||||
CREATE_SERVICE_TOKEN_V3 = "create-service-token-v3", // v3
|
CREATE_IDENTITY = "create-identity",
|
||||||
UPDATE_SERVICE_TOKEN_V3 = "update-service-token-v3", // v3
|
UPDATE_IDENTITY = "update-identity",
|
||||||
DELETE_SERVICE_TOKEN_V3 = "delete-service-token-v3", // v3
|
DELETE_IDENTITY = "delete-identity",
|
||||||
|
LOGIN_IDENTITY_UNIVERSAL_AUTH = "login-identity-universal-auth",
|
||||||
|
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
|
||||||
|
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
|
||||||
|
GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth",
|
||||||
|
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
|
||||||
|
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
|
||||||
|
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
|
||||||
CREATE_ENVIRONMENT = "create-environment",
|
CREATE_ENVIRONMENT = "create-environment",
|
||||||
UPDATE_ENVIRONMENT = "update-environment",
|
UPDATE_ENVIRONMENT = "update-environment",
|
||||||
DELETE_ENVIRONMENT = "delete-environment",
|
DELETE_ENVIRONMENT = "delete-environment",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ActorType, EventType } from "./enums";
|
import { ActorType, EventType } from "./enums";
|
||||||
import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3";
|
import { IIdentityTrustedIp } from "../../../models";
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
userId: string;
|
userId: string;
|
||||||
@ -11,6 +11,11 @@ interface ServiceActorMetadata {
|
|||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IdentityActorMetadata {
|
||||||
|
identityId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UserActor {
|
export interface UserActor {
|
||||||
type: ActorType.USER;
|
type: ActorType.USER;
|
||||||
metadata: UserActorMetadata;
|
metadata: UserActorMetadata;
|
||||||
@ -21,16 +26,12 @@ export interface ServiceActor {
|
|||||||
metadata: ServiceActorMetadata;
|
metadata: ServiceActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceActorV3 {
|
export interface IdentityActor {
|
||||||
type: ActorType.SERVICE_V3;
|
type: ActorType.IDENTITY;
|
||||||
metadata: ServiceActorMetadata;
|
metadata: IdentityActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
// export interface MachineActor {
|
export type Actor = UserActor | ServiceActor | IdentityActor;
|
||||||
// type: ActorType.Machine;
|
|
||||||
// }
|
|
||||||
|
|
||||||
export type Actor = UserActor | ServiceActor | ServiceActorV3;
|
|
||||||
|
|
||||||
interface GetSecretsEvent {
|
interface GetSecretsEvent {
|
||||||
type: EventType.GET_SECRETS;
|
type: EventType.GET_SECRETS;
|
||||||
@ -220,36 +221,91 @@ interface DeleteServiceTokenEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateServiceTokenV3Event {
|
interface CreateIdentityEvent { // note: currently not logging org-role
|
||||||
type: EventType.CREATE_SERVICE_TOKEN_V3;
|
type: EventType.CREATE_IDENTITY;
|
||||||
metadata: {
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
name: string;
|
name: string;
|
||||||
isActive: boolean;
|
|
||||||
role: string;
|
|
||||||
trustedIps: Array<IServiceTokenV3TrustedIp>;
|
|
||||||
expiresAt?: Date;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateServiceTokenV3Event {
|
interface UpdateIdentityEvent {
|
||||||
type: EventType.UPDATE_SERVICE_TOKEN_V3;
|
type: EventType.UPDATE_IDENTITY;
|
||||||
metadata: {
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
isActive?: boolean;
|
|
||||||
role?: string;
|
|
||||||
trustedIps?: Array<IServiceTokenV3TrustedIp>;
|
|
||||||
expiresAt?: Date;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteServiceTokenV3Event {
|
interface DeleteIdentityEvent {
|
||||||
type: EventType.DELETE_SERVICE_TOKEN_V3;
|
type: EventType.DELETE_IDENTITY;
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string;
|
identityId: string;
|
||||||
isActive: boolean;
|
};
|
||||||
role: string;
|
}
|
||||||
expiresAt?: Date;
|
|
||||||
trustedIps: Array<IServiceTokenV3TrustedIp>;
|
interface LoginIdentityUniversalAuthEvent {
|
||||||
|
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH ;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
identityUniversalAuthId: string;
|
||||||
|
clientSecretId: string;
|
||||||
|
identityAccessTokenId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddIdentityUniversalAuthEvent {
|
||||||
|
type: EventType.ADD_IDENTITY_UNIVERSAL_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
clientSecretTrustedIps: Array<IIdentityTrustedIp>;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: Array<IIdentityTrustedIp>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateIdentityUniversalAuthEvent {
|
||||||
|
type: EventType.UPDATE_IDENTITY_UNIVERSAL_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
clientSecretTrustedIps?: Array<IIdentityTrustedIp>;
|
||||||
|
accessTokenTTL?: number;
|
||||||
|
accessTokenMaxTTL?: number;
|
||||||
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenTrustedIps?: Array<IIdentityTrustedIp>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetIdentityUniversalAuthEvent {
|
||||||
|
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateIdentityUniversalAuthClientSecretEvent {
|
||||||
|
type: EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET ;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
clientSecretId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetIdentityUniversalAuthClientSecretsEvent {
|
||||||
|
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface RevokeIdentityUniversalAuthClientSecretEvent {
|
||||||
|
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET ;
|
||||||
|
metadata: {
|
||||||
|
identityId: string;
|
||||||
|
clientSecretId: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,9 +551,16 @@ export type Event =
|
|||||||
| DeleteTrustedIPEvent
|
| DeleteTrustedIPEvent
|
||||||
| CreateServiceTokenEvent
|
| CreateServiceTokenEvent
|
||||||
| DeleteServiceTokenEvent
|
| DeleteServiceTokenEvent
|
||||||
| CreateServiceTokenV3Event
|
| CreateIdentityEvent
|
||||||
| UpdateServiceTokenV3Event
|
| UpdateIdentityEvent
|
||||||
| DeleteServiceTokenV3Event
|
| DeleteIdentityEvent
|
||||||
|
| LoginIdentityUniversalAuthEvent
|
||||||
|
| AddIdentityUniversalAuthEvent
|
||||||
|
| UpdateIdentityUniversalAuthEvent
|
||||||
|
| GetIdentityUniversalAuthEvent
|
||||||
|
| CreateIdentityUniversalAuthClientSecretEvent
|
||||||
|
| GetIdentityUniversalAuthClientSecretsEvent
|
||||||
|
| RevokeIdentityUniversalAuthClientSecretEvent
|
||||||
| CreateEnvironmentEvent
|
| CreateEnvironmentEvent
|
||||||
| UpdateEnvironmentEvent
|
| UpdateEnvironmentEvent
|
||||||
| DeleteEnvironmentEvent
|
| DeleteEnvironmentEvent
|
||||||
|
31
backend/src/ee/routes/v1/identities.ts
Normal file
31
backend/src/ee/routes/v1/identities.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import { requireAuth } from "../../../middleware";
|
||||||
|
import { AuthMode } from "../../../variables";
|
||||||
|
import { identitiesController } from "../../controllers/v1";
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
identitiesController.createIdentity
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:identityId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
|
identitiesController.updateIdentity
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:identityId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
|
identitiesController.deleteIdentity
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
@ -1,3 +1,4 @@
|
|||||||
|
import identities from "./identities";
|
||||||
import secret from "./secret";
|
import secret from "./secret";
|
||||||
import secretSnapshot from "./secretSnapshot";
|
import secretSnapshot from "./secretSnapshot";
|
||||||
import organizations from "./organizations";
|
import organizations from "./organizations";
|
||||||
@ -13,6 +14,7 @@ import secretRotationProvider from "./secretRotationProvider";
|
|||||||
import secretRotation from "./secretRotation";
|
import secretRotation from "./secretRotation";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
identities,
|
||||||
secret,
|
secret,
|
||||||
secretSnapshot,
|
secretSnapshot,
|
||||||
organizations,
|
organizations,
|
||||||
|
@ -7,7 +7,7 @@ import { workspaceController } from "../../controllers/v1";
|
|||||||
router.get(
|
router.get(
|
||||||
"/:workspaceId/secret-snapshots",
|
"/:workspaceId/secret-snapshots",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
workspaceController.getWorkspaceSecretSnapshots
|
workspaceController.getWorkspaceSecretSnapshots
|
||||||
);
|
);
|
||||||
@ -23,7 +23,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
"/:workspaceId/secret-snapshots/rollback",
|
"/:workspaceId/secret-snapshots/rollback",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
workspaceController.rollbackWorkspaceSecretSnapshot
|
workspaceController.rollbackWorkspaceSecretSnapshot
|
||||||
);
|
);
|
||||||
@ -31,7 +31,7 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
"/:workspaceId/audit-logs",
|
"/:workspaceId/audit-logs",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
workspaceController.getWorkspaceAuditLogs
|
workspaceController.getWorkspaceAuditLogs
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import serviceTokenData from "./serviceTokenData";
|
|
||||||
import apiKeyData from "./apiKeyData";
|
import apiKeyData from "./apiKeyData";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
serviceTokenData,
|
|
||||||
apiKeyData
|
apiKeyData
|
||||||
}
|
}
|
@ -1,44 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
const router = express.Router();
|
|
||||||
import { requireAuth } from "../../../middleware";
|
|
||||||
import { AuthMode } from "../../../variables";
|
|
||||||
import { serviceTokenDataController } from "../../controllers/v3";
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/me/key",
|
|
||||||
requireAuth({
|
|
||||||
acceptedAuthModes: [AuthMode.SERVICE_ACCESS_TOKEN]
|
|
||||||
}),
|
|
||||||
serviceTokenDataController.getServiceTokenDataKey
|
|
||||||
);
|
|
||||||
|
|
||||||
router.post(
|
|
||||||
"/me/token",
|
|
||||||
serviceTokenDataController.refreshToken
|
|
||||||
);
|
|
||||||
|
|
||||||
router.post(
|
|
||||||
"/",
|
|
||||||
requireAuth({
|
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
|
||||||
}),
|
|
||||||
serviceTokenDataController.createServiceTokenData
|
|
||||||
);
|
|
||||||
|
|
||||||
router.patch(
|
|
||||||
"/:serviceTokenDataId",
|
|
||||||
requireAuth({
|
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
|
||||||
}),
|
|
||||||
serviceTokenDataController.updateServiceTokenData
|
|
||||||
);
|
|
||||||
|
|
||||||
router.delete(
|
|
||||||
"/:serviceTokenDataId",
|
|
||||||
requireAuth({
|
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
|
||||||
}),
|
|
||||||
serviceTokenDataController.deleteServiceTokenData
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
|
@ -3,7 +3,6 @@ import { AuditLog, Event } from "../models";
|
|||||||
import { AuthData } from "../../interfaces/middleware";
|
import { AuthData } from "../../interfaces/middleware";
|
||||||
import EELicenseService from "./EELicenseService";
|
import EELicenseService from "./EELicenseService";
|
||||||
import { Workspace } from "../../models";
|
import { Workspace } from "../../models";
|
||||||
import { OrganizationNotFoundError } from "../../utils/errors";
|
|
||||||
|
|
||||||
interface EventScope {
|
interface EventScope {
|
||||||
workspaceId?: Types.ObjectId;
|
workspaceId?: Types.ObjectId;
|
||||||
@ -14,31 +13,42 @@ type ValidEventScope =
|
|||||||
| Required<Pick<EventScope, "workspaceId">>
|
| Required<Pick<EventScope, "workspaceId">>
|
||||||
| Required<Pick<EventScope, "organizationId">>
|
| Required<Pick<EventScope, "organizationId">>
|
||||||
| Required<EventScope>
|
| Required<EventScope>
|
||||||
|
| Record<string, never>;
|
||||||
|
|
||||||
export default class EEAuditLogService {
|
export default class EEAuditLogService {
|
||||||
static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope, shouldSave = true) {
|
static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope = {}, shouldSave = true) {
|
||||||
|
|
||||||
const MS_IN_DAY = 24 * 60 * 60 * 1000;
|
const MS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
const organizationId = ("organizationId" in eventScope)
|
let organizationId;
|
||||||
? eventScope.organizationId
|
if ("organizationId" in eventScope) {
|
||||||
: (await Workspace.findById(eventScope.workspaceId).select("organization").lean())?.organization;
|
organizationId = eventScope.organizationId;
|
||||||
|
}
|
||||||
|
|
||||||
if (!organizationId) throw OrganizationNotFoundError({
|
let workspaceId;
|
||||||
message: "createAuditLog: Failed to create audit log due to missing organizationId"
|
if ("workspaceId" in eventScope) {
|
||||||
});
|
workspaceId = eventScope.workspaceId;
|
||||||
|
|
||||||
const ttl = (await EELicenseService.getPlan(organizationId)).auditLogsRetentionDays * MS_IN_DAY;
|
if (!organizationId) {
|
||||||
|
organizationId = (await Workspace.findById(workspaceId).select("organization").lean())?.organization;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let expiresAt;
|
||||||
|
if (organizationId) {
|
||||||
|
const ttl = (await EELicenseService.getPlan(organizationId)).auditLogsRetentionDays * MS_IN_DAY;
|
||||||
|
expiresAt = new Date(Date.now() + ttl);
|
||||||
|
}
|
||||||
|
|
||||||
const auditLog = await new AuditLog({
|
const auditLog = await new AuditLog({
|
||||||
actor: authData.actor,
|
actor: authData.actor,
|
||||||
organization: organizationId,
|
organization: organizationId,
|
||||||
workspace: ("workspaceId" in eventScope) ? eventScope.workspaceId : undefined,
|
workspace: workspaceId,
|
||||||
ipAddress: authData.ipAddress,
|
ipAddress: authData.ipAddress,
|
||||||
event,
|
event,
|
||||||
userAgent: authData.userAgent,
|
userAgent: authData.userAgent,
|
||||||
userAgentType: authData.userAgentType,
|
userAgentType: authData.userAgentType,
|
||||||
expiresAt: new Date(Date.now() + ttl)
|
expiresAt
|
||||||
});
|
});
|
||||||
|
|
||||||
if (shouldSave) {
|
if (shouldSave) {
|
||||||
|
@ -11,10 +11,15 @@ import { UnauthorizedRequestError } from "../../utils/errors";
|
|||||||
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
|
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
import { AuthData } from "../../interfaces/middleware";
|
import { AuthData } from "../../interfaces/middleware";
|
||||||
import { ActorType, IRole } from "../models";
|
import { ActorType, IRole, Role } from "../models";
|
||||||
import { Membership, ServiceTokenData, ServiceTokenDataV3 } from "../../models";
|
import {
|
||||||
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables";
|
IIdentity,
|
||||||
import { checkIPAgainstBlocklist } from "../../utils/ip";
|
IdentityMembership,
|
||||||
|
Membership,
|
||||||
|
ServiceTokenData
|
||||||
|
} from "../../models";
|
||||||
|
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../../variables";
|
||||||
|
import { BadRequestError } from "../../utils/errors";
|
||||||
|
|
||||||
const $glob: FieldInstruction<string> = {
|
const $glob: FieldInstruction<string> = {
|
||||||
type: "field",
|
type: "field",
|
||||||
@ -55,7 +60,8 @@ export enum ProjectPermissionSub {
|
|||||||
Secrets = "secrets",
|
Secrets = "secrets",
|
||||||
SecretRollback = "secret-rollback",
|
SecretRollback = "secret-rollback",
|
||||||
SecretApproval = "secret-approval",
|
SecretApproval = "secret-approval",
|
||||||
SecretRotation = "secret-rotation"
|
SecretRotation = "secret-rotation",
|
||||||
|
Identity = "identity"
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubjectFields = {
|
type SubjectFields = {
|
||||||
@ -80,6 +86,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretRotation]
|
| [ProjectPermissionActions, ProjectPermissionSub.SecretRotation]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.Identity]
|
||||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace]
|
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace]
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace]
|
||||||
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
||||||
@ -126,6 +133,11 @@ const buildAdminPermission = () => {
|
|||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Identity);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
||||||
@ -191,6 +203,11 @@ const buildMemberPermission = () => {
|
|||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Identity);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
||||||
@ -231,6 +248,7 @@ const buildViewerPermission = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
||||||
@ -243,6 +261,13 @@ const buildViewerPermission = () => {
|
|||||||
|
|
||||||
export const viewerProjectPermission = buildViewerPermission();
|
export const viewerProjectPermission = buildViewerPermission();
|
||||||
|
|
||||||
|
const buildNoAccessProjectPermission = () => {
|
||||||
|
const { build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
return build({ conditionsMatcher });
|
||||||
|
}
|
||||||
|
|
||||||
|
export const noAccessProjectPermissions = buildNoAccessProjectPermission();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return permissions for user/service pertaining to workspace with id [workspaceId]
|
* Return permissions for user/service pertaining to workspace with id [workspaceId]
|
||||||
*
|
*
|
||||||
@ -256,7 +281,7 @@ export const getAuthDataProjectPermissions = async ({
|
|||||||
authData: AuthData;
|
authData: AuthData;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
}) => {
|
}) => {
|
||||||
let role: "admin" | "member" | "viewer" | "custom";
|
let role: "admin" | "member" | "viewer" | "no-access" | "custom";
|
||||||
let customRole;
|
let customRole;
|
||||||
|
|
||||||
switch (authData.actor.type) {
|
switch (authData.actor.type) {
|
||||||
@ -265,10 +290,10 @@ export const getAuthDataProjectPermissions = async ({
|
|||||||
user: authData.authPayload._id,
|
user: authData.authPayload._id,
|
||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
})
|
})
|
||||||
.populate<{
|
.populate<{
|
||||||
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
||||||
}>("customRole")
|
}>("customRole")
|
||||||
.exec();
|
.exec();
|
||||||
|
|
||||||
if (!membership || (membership.role === "custom" && !membership.customRole)) {
|
if (!membership || (membership.role === "custom" && !membership.customRole)) {
|
||||||
throw UnauthorizedRequestError();
|
throw UnauthorizedRequestError();
|
||||||
@ -284,25 +309,24 @@ export const getAuthDataProjectPermissions = async ({
|
|||||||
role = "viewer";
|
role = "viewer";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ActorType.SERVICE_V3: {
|
case ActorType.IDENTITY: {
|
||||||
const serviceTokenData = await ServiceTokenDataV3
|
const identityMembership = await IdentityMembership.findOne({
|
||||||
.findById(authData.authPayload._id)
|
identity: authData.authPayload._id,
|
||||||
.populate<{
|
workspace: workspaceId
|
||||||
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
})
|
||||||
}>("customRole")
|
.populate<{
|
||||||
.exec();
|
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
||||||
|
identity: IIdentity
|
||||||
if (!serviceTokenData || (serviceTokenData.role === "custom" && !serviceTokenData.customRole)) {
|
}>("customRole identity")
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (!identityMembership || (identityMembership.role === "custom" && !identityMembership.customRole)) {
|
||||||
throw UnauthorizedRequestError();
|
throw UnauthorizedRequestError();
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIPAgainstBlocklist({
|
|
||||||
ipAddress: authData.ipAddress,
|
|
||||||
trustedIps: serviceTokenData.trustedIps
|
|
||||||
});
|
|
||||||
|
|
||||||
role = serviceTokenData.role;
|
role = identityMembership.role;
|
||||||
customRole = serviceTokenData.customRole;
|
customRole = identityMembership.customRole;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -316,6 +340,8 @@ export const getAuthDataProjectPermissions = async ({
|
|||||||
return { permission: memberProjectPermissions };
|
return { permission: memberProjectPermissions };
|
||||||
case VIEWER:
|
case VIEWER:
|
||||||
return { permission: viewerProjectPermission };
|
return { permission: viewerProjectPermission };
|
||||||
|
case NO_ACCESS:
|
||||||
|
return { permission: noAccessProjectPermissions };
|
||||||
case CUSTOM: {
|
case CUSTOM: {
|
||||||
if (!customRole) throw UnauthorizedRequestError();
|
if (!customRole) throw UnauthorizedRequestError();
|
||||||
return {
|
return {
|
||||||
@ -329,3 +355,61 @@ export const getAuthDataProjectPermissions = async ({
|
|||||||
throw UnauthorizedRequestError();
|
throw UnauthorizedRequestError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getWorkspaceRolePermissions = async (role: string, workspaceId: string) => {
|
||||||
|
const isCustomRole = ![ADMIN, MEMBER, VIEWER, NO_ACCESS].includes(role);
|
||||||
|
if (isCustomRole) {
|
||||||
|
const workspaceRole = await Role.findOne({
|
||||||
|
slug: role,
|
||||||
|
isOrgRole: false,
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!workspaceRole) throw BadRequestError({ message: "Role not found" });
|
||||||
|
|
||||||
|
return createMongoAbility<ProjectPermissionSet>(workspaceRole.permissions as RawRuleOf<MongoAbility<ProjectPermissionSet>>[], {
|
||||||
|
conditionsMatcher
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case ADMIN:
|
||||||
|
return adminProjectPermissions;
|
||||||
|
case MEMBER:
|
||||||
|
return memberProjectPermissions;
|
||||||
|
case VIEWER:
|
||||||
|
return viewerProjectPermission;
|
||||||
|
case NO_ACCESS:
|
||||||
|
return noAccessProjectPermissions;
|
||||||
|
default:
|
||||||
|
throw BadRequestError({ message: "Role not found" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts and formats permissions from a CASL Ability object or a raw permission set.
|
||||||
|
* @param ability
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const extractPermissions = (ability: any) => {
|
||||||
|
return ability.A.map((permission: any) => `${permission.action}_${permission.subject}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two sets of permissions to determine if the first set is at least as privileged as the second set.
|
||||||
|
* The function checks if all permissions in the second set are contained within the first set and if the first set has equal or more permissions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const isAtLeastAsPrivilegedWorkspace = (permissions1: MongoAbility<ProjectPermissionSet> | ProjectPermissionSet, permissions2: MongoAbility<ProjectPermissionSet> | ProjectPermissionSet) => {
|
||||||
|
|
||||||
|
const set1 = new Set(extractPermissions(permissions1));
|
||||||
|
const set2 = new Set(extractPermissions(permissions2));
|
||||||
|
|
||||||
|
for (const perm of set2) {
|
||||||
|
if (!set1.has(perm)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return set1.size >= set2.size;
|
||||||
|
}
|
@ -1,9 +1,15 @@
|
|||||||
|
import { Types } from "mongoose";
|
||||||
import { AbilityBuilder, MongoAbility, RawRuleOf, createMongoAbility } from "@casl/ability";
|
import { AbilityBuilder, MongoAbility, RawRuleOf, createMongoAbility } from "@casl/ability";
|
||||||
import { MembershipOrg } from "../../models";
|
import {
|
||||||
import { IRole } from "../models/role";
|
IIdentity,
|
||||||
|
IdentityMembershipOrg,
|
||||||
|
MembershipOrg
|
||||||
|
} from "../../models";
|
||||||
|
import { ActorType, IRole, Role } from "../models";
|
||||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||||
import { ACCEPTED } from "../../variables";
|
import { ACCEPTED, ADMIN, CUSTOM, MEMBER, NO_ACCESS} from "../../variables";
|
||||||
import { conditionsMatcher } from "./ProjectRoleService";
|
import { conditionsMatcher } from "./ProjectRoleService";
|
||||||
|
import { AuthData } from "../../interfaces/middleware";
|
||||||
|
|
||||||
export enum OrgPermissionActions {
|
export enum OrgPermissionActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
@ -20,7 +26,8 @@ export enum OrgPermissionSubjects {
|
|||||||
IncidentAccount = "incident-contact",
|
IncidentAccount = "incident-contact",
|
||||||
Sso = "sso",
|
Sso = "sso",
|
||||||
Billing = "billing",
|
Billing = "billing",
|
||||||
SecretScanning = "secret-scanning"
|
SecretScanning = "secret-scanning",
|
||||||
|
Identity = "identity"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OrgPermissionSet =
|
export type OrgPermissionSet =
|
||||||
@ -32,7 +39,8 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing];
|
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
||||||
|
|
||||||
const buildAdminPermission = () => {
|
const buildAdminPermission = () => {
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
@ -75,6 +83,11 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Billing);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Billing);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
return build({ conditionsMatcher });
|
return build({ conditionsMatcher });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,13 +111,26 @@ const buildMemberPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.SecretScanning);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
return build({ conditionsMatcher });
|
return build({ conditionsMatcher });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const memberPermissions = buildMemberPermission();
|
export const memberPermissions = buildMemberPermission();
|
||||||
|
|
||||||
|
const buildNoAccessPermission = () => {
|
||||||
|
const { build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
|
return build({ conditionsMatcher });
|
||||||
|
}
|
||||||
|
|
||||||
|
export const noAccessPermissions = buildNoAccessPermission();
|
||||||
|
|
||||||
export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
||||||
// TODO(akhilmhdh): speed this up by pulling from cache later
|
// TODO(akhilmhdh): speed this up by pulling from cache later
|
||||||
|
|
||||||
const membership = await MembershipOrg.findOne({
|
const membership = await MembershipOrg.findOne({
|
||||||
user: userId,
|
user: userId,
|
||||||
organization: orgId,
|
organization: orgId,
|
||||||
@ -119,11 +145,13 @@ export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
|||||||
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (membership.role === "admin") return { permission: adminPermissions, membership };
|
if (membership.role === ADMIN) return { permission: adminPermissions, membership };
|
||||||
|
|
||||||
if (membership.role === "member") return { permission: memberPermissions, membership };
|
if (membership.role === MEMBER) return { permission: memberPermissions, membership };
|
||||||
|
|
||||||
|
if (membership.role === NO_ACCESS) return { permission: noAccessPermissions, membership }
|
||||||
|
|
||||||
if (membership.role === "custom") {
|
if (membership.role === CUSTOM) {
|
||||||
const permission = createMongoAbility<OrgPermissionSet>(membership.customRole.permissions, {
|
const permission = createMongoAbility<OrgPermissionSet>(membership.customRole.permissions, {
|
||||||
conditionsMatcher
|
conditionsMatcher
|
||||||
});
|
});
|
||||||
@ -132,3 +160,142 @@ export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
|||||||
|
|
||||||
throw BadRequestError({ message: "User role not found" });
|
throw BadRequestError({ message: "User role not found" });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return permissions for user/service pertaining to organization with id [organizationId]
|
||||||
|
*
|
||||||
|
* Note: should not rely on this function for ST V2 authorization logic
|
||||||
|
* b/c ST V2 does not support role-based access control but also not organization-level resources
|
||||||
|
*/
|
||||||
|
export const getAuthDataOrgPermissions = async ({
|
||||||
|
authData,
|
||||||
|
organizationId
|
||||||
|
}: {
|
||||||
|
authData: AuthData;
|
||||||
|
organizationId: Types.ObjectId;
|
||||||
|
}) => {
|
||||||
|
let role: "admin" | "member" | "no-access" | "custom";
|
||||||
|
let customRole;
|
||||||
|
|
||||||
|
switch (authData.actor.type) {
|
||||||
|
case ActorType.USER: {
|
||||||
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
|
user: authData.authPayload._id,
|
||||||
|
organization: organizationId,
|
||||||
|
status: ACCEPTED
|
||||||
|
})
|
||||||
|
.populate<{ customRole: IRole & { permissions: RawRuleOf<MongoAbility<OrgPermissionSet>>[] } }>(
|
||||||
|
"customRole"
|
||||||
|
)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (!membershipOrg || (membershipOrg.role === "custom" && !membershipOrg.customRole)) {
|
||||||
|
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
||||||
|
}
|
||||||
|
|
||||||
|
role = membershipOrg.role;
|
||||||
|
customRole = membershipOrg.customRole;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ActorType.SERVICE: {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to access organization-level resources with service token"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case ActorType.IDENTITY: {
|
||||||
|
const identityMembershipOrg = await IdentityMembershipOrg.findOne({
|
||||||
|
identity: authData.authPayload._id,
|
||||||
|
organization: organizationId
|
||||||
|
})
|
||||||
|
.populate<{
|
||||||
|
customRole: IRole & { permissions: RawRuleOf<MongoAbility<OrgPermissionSet>>[] };
|
||||||
|
identity: IIdentity
|
||||||
|
}>("customRole identity")
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (!identityMembershipOrg || (identityMembershipOrg.role === "custom" && !identityMembershipOrg.customRole)) {
|
||||||
|
throw UnauthorizedRequestError();
|
||||||
|
}
|
||||||
|
|
||||||
|
role = identityMembershipOrg.role;
|
||||||
|
customRole = identityMembershipOrg.customRole;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw UnauthorizedRequestError();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case ADMIN:
|
||||||
|
return { permission: adminPermissions };
|
||||||
|
case MEMBER:
|
||||||
|
return { permission: memberPermissions };
|
||||||
|
case NO_ACCESS:
|
||||||
|
return { permission: noAccessPermissions };
|
||||||
|
case CUSTOM: {
|
||||||
|
if (!customRole) throw UnauthorizedRequestError();
|
||||||
|
return {
|
||||||
|
permission: createMongoAbility<OrgPermissionSet>(
|
||||||
|
customRole.permissions,
|
||||||
|
{ conditionsMatcher }
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getOrgRolePermissions = async (role: string, orgId: string) => {
|
||||||
|
const isCustomRole = ![ADMIN, MEMBER, NO_ACCESS].includes(role);
|
||||||
|
if (isCustomRole) {
|
||||||
|
const orgRole = await Role.findOne({
|
||||||
|
slug: role,
|
||||||
|
isOrgRole: true,
|
||||||
|
organization: new Types.ObjectId(orgId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!orgRole) throw BadRequestError({ message: "Org Role not found" });
|
||||||
|
|
||||||
|
return createMongoAbility<OrgPermissionSet>(orgRole.permissions as RawRuleOf<MongoAbility<OrgPermissionSet>>[], {
|
||||||
|
conditionsMatcher
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case ADMIN:
|
||||||
|
return adminPermissions;
|
||||||
|
case MEMBER:
|
||||||
|
return memberPermissions;
|
||||||
|
case NO_ACCESS:
|
||||||
|
return noAccessPermissions;
|
||||||
|
default:
|
||||||
|
throw BadRequestError({ message: "User org role not found" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts and formats permissions from a CASL Ability object or a raw permission set.
|
||||||
|
* @param ability
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const extractPermissions = (ability: any) => {
|
||||||
|
return ability.A.map((permission: any) => `${permission.action}_${permission.subject}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two sets of permissions to determine if the first set is at least as privileged as the second set.
|
||||||
|
* The function checks if all permissions in the second set are contained within the first set and if the first set has equal or more permissions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const isAtLeastAsPrivilegedOrg = (permissions1: MongoAbility<OrgPermissionSet> | OrgPermissionSet, permissions2: MongoAbility<OrgPermissionSet> | OrgPermissionSet) => {
|
||||||
|
|
||||||
|
const set1 = new Set(extractPermissions(permissions1));
|
||||||
|
const set2 = new Set(extractPermissions(permissions2));
|
||||||
|
|
||||||
|
for (const perm of set2) {
|
||||||
|
if (!set1.has(perm)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return set1.size >= set2.size;
|
||||||
|
}
|
@ -13,7 +13,7 @@ import {
|
|||||||
SECRET_SHARED
|
SECRET_SHARED
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||||
import { InternalServerError } from "../utils/errors";
|
import { BotNotFoundError, InternalServerError } from "../utils/errors";
|
||||||
import { Folder } from "../models";
|
import { Folder } from "../models";
|
||||||
import { getFolderByPath } from "../services/FolderService";
|
import { getFolderByPath } from "../services/FolderService";
|
||||||
import { getAllImportedSecrets } from "../services/SecretImportService";
|
import { getAllImportedSecrets } from "../services/SecretImportService";
|
||||||
@ -223,7 +223,7 @@ export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) =
|
|||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
}).populate<{ sender: IUser }>("sender", "publicKey");
|
}).populate<{ sender: IUser }>("sender", "publicKey");
|
||||||
|
|
||||||
if (!botKey) throw new Error("Failed to find bot key");
|
if (!botKey) throw BotNotFoundError({ message: `getKey: Failed to find bot key for [workspaceId=${workspaceId}]` })
|
||||||
|
|
||||||
const bot = await Bot.findOne({
|
const bot = await Bot.findOne({
|
||||||
workspace: workspaceId
|
workspace: workspaceId
|
||||||
|
@ -17,7 +17,7 @@ export const validateMembership = async ({
|
|||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId | string;
|
userId: Types.ObjectId | string;
|
||||||
workspaceId: Types.ObjectId | string;
|
workspaceId: Types.ObjectId | string;
|
||||||
acceptedRoles?: Array<"admin" | "member" | "custom" | "viewer">;
|
acceptedRoles?: Array<"admin" | "member" | "custom" | "viewer" | "no-access">;
|
||||||
}) => {
|
}) => {
|
||||||
const membership = await Membership.findOne({
|
const membership = await Membership.findOne({
|
||||||
user: userId,
|
user: userId,
|
||||||
|
@ -18,7 +18,7 @@ export const validateMembershipOrg = async ({
|
|||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId;
|
userId: Types.ObjectId;
|
||||||
organizationId: Types.ObjectId;
|
organizationId: Types.ObjectId;
|
||||||
acceptedRoles?: Array<"owner" | "admin" | "member" | "custom">;
|
acceptedRoles?: Array<"owner" | "admin" | "member" | "custom" | "no-access">;
|
||||||
acceptedStatuses?: Array<"invited" | "accepted">;
|
acceptedStatuses?: Array<"invited" | "accepted">;
|
||||||
}) => {
|
}) => {
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
|
@ -4,6 +4,11 @@ import {
|
|||||||
BotKey,
|
BotKey,
|
||||||
BotOrg,
|
BotOrg,
|
||||||
Folder,
|
Folder,
|
||||||
|
Identity,
|
||||||
|
IdentityMembership,
|
||||||
|
IdentityMembershipOrg,
|
||||||
|
IdentityUniversalAuth,
|
||||||
|
IdentityUniversalAuthClientSecret,
|
||||||
IncidentContactOrg,
|
IncidentContactOrg,
|
||||||
Integration,
|
Integration,
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
@ -16,8 +21,6 @@ import {
|
|||||||
SecretImport,
|
SecretImport,
|
||||||
ServiceToken,
|
ServiceToken,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
ServiceTokenDataV3,
|
|
||||||
ServiceTokenDataV3Key,
|
|
||||||
Tag,
|
Tag,
|
||||||
Webhook,
|
Webhook,
|
||||||
Workspace
|
Workspace
|
||||||
@ -123,6 +126,32 @@ export const deleteOrganization = async ({
|
|||||||
await MembershipOrg.deleteMany({
|
await MembershipOrg.deleteMany({
|
||||||
organization: organization._id
|
organization: organization._id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const identityIds = await IdentityMembershipOrg.distinct("identity", {
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await IdentityMembershipOrg.deleteMany({
|
||||||
|
organization: organization._id
|
||||||
|
});
|
||||||
|
|
||||||
|
await Identity.deleteMany({
|
||||||
|
_id: {
|
||||||
|
$in: identityIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await IdentityUniversalAuth.deleteMany({
|
||||||
|
identity: {
|
||||||
|
$in: identityIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await IdentityUniversalAuthClientSecret.deleteMany({
|
||||||
|
identity: {
|
||||||
|
$in: identityIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await BotOrg.deleteMany({
|
await BotOrg.deleteMany({
|
||||||
organization: organization._id
|
organization: organization._id
|
||||||
@ -268,13 +297,7 @@ export const deleteOrganization = async ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await ServiceTokenDataV3.deleteMany({
|
await IdentityMembership.deleteMany({
|
||||||
workspace: {
|
|
||||||
$in: workspaceIds
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceTokenDataV3Key.deleteMany({
|
|
||||||
workspace: {
|
workspace: {
|
||||||
$in: workspaceIds
|
$in: workspaceIds
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ export const apiLimiter = rateLimit({
|
|||||||
// errorHandler: console.error.bind(null, 'rate-limit-mongo')
|
// errorHandler: console.error.bind(null, 'rate-limit-mongo')
|
||||||
// }),
|
// }),
|
||||||
windowMs: 60 * 1000,
|
windowMs: 60 * 1000,
|
||||||
max: 350,
|
max: 480,
|
||||||
standardHeaders: true,
|
standardHeaders: true,
|
||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
skip: (request) => {
|
skip: (request) => {
|
||||||
@ -30,7 +30,7 @@ const authLimit = rateLimit({
|
|||||||
// collectionName: "expressRateRecords-authLimit",
|
// collectionName: "expressRateRecords-authLimit",
|
||||||
// }),
|
// }),
|
||||||
windowMs: 60 * 1000,
|
windowMs: 60 * 1000,
|
||||||
max: 100,
|
max: 300,
|
||||||
standardHeaders: true,
|
standardHeaders: true,
|
||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
keyGenerator: (req, res) => {
|
keyGenerator: (req, res) => {
|
||||||
@ -46,8 +46,8 @@ export const passwordLimiter = rateLimit({
|
|||||||
// errorHandler: console.error.bind(null, 'rate-limit-mongo'),
|
// errorHandler: console.error.bind(null, 'rate-limit-mongo'),
|
||||||
// collectionName: "expressRateRecords-passwordLimiter",
|
// collectionName: "expressRateRecords-passwordLimiter",
|
||||||
// }),
|
// }),
|
||||||
windowMs: 60 * 60 * 1000,
|
windowMs: 60 * 1000,
|
||||||
max: 10,
|
max: 300,
|
||||||
standardHeaders: true,
|
standardHeaders: true,
|
||||||
legacyHeaders: false,
|
legacyHeaders: false,
|
||||||
keyGenerator: (req, res) => {
|
keyGenerator: (req, res) => {
|
||||||
|
@ -579,7 +579,9 @@ export const getSecretsHelper = async ({
|
|||||||
event: "secrets pulled",
|
event: "secrets pulled",
|
||||||
distinctId: await TelemetryService.getDistinctId({ authData }),
|
distinctId: await TelemetryService.getDistinctId({ authData }),
|
||||||
properties: {
|
properties: {
|
||||||
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
|
numberOfSecrets: shouldRecordK8Event
|
||||||
|
? approximateForNoneCapturedEvents
|
||||||
|
: secrets.length,
|
||||||
environment,
|
environment,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
folderId,
|
folderId,
|
||||||
@ -611,42 +613,86 @@ export const getSecretHelper = async ({
|
|||||||
type,
|
type,
|
||||||
authData,
|
authData,
|
||||||
secretPath = "/",
|
secretPath = "/",
|
||||||
include_imports = true
|
include_imports = true,
|
||||||
|
version
|
||||||
}: GetSecretParams) => {
|
}: GetSecretParams) => {
|
||||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||||
secretName,
|
secretName,
|
||||||
workspaceId: new Types.ObjectId(workspaceId)
|
workspaceId: new Types.ObjectId(workspaceId)
|
||||||
});
|
});
|
||||||
let secret: ISecret | null | undefined = null;
|
let secret: ISecret | null | undefined = null;
|
||||||
|
|
||||||
// if using service token filter towards the folderId by secretpath
|
// if using service token filter towards the folderId by secretpath
|
||||||
|
|
||||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||||
|
|
||||||
// try getting personal secret first (if exists)
|
// try getting personal secret first (if exists)
|
||||||
secret = await Secret.findOne({
|
if (version === undefined) {
|
||||||
secretBlindIndex,
|
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
|
||||||
environment,
|
|
||||||
folder: folderId,
|
|
||||||
type: type ?? SECRET_PERSONAL,
|
|
||||||
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
|
||||||
}).lean();
|
|
||||||
|
|
||||||
if (!secret) {
|
|
||||||
// case: failed to find personal secret matching criteria
|
|
||||||
// -> find shared secret matching criteria
|
|
||||||
secret = await Secret.findOne({
|
secret = await Secret.findOne({
|
||||||
secretBlindIndex,
|
secretBlindIndex,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
environment,
|
environment,
|
||||||
folder: folderId,
|
folder: folderId,
|
||||||
type: SECRET_SHARED
|
type: type ?? SECRET_PERSONAL,
|
||||||
|
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {})
|
||||||
}).lean();
|
}).lean();
|
||||||
|
} else {
|
||||||
|
const secretVersion = await SecretVersion.findOne({
|
||||||
|
secretBlindIndex,
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
folder: folderId,
|
||||||
|
type: type ?? SECRET_PERSONAL,
|
||||||
|
version
|
||||||
|
}).lean();
|
||||||
|
|
||||||
|
if (secretVersion) {
|
||||||
|
secret = await new Secret({
|
||||||
|
...secretVersion,
|
||||||
|
_id: secretVersion?.secret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secret) {
|
||||||
|
// case: failed to find personal secret matching criteria
|
||||||
|
// -> find shared secret matching criteria
|
||||||
|
if (version === undefined) {
|
||||||
|
secret = await Secret.findOne({
|
||||||
|
secretBlindIndex,
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
folder: folderId,
|
||||||
|
type: SECRET_SHARED
|
||||||
|
}).lean();
|
||||||
|
} else {
|
||||||
|
const secretVersion = await SecretVersion.findOne({
|
||||||
|
secretBlindIndex,
|
||||||
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
|
environment,
|
||||||
|
folder: folderId,
|
||||||
|
type: SECRET_SHARED,
|
||||||
|
version
|
||||||
|
}).lean();
|
||||||
|
|
||||||
|
if (secretVersion) {
|
||||||
|
secret = await new Secret({
|
||||||
|
...secretVersion,
|
||||||
|
_id: secretVersion?.secret
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!secret && include_imports) {
|
if (!secret && include_imports) {
|
||||||
// if still no secret found search in imported secret and retreive
|
// if still no secret found search in imported secret and retreive
|
||||||
secret = await getAnImportedSecret(secretName, workspaceId.toString(), environment, folderId);
|
secret = await getAnImportedSecret(
|
||||||
|
secretName,
|
||||||
|
workspaceId.toString(),
|
||||||
|
environment,
|
||||||
|
folderId,
|
||||||
|
version
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!secret) throw SecretNotFoundError();
|
if (!secret) throw SecretNotFoundError();
|
||||||
@ -1141,11 +1187,12 @@ const recursivelyExpandSecret = async (
|
|||||||
const secRefKey = entities[entities.length - 1];
|
const secRefKey = entities[entities.length - 1];
|
||||||
|
|
||||||
const val = await fetchCrossEnv(secRefEnv, secRefPath, secRefKey);
|
const val = await fetchCrossEnv(secRefEnv, secRefPath, secRefKey);
|
||||||
interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val);
|
if (val !== undefined) {
|
||||||
|
interpolatedValue = interpolatedValue.replaceAll(interpolationSyntax, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expandedSec[key] = interpolatedValue;
|
expandedSec[key] = interpolatedValue;
|
||||||
return interpolatedValue;
|
return interpolatedValue;
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
Bot,
|
Bot,
|
||||||
BotKey,
|
BotKey,
|
||||||
Folder,
|
Folder,
|
||||||
|
IdentityMembership,
|
||||||
Integration,
|
Integration,
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
Key,
|
Key,
|
||||||
@ -12,8 +13,6 @@ import {
|
|||||||
SecretImport,
|
SecretImport,
|
||||||
ServiceToken,
|
ServiceToken,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
ServiceTokenDataV3,
|
|
||||||
ServiceTokenDataV3Key,
|
|
||||||
Tag,
|
Tag,
|
||||||
Webhook,
|
Webhook,
|
||||||
Workspace
|
Workspace
|
||||||
@ -178,12 +177,8 @@ export const deleteWorkspace = async ({
|
|||||||
await ServiceTokenData.deleteMany({
|
await ServiceTokenData.deleteMany({
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
});
|
});
|
||||||
|
|
||||||
await ServiceTokenDataV3.deleteMany({
|
await IdentityMembership.deleteMany({
|
||||||
workspace: workspace._id
|
|
||||||
});
|
|
||||||
|
|
||||||
await ServiceTokenDataV3Key.deleteMany({
|
|
||||||
workspace: workspace._id
|
workspace: workspace._id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import {
|
|||||||
secretSnapshot as eeSecretSnapshotRouter,
|
secretSnapshot as eeSecretSnapshotRouter,
|
||||||
users as eeUsersRouter,
|
users as eeUsersRouter,
|
||||||
workspace as eeWorkspaceRouter,
|
workspace as eeWorkspaceRouter,
|
||||||
|
identities as v1IdentitiesRouter,
|
||||||
roles as v1RoleRouter,
|
roles as v1RoleRouter,
|
||||||
secretApprovalPolicy as v1SecretApprovalPolicyRouter,
|
secretApprovalPolicy as v1SecretApprovalPolicyRouter,
|
||||||
secretApprovalRequest as v1SecretApprovalRequestRouter,
|
secretApprovalRequest as v1SecretApprovalRequestRouter,
|
||||||
@ -33,7 +34,6 @@ import {
|
|||||||
secretScanning as v1SecretScanningRouter
|
secretScanning as v1SecretScanningRouter
|
||||||
} from "./ee/routes/v1";
|
} from "./ee/routes/v1";
|
||||||
import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3";
|
import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3";
|
||||||
import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3";
|
|
||||||
import {
|
import {
|
||||||
admin as v1AdminRouter,
|
admin as v1AdminRouter,
|
||||||
auth as v1AuthRouter,
|
auth as v1AuthRouter,
|
||||||
@ -52,6 +52,7 @@ import {
|
|||||||
secretsFolder as v1SecretsFolder,
|
secretsFolder as v1SecretsFolder,
|
||||||
serviceToken as v1ServiceTokenRouter,
|
serviceToken as v1ServiceTokenRouter,
|
||||||
signup as v1SignupRouter,
|
signup as v1SignupRouter,
|
||||||
|
universalAuth as v1UniversalAuthRouter,
|
||||||
userAction as v1UserActionRouter,
|
userAction as v1UserActionRouter,
|
||||||
user as v1UserRouter,
|
user as v1UserRouter,
|
||||||
webhooks as v1WebhooksRouter,
|
webhooks as v1WebhooksRouter,
|
||||||
@ -198,6 +199,7 @@ const main = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// (EE) routes
|
// (EE) routes
|
||||||
|
app.use("/api/v1/identities", v1IdentitiesRouter);
|
||||||
app.use("/api/v1/secret", eeSecretRouter);
|
app.use("/api/v1/secret", eeSecretRouter);
|
||||||
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
||||||
app.use("/api/v1/users", eeUsersRouter);
|
app.use("/api/v1/users", eeUsersRouter);
|
||||||
@ -205,14 +207,14 @@ const main = async () => {
|
|||||||
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
||||||
app.use("/api/v1/sso", eeSSORouter);
|
app.use("/api/v1/sso", eeSSORouter);
|
||||||
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
||||||
app.use("/api/v3/api-key", v3apiKeyDataRouter); // new
|
app.use("/api/v3/api-key", v3apiKeyDataRouter);
|
||||||
app.use("/api/v3/service-token", v3ServiceTokenDataRouter); // new
|
|
||||||
app.use("/api/v1/secret-rotation-providers", v1SecretRotationProviderRouter);
|
app.use("/api/v1/secret-rotation-providers", v1SecretRotationProviderRouter);
|
||||||
app.use("/api/v1/secret-rotations", v1SecretRotation);
|
app.use("/api/v1/secret-rotations", v1SecretRotation);
|
||||||
|
|
||||||
// v1 routes
|
// v1 routes
|
||||||
app.use("/api/v1/signup", v1SignupRouter);
|
app.use("/api/v1/signup", v1SignupRouter);
|
||||||
app.use("/api/v1/auth", v1AuthRouter);
|
app.use("/api/v1/auth", v1AuthRouter);
|
||||||
|
app.use("/api/v1/auth", v1UniversalAuthRouter); // new
|
||||||
app.use("/api/v1/admin", v1AdminRouter);
|
app.use("/api/v1/admin", v1AdminRouter);
|
||||||
app.use("/api/v1/bot", v1BotRouter);
|
app.use("/api/v1/bot", v1BotRouter);
|
||||||
app.use("/api/v1/user", v1UserRouter);
|
app.use("/api/v1/user", v1UserRouter);
|
||||||
@ -220,7 +222,7 @@ const main = async () => {
|
|||||||
app.use("/api/v1/organization", v1OrganizationRouter);
|
app.use("/api/v1/organization", v1OrganizationRouter);
|
||||||
app.use("/api/v1/workspace", v1WorkspaceRouter);
|
app.use("/api/v1/workspace", v1WorkspaceRouter);
|
||||||
app.use("/api/v1/membership-org", v1MembershipOrgRouter);
|
app.use("/api/v1/membership-org", v1MembershipOrgRouter);
|
||||||
app.use("/api/v1/membership", v1MembershipRouter); //
|
app.use("/api/v1/membership", v1MembershipRouter);
|
||||||
app.use("/api/v1/key", v1KeyRouter);
|
app.use("/api/v1/key", v1KeyRouter);
|
||||||
app.use("/api/v1/invite-org", v1InviteOrgRouter);
|
app.use("/api/v1/invite-org", v1InviteOrgRouter);
|
||||||
app.use("/api/v1/secret", v1SecretRouter); // deprecate
|
app.use("/api/v1/secret", v1SecretRouter); // deprecate
|
||||||
@ -247,7 +249,7 @@ const main = async () => {
|
|||||||
app.use("/api/v2/workspace", v2TagsRouter);
|
app.use("/api/v2/workspace", v2TagsRouter);
|
||||||
app.use("/api/v2/workspace", v2WorkspaceRouter);
|
app.use("/api/v2/workspace", v2WorkspaceRouter);
|
||||||
app.use("/api/v2/secret", v2SecretRouter); // deprecate
|
app.use("/api/v2/secret", v2SecretRouter); // deprecate
|
||||||
app.use("/api/v2/secrets", v2SecretsRouter); // note: in the process of moving to v3/secrets
|
app.use("/api/v2/secrets", v2SecretsRouter);
|
||||||
app.use("/api/v2/service-token", v2ServiceTokenDataRouter);
|
app.use("/api/v2/service-token", v2ServiceTokenDataRouter);
|
||||||
|
|
||||||
// v3 routes (experimental)
|
// v3 routes (experimental)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { IServiceTokenData, IServiceTokenDataV3, IUser } from "../../models";
|
import { IIdentity, IServiceTokenData, IUser } from "../../models";
|
||||||
import { ServiceActor, ServiceActorV3, UserActor, UserAgentType } from "../../ee/models";
|
import { IdentityActor, ServiceActor, UserActor, UserAgentType } from "../../ee/models";
|
||||||
|
|
||||||
interface BaseAuthData {
|
interface BaseAuthData {
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
@ -14,9 +14,9 @@ export interface UserAuthData extends BaseAuthData {
|
|||||||
authPayload: IUser;
|
authPayload: IUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceTokenV3AuthData extends BaseAuthData {
|
export interface IdentityAuthData extends BaseAuthData {
|
||||||
actor: ServiceActorV3;
|
actor: IdentityActor;
|
||||||
authPayload: IServiceTokenDataV3;
|
authPayload: IIdentity;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceTokenAuthData extends BaseAuthData {
|
export interface ServiceTokenAuthData extends BaseAuthData {
|
||||||
@ -24,4 +24,4 @@ export interface ServiceTokenAuthData extends BaseAuthData {
|
|||||||
authPayload: IServiceTokenData;
|
authPayload: IServiceTokenData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AuthData = UserAuthData | ServiceTokenV3AuthData | ServiceTokenAuthData;
|
export type AuthData = UserAuthData | IdentityAuthData | ServiceTokenAuthData;
|
@ -38,6 +38,7 @@ export interface GetSecretParams {
|
|||||||
type?: "shared" | "personal";
|
type?: "shared" | "personal";
|
||||||
authData: AuthData;
|
authData: AuthData;
|
||||||
include_imports?: boolean;
|
include_imports?: boolean;
|
||||||
|
version?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateSecretParams {
|
export interface UpdateSecretParams {
|
||||||
|
@ -39,8 +39,9 @@ export const requestErrorHandler: ErrorRequestHandler = async (
|
|||||||
|
|
||||||
Sentry.captureException(error);
|
Sentry.captureException(error);
|
||||||
|
|
||||||
delete (<any>error).stacktrace // remove stack trace from being sent to client
|
res.status((<RequestError>error).statusCode).send(
|
||||||
res.status((<RequestError>error).statusCode).json(error); // revise json part here
|
await error.format(req)
|
||||||
|
);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
@ -50,7 +50,7 @@ const requireAuth = ({
|
|||||||
case AuthMode.SERVICE_TOKEN:
|
case AuthMode.SERVICE_TOKEN:
|
||||||
req.serviceTokenData = authData.authPayload;
|
req.serviceTokenData = authData.authPayload;
|
||||||
break;
|
break;
|
||||||
case AuthMode.SERVICE_ACCESS_TOKEN:
|
case AuthMode.IDENTITY_ACCESS_TOKEN:
|
||||||
req.serviceTokenData = authData.authPayload;
|
req.serviceTokenData = authData.authPayload;
|
||||||
break;
|
break;
|
||||||
case AuthMode.API_KEY:
|
case AuthMode.API_KEY:
|
||||||
|
38
backend/src/models/identity.ts
Normal file
38
backend/src/models/identity.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
import { IPType } from "../ee/models";
|
||||||
|
|
||||||
|
export interface IIdentityTrustedIp {
|
||||||
|
ipAddress: string;
|
||||||
|
type: IPType;
|
||||||
|
prefix: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum IdentityAuthMethod {
|
||||||
|
UNIVERSAL_AUTH = "universal-auth"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IIdentity extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
name: string;
|
||||||
|
authMethod?: IdentityAuthMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
const identitySchema = new Schema(
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
authMethod: {
|
||||||
|
type: String,
|
||||||
|
enum: IdentityAuthMethod,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Identity = model<IIdentity>("Identity", identitySchema);
|
104
backend/src/models/identityAccessToken.ts
Normal file
104
backend/src/models/identityAccessToken.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
import { IIdentityTrustedIp } from "./identity";
|
||||||
|
import { IPType } from "../ee/models/trustedIp";
|
||||||
|
|
||||||
|
export interface IIdentityAccessToken extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
identity: Types.ObjectId;
|
||||||
|
identityUniversalAuthClientSecret?: Types.ObjectId;
|
||||||
|
accessTokenLastUsedAt?: Date;
|
||||||
|
accessTokenLastRenewedAt?: Date;
|
||||||
|
accessTokenNumUses: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenTrustedIps: Array<IIdentityTrustedIp>;
|
||||||
|
isAccessTokenRevoked: boolean;
|
||||||
|
updatedAt: Date;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityAccessTokenSchema = new Schema(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Identity",
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
identityUniversalAuthClientSecret: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "IdentityUniversalAuthClientSecret",
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
accessTokenLastUsedAt: {
|
||||||
|
type: Date,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
accessTokenLastRenewedAt: {
|
||||||
|
type: Date,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
accessTokenNumUses: {
|
||||||
|
// number of times access token has been used
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessTokenNumUsesLimit: {
|
||||||
|
// number of times access token can be used for
|
||||||
|
type: Number,
|
||||||
|
default: 0, // default: used as many times as needed
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessTokenTTL: { // seconds
|
||||||
|
// incremental lifetime
|
||||||
|
type: Number,
|
||||||
|
default: 2592000, // 30 days
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessTokenMaxTTL: { // seconds
|
||||||
|
// max lifetime
|
||||||
|
type: Number,
|
||||||
|
default: 2592000, // 30 days
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessTokenTrustedIps: {
|
||||||
|
type: [
|
||||||
|
{
|
||||||
|
ipAddress: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
IPType.IPV4,
|
||||||
|
IPType.IPV6
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
prefix: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: [{
|
||||||
|
ipAddress: "0.0.0.0",
|
||||||
|
type: IPType.IPV4.toString(),
|
||||||
|
prefix: 0
|
||||||
|
}],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isAccessTokenRevoked: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const IdentityAccessToken = model<IIdentityAccessToken>("IdentityAccessToken", identityAccessTokenSchema);
|
39
backend/src/models/identityMembership.ts
Normal file
39
backend/src/models/identityMembership.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../variables";
|
||||||
|
|
||||||
|
export interface IIdentityMembership {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
identity: Types.ObjectId;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
role: "admin" | "member" | "viewer" | "no-access" | "custom";
|
||||||
|
customRole: Types.ObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityMembershipSchema = new Schema<IIdentityMembership>(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Identity"
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Workspace",
|
||||||
|
required: true,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: String,
|
||||||
|
enum: [ADMIN, MEMBER, VIEWER, CUSTOM, NO_ACCESS],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
customRole: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Role"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const IdentityMembership = model<IIdentityMembership>("IdentityMembership", identityMembershipSchema);
|
37
backend/src/models/identityMembershipOrg.ts
Normal file
37
backend/src/models/identityMembershipOrg.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS} from "../variables";
|
||||||
|
|
||||||
|
export interface IIdentityMembershipOrg {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
identity: Types.ObjectId;
|
||||||
|
organization: Types.ObjectId;
|
||||||
|
role: "admin" | "member" | "no-access" | "custom";
|
||||||
|
customRole: Types.ObjectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityMembershipOrgSchema = new Schema<IIdentityMembershipOrg>(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Identity"
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Organization"
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: String,
|
||||||
|
enum: [ADMIN, MEMBER, NO_ACCESS, CUSTOM],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
customRole: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Role"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const IdentityMembershipOrg = model<IIdentityMembershipOrg>("IdentityMembershipOrg", identityMembershipOrgSchema);
|
107
backend/src/models/identityUniversalAuth.ts
Normal file
107
backend/src/models/identityUniversalAuth.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
import { IPType } from "../ee/models";
|
||||||
|
import { IIdentityTrustedIp } from "./identity";
|
||||||
|
|
||||||
|
export interface IIdentityUniversalAuth extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
identity: Types.ObjectId;
|
||||||
|
clientId: string;
|
||||||
|
clientSecretTrustedIps: Array<IIdentityTrustedIp>;
|
||||||
|
accessTokenTTL: number;
|
||||||
|
accessTokenMaxTTL: number;
|
||||||
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenTrustedIps: Array<IIdentityTrustedIp>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityUniversalAuthSchema = new Schema(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Identity",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
clientId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
clientSecretTrustedIps: {
|
||||||
|
type: [
|
||||||
|
{
|
||||||
|
ipAddress: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
IPType.IPV4,
|
||||||
|
IPType.IPV6
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
prefix: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: [{
|
||||||
|
ipAddress: "0.0.0.0",
|
||||||
|
type: IPType.IPV4.toString(),
|
||||||
|
prefix: 0
|
||||||
|
}],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessTokenTTL: { // seconds
|
||||||
|
// incremental lifetime
|
||||||
|
type: Number,
|
||||||
|
default: 7200,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessTokenMaxTTL: { // seconds
|
||||||
|
// max lifetime
|
||||||
|
type: Number,
|
||||||
|
default: 7200,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessTokenNumUsesLimit: {
|
||||||
|
// number of times access token can be used for
|
||||||
|
type: Number,
|
||||||
|
default: 0, // default: used as many times as needed
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
accessTokenTrustedIps: {
|
||||||
|
type: [
|
||||||
|
{
|
||||||
|
ipAddress: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: [
|
||||||
|
IPType.IPV4,
|
||||||
|
IPType.IPV6
|
||||||
|
],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
prefix: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: [{
|
||||||
|
ipAddress: "0.0.0.0",
|
||||||
|
type: IPType.IPV4.toString(),
|
||||||
|
prefix: 0
|
||||||
|
}],
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const IdentityUniversalAuth = model<IIdentityUniversalAuth>("IdentityUniversalAuth", identityUniversalAuthSchema);
|
81
backend/src/models/identityUniversalAuthClientSecret.ts
Normal file
81
backend/src/models/identityUniversalAuthClientSecret.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
export interface IIdentityUniversalAuthClientSecret extends Document {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
identity: Types.ObjectId;
|
||||||
|
identityUniversalAuth : Types.ObjectId;
|
||||||
|
description: string;
|
||||||
|
clientSecretPrefix: string;
|
||||||
|
clientSecretHash: string;
|
||||||
|
clientSecretLastUsedAt?: Date;
|
||||||
|
clientSecretNumUses: number;
|
||||||
|
clientSecretNumUsesLimit: number;
|
||||||
|
clientSecretTTL: number;
|
||||||
|
updatedAt: Date;
|
||||||
|
createdAt: Date;
|
||||||
|
isClientSecretRevoked: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityUniversalAuthClientSecretSchema = new Schema(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Identity",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
identityUniversalAuth: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "IdentityUniversalAuth",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
clientSecretPrefix: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
clientSecretHash: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
clientSecretLastUsedAt: {
|
||||||
|
type: Date,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
clientSecretNumUses: {
|
||||||
|
// number of times client secret has been used
|
||||||
|
// in login operation
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
clientSecretNumUsesLimit: {
|
||||||
|
// number of times client secret can be used for
|
||||||
|
// a login operation
|
||||||
|
type: Number,
|
||||||
|
default: 0, // default: used as many times as needed
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
clientSecretTTL: {
|
||||||
|
type: Number,
|
||||||
|
default: 0, // default: does not expire
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isClientSecretRevoked: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
identityUniversalAuthClientSecretSchema.index(
|
||||||
|
{ identityUniversalAuth: 1, isClientSecretRevoked: 1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
export const IdentityUniversalAuthClientSecret = model<IIdentityUniversalAuthClientSecret>("IdentityUniversalAuthClientSecret", identityUniversalAuthClientSecretSchema);
|
@ -20,8 +20,15 @@ export * from "./user";
|
|||||||
export * from "./userAction";
|
export * from "./userAction";
|
||||||
export * from "./workspace";
|
export * from "./workspace";
|
||||||
export * from "./serviceTokenData"; // TODO: deprecate
|
export * from "./serviceTokenData"; // TODO: deprecate
|
||||||
export * from "./serviceTokenDataV3";
|
|
||||||
export * from "./serviceTokenDataV3Key";
|
// new
|
||||||
|
export * from "./identity";
|
||||||
|
export * from "./identityMembership";
|
||||||
|
export * from "./identityMembershipOrg";
|
||||||
|
export * from "./identityUniversalAuth";
|
||||||
|
export * from "./identityUniversalAuthClientSecret";
|
||||||
|
export * from "./identityAccessToken";
|
||||||
|
|
||||||
export * from "./apiKeyData"; // TODO: deprecate
|
export * from "./apiKeyData"; // TODO: deprecate
|
||||||
export * from "./apiKeyDataV2";
|
export * from "./apiKeyDataV2";
|
||||||
export * from "./loginSRPDetail";
|
export * from "./loginSRPDetail";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Schema, Types, model } from "mongoose";
|
import { Schema, Types, model } from "mongoose";
|
||||||
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../variables";
|
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../variables";
|
||||||
|
|
||||||
export interface IMembershipPermission {
|
export interface IMembershipPermission {
|
||||||
environmentSlug: string;
|
environmentSlug: string;
|
||||||
@ -11,7 +11,7 @@ export interface IMembership {
|
|||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
inviteEmail?: string;
|
inviteEmail?: string;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
role: "admin" | "member" | "viewer" | "custom";
|
role: "admin" | "member" | "viewer" | "no-access" | "custom";
|
||||||
customRole: Types.ObjectId;
|
customRole: Types.ObjectId;
|
||||||
deniedPermissions: IMembershipPermission[];
|
deniedPermissions: IMembershipPermission[];
|
||||||
}
|
}
|
||||||
@ -44,7 +44,7 @@ const membershipSchema = new Schema<IMembership>(
|
|||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ADMIN, MEMBER, VIEWER, CUSTOM],
|
enum: [ADMIN, MEMBER, VIEWER, NO_ACCESS, CUSTOM],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
customRole: {
|
customRole: {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Document, Schema, Types, model } from "mongoose";
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
import { ACCEPTED, ADMIN, CUSTOM, INVITED, MEMBER } from "../variables";
|
import { ACCEPTED, ADMIN, CUSTOM, INVITED, MEMBER, NO_ACCESS } from "../variables";
|
||||||
|
|
||||||
export interface IMembershipOrg extends Document {
|
export interface IMembershipOrg extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
inviteEmail: string;
|
inviteEmail: string;
|
||||||
organization: Types.ObjectId;
|
organization: Types.ObjectId;
|
||||||
role: "owner" | "admin" | "member" | "custom";
|
role: "admin" | "member" | "no-access" | "custom";
|
||||||
customRole: Types.ObjectId;
|
customRole: Types.ObjectId;
|
||||||
status: "invited" | "accepted";
|
status: "invited" | "accepted";
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@ const membershipOrgSchema = new Schema(
|
|||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ADMIN, MEMBER, CUSTOM],
|
enum: [ADMIN, MEMBER, NO_ACCESS, CUSTOM],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
|
@ -1,137 +0,0 @@
|
|||||||
import { Document, Schema, Types, model } from "mongoose";
|
|
||||||
import { IPType } from "../ee/models";
|
|
||||||
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../variables";
|
|
||||||
|
|
||||||
export interface IServiceTokenV3TrustedIp {
|
|
||||||
ipAddress: string;
|
|
||||||
type: IPType;
|
|
||||||
prefix: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IServiceTokenDataV3 extends Document {
|
|
||||||
_id: Types.ObjectId;
|
|
||||||
name: string;
|
|
||||||
workspace: Types.ObjectId;
|
|
||||||
user: Types.ObjectId;
|
|
||||||
publicKey: string;
|
|
||||||
isActive: boolean;
|
|
||||||
refreshTokenLastUsed?: Date;
|
|
||||||
accessTokenLastUsed?: Date;
|
|
||||||
refreshTokenUsageCount: number;
|
|
||||||
accessTokenUsageCount: number;
|
|
||||||
tokenVersion: number;
|
|
||||||
isRefreshTokenRotationEnabled: boolean;
|
|
||||||
expiresAt?: Date;
|
|
||||||
accessTokenTTL: number;
|
|
||||||
role: "admin" | "member" | "viewer" | "custom";
|
|
||||||
customRole: Types.ObjectId;
|
|
||||||
trustedIps: Array<IServiceTokenV3TrustedIp>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serviceTokenDataV3Schema = new Schema(
|
|
||||||
{
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
workspace: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: "Workspace",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: "User",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
publicKey: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
isActive: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
refreshTokenLastUsed: {
|
|
||||||
type: Date,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
accessTokenLastUsed: {
|
|
||||||
type: Date,
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
refreshTokenUsageCount: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
accessTokenUsageCount: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
tokenVersion: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
isRefreshTokenRotationEnabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
expiresAt: { // consider revising field name
|
|
||||||
type: Date,
|
|
||||||
required: false,
|
|
||||||
// expires: 0
|
|
||||||
},
|
|
||||||
accessTokenTTL: { // seconds
|
|
||||||
type: Number,
|
|
||||||
default: 7200,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
role: {
|
|
||||||
type: String,
|
|
||||||
enum: [ADMIN, MEMBER, VIEWER, CUSTOM],
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
customRole: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: "Role"
|
|
||||||
},
|
|
||||||
trustedIps: {
|
|
||||||
type: [
|
|
||||||
{
|
|
||||||
ipAddress: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
enum: [
|
|
||||||
IPType.IPV4,
|
|
||||||
IPType.IPV6
|
|
||||||
],
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
prefix: {
|
|
||||||
type: Number,
|
|
||||||
required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
default: [{
|
|
||||||
ipAddress: "0.0.0.0",
|
|
||||||
type: IPType.IPV4.toString(),
|
|
||||||
prefix: 0
|
|
||||||
}],
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timestamps: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ServiceTokenDataV3 = model<IServiceTokenDataV3>("ServiceTokenDataV3", serviceTokenDataV3Schema);
|
|
@ -1,43 +0,0 @@
|
|||||||
import { Document, Schema, Types, model } from "mongoose";
|
|
||||||
|
|
||||||
export interface IServiceTokenDataV3Key extends Document {
|
|
||||||
_id: Types.ObjectId;
|
|
||||||
encryptedKey: string;
|
|
||||||
nonce: string;
|
|
||||||
sender: Types.ObjectId;
|
|
||||||
serviceTokenData: Types.ObjectId;
|
|
||||||
workspace: Types.ObjectId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serviceTokenDataV3KeySchema = new Schema(
|
|
||||||
{
|
|
||||||
encryptedKey: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
nonce: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
sender: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: "User",
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
serviceTokenData: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: "ServiceTokenDataV3",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
workspace: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: "Workspace",
|
|
||||||
required: true,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
timestamps: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ServiceTokenDataV3Key = model<IServiceTokenDataV3Key>("ServiceTokenDataV3Key", serviceTokenDataV3KeySchema);
|
|
@ -1,6 +1,7 @@
|
|||||||
import signup from "./signup";
|
import signup from "./signup";
|
||||||
import bot from "./bot";
|
import bot from "./bot";
|
||||||
import auth from "./auth";
|
import auth from "./auth";
|
||||||
|
import universalAuth from "./universalAuth";
|
||||||
import user from "./user";
|
import user from "./user";
|
||||||
import userAction from "./userAction";
|
import userAction from "./userAction";
|
||||||
import organization from "./organization";
|
import organization from "./organization";
|
||||||
@ -23,6 +24,7 @@ import admin from "./admin";
|
|||||||
export {
|
export {
|
||||||
signup,
|
signup,
|
||||||
auth,
|
auth,
|
||||||
|
universalAuth,
|
||||||
bot,
|
bot,
|
||||||
user,
|
user,
|
||||||
userAction,
|
userAction,
|
||||||
|
@ -156,12 +156,20 @@ router.get(
|
|||||||
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
|
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
|
integrationAuthController.deleteIntegrationAuths
|
||||||
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:integrationAuthId",
|
"/:integrationAuthId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
integrationAuthController.deleteIntegrationAuth
|
integrationAuthController.deleteIntegrationAuthById
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
|
|||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
secretImpsController.createSecretImp
|
secretImpsController.createSecretImp
|
||||||
);
|
);
|
||||||
@ -15,7 +15,7 @@ router.post(
|
|||||||
router.put(
|
router.put(
|
||||||
"/:id",
|
"/:id",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
secretImpsController.updateSecretImport
|
secretImpsController.updateSecretImport
|
||||||
);
|
);
|
||||||
@ -23,7 +23,7 @@ router.put(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/:id",
|
"/:id",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
secretImpsController.deleteSecretImport
|
secretImpsController.deleteSecretImport
|
||||||
);
|
);
|
||||||
@ -31,7 +31,7 @@ router.delete(
|
|||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
secretImpsController.getSecretImports
|
secretImpsController.getSecretImports
|
||||||
);
|
);
|
||||||
|
@ -12,7 +12,7 @@ import { AuthMode } from "../../variables";
|
|||||||
router.post(
|
router.post(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
createFolder
|
createFolder
|
||||||
);
|
);
|
||||||
@ -20,7 +20,7 @@ router.post(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/:folderName",
|
"/:folderName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
updateFolderById
|
updateFolderById
|
||||||
);
|
);
|
||||||
@ -28,7 +28,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/:folderName",
|
"/:folderName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
deleteFolder
|
deleteFolder
|
||||||
);
|
);
|
||||||
@ -36,7 +36,7 @@ router.delete(
|
|||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
getFolders
|
getFolders
|
||||||
);
|
);
|
||||||
|
66
backend/src/routes/v1/universalAuth.ts
Normal file
66
backend/src/routes/v1/universalAuth.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
import express from "express";
|
||||||
|
const router = express.Router();
|
||||||
|
import { requireAuth } from "../../middleware";
|
||||||
|
import { universalAuthController } from "../../controllers/v1";
|
||||||
|
import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/token/renew",
|
||||||
|
universalAuthController.renewAccessToken
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/universal-auth/login",
|
||||||
|
universalAuthController.loginIdentityUniversalAuth
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/universal-auth/identities/:identityId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
universalAuthController.attachIdentityUniversalAuth
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/universal-auth/identities/:identityId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
universalAuthController.updateIdentityUniversalAuth
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/universal-auth/identities/:identityId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
universalAuthController.getIdentityUniversalAuth
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/universal-auth/identities/:identityId/client-secrets",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
universalAuthController.createUniversalAuthClientSecret
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/universal-auth/identities/:identityId/client-secrets",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
universalAuthController.getUniversalAuthClientSecretsDetails
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/universal-auth/identities/:identityId/client-secrets/:clientSecretId/revoke",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
universalAuthController.revokeUniversalAuthClientSecret
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
|
|||||||
router.post(
|
router.post(
|
||||||
"/:workspaceId/environments",
|
"/:workspaceId/environments",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
environmentController.createWorkspaceEnvironment
|
environmentController.createWorkspaceEnvironment
|
||||||
);
|
);
|
||||||
@ -15,7 +15,7 @@ router.post(
|
|||||||
router.put(
|
router.put(
|
||||||
"/:workspaceId/environments",
|
"/:workspaceId/environments",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
environmentController.renameWorkspaceEnvironment
|
environmentController.renameWorkspaceEnvironment
|
||||||
);
|
);
|
||||||
@ -23,7 +23,7 @@ router.put(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/:workspaceId/environments",
|
"/:workspaceId/environments",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
environmentController.reorderWorkspaceEnvironments
|
environmentController.reorderWorkspaceEnvironments
|
||||||
);
|
);
|
||||||
@ -31,7 +31,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/:workspaceId/environments",
|
"/:workspaceId/environments",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
environmentController.deleteWorkspaceEnvironment
|
environmentController.deleteWorkspaceEnvironment
|
||||||
);
|
);
|
||||||
|
@ -9,7 +9,7 @@ import { organizationsController } from "../../controllers/v2";
|
|||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/memberships",
|
"/:organizationId/memberships",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
organizationsController.getOrganizationMemberships
|
organizationsController.getOrganizationMemberships
|
||||||
);
|
);
|
||||||
@ -17,7 +17,7 @@ router.get(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/:organizationId/memberships/:membershipId",
|
"/:organizationId/memberships/:membershipId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
organizationsController.updateOrganizationMembership
|
organizationsController.updateOrganizationMembership
|
||||||
);
|
);
|
||||||
@ -25,7 +25,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/:organizationId/memberships/:membershipId",
|
"/:organizationId/memberships/:membershipId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
organizationsController.deleteOrganizationMembership
|
organizationsController.deleteOrganizationMembership
|
||||||
);
|
);
|
||||||
@ -33,7 +33,7 @@ router.delete(
|
|||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/workspaces",
|
"/:organizationId/workspaces",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
organizationsController.getOrganizationWorkspaces
|
organizationsController.getOrganizationWorkspaces
|
||||||
);
|
);
|
||||||
@ -54,4 +54,12 @@ router.delete(
|
|||||||
organizationsController.deleteOrganizationById
|
organizationsController.deleteOrganizationById
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/:organizationId/identity-memberships",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
|
organizationsController.getOrganizationIdentityMemberships
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
import { AuthMode } from "../../variables";
|
import { AuthMode } from "../../variables";
|
||||||
import { serviceTokenDataController } from "../../controllers/v2";
|
import { serviceTokenDataController } from "../../controllers/v2";
|
||||||
|
|
||||||
router.get( // TODO: deprecate (moving to ST V3)
|
router.get( // TODO: deprecate (moving to identity)
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.SERVICE_TOKEN]
|
||||||
@ -14,7 +14,7 @@ router.get( // TODO: deprecate (moving to ST V3)
|
|||||||
serviceTokenDataController.getServiceTokenData
|
serviceTokenDataController.getServiceTokenData
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post( // TODO: deprecate (moving to ST V3)
|
router.post( // TODO: deprecate (moving to identity)
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
@ -22,7 +22,7 @@ router.post( // TODO: deprecate (moving to ST V3)
|
|||||||
serviceTokenDataController.createServiceTokenData
|
serviceTokenDataController.createServiceTokenData
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete( // TODO: deprecate (moving to ST V3)
|
router.delete( // TODO: deprecate (moving to identity)
|
||||||
"/:serviceTokenDataId",
|
"/:serviceTokenDataId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
@ -62,7 +62,7 @@ router.get(
|
|||||||
// new - TODO: rewire dashboard to this route
|
// new - TODO: rewire dashboard to this route
|
||||||
"/:workspaceId/memberships",
|
"/:workspaceId/memberships",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
workspaceController.getWorkspaceMemberships
|
workspaceController.getWorkspaceMemberships
|
||||||
);
|
);
|
||||||
@ -71,7 +71,7 @@ router.patch(
|
|||||||
// TODO - rewire dashboard to this route
|
// TODO - rewire dashboard to this route
|
||||||
"/:workspaceId/memberships/:membershipId",
|
"/:workspaceId/memberships/:membershipId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
workspaceController.updateWorkspaceMembership
|
workspaceController.updateWorkspaceMembership
|
||||||
);
|
);
|
||||||
@ -80,7 +80,7 @@ router.delete(
|
|||||||
// TODO - rewire dashboard to this route
|
// TODO - rewire dashboard to this route
|
||||||
"/:workspaceId/memberships/:membershipId",
|
"/:workspaceId/memberships/:membershipId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
workspaceController.deleteWorkspaceMembership
|
workspaceController.deleteWorkspaceMembership
|
||||||
);
|
);
|
||||||
@ -93,4 +93,37 @@ router.patch(
|
|||||||
workspaceController.toggleAutoCapitalization
|
workspaceController.toggleAutoCapitalization
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
"/:workspaceId/identity-memberships/:identityId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
workspaceController.addIdentityToWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/:workspaceId/identity-memberships/:identityId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
workspaceController.updateIdentityWorkspaceRole
|
||||||
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:workspaceId/identity-memberships/:identityId",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
workspaceController.deleteIdentityFromWorkspace
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/:workspaceId/identity-memberships",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
|
}),
|
||||||
|
workspaceController.getWorkspaceIdentityMemberships
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
|
|||||||
router.get(
|
router.get(
|
||||||
"/raw",
|
"/raw",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretsRaw
|
secretsController.getSecretsRaw
|
||||||
);
|
);
|
||||||
@ -15,7 +15,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@ -29,7 +29,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@ -43,7 +43,7 @@ router.post(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@ -57,7 +57,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@ -71,7 +71,7 @@ router.delete(
|
|||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@ -116,7 +116,7 @@ router.delete(
|
|||||||
router.post(
|
router.post(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@ -127,7 +127,7 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@ -138,7 +138,7 @@ router.get(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@ -149,7 +149,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
|
@ -34,12 +34,4 @@ router.post(
|
|||||||
|
|
||||||
// --
|
// --
|
||||||
|
|
||||||
router.get(
|
|
||||||
"/:workspaceId/service-token",
|
|
||||||
requireAuth({
|
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
|
||||||
}),
|
|
||||||
workspacesController.getWorkspaceServiceTokenData
|
|
||||||
);
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { generateSecretBlindIndexHelper } from "../helpers";
|
import { generateSecretBlindIndexHelper } from "../helpers";
|
||||||
|
import { SecretVersion } from "../ee/models";
|
||||||
import { Folder, ISecret, Secret, SecretImport } from "../models";
|
import { Folder, ISecret, Secret, SecretImport } from "../models";
|
||||||
import { getFolderByPath } from "./FolderService";
|
import { getFolderByPath } from "./FolderService";
|
||||||
|
|
||||||
@ -9,7 +10,8 @@ export const getAnImportedSecret = async (
|
|||||||
secretName: string,
|
secretName: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
environment: string,
|
environment: string,
|
||||||
folderId = "root"
|
folderId = "root",
|
||||||
|
version?: number
|
||||||
) => {
|
) => {
|
||||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||||
secretName,
|
secretName,
|
||||||
@ -48,10 +50,26 @@ export const getAnImportedSecret = async (
|
|||||||
});
|
});
|
||||||
if (importedSecByFid.length === 0) return;
|
if (importedSecByFid.length === 0) return;
|
||||||
|
|
||||||
const secret = await Secret.findOne({
|
let secret;
|
||||||
workspace: workspaceId,
|
if (version === undefined) {
|
||||||
secretBlindIndex
|
secret = await Secret.findOne({
|
||||||
}).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean()
|
workspace: workspaceId,
|
||||||
|
secretBlindIndex
|
||||||
|
}).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean()
|
||||||
|
} else {
|
||||||
|
const secretVersion = await SecretVersion.findOne({
|
||||||
|
workspace: workspaceId,
|
||||||
|
secretBlindIndex,
|
||||||
|
version
|
||||||
|
}).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean();
|
||||||
|
|
||||||
|
if (secretVersion) {
|
||||||
|
secret = await new Secret({
|
||||||
|
...secretVersion,
|
||||||
|
_id: secretVersion.secret,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return secret;
|
return secret;
|
||||||
};
|
};
|
||||||
|
@ -8,12 +8,12 @@ import {
|
|||||||
getTelemetryEnabled,
|
getTelemetryEnabled,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import {
|
import {
|
||||||
|
Identity,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
User,
|
User
|
||||||
} from "../models";
|
} from "../models";
|
||||||
import {
|
import {
|
||||||
AccountNotFoundError,
|
AccountNotFoundError,
|
||||||
BadRequestError,
|
|
||||||
} from "../utils/errors";
|
} from "../utils/errors";
|
||||||
|
|
||||||
class Telemetry {
|
class Telemetry {
|
||||||
@ -22,7 +22,7 @@ class Telemetry {
|
|||||||
*/
|
*/
|
||||||
static logTelemetryMessage = async () => {
|
static logTelemetryMessage = async () => {
|
||||||
|
|
||||||
if(!(await getTelemetryEnabled())){
|
if (!(await getTelemetryEnabled())) {
|
||||||
[
|
[
|
||||||
"To improve, Infisical collects telemetry data about general usage.",
|
"To improve, Infisical collects telemetry data about general usage.",
|
||||||
"This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.",
|
"This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.",
|
||||||
@ -42,8 +42,8 @@ class Telemetry {
|
|||||||
postHogClient = new PostHog(await getPostHogProjectApiKey(), {
|
postHogClient = new PostHog(await getPostHogProjectApiKey(), {
|
||||||
host: await getPostHogHost(),
|
host: await getPostHogHost(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return postHogClient;
|
return postHogClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +52,7 @@ class Telemetry {
|
|||||||
}: {
|
}: {
|
||||||
authData: AuthData;
|
authData: AuthData;
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
let distinctId = "";
|
let distinctId = "";
|
||||||
if (authData.authPayload instanceof User) {
|
if (authData.authPayload instanceof User) {
|
||||||
distinctId = authData.authPayload.email;
|
distinctId = authData.authPayload.email;
|
||||||
@ -59,14 +60,14 @@ class Telemetry {
|
|||||||
if (authData.authPayload.user) {
|
if (authData.authPayload.user) {
|
||||||
const user = await User.findById(authData.authPayload.user, "email");
|
const user = await User.findById(authData.authPayload.user, "email");
|
||||||
if (!user) throw AccountNotFoundError();
|
if (!user) throw AccountNotFoundError();
|
||||||
distinctId = user.email;
|
distinctId = user.email;
|
||||||
}
|
}
|
||||||
|
} else if (authData.authPayload instanceof Identity) {
|
||||||
|
distinctId = `identity-${authData.authPayload._id.toString()}`
|
||||||
|
} else {
|
||||||
|
distinctId = "unknown-auth-data"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (distinctId === "") throw BadRequestError({
|
|
||||||
message: "Failed to obtain distinct id for logging telemetry",
|
|
||||||
});
|
|
||||||
|
|
||||||
return distinctId;
|
return distinctId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
104
backend/src/utils/authn/authModeValidators/identity.ts
Normal file
104
backend/src/utils/authn/authModeValidators/identity.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import { IIdentity, IdentityAccessToken } from "../../../models";
|
||||||
|
import { getAuthSecret } from "../../../config";
|
||||||
|
import { AuthTokenType } from "../../../variables";
|
||||||
|
import { UnauthorizedRequestError } from "../../errors";
|
||||||
|
import { checkIPAgainstBlocklist } from "../../../utils/ip";
|
||||||
|
|
||||||
|
interface ValidateIdentityParams {
|
||||||
|
authTokenValue: string;
|
||||||
|
ipAddress: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateIdentity = async ({
|
||||||
|
authTokenValue,
|
||||||
|
ipAddress
|
||||||
|
}: ValidateIdentityParams) => {
|
||||||
|
const decodedToken = <jwt.IdentityAccessTokenJwtPayload>(
|
||||||
|
jwt.verify(authTokenValue, await getAuthSecret())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
const identityAccessToken = await IdentityAccessToken
|
||||||
|
.findOne({
|
||||||
|
_id: decodedToken.identityAccessTokenId,
|
||||||
|
isAccessTokenRevoked: false
|
||||||
|
})
|
||||||
|
.populate<{ identity: IIdentity }>("identity");
|
||||||
|
|
||||||
|
if (!identityAccessToken || !identityAccessToken?.identity) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
const {
|
||||||
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenNumUses,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenLastRenewedAt,
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
createdAt: accessTokenCreatedAt
|
||||||
|
} = identityAccessToken;
|
||||||
|
|
||||||
|
checkIPAgainstBlocklist({
|
||||||
|
ipAddress,
|
||||||
|
trustedIps: identityAccessToken.accessTokenTrustedIps
|
||||||
|
});
|
||||||
|
|
||||||
|
// ttl check
|
||||||
|
if (accessTokenTTL > 0) {
|
||||||
|
const currentDate = new Date();
|
||||||
|
if (accessTokenLastRenewedAt) {
|
||||||
|
// access token has been renewed
|
||||||
|
const accessTokenRenewed = new Date(accessTokenLastRenewedAt);
|
||||||
|
const ttlInMilliseconds = accessTokenTTL * 1000;
|
||||||
|
const expirationDate = new Date(accessTokenRenewed.getTime() + ttlInMilliseconds);
|
||||||
|
|
||||||
|
if (currentDate > expirationDate) throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate identity access token due to TTL expiration"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// access token has never been renewed
|
||||||
|
const accessTokenCreated = new Date(accessTokenCreatedAt);
|
||||||
|
const ttlInMilliseconds = accessTokenTTL * 1000;
|
||||||
|
const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds);
|
||||||
|
|
||||||
|
if (currentDate > expirationDate) throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate identity access token due to TTL expiration"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// max ttl check
|
||||||
|
if (accessTokenMaxTTL > 0) {
|
||||||
|
const accessTokenCreated = new Date(accessTokenCreatedAt);
|
||||||
|
const ttlInMilliseconds = accessTokenMaxTTL * 1000;
|
||||||
|
const currentDate = new Date();
|
||||||
|
const expirationDate = new Date(accessTokenCreated.getTime() + ttlInMilliseconds);
|
||||||
|
|
||||||
|
if (currentDate > expirationDate) throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate identity access token due to Max TTL expiration"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// num uses check
|
||||||
|
if (
|
||||||
|
accessTokenNumUsesLimit > 0
|
||||||
|
&& accessTokenNumUses === accessTokenNumUsesLimit
|
||||||
|
) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate MI access token due to access token number of uses limit reached"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await IdentityAccessToken.findByIdAndUpdate(
|
||||||
|
identityAccessToken._id,
|
||||||
|
{
|
||||||
|
accessTokenLastUsedAt: new Date(),
|
||||||
|
$inc: { accessTokenNumUses: 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return identityAccessToken.identity;
|
||||||
|
}
|
@ -2,4 +2,4 @@ export * from "./apiKey";
|
|||||||
export * from "./apiKeyV2";
|
export * from "./apiKeyV2";
|
||||||
export * from "./jwt";
|
export * from "./jwt";
|
||||||
export * from "./serviceTokenV2";
|
export * from "./serviceTokenV2";
|
||||||
export * from "./serviceTokenV3";
|
export * from "./identity";
|
@ -1,64 +0,0 @@
|
|||||||
import jwt from "jsonwebtoken";
|
|
||||||
import { Types } from "mongoose";
|
|
||||||
import { ServiceTokenDataV3 } from "../../../models";
|
|
||||||
import { getAuthSecret } from "../../../config";
|
|
||||||
import { AuthTokenType } from "../../../variables";
|
|
||||||
import { UnauthorizedRequestError } from "../../errors";
|
|
||||||
|
|
||||||
interface ValidateServiceTokenV3Params {
|
|
||||||
authTokenValue: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const validateServiceTokenV3 = async ({
|
|
||||||
authTokenValue
|
|
||||||
}: ValidateServiceTokenV3Params) => {
|
|
||||||
const decodedToken = <jwt.ServiceRefreshTokenJwtPayload>(
|
|
||||||
jwt.verify(authTokenValue, await getAuthSecret())
|
|
||||||
);
|
|
||||||
|
|
||||||
if (decodedToken.authTokenType !== AuthTokenType.SERVICE_ACCESS_TOKEN) throw UnauthorizedRequestError();
|
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenDataV3.findOne({
|
|
||||||
_id: new Types.ObjectId(decodedToken.serviceTokenDataId),
|
|
||||||
isActive: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!serviceTokenData) {
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to authenticate"
|
|
||||||
});
|
|
||||||
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
|
|
||||||
// case: service token expired
|
|
||||||
await ServiceTokenDataV3.findByIdAndUpdate(
|
|
||||||
serviceTokenData._id,
|
|
||||||
{
|
|
||||||
isActive: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to authenticate",
|
|
||||||
});
|
|
||||||
} else if (decodedToken.tokenVersion !== serviceTokenData.tokenVersion) {
|
|
||||||
// TODO: raise alarm
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to authenticate",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await ServiceTokenDataV3.findByIdAndUpdate(
|
|
||||||
serviceTokenData._id,
|
|
||||||
{
|
|
||||||
accessTokenLastUsed: new Date(),
|
|
||||||
$inc: { accessTokenUsageCount: 1 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return serviceTokenData;
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
import { AuthData } from "../../../interfaces/middleware";
|
import { AuthData } from "../../../interfaces/middleware";
|
||||||
import {
|
import {
|
||||||
|
Identity,
|
||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
ServiceTokenDataV3,
|
|
||||||
User
|
User
|
||||||
} from "../../../models";
|
} from "../../../models";
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ import {
|
|||||||
return { serviceTokenDataId: authData.authPayload._id };
|
return { serviceTokenDataId: authData.authPayload._id };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authPayload instanceof ServiceTokenDataV3) {
|
if (authData.authPayload instanceof Identity) {
|
||||||
return { serviceTokenDataId: authData.authPayload._id };
|
return { serviceTokenDataId: authData.authPayload._id };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -38,7 +38,7 @@ export const getAuthDataPayloadUserObj = (authData: AuthData) => {
|
|||||||
return { user: authData.authPayload.user };
|
return { user: authData.authPayload.user };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authPayload instanceof ServiceTokenDataV3) {
|
if (authData.authPayload instanceof Identity) {
|
||||||
return { user: authData.authPayload.user };
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,9 +7,9 @@ import { UnauthorizedRequestError } from "../../errors";
|
|||||||
import {
|
import {
|
||||||
validateAPIKey,
|
validateAPIKey,
|
||||||
validateAPIKeyV2,
|
validateAPIKeyV2,
|
||||||
|
validateIdentity,
|
||||||
validateJWT,
|
validateJWT,
|
||||||
validateServiceTokenV2,
|
validateServiceTokenV2
|
||||||
validateServiceTokenV3
|
|
||||||
} from "../authModeValidators";
|
} from "../authModeValidators";
|
||||||
import { getUserAgentType } from "../../posthog";
|
import { getUserAgentType } from "../../posthog";
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ interface GetAuthDataParams {
|
|||||||
* - SERVICE_TOKEN
|
* - SERVICE_TOKEN
|
||||||
* - API_KEY
|
* - API_KEY
|
||||||
* - JWT
|
* - JWT
|
||||||
* - SERVICE_ACCESS_TOKEN (from ST V3)
|
* - IDENTITY_ACCESS_TOKEN (from identity)
|
||||||
* - API_KEY_V2
|
* - API_KEY_V2
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {Object.<string, (string|string[]|undefined)>} params.headers - The HTTP request headers, usually from Express's `req.headers`.
|
* @param {Object.<string, (string|string[]|undefined)>} params.headers - The HTTP request headers, usually from Express's `req.headers`.
|
||||||
@ -77,8 +77,8 @@ export const extractAuthMode = async ({
|
|||||||
return { authMode: AuthMode.JWT, authTokenValue };
|
return { authMode: AuthMode.JWT, authTokenValue };
|
||||||
case AuthTokenType.API_KEY:
|
case AuthTokenType.API_KEY:
|
||||||
return { authMode: AuthMode.API_KEY_V2, authTokenValue };
|
return { authMode: AuthMode.API_KEY_V2, authTokenValue };
|
||||||
case AuthTokenType.SERVICE_ACCESS_TOKEN:
|
case AuthTokenType.IDENTITY_ACCESS_TOKEN:
|
||||||
return { authMode: AuthMode.SERVICE_ACCESS_TOKEN, authTokenValue };
|
return { authMode: AuthMode.IDENTITY_ACCESS_TOKEN, authTokenValue };
|
||||||
default:
|
default:
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed to authenticate unknown authentication method"
|
message: "Failed to authenticate unknown authentication method"
|
||||||
@ -115,20 +115,21 @@ export const getAuthData = async ({
|
|||||||
userAgentType
|
userAgentType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case AuthMode.SERVICE_ACCESS_TOKEN: {
|
case AuthMode.IDENTITY_ACCESS_TOKEN: {
|
||||||
const serviceTokenData = await validateServiceTokenV3({
|
const identity = await validateIdentity({
|
||||||
authTokenValue
|
authTokenValue,
|
||||||
|
ipAddress
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actor: {
|
actor: {
|
||||||
type: ActorType.SERVICE_V3,
|
type: ActorType.IDENTITY,
|
||||||
metadata: {
|
metadata: {
|
||||||
serviceId: serviceTokenData._id.toString(),
|
identityId: identity._id.toString(),
|
||||||
name: serviceTokenData.name
|
name: identity.name
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
authPayload: serviceTokenData,
|
authPayload: identity,
|
||||||
ipAddress,
|
ipAddress,
|
||||||
userAgent,
|
userAgent,
|
||||||
userAgentType
|
userAgentType
|
||||||
|
@ -75,7 +75,7 @@ export const initializeSamlStrategy = async () => {
|
|||||||
const organization = await Organization.findById(req.ssoConfig.organization);
|
const organization = await Organization.findById(req.ssoConfig.organization);
|
||||||
|
|
||||||
if (!organization) return done(OrganizationNotFoundError());
|
if (!organization) return done(OrganizationNotFoundError());
|
||||||
|
|
||||||
const email = profile.email;
|
const email = profile.email;
|
||||||
const firstName = profile.firstName;
|
const firstName = profile.firstName;
|
||||||
const lastName = profile.lastName;
|
const lastName = profile.lastName;
|
||||||
@ -154,6 +154,7 @@ export const initializeSamlStrategy = async () => {
|
|||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
organizationName: organization?.name,
|
organizationName: organization?.name,
|
||||||
|
organizationId: organization?._id,
|
||||||
authMethod: req.ssoConfig.authProvider,
|
authMethod: req.ssoConfig.authProvider,
|
||||||
isUserCompleted,
|
isUserCompleted,
|
||||||
...(req.body.RelayState ? {
|
...(req.body.RelayState ? {
|
||||||
|
@ -7,8 +7,14 @@ export const getUserAgentType = function (userAgent: string | undefined) {
|
|||||||
return UserAgentType.CLI;
|
return UserAgentType.CLI;
|
||||||
} else if (userAgent == UserAgentType.K8_OPERATOR) {
|
} else if (userAgent == UserAgentType.K8_OPERATOR) {
|
||||||
return UserAgentType.K8_OPERATOR;
|
return UserAgentType.K8_OPERATOR;
|
||||||
|
} else if (userAgent == UserAgentType.TERRAFORM) {
|
||||||
|
return UserAgentType.TERRAFORM;
|
||||||
} else if (userAgent.toLowerCase().includes("mozilla")) {
|
} else if (userAgent.toLowerCase().includes("mozilla")) {
|
||||||
return UserAgentType.WEB;
|
return UserAgentType.WEB;
|
||||||
|
} else if (userAgent.includes(UserAgentType.NODE_SDK)) {
|
||||||
|
return UserAgentType.NODE_SDK;
|
||||||
|
} else if (userAgent.includes(UserAgentType.PYTHON_SDK)) {
|
||||||
|
return UserAgentType.PYTHON_SDK;
|
||||||
} else {
|
} else {
|
||||||
return UserAgentType.OTHER;
|
return UserAgentType.OTHER;
|
||||||
}
|
}
|
||||||
|
@ -53,9 +53,10 @@ export default class RequestError extends Error {
|
|||||||
){
|
){
|
||||||
|
|
||||||
super(message)
|
super(message)
|
||||||
this._logLevel = logLevel || LogLevel.INFO
|
this._logLevel = logLevel || LogLevel.INFO;
|
||||||
this._logName = LogLevel[this._logLevel];
|
this._logName = LogLevel[this._logLevel];
|
||||||
this.statusCode = statusCode
|
this.statusCode = statusCode;
|
||||||
|
this.message = message;
|
||||||
this.type = type
|
this.type = type
|
||||||
this.context = context || {}
|
this.context = context || {}
|
||||||
this.extra = []
|
this.extra = []
|
||||||
|
@ -84,6 +84,105 @@ export const ResetPasswordV1 = z.object({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const RenewAccessTokenV1 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
accessToken: z.string().trim(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LoginUniversalAuthV1 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
clientId: z.string().trim(),
|
||||||
|
clientSecret: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AddUniversalAuthToIdentityV1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
clientSecretTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim(),
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
|
||||||
|
accessTokenTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim(),
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
|
||||||
|
accessTokenTTL: z.number().int().min(1).refine(value => value !== 0, {
|
||||||
|
message: "accessTokenTTL must have a non zero number",
|
||||||
|
}).default(2592000),
|
||||||
|
accessTokenMaxTTL: z.number().int().refine(value => value !== 0, {
|
||||||
|
message: "accessTokenMaxTTL must have a non zero number",
|
||||||
|
}).default(2592000), // 30 days
|
||||||
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateUniversalAuthToIdentityV1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
clientSecretTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.optional(),
|
||||||
|
accessTokenTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim(),
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.optional(),
|
||||||
|
accessTokenTTL: z.number().int().min(0).optional(),
|
||||||
|
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
|
||||||
|
accessTokenMaxTTL: z.number().int().refine(value => value !== 0, {
|
||||||
|
message: "accessTokenMaxTTL must have a non zero number",
|
||||||
|
}).optional(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetUniversalAuthForIdentityV1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateUniversalAuthClientSecretV1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
description: z.string().trim().default(""),
|
||||||
|
numUsesLimit: z.number().min(0).default(0),
|
||||||
|
ttl: z.number().min(0).default(0),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetUniversalAuthClientSecretsV1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const RevokeUniversalAuthClientSecretV1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string(),
|
||||||
|
clientSecretId: z.string()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
export const VerifyMfaTokenV2 = z.object({
|
export const VerifyMfaTokenV2 = z.object({
|
||||||
body: z.object({
|
body: z.object({
|
||||||
mfaToken: z.string().trim()
|
mfaToken: z.string().trim()
|
||||||
|
26
backend/src/validation/identities.ts
Normal file
26
backend/src/validation/identities.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { NO_ACCESS } from "../variables";
|
||||||
|
|
||||||
|
export const CreateIdentityV1 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().trim(),
|
||||||
|
organizationId: z.string().trim(),
|
||||||
|
role: z.string().trim().min(1).default(NO_ACCESS)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateIdentityV1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().trim().optional(),
|
||||||
|
role: z.string().trim().min(1).optional()
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DeleteIdentityV1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
identityId: z.string()
|
||||||
|
}),
|
||||||
|
});
|
@ -8,5 +8,5 @@ export * from "./membershipOrg";
|
|||||||
export * from "./organization";
|
export * from "./organization";
|
||||||
export * from "./secrets";
|
export * from "./secrets";
|
||||||
export * from "./serviceTokenData";
|
export * from "./serviceTokenData";
|
||||||
export * from "./serviceTokenDataV3";
|
export * from "./identities";
|
||||||
export * from "./apiKeyDataV3";
|
export * from "./apiKeyDataV3";
|
||||||
|
@ -58,9 +58,9 @@ const validateClientForIntegrationAuth = async ({
|
|||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed service token authorization for integration authorization"
|
message: "Failed service token authorization for integration authorization"
|
||||||
});
|
});
|
||||||
case ActorType.SERVICE_V3:
|
case ActorType.IDENTITY:
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed service token authorization for integration authorization"
|
message: "Failed identity authorization for integration authorization"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -192,6 +192,13 @@ export const GetIntegrationAuthNorthflankSecretGroupsV1 = z.object({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const DeleteIntegrationAuthsV1 = z.object({
|
||||||
|
query: z.object({
|
||||||
|
integration: z.string().trim(),
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
export const DeleteIntegrationAuthV1 = z.object({
|
export const DeleteIntegrationAuthV1 = z.object({
|
||||||
params: z.object({
|
params: z.object({
|
||||||
integrationAuthId: z.string().trim()
|
integrationAuthId: z.string().trim()
|
||||||
|
@ -46,9 +46,9 @@ export const validateClientForOrganization = async ({
|
|||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed service token authorization for organization"
|
message: "Failed service token authorization for organization"
|
||||||
});
|
});
|
||||||
case ActorType.SERVICE_V3:
|
case ActorType.IDENTITY:
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed service token authorization for organization"
|
message: "Failed identity authorization for organization"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -212,4 +212,12 @@ export const CreateOrgv2 = z.object({
|
|||||||
|
|
||||||
export const DeleteOrgv2 = z.object({
|
export const DeleteOrgv2 = z.object({
|
||||||
params: z.object({ organizationId: z.string().trim() })
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgServiceMembersV2 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgIdentityMembershipsV2 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
});
|
});
|
@ -246,7 +246,15 @@ export const GetSecretByNameRawV3 = z.object({
|
|||||||
include_imports: z
|
include_imports: z
|
||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.default("true")
|
.default("true")
|
||||||
.transform((value) => value === "true")
|
.transform((value) => value === "true"),
|
||||||
|
version: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.optional()
|
||||||
|
.transform((value) => value === undefined ? undefined : parseInt(value, 10))
|
||||||
|
.refine((value) => value === undefined || !isNaN(value), {
|
||||||
|
message: "Version must be a number",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -318,7 +326,15 @@ export const GetSecretByNameV3 = z.object({
|
|||||||
include_imports: z
|
include_imports: z
|
||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.default("true")
|
.default("true")
|
||||||
.transform((value) => value === "true")
|
.transform((value) => value === "true"),
|
||||||
|
version: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.optional()
|
||||||
|
.transform((value) => value === undefined ? undefined : parseInt(value, 10))
|
||||||
|
.refine((value) => value === undefined || !isNaN(value), {
|
||||||
|
message: "Version must be a number",
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretName: z.string().trim()
|
secretName: z.string().trim()
|
||||||
|
@ -158,7 +158,7 @@ export const CreateServiceTokenV2 = z.object({
|
|||||||
encryptedKey: z.string().trim(),
|
encryptedKey: z.string().trim(),
|
||||||
iv: z.string().trim(),
|
iv: z.string().trim(),
|
||||||
tag: z.string().trim(),
|
tag: z.string().trim(),
|
||||||
expiresIn: z.number(),
|
expiresIn: z.number().nullable().optional(),
|
||||||
permissions: z.enum(["read", "write"]).array()
|
permissions: z.enum(["read", "write"]).array()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { MEMBER } from "../variables";
|
|
||||||
|
|
||||||
export const RefreshTokenV3 = z.object({
|
|
||||||
body: z.object({
|
|
||||||
refresh_token: z.string().trim()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export const CreateServiceTokenV3 = z.object({
|
|
||||||
body: z.object({
|
|
||||||
name: z.string().trim(),
|
|
||||||
workspaceId: z.string().trim(),
|
|
||||||
publicKey: z.string().trim(),
|
|
||||||
role: z.string().trim().min(1).default(MEMBER),
|
|
||||||
trustedIps: z // TODO: provide default
|
|
||||||
.object({
|
|
||||||
ipAddress: z.string().trim(),
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
.min(1)
|
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }]),
|
|
||||||
expiresIn: z.number().optional(),
|
|
||||||
accessTokenTTL: z.number().int().min(1),
|
|
||||||
encryptedKey: z.string().trim(),
|
|
||||||
nonce: z.string().trim(),
|
|
||||||
isRefreshTokenRotationEnabled: z.boolean().default(false)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
export const UpdateServiceTokenV3 = z.object({
|
|
||||||
params: z.object({
|
|
||||||
serviceTokenDataId: z.string()
|
|
||||||
}),
|
|
||||||
body: z.object({
|
|
||||||
name: z.string().trim().optional(),
|
|
||||||
isActive: z.boolean().optional(),
|
|
||||||
role: z.string().trim().min(1).optional(),
|
|
||||||
trustedIps: z
|
|
||||||
.object({
|
|
||||||
ipAddress: z.string().trim()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
.min(1)
|
|
||||||
.optional(),
|
|
||||||
expiresIn: z.number().optional(),
|
|
||||||
accessTokenTTL: z.number().int().min(1).optional(),
|
|
||||||
isRefreshTokenRotationEnabled: z.boolean().optional()
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const DeleteServiceTokenV3 = z.object({
|
|
||||||
params: z.object({
|
|
||||||
serviceTokenDataId: z.string()
|
|
||||||
}),
|
|
||||||
});
|
|
@ -8,6 +8,7 @@ import { AuthData } from "../interfaces/middleware";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { EventType, UserAgentType } from "../ee/models";
|
import { EventType, UserAgentType } from "../ee/models";
|
||||||
import { UnauthorizedRequestError } from "../utils/errors";
|
import { UnauthorizedRequestError } from "../utils/errors";
|
||||||
|
import { NO_ACCESS } from "../variables";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate authenticated clients for workspace with id [workspaceId] based
|
* Validate authenticated clients for workspace with id [workspaceId] based
|
||||||
@ -59,9 +60,9 @@ export const validateClientForWorkspace = async ({
|
|||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
return { membership, workspace };
|
return { membership, workspace };
|
||||||
case ActorType.SERVICE_V3:
|
case ActorType.IDENTITY:
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed service token authorization for organization"
|
message: "Failed identity authorization for organization"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -279,6 +280,39 @@ export const ToggleAutoCapitalizationV2 = z.object({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const AddIdentityToWorkspaceV2 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim(),
|
||||||
|
identityId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
role: z.string().trim().min(1).default(NO_ACCESS),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateIdentityWorkspaceRoleV2 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim(),
|
||||||
|
identityId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
role: z.string().trim().min(1).default(NO_ACCESS),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DeleteIdentityFromWorkspaceV2 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim(),
|
||||||
|
identityId: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetWorkspaceIdentityMembersV2 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
export const GetWorkspaceBlinkIndexStatusV3 = z.object({
|
export const GetWorkspaceBlinkIndexStatusV3 = z.object({
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@ -304,9 +338,3 @@ export const NameWorkspaceSecretsV3 = z.object({
|
|||||||
.array()
|
.array()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GetWorkspaceServiceTokenDataV3 = z.object({
|
|
||||||
params: z.object({
|
|
||||||
workspaceId: z.string().trim()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
@ -7,14 +7,13 @@ export enum AuthTokenType {
|
|||||||
MFA_TOKEN = "mfaToken", // TODO: remove in favor of claim
|
MFA_TOKEN = "mfaToken", // TODO: remove in favor of claim
|
||||||
PROVIDER_TOKEN = "providerToken", // TODO: remove in favor of claim
|
PROVIDER_TOKEN = "providerToken", // TODO: remove in favor of claim
|
||||||
API_KEY = "apiKey",
|
API_KEY = "apiKey",
|
||||||
SERVICE_ACCESS_TOKEN = "serviceAccessToken",
|
IDENTITY_ACCESS_TOKEN = "identityAccessToken",
|
||||||
SERVICE_REFRESH_TOKEN = "serviceRefreshToken"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AuthMode {
|
export enum AuthMode {
|
||||||
JWT = "jwt",
|
JWT = "jwt",
|
||||||
SERVICE_TOKEN = "serviceToken",
|
SERVICE_TOKEN = "serviceToken",
|
||||||
SERVICE_ACCESS_TOKEN = "serviceAccessToken",
|
IDENTITY_ACCESS_TOKEN = "identityAccessToken",
|
||||||
API_KEY = "apiKey",
|
API_KEY = "apiKey",
|
||||||
API_KEY_V2 = "apiKeyV2"
|
API_KEY_V2 = "apiKeyV2"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ export const OWNER = "owner"; // depreciated
|
|||||||
export const ADMIN = "admin";
|
export const ADMIN = "admin";
|
||||||
export const MEMBER = "member";
|
export const MEMBER = "member";
|
||||||
export const VIEWER = "viewer";
|
export const VIEWER = "viewer";
|
||||||
|
export const NO_ACCESS = "no-access";
|
||||||
export const CUSTOM = "custom";
|
export const CUSTOM = "custom";
|
||||||
|
|
||||||
// membership statuses
|
// membership statuses
|
||||||
|
@ -30,7 +30,7 @@ const generateOpenAPISpec = async () => {
|
|||||||
type: "http",
|
type: "http",
|
||||||
scheme: "bearer",
|
scheme: "bearer",
|
||||||
bearerFormat: "JWT",
|
bearerFormat: "JWT",
|
||||||
description: "A service token in Infisical"
|
description: "An access token in Infisical"
|
||||||
},
|
},
|
||||||
apiKeyAuth: {
|
apiKeyAuth: {
|
||||||
type: "apiKey",
|
type: "apiKey",
|
||||||
@ -52,6 +52,41 @@ const generateOpenAPISpec = async () => {
|
|||||||
updatedAt: "2023-01-13T14:16:12.210Z",
|
updatedAt: "2023-01-13T14:16:12.210Z",
|
||||||
createdAt: "2023-01-13T14:16:12.210Z"
|
createdAt: "2023-01-13T14:16:12.210Z"
|
||||||
},
|
},
|
||||||
|
Identity: {
|
||||||
|
_id: "",
|
||||||
|
name: "Machine 1",
|
||||||
|
authMethod: "universal-auth"
|
||||||
|
},
|
||||||
|
IdentityUniversalAuth: {
|
||||||
|
_id: "",
|
||||||
|
identity: "",
|
||||||
|
clientId: "...",
|
||||||
|
clientSecretTrustedIps: [{
|
||||||
|
ipAddress: "0.0.0.0",
|
||||||
|
type: "ipv4",
|
||||||
|
prefix: "0"
|
||||||
|
}],
|
||||||
|
accessTokenTTL: 7200,
|
||||||
|
accessTokenMaxTTL: 2592000,
|
||||||
|
accessTokenNumUsesLimit: 0,
|
||||||
|
accessTokenTrustedIps: [{
|
||||||
|
ipAddress: "0.0.0.0",
|
||||||
|
type: "ipv4",
|
||||||
|
prefix: "0"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
IdentityUniversalAuthClientSecretData: {
|
||||||
|
_id: "",
|
||||||
|
identityUniversalAuth: "",
|
||||||
|
isClientSecretRevoked: false,
|
||||||
|
description: "",
|
||||||
|
clientSecretPrefix: "abc",
|
||||||
|
clientSecretNumUses: 0,
|
||||||
|
clientSecretNumUsesLimit: 0,
|
||||||
|
clientSecretTTL: 0,
|
||||||
|
createdAt: "2023-01-13T14:16:12.210Z",
|
||||||
|
updatedAt: "2023-01-13T14:16:12.210Z"
|
||||||
|
},
|
||||||
Membership: {
|
Membership: {
|
||||||
user: {
|
user: {
|
||||||
_id: "",
|
_id: "",
|
||||||
@ -79,6 +114,25 @@ const generateOpenAPISpec = async () => {
|
|||||||
role: "owner",
|
role: "owner",
|
||||||
status: "accepted"
|
status: "accepted"
|
||||||
},
|
},
|
||||||
|
IdentityMembership: {
|
||||||
|
identity: {
|
||||||
|
_id: "",
|
||||||
|
name: "Machine 1",
|
||||||
|
authMethod: "universal-auth"
|
||||||
|
},
|
||||||
|
workspace: "",
|
||||||
|
role: "member"
|
||||||
|
},
|
||||||
|
IdentityMembershipOrg: {
|
||||||
|
identity: {
|
||||||
|
_id: "",
|
||||||
|
name: "Machine 1",
|
||||||
|
authMethod: "universal-auth"
|
||||||
|
},
|
||||||
|
organization: "",
|
||||||
|
role: "member",
|
||||||
|
status: "accepted"
|
||||||
|
},
|
||||||
Organization: {
|
Organization: {
|
||||||
_id: "",
|
_id: "",
|
||||||
name: "Acme Corp.",
|
name: "Acme Corp.",
|
||||||
|
15
cli/agent-config.yaml
Normal file
15
cli/agent-config.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
infisical:
|
||||||
|
address: "http://localhost:8080"
|
||||||
|
auth:
|
||||||
|
type: "universal-auth"
|
||||||
|
config:
|
||||||
|
client-id: "./client-id"
|
||||||
|
client-secret: "./client-secret"
|
||||||
|
remove_client_secret_on_read: false
|
||||||
|
sinks:
|
||||||
|
- type: "file"
|
||||||
|
config:
|
||||||
|
path: "access-token"
|
||||||
|
templates:
|
||||||
|
- source-path: my-dot-ev-secret-template
|
||||||
|
destination-path: my-dot-env.env
|
@ -1,4 +0,0 @@
|
|||||||
FROM alpine
|
|
||||||
RUN apk add --no-cache tini
|
|
||||||
COPY infisical /bin/infisical
|
|
||||||
ENTRYPOINT ["/sbin/tini", "--", "/bin/infisical"]
|
|
9
cli/docker/alpine
Normal file
9
cli/docker/alpine
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM alpine
|
||||||
|
RUN apk add --no-cache tini
|
||||||
|
|
||||||
|
## Upgrade OpenSSL libraries to mitigate known vulnerabilities as the current Alpine image has not been patched yet.
|
||||||
|
RUN apk update && apk upgrade --no-cache libcrypto3 libssl3
|
||||||
|
|
||||||
|
|
||||||
|
COPY infisical /bin/infisical
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--", "/bin/infisical"]
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user