mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-27 09:40:45 +00:00
Compare commits
189 Commits
upgrade-ax
...
multi-inte
Author | SHA1 | Date | |
---|---|---|---|
5287b322d8 | |||
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 | |||
13ee8c4e13 | |||
6ea9fc7134 | |||
00e1742a30 | |||
5055b5402c | |||
ff9418c0c7 | |||
d03921eef3 | |||
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 |
.goreleaser.yamlREADME.mdspec.yaml
backend
package-lock.jsonpackage.jsonspec.json
src
controllers
v1
authController.tsindex.tsintegrationAuthController.tsintegrationController.tsmembershipController.tsmembershipOrgController.tsorganizationController.tssecretImpsController.tssecretScanningController.tsuniversalAuthController.tsworkspaceController.ts
v2
v3
ee
controllers
v1
identitiesController.tsindex.tsorganizationsController.tsroleController.tssecretApprovalRequestsController.tsssoController.tsworkspaceController.ts
v3
models/auditLog
routes
services
helpers
index.tsinterfaces/middleware
middleware
models
identity.tsidentityAccessToken.tsidentityMembership.tsidentityMembershipOrg.tsidentityUniversalAuth.tsidentityUniversalAuthClientSecret.tsindex.tsmembership.tsmembershipOrg.tsserviceTokenDataV3.tsserviceTokenDataV3Key.ts
routes
v1
v2
v3
services
utils
validation
auth.tsidentities.tsindex.tsintegrationAuth.tsorganization.tsserviceTokenData.tsserviceTokenDataV3.tsworkspace.ts
variables
swagger
cli
docs
api-reference
endpoints
environments
identities
organizations
delete-membership.mdxlist-identity-memberships.mdxmemberships.mdxupdate-membership.mdxworkspaces.mdx
service-tokens
universal-auth
attach.mdxcreate-client-secret.mdxlist-client-secrets.mdxlogin.mdxrenew-access-token.mdxretrieve.mdxrevoke-client-secret.mdxupdate.mdx
users
workspaces
overview
changelog
contributing
getting-started
platform
sdk
documentation
getting-started
guides
platform
images
getting-started/api
org-create-project-1.pngorg-create-project-2.pngproject-create-secret.pngproject-dashboard.pngproject-explore-env.png
platform/identities
identities-org-client-secret-create-1.pngidentities-org-client-secret-create-2.pngidentities-org-client-secret.pngidentities-org-create-auth-method.pngidentities-org-create.pngidentities-org.pngidentities-project-create.pngidentities-project.png
sdk-flow.pngself-hosting/deployment-options
aws-lightsail
awsl-container-service-overview.pngawsl-create-container-service-capacity.pngawsl-create-container-service-deployment.pngawsl-create-container-service-envars.pngawsl-create-container-service-public-endpoint.pngawsl-create-container-service-summary.pngawsl-create-container-service.pngawsl-select-lightsail.png
azure-app-services
aas-app-service-configuration.pngaas-app-service-deployment-complete.pngaas-app-service-overview.pngaas-create-app-service-basics.pngaas-create-app-service-docker.pngaas-create-app-service-review.pngaas-create-app-service.pngaas-select-app-services.png
azure-container-instances
aci-container-instance-overview.pngaci-create-container-instance-advanced.pngaci-create-container-instance-basics-1.pngaci-create-container-instance-basics-2.pngaci-create-container-instance-networking.pngaci-create-container-instance-review.pngaci-create-container-instance.pngaci-select-container-instances.png
flyio
gcp-cloud-run
gcp-cloud-run-create-project-2.pnggcp-cloud-run-create-project.pnggcp-cloud-run-create-service-docker-image.pnggcp-cloud-run-create-service-envars.pnggcp-cloud-run-create-service.pnggcp-cloud-run-select-cloud-run.pnggcp-cloud-run-service-details.png
railway
infisical-agent
integrations
cicd
cloud
aws-parameter-store.mdxaws-secret-manager.mdxazure-key-vault.mdxcheckly.mdxcloudflare-pages.mdxcloudflare-workers.mdxflyio.mdxgcp-secret-manager.mdxhasura-cloud.mdxheroku.mdxlaravel-forge.mdxnetlify.mdxnorthflank.mdxqovery.mdxrailway.mdxrender.mdxsupabase.mdxteamcity.mdxterraform-cloud.mdxvercel.mdxwindmill.mdx
internals
mint.jsonsdks
self-hosting
configuration
deployment-options
aws-lightsail.mdxazure-app-services.mdxazure-container-instances.mdxdocker-compose.mdxfly.io.mdxgcp-cloud-run.mdxkubernetes-helm.mdxrailway.mdxstandalone-infisical.mdx
overview.mdxfrontend
package-lock.jsonpackage.json
src
components/signup
context
hooks/api
layouts/AppLayout
pages
views
IntegrationsPage
Login
Org/MembersPage
MembersPage.tsx
components
OrgIdentityTab
OrgIdentityTab.tsxindex.tsx
components
IdentitySection
IdentityAuthMethodModal.tsxIdentityModal.tsxIdentitySection.tsxIdentityTable.tsxIdentityUniversalAuthClientSecretModal.tsxIdentityUniversalAuthForm.tsxindex.tsx
index.tsxOrgMembersTab
OrgMembersTable
OrgRoleTabSection
index.tsxProject
AuditLogsPage/components
MembersPage
MembersPage.tsx
components
IdentityTab
MemberListTab
ProjectRoleListTab/components
ProjectRoleList
ProjectRoleModifySection
ServiceTokenTab
index.tsxSecretApprovalPage
SecretApprovalPage.tsx
components/SecretApprovalRequest
SecretMainPage/components
SecretOverviewPage
SecretRotationPage
Signup
admin/SignUpPage
helm-charts/secrets-operator
k8-operator
@ -108,7 +108,7 @@ brews:
|
||||
zsh_completion.install "completions/infisical.zsh" => "_infisical"
|
||||
fish_completion.install "completions/infisical.fish"
|
||||
man1.install "manpages/infisical.1.gz"
|
||||
- name: 'infisical@{{.Version}}'
|
||||
- name: "infisical@{{.Version}}"
|
||||
tap:
|
||||
owner: Infisical
|
||||
name: homebrew-get-cli
|
||||
@ -186,12 +186,14 @@ aurs:
|
||||
# man pages
|
||||
install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz"
|
||||
|
||||
# dockers:
|
||||
# - dockerfile: cli/docker/Dockerfile
|
||||
# goos: linux
|
||||
# goarch: amd64
|
||||
# ids:
|
||||
# - infisical
|
||||
# image_templates:
|
||||
# - "infisical/cli:{{ .Version }}"
|
||||
# - "infisical/cli:latest"
|
||||
dockers:
|
||||
- dockerfile: docker/alpine
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
ids:
|
||||
- all-other-builds
|
||||
image_templates:
|
||||
- "infisical/cli:{{ .Version }}"
|
||||
- "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
|
||||
|
||||
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:
|
||||
|
||||
|
30
backend/package-lock.json
generated
30
backend/package-lock.json
generated
@ -60,7 +60,7 @@
|
||||
"pino": "^8.16.1",
|
||||
"pino-http": "^8.5.1",
|
||||
"posthog-node": "^2.6.0",
|
||||
"probot": "^12.3.1",
|
||||
"probot": "^12.3.3",
|
||||
"query-string": "^7.1.3",
|
||||
"rate-limit-mongo": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
@ -5991,9 +5991,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/webhooks": {
|
||||
"version": "9.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz",
|
||||
"integrity": "sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA==",
|
||||
"version": "9.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.3.tgz",
|
||||
"integrity": "sha512-DLGk+gzeVq5oK89Bo601txYmyrelMQ7Fi5EnjHE0Xs8CWicy2xkmnJMKptKJrBJpstqbd/9oeDFi/Zj2pudBDQ==",
|
||||
"dependencies": {
|
||||
"@octokit/request-error": "^2.0.2",
|
||||
"@octokit/webhooks-methods": "^2.0.0",
|
||||
@ -16306,9 +16306,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/probot": {
|
||||
"version": "12.3.1",
|
||||
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.1.tgz",
|
||||
"integrity": "sha512-ECSgycmAC0ILEK6cOa+x3QPufP5JybsuohOFCYr3glQU5SkbmypZJE/Sfio9mxAFHK5LCXveIDsfZCxf6ck4JA==",
|
||||
"version": "12.3.3",
|
||||
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.3.tgz",
|
||||
"integrity": "sha512-cdtKd+xISzi8sw6++BYBXleRknCA6hqUMoHj/sJqQBrjbNxQLhfeFCq9O2d0Z4eShsy5YFRR3MWwDKJ9uAE0CA==",
|
||||
"dependencies": {
|
||||
"@octokit/core": "^3.2.4",
|
||||
"@octokit/plugin-enterprise-compatibility": "^1.2.8",
|
||||
@ -16317,7 +16317,7 @@
|
||||
"@octokit/plugin-retry": "^3.0.6",
|
||||
"@octokit/plugin-throttling": "^3.3.4",
|
||||
"@octokit/types": "^8.0.0",
|
||||
"@octokit/webhooks": "^9.8.4",
|
||||
"@octokit/webhooks": "^9.26.3",
|
||||
"@probot/get-private-key": "^1.1.0",
|
||||
"@probot/octokit-plugin-config": "^1.0.0",
|
||||
"@probot/pino": "^2.2.0",
|
||||
@ -23392,9 +23392,9 @@
|
||||
}
|
||||
},
|
||||
"@octokit/webhooks": {
|
||||
"version": "9.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz",
|
||||
"integrity": "sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA==",
|
||||
"version": "9.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.3.tgz",
|
||||
"integrity": "sha512-DLGk+gzeVq5oK89Bo601txYmyrelMQ7Fi5EnjHE0Xs8CWicy2xkmnJMKptKJrBJpstqbd/9oeDFi/Zj2pudBDQ==",
|
||||
"requires": {
|
||||
"@octokit/request-error": "^2.0.2",
|
||||
"@octokit/webhooks-methods": "^2.0.0",
|
||||
@ -31039,9 +31039,9 @@
|
||||
}
|
||||
},
|
||||
"probot": {
|
||||
"version": "12.3.1",
|
||||
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.1.tgz",
|
||||
"integrity": "sha512-ECSgycmAC0ILEK6cOa+x3QPufP5JybsuohOFCYr3glQU5SkbmypZJE/Sfio9mxAFHK5LCXveIDsfZCxf6ck4JA==",
|
||||
"version": "12.3.3",
|
||||
"resolved": "https://registry.npmjs.org/probot/-/probot-12.3.3.tgz",
|
||||
"integrity": "sha512-cdtKd+xISzi8sw6++BYBXleRknCA6hqUMoHj/sJqQBrjbNxQLhfeFCq9O2d0Z4eShsy5YFRR3MWwDKJ9uAE0CA==",
|
||||
"requires": {
|
||||
"@octokit/core": "^3.2.4",
|
||||
"@octokit/plugin-enterprise-compatibility": "^1.2.8",
|
||||
@ -31050,7 +31050,7 @@
|
||||
"@octokit/plugin-retry": "^3.0.6",
|
||||
"@octokit/plugin-throttling": "^3.3.4",
|
||||
"@octokit/types": "^8.0.0",
|
||||
"@octokit/webhooks": "^9.8.4",
|
||||
"@octokit/webhooks": "^9.26.3",
|
||||
"@probot/get-private-key": "^1.1.0",
|
||||
"@probot/octokit-plugin-config": "^1.0.0",
|
||||
"@probot/pino": "^2.2.0",
|
||||
|
@ -51,7 +51,7 @@
|
||||
"pino": "^8.16.1",
|
||||
"pino-http": "^8.5.1",
|
||||
"posthog-node": "^2.6.0",
|
||||
"probot": "^12.3.1",
|
||||
"probot": "^12.3.3",
|
||||
"query-string": "^7.1.3",
|
||||
"rate-limit-mongo": "^2.3.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";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const jsrp = require("jsrp");
|
||||
import { LoginSRPDetail, TokenVersion, User } from "../../models";
|
||||
import {
|
||||
LoginSRPDetail,
|
||||
TokenVersion,
|
||||
User
|
||||
} from "../../models";
|
||||
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
|
||||
import { checkUserDevice } from "../../helpers/user";
|
||||
import { AuthTokenType } from "../../variables";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import {
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError
|
||||
} from "../../utils/errors";
|
||||
import {
|
||||
getAuthSecret,
|
||||
getHttpsEnabled,
|
||||
getJwtAuthLifetime
|
||||
getJwtAuthLifetime,
|
||||
} from "../../config";
|
||||
import { ActorType } from "../../ee/models";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
@ -25,10 +32,11 @@ declare module "jsonwebtoken" {
|
||||
userId: string;
|
||||
refreshVersion?: number;
|
||||
}
|
||||
export interface ServiceRefreshTokenJwtPayload extends jwt.JwtPayload {
|
||||
serviceTokenDataId: string;
|
||||
export interface IdentityAccessTokenJwtPayload extends jwt.JwtPayload {
|
||||
_id: string;
|
||||
clientSecretId: string;
|
||||
identityAccessTokenId: string;
|
||||
authTokenType: string;
|
||||
tokenVersion: number;
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,4 +274,4 @@ export const getNewToken = async (req: Request, res: Response) => {
|
||||
|
||||
export const handleAuthProviderCallback = (req: Request, res: Response) => {
|
||||
res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||
};
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import * as authController from "./authController";
|
||||
import * as universalAuthController from "./universalAuthController";
|
||||
import * as botController from "./botController";
|
||||
import * as integrationAuthController from "./integrationAuthController";
|
||||
import * as integrationController from "./integrationController";
|
||||
@ -20,6 +21,7 @@ import * as adminController from "./adminController";
|
||||
|
||||
export {
|
||||
authController,
|
||||
universalAuthController,
|
||||
botController,
|
||||
integrationAuthController,
|
||||
integrationController,
|
||||
|
@ -2,7 +2,7 @@ import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { standardRequest } from "../../config/request";
|
||||
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 { IntegrationService } from "../../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) => {
|
||||
// TODO: refactor
|
||||
// TODO: check if access token is valid for each integration
|
||||
let integrationAuth;
|
||||
const {
|
||||
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
|
||||
} = 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");
|
||||
|
||||
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||
{
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration
|
||||
},
|
||||
{
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration,
|
||||
url,
|
||||
namespace,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
...(integration === INTEGRATION_GCP_SECRET_MANAGER
|
||||
? {
|
||||
metadata: {
|
||||
authMethod: "serviceAccount"
|
||||
}
|
||||
let integrationAuth = await new IntegrationAuth({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
integration,
|
||||
url,
|
||||
namespace,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
...(integration === INTEGRATION_GCP_SECRET_MANAGER
|
||||
? {
|
||||
metadata: {
|
||||
authMethod: "serviceAccount"
|
||||
}
|
||||
: {})
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
}
|
||||
: {})
|
||||
}).save();
|
||||
|
||||
// encrypt and save integration access details
|
||||
if (refreshToken) {
|
||||
@ -188,12 +177,12 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
|
||||
|
||||
// encrypt and save integration access details
|
||||
if (accessId || accessToken) {
|
||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuth = (await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt: undefined
|
||||
});
|
||||
})) as IIntegrationAuth;
|
||||
}
|
||||
|
||||
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]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
||||
export const deleteIntegrationAuthById = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId }
|
||||
} = 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");
|
||||
|
||||
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(
|
||||
req.authData,
|
||||
|
@ -4,9 +4,9 @@ import { IUser, Key, Membership, MembershipOrg, User, Workspace } from "../../mo
|
||||
import { EventType, Role } from "../../ee/models";
|
||||
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
|
||||
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 { EEAuditLogService } from "../../ee/services";
|
||||
import { EEAuditLogService, EELicenseService } from "../../ee/services";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
import * as reqValidator from "../../validation/membership";
|
||||
import {
|
||||
@ -129,7 +129,7 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
|
||||
ProjectPermissionSub.Member
|
||||
);
|
||||
|
||||
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
|
||||
const isCustomRole = ![ADMIN, MEMBER, VIEWER, NO_ACCESS].includes(role);
|
||||
if (isCustomRole) {
|
||||
const wsRole = await Role.findOne({
|
||||
slug: role,
|
||||
@ -137,6 +137,13 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
|
||||
workspace: membershipToChangeRole.workspace
|
||||
});
|
||||
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, {
|
||||
role: CUSTOM,
|
||||
customRole: wsRole
|
||||
|
@ -21,7 +21,7 @@ import { validateRequest } from "../../helpers/validation";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
getUserOrgPermissions
|
||||
getAuthDataOrgPermissions
|
||||
} from "../../ee/services/RoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
@ -44,11 +44,12 @@ export const deleteMembershipOrg = async (req: Request, _res: Response) => {
|
||||
if (!membershipOrgToDelete) {
|
||||
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(
|
||||
OrgPermissionActions.Delete,
|
||||
OrgPermissionSubjects.Member
|
||||
@ -60,7 +61,7 @@ export const deleteMembershipOrg = async (req: Request, _res: Response) => {
|
||||
});
|
||||
|
||||
await updateSubscriptionOrgQuantity({
|
||||
organizationId: membershipOrg.organization.toString()
|
||||
organizationId: membershipOrgToDelete.organization.toString()
|
||||
});
|
||||
|
||||
return membershipOrgToDelete;
|
||||
@ -96,7 +97,11 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
body: { inviteeEmail, organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.Member
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IncidentContactOrg,
|
||||
Membership,
|
||||
@ -14,7 +15,7 @@ import { ACCEPTED } from "../../variables";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
getUserOrgPermissions
|
||||
getAuthDataOrgPermissions
|
||||
} from "../../ee/services/RoleService";
|
||||
import { OrganizationNotFoundError } from "../../utils/errors";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
@ -44,7 +45,10 @@ export const getOrganization = async (req: Request, res: Response) => {
|
||||
} = await validateRequest(reqValidator.GetOrgv1, req);
|
||||
|
||||
// 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);
|
||||
if (!organization) {
|
||||
@ -68,8 +72,12 @@ export const getOrganizationMembers = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Member
|
||||
@ -95,7 +103,10 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Workspace
|
||||
@ -137,7 +148,10 @@ export const changeOrganizationName = async (req: Request, res: Response) => {
|
||||
body: { name }
|
||||
} = 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(
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.Settings
|
||||
@ -172,7 +186,10 @@ export const getOrganizationIncidentContacts = async (req: Request, res: Respons
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.IncidentAccount
|
||||
@ -199,7 +216,10 @@ export const addOrganizationIncidentContact = async (req: Request, res: Response
|
||||
body: { email }
|
||||
} = 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(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.IncidentAccount
|
||||
@ -228,7 +248,10 @@ export const deleteOrganizationIncidentContact = async (req: Request, res: Respo
|
||||
body: { email }
|
||||
} = 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(
|
||||
OrgPermissionActions.Delete,
|
||||
OrgPermissionSubjects.IncidentAccount
|
||||
@ -257,7 +280,10 @@ export const createOrganizationPortalSession = async (req: Request, res: Respons
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -321,7 +347,10 @@ export const getOrganizationMembersAndTheirWorkspaces = async (req: Request, res
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Member
|
||||
|
@ -111,11 +111,17 @@ export const createSecretImp = async (req: Request, res: Response) => {
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
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({
|
||||
@ -323,7 +329,7 @@ export const updateSecretImport = async (req: Request, res: Response) => {
|
||||
authData: req.authData,
|
||||
workspaceId: importSecDoc.workspace
|
||||
});
|
||||
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
@ -331,6 +337,13 @@ export const updateSecretImport = async (req: Request, res: Response) => {
|
||||
secretPath
|
||||
})
|
||||
);
|
||||
|
||||
secretImports.forEach(({ environment, secretPath }) => {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
const orderBefore = importSecDoc.imports;
|
||||
@ -453,7 +466,7 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
|
||||
authData: req.authData,
|
||||
workspaceId: importSecDoc.workspace
|
||||
});
|
||||
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
@ -620,7 +633,7 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
@ -677,7 +690,7 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
||||
authData: req.authData,
|
||||
workspaceId: importSecDoc.workspace
|
||||
});
|
||||
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
|
@ -21,7 +21,7 @@ import * as reqValidator from "../../validation/secretScanning";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
getUserOrgPermissions
|
||||
getAuthDataOrgPermissions
|
||||
} from "../../ee/services/RoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
@ -37,8 +37,11 @@ export const createInstallationSession = async (req: Request, res: Response) =>
|
||||
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(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.SecretScanning
|
||||
@ -70,11 +73,12 @@ export const linkInstallationToOrganization = async (req: Request, res: Response
|
||||
if (!installationSession) {
|
||||
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(
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.SecretScanning
|
||||
@ -142,7 +146,10 @@ export const getRisksForOrganization = async (req: Request, res: Response) => {
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.SecretScanning
|
||||
@ -162,7 +169,10 @@ export const updateRisksStatus = async (req: Request, res: Response) => {
|
||||
body: { status }
|
||||
} = 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(
|
||||
OrgPermissionActions.Edit,
|
||||
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 {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
getUserOrgPermissions
|
||||
getAuthDataOrgPermissions
|
||||
} from "../../ee/services/RoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
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(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.Workspace
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { Request, Response } from "express";
|
||||
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 { deleteMembershipOrg } from "../../helpers/membershipOrg";
|
||||
import {
|
||||
@ -9,15 +18,16 @@ import {
|
||||
updateSubscriptionOrgQuantity
|
||||
} from "../../helpers/organization";
|
||||
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { ACCEPTED, ADMIN, CUSTOM } from "../../variables";
|
||||
import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { ACCEPTED, ADMIN, CUSTOM, MEMBER, NO_ACCESS } from "../../variables";
|
||||
import * as reqValidator from "../../validation/organization";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
getUserOrgPermissions
|
||||
getAuthDataOrgPermissions
|
||||
} from "../../ee/services/RoleService";
|
||||
import { EELicenseService } from "../../ee/services";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
/**
|
||||
@ -27,11 +37,12 @@ import { ForbiddenError } from "@casl/ability";
|
||||
*/
|
||||
export const getOrganizationMemberships = async (req: Request, res: Response) => {
|
||||
/*
|
||||
#swagger.summary = 'Return organization memberships'
|
||||
#swagger.description = 'Return organization memberships'
|
||||
#swagger.summary = 'Return organization user memberships'
|
||||
#swagger.description = 'Return organization user memberships'
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
"apiKeyAuth": [],
|
||||
"bearerAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['organizationId'] = {
|
||||
@ -63,7 +74,10 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Member
|
||||
@ -85,11 +99,12 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
|
||||
*/
|
||||
export const updateOrganizationMembership = async (req: Request, res: Response) => {
|
||||
/*
|
||||
#swagger.summary = 'Update organization membership'
|
||||
#swagger.description = 'Update organization membership'
|
||||
#swagger.summary = 'Update organization user membership'
|
||||
#swagger.description = 'Update organization user membership'
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
"apiKeyAuth": [],
|
||||
"bearerAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['organizationId'] = {
|
||||
@ -141,16 +156,32 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
|
||||
params: { organizationId, membershipId },
|
||||
body: { role }
|
||||
} = 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(
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.Member
|
||||
);
|
||||
|
||||
const isCustomRole = !["admin", "member"].includes(role);
|
||||
const isCustomRole = ![ADMIN, MEMBER, NO_ACCESS].includes(role);
|
||||
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" });
|
||||
|
||||
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, {
|
||||
role: CUSTOM,
|
||||
@ -189,11 +220,12 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
|
||||
*/
|
||||
export const deleteOrganizationMembership = async (req: Request, res: Response) => {
|
||||
/*
|
||||
#swagger.summary = 'Delete organization membership'
|
||||
#swagger.description = 'Delete organization membership'
|
||||
#swagger.summary = 'Delete organization user membership'
|
||||
#swagger.description = 'Delete organization user membership'
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
"apiKeyAuth": [],
|
||||
"bearerAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['organizationId'] = {
|
||||
@ -227,7 +259,18 @@ export const deleteOrganizationMembership = async (req: Request, res: Response)
|
||||
const {
|
||||
params: { organizationId, membershipId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Delete,
|
||||
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.security = [{
|
||||
"apiKeyAuth": []
|
||||
"apiKeyAuth": [],
|
||||
"bearerAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['organizationId'] = {
|
||||
@ -287,11 +331,16 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
const {
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Workspace
|
||||
@ -308,13 +357,27 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
||||
).map((w) => w._id.toString())
|
||||
);
|
||||
|
||||
const workspaces = (
|
||||
await Membership.find({
|
||||
user: req.user._id
|
||||
}).populate("workspace")
|
||||
)
|
||||
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
||||
.map((m) => m.workspace);
|
||||
let workspaces: IWorkspace[] = [];
|
||||
|
||||
if (req.authData.authPayload instanceof Identity) {
|
||||
workspaces = (
|
||||
await IdentityMembership.find({
|
||||
identity: req.authData.authPayload._id
|
||||
}).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({
|
||||
workspaces
|
||||
@ -377,3 +440,66 @@ export const deleteOrganizationById = async (req: Request, res: Response) => {
|
||||
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,
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
/**
|
||||
@ -86,6 +86,14 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
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 secretHash = await bcrypt.hash(secret, await getSaltRounds());
|
||||
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { Request, Response } from "express";
|
||||
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 {
|
||||
pullSecrets as pull,
|
||||
v2PushSecrets as push,
|
||||
@ -16,9 +25,13 @@ import * as reqValidator from "../../validation";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getAuthDataProjectPermissions
|
||||
getAuthDataProjectPermissions,
|
||||
getWorkspaceRolePermissions,
|
||||
isAtLeastAsPrivilegedWorkspace
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { BadRequestError, ForbiddenRequestError, ResourceNotFoundError } from "../../utils/errors";
|
||||
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../../variables";
|
||||
|
||||
interface V2PushSecret {
|
||||
type: string; // personal or shared
|
||||
@ -169,11 +182,11 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
||||
"apiKeyAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of project",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of project",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.responses[200] = {
|
||||
content: {
|
||||
@ -198,7 +211,7 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
||||
receiver: req.user._id
|
||||
}).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(
|
||||
req.authData,
|
||||
@ -236,33 +249,34 @@ export const getWorkspaceServiceTokenData = async (req: Request, res: Response)
|
||||
*/
|
||||
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||
/*
|
||||
#swagger.summary = 'Return project memberships'
|
||||
#swagger.description = 'Return project memberships'
|
||||
#swagger.summary = 'Return project user memberships'
|
||||
#swagger.description = 'Return project user memberships'
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
"apiKeyAuth": [],
|
||||
"bearerAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of project",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of project",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.responses[200] = {
|
||||
content: {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"memberships": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
$ref: "#/components/schemas/Membership"
|
||||
},
|
||||
"description": "Memberships of project"
|
||||
}
|
||||
}
|
||||
"properties": {
|
||||
"memberships": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
$ref: "#/components/schemas/Membership"
|
||||
},
|
||||
"description": "Memberships of project"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -299,26 +313,27 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const updateWorkspaceMembership = async (req: Request, res: Response) => {
|
||||
/*
|
||||
#swagger.summary = 'Update project membership'
|
||||
#swagger.description = 'Update project membership'
|
||||
#swagger.summary = 'Update project user membership'
|
||||
#swagger.description = 'Update project user membership'
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
"apiKeyAuth": [],
|
||||
"bearerAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of project",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of project",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.parameters['membershipId'] = {
|
||||
"description": "ID of project membership to update",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
#swagger.parameters['membershipId'] = {
|
||||
"description": "ID of project membership to update",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.requestBody = {
|
||||
#swagger.requestBody = {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
@ -327,7 +342,7 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
||||
"properties": {
|
||||
"role": {
|
||||
"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: {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"membership": {
|
||||
$ref: "#/components/schemas/Membership",
|
||||
"description": "Updated membership"
|
||||
}
|
||||
}
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"membership": {
|
||||
$ref: "#/components/schemas/Membership",
|
||||
"description": "Updated membership"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -389,36 +404,37 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
||||
*/
|
||||
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
|
||||
/*
|
||||
#swagger.summary = 'Delete project membership'
|
||||
#swagger.description = 'Delete project membership'
|
||||
#swagger.summary = 'Delete project user membership'
|
||||
#swagger.description = 'Delete project user membership'
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
"apiKeyAuth": [],
|
||||
"bearerAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of project",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of project",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.parameters['membershipId'] = {
|
||||
"description": "ID of project membership to delete",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
#swagger.parameters['membershipId'] = {
|
||||
"description": "ID of project membership to delete",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.responses[200] = {
|
||||
content: {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"membership": {
|
||||
$ref: "#/components/schemas/Membership",
|
||||
"description": "Deleted membership"
|
||||
}
|
||||
}
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"membership": {
|
||||
$ref: "#/components/schemas/Membership",
|
||||
"description": "Deleted membership"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -491,3 +507,377 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
||||
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 };
|
||||
}
|
||||
case ActorType.SERVICE_V3: {
|
||||
case ActorType.IDENTITY: {
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
import { Membership, Secret, ServiceTokenDataV3, User } from "../../models";
|
||||
import { Membership, Secret, User } from "../../models";
|
||||
import { SecretService } from "../../services";
|
||||
import { getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService";
|
||||
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||
@ -140,17 +140,3 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
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 secretSnapshotController from "./secretSnapshotController";
|
||||
import * as organizationsController from "./organizationsController";
|
||||
@ -13,6 +14,7 @@ import * as secretRotationProviderController from "./secretRotationProviderContr
|
||||
import * as secretRotationController from "./secretRotationController";
|
||||
|
||||
export {
|
||||
identitiesController,
|
||||
secretController,
|
||||
secretSnapshotController,
|
||||
organizationsController,
|
||||
|
@ -8,7 +8,7 @@ import * as reqValidator from "../../../validation/organization";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
getUserOrgPermissions
|
||||
getAuthDataOrgPermissions,
|
||||
} from "../../services/RoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { Organization } from "../../../models";
|
||||
@ -20,7 +20,10 @@ export const getOrganizationPlansTable = async (req: Request, res: Response) =>
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -42,7 +45,10 @@ export const getOrganizationPlan = async (req: Request, res: Response) => {
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -70,7 +76,10 @@ export const startOrganizationTrial = async (req: Request, res: Response) => {
|
||||
body: { success_url }
|
||||
} = 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(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -116,7 +125,10 @@ export const getOrganizationPlanBillingInfo = async (req: Request, res: Response
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -149,7 +161,10 @@ export const getOrganizationPlanTable = async (req: Request, res: Response) => {
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -176,7 +191,10 @@ export const getOrganizationBillingDetails = async (req: Request, res: Response)
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -204,7 +222,10 @@ export const updateOrganizationBillingDetails = async (req: Request, res: Respon
|
||||
body: { name, email }
|
||||
} = 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(
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -238,7 +259,10 @@ export const getOrganizationPmtMethods = async (req: Request, res: Response) =>
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -271,7 +295,10 @@ export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
|
||||
body: { success_url, cancel_url }
|
||||
} = 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(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -312,7 +339,10 @@ export const deleteOrganizationPmtMethod = async (req: Request, res: Response) =
|
||||
params: { organizationId, pmtMethodId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Delete,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -342,7 +372,10 @@ export const getOrganizationTaxIds = async (req: Request, res: Response) => {
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -375,7 +408,10 @@ export const addOrganizationTaxId = async (req: Request, res: Response) => {
|
||||
body: { type, value }
|
||||
} = 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(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -412,7 +448,10 @@ export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
|
||||
params: { organizationId, taxId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Delete,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -445,7 +484,10 @@ export const getOrganizationInvoices = async (req: Request, res: Response) => {
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
@ -480,7 +522,10 @@ export const getOrganizationLicenses = async (req: Request, res: Response) => {
|
||||
params: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Billing
|
||||
|
@ -15,14 +15,17 @@ import {
|
||||
adminProjectPermissions,
|
||||
getAuthDataProjectPermissions,
|
||||
memberProjectPermissions,
|
||||
noAccessProjectPermissions,
|
||||
viewerProjectPermission
|
||||
} from "../../services/ProjectRoleService";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
adminPermissions,
|
||||
getAuthDataOrgPermissions,
|
||||
getUserOrgPermissions,
|
||||
memberPermissions
|
||||
memberPermissions,
|
||||
noAccessPermissions
|
||||
} from "../../services/RoleService";
|
||||
import { BadRequestError } from "../../../utils/errors";
|
||||
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
|
||||
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)) {
|
||||
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 }
|
||||
} = await validateRequest(UpdateRoleSchema, req);
|
||||
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
|
||||
|
||||
|
||||
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)) {
|
||||
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;
|
||||
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)) {
|
||||
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;
|
||||
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)) {
|
||||
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",
|
||||
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",
|
||||
name: isOrgRole ? "Member" : "Developer",
|
||||
@ -229,7 +252,7 @@ export const getUserPermissions = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { orgId }
|
||||
} = await validateRequest(GetUserPermission, req);
|
||||
|
||||
|
||||
const { permission, membership } = await getUserOrgPermissions(req.user._id, orgId);
|
||||
|
||||
res.status(200).json({
|
||||
|
@ -17,12 +17,12 @@ export const getSecretApprovalRequestCount = async (req: Request, res: Response)
|
||||
} = await validateRequest(reqValidator.getSecretApprovalRequestCount, req);
|
||||
|
||||
if (!(req.authData.authPayload instanceof User)) return;
|
||||
|
||||
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
const approvalRequestCount = await SecretApprovalRequest.aggregate([
|
||||
@ -73,12 +73,12 @@ export const getSecretApprovalRequests = async (req: Request, res: Response) =>
|
||||
} = await validateRequest(reqValidator.getSecretApprovalRequests, req);
|
||||
|
||||
if (!(req.authData.authPayload instanceof User)) return;
|
||||
|
||||
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
const query = {
|
||||
@ -168,13 +168,13 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: secretApprovalRequest.workspace
|
||||
});
|
||||
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
// allow to fetch only if its admin or is the committer or approver
|
||||
if (
|
||||
membership.role !== "admin" &&
|
||||
secretApprovalRequest.committer !== membership.id &&
|
||||
!secretApprovalRequest.committer.equals(membership.id) &&
|
||||
!secretApprovalRequest.policy.approvers.find(
|
||||
(approverId) => approverId.toString() === membership._id.toString()
|
||||
)
|
||||
@ -215,7 +215,7 @@ export const updateSecretApprovalReviewStatus = async (req: Request, res: Respon
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: secretApprovalRequest.workspace
|
||||
});
|
||||
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
if (
|
||||
@ -257,7 +257,7 @@ export const mergeSecretApprovalRequest = async (req: Request, res: Response) =>
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: secretApprovalRequest.workspace
|
||||
});
|
||||
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
if (
|
||||
@ -307,7 +307,7 @@ export const updateSecretApprovalRequestStatus = async (req: Request, res: Respo
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: secretApprovalRequest.workspace
|
||||
});
|
||||
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
if (
|
||||
|
@ -13,7 +13,7 @@ import { validateRequest } from "../../../helpers/validation";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
getUserOrgPermissions
|
||||
getAuthDataOrgPermissions
|
||||
} from "../../services/RoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
@ -47,7 +47,10 @@ export const getSSOConfig = async (req: Request, res: Response) => {
|
||||
query: { organizationId }
|
||||
} = 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(
|
||||
OrgPermissionActions.Read,
|
||||
OrgPermissionSubjects.Sso
|
||||
@ -71,7 +74,10 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
|
||||
body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
|
||||
} = 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(
|
||||
OrgPermissionActions.Edit,
|
||||
OrgPermissionSubjects.Sso
|
||||
@ -206,7 +212,10 @@ export const createSSOConfig = async (req: Request, res: Response) => {
|
||||
body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
|
||||
} = 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(
|
||||
OrgPermissionActions.Create,
|
||||
OrgPermissionSubjects.Sso
|
||||
|
@ -2,10 +2,11 @@ import { Request, Response } from "express";
|
||||
import { PipelineStage, Types } from "mongoose";
|
||||
import {
|
||||
Folder,
|
||||
Identity,
|
||||
IdentityMembership,
|
||||
Membership,
|
||||
Secret,
|
||||
ServiceTokenData,
|
||||
ServiceTokenDataV3,
|
||||
TFolderSchema,
|
||||
User,
|
||||
Workspace
|
||||
@ -17,10 +18,10 @@ import {
|
||||
FolderVersion,
|
||||
IPType,
|
||||
ISecretVersion,
|
||||
IdentityActor,
|
||||
SecretSnapshot,
|
||||
SecretVersion,
|
||||
ServiceActor,
|
||||
ServiceActorV3,
|
||||
TFolderRootVersionSchema,
|
||||
TrustedIP,
|
||||
UserActor
|
||||
@ -61,15 +62,30 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) =
|
||||
#swagger.description = 'Return project secret snapshots ids'
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
"apiKeyAuth": [],
|
||||
"bearerAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of project",
|
||||
"description": "ID of project where to get secret snapshots for",
|
||||
"required": true,
|
||||
"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'] = {
|
||||
"description": "Number of secret snapshots to skip",
|
||||
"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.security = [{
|
||||
"apiKeyAuth": []
|
||||
"apiKeyAuth": [],
|
||||
"bearerAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of project",
|
||||
"description": "ID of project where to roll back",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
@ -210,6 +227,14 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"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": {
|
||||
"type": "integer",
|
||||
"description": "Version of secret snapshot to roll back to",
|
||||
@ -669,6 +694,21 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
||||
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 = {
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
...(eventType
|
||||
@ -684,13 +724,9 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
||||
...(actor
|
||||
? {
|
||||
"actor.type": actor.substring(0, actor.lastIndexOf("-")),
|
||||
...(actor.split("-", 2)[0] === ActorType.USER
|
||||
? {
|
||||
"actor.metadata.userId": actor.substring(actor.lastIndexOf("-") + 1)
|
||||
}
|
||||
: {
|
||||
"actor.metadata.serviceId": actor.substring(actor.lastIndexOf("-") + 1)
|
||||
})
|
||||
...({
|
||||
[actorMetadataQuery]: actor.substring(actor.lastIndexOf("-") + 1)
|
||||
})
|
||||
}
|
||||
: {}),
|
||||
...(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);
|
||||
|
||||
return res.status(200).send({
|
||||
auditLogs
|
||||
});
|
||||
@ -731,6 +769,7 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
|
||||
const userIds = await Membership.distinct("user", {
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const userActors: UserActor[] = (
|
||||
await User.find({
|
||||
_id: {
|
||||
@ -757,19 +796,25 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
|
||||
}
|
||||
}));
|
||||
|
||||
const serviceV3Actors: ServiceActorV3[] = (
|
||||
await ServiceTokenDataV3.find({
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
const identityIds = await IdentityMembership.distinct("identity", {
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const identityActors: IdentityActor[] = (
|
||||
await Identity.find({
|
||||
_id: {
|
||||
$in: identityIds
|
||||
}
|
||||
})
|
||||
).map((serviceTokenData) => ({
|
||||
type: ActorType.SERVICE_V3,
|
||||
).map((identity) => ({
|
||||
type: ActorType.IDENTITY,
|
||||
metadata: {
|
||||
serviceId: serviceTokenData._id.toString(),
|
||||
name: serviceTokenData.name
|
||||
identityId: identity._id.toString(),
|
||||
name: identity.name
|
||||
}
|
||||
}));
|
||||
|
||||
const actors = [...userActors, ...serviceActors, ...serviceV3Actors];
|
||||
const actors = [...userActors, ...serviceActors, ...identityActors];
|
||||
|
||||
return res.status(200).send({
|
||||
actors
|
||||
|
@ -1,7 +1,5 @@
|
||||
import * as serviceTokenDataController from "./serviceTokenDataController";
|
||||
import * as apiKeyDataController from "./apiKeyDataController";
|
||||
|
||||
export {
|
||||
serviceTokenDataController,
|
||||
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;
|
||||
userAgent: string;
|
||||
userAgentType: UserAgentType;
|
||||
expiresAt: Date;
|
||||
expiresAt?: Date;
|
||||
}
|
||||
|
||||
const auditLogSchema = new Schema<IAuditLog>(
|
||||
|
@ -1,15 +1,17 @@
|
||||
export enum ActorType {
|
||||
USER = "user",
|
||||
SERVICE = "service",
|
||||
SERVICE_V3 = "service-v3",
|
||||
// Machine = "machine"
|
||||
export enum ActorType { // would extend to AWS, Azure, ...
|
||||
USER = "user", // userIdentity
|
||||
SERVICE = "service",
|
||||
IDENTITY = "identity"
|
||||
}
|
||||
|
||||
export enum UserAgentType {
|
||||
WEB = "web",
|
||||
CLI = "cli",
|
||||
K8_OPERATOR = "k8-operator",
|
||||
OTHER = "other"
|
||||
TERRAFORM = "terraform",
|
||||
OTHER = "other",
|
||||
PYTHON_SDK = "InfisicalPythonSDK",
|
||||
NODE_SDK = "InfisicalNodeSDK"
|
||||
}
|
||||
|
||||
export enum EventType {
|
||||
@ -32,9 +34,16 @@ export enum EventType {
|
||||
DELETE_TRUSTED_IP = "delete-trusted-ip",
|
||||
CREATE_SERVICE_TOKEN = "create-service-token", // v2
|
||||
DELETE_SERVICE_TOKEN = "delete-service-token", // v2
|
||||
CREATE_SERVICE_TOKEN_V3 = "create-service-token-v3", // v3
|
||||
UPDATE_SERVICE_TOKEN_V3 = "update-service-token-v3", // v3
|
||||
DELETE_SERVICE_TOKEN_V3 = "delete-service-token-v3", // v3
|
||||
CREATE_IDENTITY = "create-identity",
|
||||
UPDATE_IDENTITY = "update-identity",
|
||||
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",
|
||||
UPDATE_ENVIRONMENT = "update-environment",
|
||||
DELETE_ENVIRONMENT = "delete-environment",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ActorType, EventType } from "./enums";
|
||||
import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3";
|
||||
import { IIdentityTrustedIp } from "../../../models";
|
||||
|
||||
interface UserActorMetadata {
|
||||
userId: string;
|
||||
@ -11,6 +11,11 @@ interface ServiceActorMetadata {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface IdentityActorMetadata {
|
||||
identityId: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UserActor {
|
||||
type: ActorType.USER;
|
||||
metadata: UserActorMetadata;
|
||||
@ -21,16 +26,12 @@ export interface ServiceActor {
|
||||
metadata: ServiceActorMetadata;
|
||||
}
|
||||
|
||||
export interface ServiceActorV3 {
|
||||
type: ActorType.SERVICE_V3;
|
||||
metadata: ServiceActorMetadata;
|
||||
export interface IdentityActor {
|
||||
type: ActorType.IDENTITY;
|
||||
metadata: IdentityActorMetadata;
|
||||
}
|
||||
|
||||
// export interface MachineActor {
|
||||
// type: ActorType.Machine;
|
||||
// }
|
||||
|
||||
export type Actor = UserActor | ServiceActor | ServiceActorV3;
|
||||
export type Actor = UserActor | ServiceActor | IdentityActor;
|
||||
|
||||
interface GetSecretsEvent {
|
||||
type: EventType.GET_SECRETS;
|
||||
@ -220,36 +221,91 @@ interface DeleteServiceTokenEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateServiceTokenV3Event {
|
||||
type: EventType.CREATE_SERVICE_TOKEN_V3;
|
||||
interface CreateIdentityEvent { // note: currently not logging org-role
|
||||
type: EventType.CREATE_IDENTITY;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
role: string;
|
||||
trustedIps: Array<IServiceTokenV3TrustedIp>;
|
||||
expiresAt?: Date;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateServiceTokenV3Event {
|
||||
type: EventType.UPDATE_SERVICE_TOKEN_V3;
|
||||
interface UpdateIdentityEvent {
|
||||
type: EventType.UPDATE_IDENTITY;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
name?: string;
|
||||
isActive?: boolean;
|
||||
role?: string;
|
||||
trustedIps?: Array<IServiceTokenV3TrustedIp>;
|
||||
expiresAt?: Date;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteServiceTokenV3Event {
|
||||
type: EventType.DELETE_SERVICE_TOKEN_V3;
|
||||
interface DeleteIdentityEvent {
|
||||
type: EventType.DELETE_IDENTITY;
|
||||
metadata: {
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
role: string;
|
||||
expiresAt?: Date;
|
||||
trustedIps: Array<IServiceTokenV3TrustedIp>;
|
||||
identityId: string;
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
| CreateServiceTokenEvent
|
||||
| DeleteServiceTokenEvent
|
||||
| CreateServiceTokenV3Event
|
||||
| UpdateServiceTokenV3Event
|
||||
| DeleteServiceTokenV3Event
|
||||
| CreateIdentityEvent
|
||||
| UpdateIdentityEvent
|
||||
| DeleteIdentityEvent
|
||||
| LoginIdentityUniversalAuthEvent
|
||||
| AddIdentityUniversalAuthEvent
|
||||
| UpdateIdentityUniversalAuthEvent
|
||||
| GetIdentityUniversalAuthEvent
|
||||
| CreateIdentityUniversalAuthClientSecretEvent
|
||||
| GetIdentityUniversalAuthClientSecretsEvent
|
||||
| RevokeIdentityUniversalAuthClientSecretEvent
|
||||
| CreateEnvironmentEvent
|
||||
| UpdateEnvironmentEvent
|
||||
| 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 secretSnapshot from "./secretSnapshot";
|
||||
import organizations from "./organizations";
|
||||
@ -13,6 +14,7 @@ import secretRotationProvider from "./secretRotationProvider";
|
||||
import secretRotation from "./secretRotation";
|
||||
|
||||
export {
|
||||
identities,
|
||||
secret,
|
||||
secretSnapshot,
|
||||
organizations,
|
||||
|
@ -7,7 +7,7 @@ import { workspaceController } from "../../controllers/v1";
|
||||
router.get(
|
||||
"/:workspaceId/secret-snapshots",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
workspaceController.getWorkspaceSecretSnapshots
|
||||
);
|
||||
@ -23,7 +23,7 @@ router.get(
|
||||
router.post(
|
||||
"/:workspaceId/secret-snapshots/rollback",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
workspaceController.rollbackWorkspaceSecretSnapshot
|
||||
);
|
||||
@ -31,7 +31,7 @@ router.post(
|
||||
router.get(
|
||||
"/:workspaceId/audit-logs",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
workspaceController.getWorkspaceAuditLogs
|
||||
);
|
||||
|
@ -1,7 +1,5 @@
|
||||
import serviceTokenData from "./serviceTokenData";
|
||||
import apiKeyData from "./apiKeyData";
|
||||
|
||||
export {
|
||||
serviceTokenData,
|
||||
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 EELicenseService from "./EELicenseService";
|
||||
import { Workspace } from "../../models";
|
||||
import { OrganizationNotFoundError } from "../../utils/errors";
|
||||
|
||||
interface EventScope {
|
||||
workspaceId?: Types.ObjectId;
|
||||
@ -14,31 +13,42 @@ type ValidEventScope =
|
||||
| Required<Pick<EventScope, "workspaceId">>
|
||||
| Required<Pick<EventScope, "organizationId">>
|
||||
| Required<EventScope>
|
||||
| Record<string, never>;
|
||||
|
||||
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 organizationId = ("organizationId" in eventScope)
|
||||
? eventScope.organizationId
|
||||
: (await Workspace.findById(eventScope.workspaceId).select("organization").lean())?.organization;
|
||||
let organizationId;
|
||||
if ("organizationId" in eventScope) {
|
||||
organizationId = eventScope.organizationId;
|
||||
}
|
||||
|
||||
if (!organizationId) throw OrganizationNotFoundError({
|
||||
message: "createAuditLog: Failed to create audit log due to missing organizationId"
|
||||
});
|
||||
|
||||
const ttl = (await EELicenseService.getPlan(organizationId)).auditLogsRetentionDays * MS_IN_DAY;
|
||||
let workspaceId;
|
||||
if ("workspaceId" in eventScope) {
|
||||
workspaceId = eventScope.workspaceId;
|
||||
|
||||
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({
|
||||
actor: authData.actor,
|
||||
organization: organizationId,
|
||||
workspace: ("workspaceId" in eventScope) ? eventScope.workspaceId : undefined,
|
||||
workspace: workspaceId,
|
||||
ipAddress: authData.ipAddress,
|
||||
event,
|
||||
userAgent: authData.userAgent,
|
||||
userAgentType: authData.userAgentType,
|
||||
expiresAt: new Date(Date.now() + ttl)
|
||||
expiresAt
|
||||
});
|
||||
|
||||
if (shouldSave) {
|
||||
|
@ -11,10 +11,15 @@ import { UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
|
||||
import picomatch from "picomatch";
|
||||
import { AuthData } from "../../interfaces/middleware";
|
||||
import { ActorType, IRole } from "../models";
|
||||
import { Membership, ServiceTokenData, ServiceTokenDataV3 } from "../../models";
|
||||
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables";
|
||||
import { checkIPAgainstBlocklist } from "../../utils/ip";
|
||||
import { ActorType, IRole, Role } from "../models";
|
||||
import {
|
||||
IIdentity,
|
||||
IdentityMembership,
|
||||
Membership,
|
||||
ServiceTokenData
|
||||
} from "../../models";
|
||||
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../../variables";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
|
||||
const $glob: FieldInstruction<string> = {
|
||||
type: "field",
|
||||
@ -55,7 +60,8 @@ export enum ProjectPermissionSub {
|
||||
Secrets = "secrets",
|
||||
SecretRollback = "secret-rollback",
|
||||
SecretApproval = "secret-approval",
|
||||
SecretRotation = "secret-rotation"
|
||||
SecretRotation = "secret-rotation",
|
||||
Identity = "identity"
|
||||
}
|
||||
|
||||
type SubjectFields = {
|
||||
@ -80,6 +86,7 @@ export type ProjectPermissionSet =
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretRotation]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Identity]
|
||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace]
|
||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace]
|
||||
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
||||
@ -126,6 +133,11 @@ const buildAdminPermission = () => {
|
||||
can(ProjectPermissionActions.Edit, 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.Create, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
||||
@ -191,6 +203,11 @@ const buildMemberPermission = () => {
|
||||
can(ProjectPermissionActions.Edit, 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.Create, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
||||
@ -231,6 +248,7 @@ const buildViewerPermission = () => {
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
||||
@ -243,6 +261,13 @@ const 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]
|
||||
*
|
||||
@ -256,7 +281,7 @@ export const getAuthDataProjectPermissions = async ({
|
||||
authData: AuthData;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
let role: "admin" | "member" | "viewer" | "custom";
|
||||
let role: "admin" | "member" | "viewer" | "no-access" | "custom";
|
||||
let customRole;
|
||||
|
||||
switch (authData.actor.type) {
|
||||
@ -265,10 +290,10 @@ export const getAuthDataProjectPermissions = async ({
|
||||
user: authData.authPayload._id,
|
||||
workspace: workspaceId
|
||||
})
|
||||
.populate<{
|
||||
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
||||
}>("customRole")
|
||||
.exec();
|
||||
.populate<{
|
||||
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
||||
}>("customRole")
|
||||
.exec();
|
||||
|
||||
if (!membership || (membership.role === "custom" && !membership.customRole)) {
|
||||
throw UnauthorizedRequestError();
|
||||
@ -284,25 +309,24 @@ export const getAuthDataProjectPermissions = async ({
|
||||
role = "viewer";
|
||||
break;
|
||||
}
|
||||
case ActorType.SERVICE_V3: {
|
||||
const serviceTokenData = await ServiceTokenDataV3
|
||||
.findById(authData.authPayload._id)
|
||||
.populate<{
|
||||
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
||||
}>("customRole")
|
||||
.exec();
|
||||
|
||||
if (!serviceTokenData || (serviceTokenData.role === "custom" && !serviceTokenData.customRole)) {
|
||||
case ActorType.IDENTITY: {
|
||||
const identityMembership = await IdentityMembership.findOne({
|
||||
identity: authData.authPayload._id,
|
||||
workspace: workspaceId
|
||||
})
|
||||
.populate<{
|
||||
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
||||
identity: IIdentity
|
||||
}>("customRole identity")
|
||||
.exec();
|
||||
|
||||
if (!identityMembership || (identityMembership.role === "custom" && !identityMembership.customRole)) {
|
||||
throw UnauthorizedRequestError();
|
||||
}
|
||||
|
||||
checkIPAgainstBlocklist({
|
||||
ipAddress: authData.ipAddress,
|
||||
trustedIps: serviceTokenData.trustedIps
|
||||
});
|
||||
|
||||
role = serviceTokenData.role;
|
||||
customRole = serviceTokenData.customRole;
|
||||
role = identityMembership.role;
|
||||
customRole = identityMembership.customRole;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -316,6 +340,8 @@ export const getAuthDataProjectPermissions = async ({
|
||||
return { permission: memberProjectPermissions };
|
||||
case VIEWER:
|
||||
return { permission: viewerProjectPermission };
|
||||
case NO_ACCESS:
|
||||
return { permission: noAccessProjectPermissions };
|
||||
case CUSTOM: {
|
||||
if (!customRole) throw UnauthorizedRequestError();
|
||||
return {
|
||||
@ -329,3 +355,61 @@ export const getAuthDataProjectPermissions = async ({
|
||||
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 { MembershipOrg } from "../../models";
|
||||
import { IRole } from "../models/role";
|
||||
import {
|
||||
IIdentity,
|
||||
IdentityMembershipOrg,
|
||||
MembershipOrg
|
||||
} from "../../models";
|
||||
import { ActorType, IRole, Role } from "../models";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { ACCEPTED } from "../../variables";
|
||||
import { ACCEPTED, ADMIN, CUSTOM, MEMBER, NO_ACCESS} from "../../variables";
|
||||
import { conditionsMatcher } from "./ProjectRoleService";
|
||||
import { AuthData } from "../../interfaces/middleware";
|
||||
|
||||
export enum OrgPermissionActions {
|
||||
Read = "read",
|
||||
@ -20,7 +26,8 @@ export enum OrgPermissionSubjects {
|
||||
IncidentAccount = "incident-contact",
|
||||
Sso = "sso",
|
||||
Billing = "billing",
|
||||
SecretScanning = "secret-scanning"
|
||||
SecretScanning = "secret-scanning",
|
||||
Identity = "identity"
|
||||
}
|
||||
|
||||
export type OrgPermissionSet =
|
||||
@ -32,7 +39,8 @@ export type OrgPermissionSet =
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing];
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
||||
|
||||
const buildAdminPermission = () => {
|
||||
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||
@ -75,6 +83,11 @@ const buildAdminPermission = () => {
|
||||
can(OrgPermissionActions.Edit, 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 });
|
||||
};
|
||||
|
||||
@ -98,13 +111,26 @@ const buildMemberPermission = () => {
|
||||
can(OrgPermissionActions.Edit, 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 });
|
||||
};
|
||||
|
||||
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) => {
|
||||
// TODO(akhilmhdh): speed this up by pulling from cache later
|
||||
|
||||
const membership = await MembershipOrg.findOne({
|
||||
user: userId,
|
||||
organization: orgId,
|
||||
@ -119,11 +145,13 @@ export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
||||
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, {
|
||||
conditionsMatcher
|
||||
});
|
||||
@ -132,3 +160,142 @@ export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
||||
|
||||
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
|
||||
} from "../variables";
|
||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||
import { InternalServerError } from "../utils/errors";
|
||||
import { BotNotFoundError, InternalServerError } from "../utils/errors";
|
||||
import { Folder } from "../models";
|
||||
import { getFolderByPath } from "../services/FolderService";
|
||||
import { getAllImportedSecrets } from "../services/SecretImportService";
|
||||
@ -223,7 +223,7 @@ export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) =
|
||||
workspace: workspaceId
|
||||
}).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({
|
||||
workspace: workspaceId
|
||||
|
@ -17,7 +17,7 @@ export const validateMembership = async ({
|
||||
}: {
|
||||
userId: 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({
|
||||
user: userId,
|
||||
|
@ -18,7 +18,7 @@ export const validateMembershipOrg = async ({
|
||||
}: {
|
||||
userId: Types.ObjectId;
|
||||
organizationId: Types.ObjectId;
|
||||
acceptedRoles?: Array<"owner" | "admin" | "member" | "custom">;
|
||||
acceptedRoles?: Array<"owner" | "admin" | "member" | "custom" | "no-access">;
|
||||
acceptedStatuses?: Array<"invited" | "accepted">;
|
||||
}) => {
|
||||
const membershipOrg = await MembershipOrg.findOne({
|
||||
|
@ -4,6 +4,11 @@ import {
|
||||
BotKey,
|
||||
BotOrg,
|
||||
Folder,
|
||||
Identity,
|
||||
IdentityMembership,
|
||||
IdentityMembershipOrg,
|
||||
IdentityUniversalAuth,
|
||||
IdentityUniversalAuthClientSecret,
|
||||
IncidentContactOrg,
|
||||
Integration,
|
||||
IntegrationAuth,
|
||||
@ -16,8 +21,6 @@ import {
|
||||
SecretImport,
|
||||
ServiceToken,
|
||||
ServiceTokenData,
|
||||
ServiceTokenDataV3,
|
||||
ServiceTokenDataV3Key,
|
||||
Tag,
|
||||
Webhook,
|
||||
Workspace
|
||||
@ -123,6 +126,32 @@ export const deleteOrganization = async ({
|
||||
await MembershipOrg.deleteMany({
|
||||
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({
|
||||
organization: organization._id
|
||||
@ -268,13 +297,7 @@ export const deleteOrganization = async ({
|
||||
}
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3Key.deleteMany({
|
||||
await IdentityMembership.deleteMany({
|
||||
workspace: {
|
||||
$in: workspaceIds
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export const apiLimiter = rateLimit({
|
||||
// errorHandler: console.error.bind(null, 'rate-limit-mongo')
|
||||
// }),
|
||||
windowMs: 60 * 1000,
|
||||
max: 350,
|
||||
max: 480,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
skip: (request) => {
|
||||
@ -30,7 +30,7 @@ const authLimit = rateLimit({
|
||||
// collectionName: "expressRateRecords-authLimit",
|
||||
// }),
|
||||
windowMs: 60 * 1000,
|
||||
max: 100,
|
||||
max: 300,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req, res) => {
|
||||
@ -46,8 +46,8 @@ export const passwordLimiter = rateLimit({
|
||||
// errorHandler: console.error.bind(null, 'rate-limit-mongo'),
|
||||
// collectionName: "expressRateRecords-passwordLimiter",
|
||||
// }),
|
||||
windowMs: 60 * 60 * 1000,
|
||||
max: 10,
|
||||
windowMs: 60 * 1000,
|
||||
max: 300,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
keyGenerator: (req, res) => {
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
Bot,
|
||||
BotKey,
|
||||
Folder,
|
||||
IdentityMembership,
|
||||
Integration,
|
||||
IntegrationAuth,
|
||||
Key,
|
||||
@ -12,8 +13,6 @@ import {
|
||||
SecretImport,
|
||||
ServiceToken,
|
||||
ServiceTokenData,
|
||||
ServiceTokenDataV3,
|
||||
ServiceTokenDataV3Key,
|
||||
Tag,
|
||||
Webhook,
|
||||
Workspace
|
||||
@ -178,12 +177,8 @@ export const deleteWorkspace = async ({
|
||||
await ServiceTokenData.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
await ServiceTokenDataV3Key.deleteMany({
|
||||
|
||||
await IdentityMembership.deleteMany({
|
||||
workspace: workspace._id
|
||||
});
|
||||
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
secretSnapshot as eeSecretSnapshotRouter,
|
||||
users as eeUsersRouter,
|
||||
workspace as eeWorkspaceRouter,
|
||||
identities as v1IdentitiesRouter,
|
||||
roles as v1RoleRouter,
|
||||
secretApprovalPolicy as v1SecretApprovalPolicyRouter,
|
||||
secretApprovalRequest as v1SecretApprovalRequestRouter,
|
||||
@ -33,7 +34,6 @@ import {
|
||||
secretScanning as v1SecretScanningRouter
|
||||
} from "./ee/routes/v1";
|
||||
import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3";
|
||||
import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3";
|
||||
import {
|
||||
admin as v1AdminRouter,
|
||||
auth as v1AuthRouter,
|
||||
@ -52,6 +52,7 @@ import {
|
||||
secretsFolder as v1SecretsFolder,
|
||||
serviceToken as v1ServiceTokenRouter,
|
||||
signup as v1SignupRouter,
|
||||
universalAuth as v1UniversalAuthRouter,
|
||||
userAction as v1UserActionRouter,
|
||||
user as v1UserRouter,
|
||||
webhooks as v1WebhooksRouter,
|
||||
@ -198,6 +199,7 @@ const main = async () => {
|
||||
}
|
||||
|
||||
// (EE) routes
|
||||
app.use("/api/v1/identities", v1IdentitiesRouter);
|
||||
app.use("/api/v1/secret", eeSecretRouter);
|
||||
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
||||
app.use("/api/v1/users", eeUsersRouter);
|
||||
@ -205,14 +207,14 @@ const main = async () => {
|
||||
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
||||
app.use("/api/v1/sso", eeSSORouter);
|
||||
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
||||
app.use("/api/v3/api-key", v3apiKeyDataRouter); // new
|
||||
app.use("/api/v3/service-token", v3ServiceTokenDataRouter); // new
|
||||
app.use("/api/v3/api-key", v3apiKeyDataRouter);
|
||||
app.use("/api/v1/secret-rotation-providers", v1SecretRotationProviderRouter);
|
||||
app.use("/api/v1/secret-rotations", v1SecretRotation);
|
||||
|
||||
// v1 routes
|
||||
app.use("/api/v1/signup", v1SignupRouter);
|
||||
app.use("/api/v1/auth", v1AuthRouter);
|
||||
app.use("/api/v1/auth", v1UniversalAuthRouter); // new
|
||||
app.use("/api/v1/admin", v1AdminRouter);
|
||||
app.use("/api/v1/bot", v1BotRouter);
|
||||
app.use("/api/v1/user", v1UserRouter);
|
||||
@ -220,7 +222,7 @@ const main = async () => {
|
||||
app.use("/api/v1/organization", v1OrganizationRouter);
|
||||
app.use("/api/v1/workspace", v1WorkspaceRouter);
|
||||
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/invite-org", v1InviteOrgRouter);
|
||||
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", v2WorkspaceRouter);
|
||||
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);
|
||||
|
||||
// v3 routes (experimental)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Types } from "mongoose";
|
||||
import { IServiceTokenData, IServiceTokenDataV3, IUser } from "../../models";
|
||||
import { ServiceActor, ServiceActorV3, UserActor, UserAgentType } from "../../ee/models";
|
||||
import { IIdentity, IServiceTokenData, IUser } from "../../models";
|
||||
import { IdentityActor, ServiceActor, UserActor, UserAgentType } from "../../ee/models";
|
||||
|
||||
interface BaseAuthData {
|
||||
ipAddress: string;
|
||||
@ -14,9 +14,9 @@ export interface UserAuthData extends BaseAuthData {
|
||||
authPayload: IUser;
|
||||
}
|
||||
|
||||
export interface ServiceTokenV3AuthData extends BaseAuthData {
|
||||
actor: ServiceActorV3;
|
||||
authPayload: IServiceTokenDataV3;
|
||||
export interface IdentityAuthData extends BaseAuthData {
|
||||
actor: IdentityActor;
|
||||
authPayload: IIdentity;
|
||||
}
|
||||
|
||||
export interface ServiceTokenAuthData extends BaseAuthData {
|
||||
@ -24,4 +24,4 @@ export interface ServiceTokenAuthData extends BaseAuthData {
|
||||
authPayload: IServiceTokenData;
|
||||
}
|
||||
|
||||
export type AuthData = UserAuthData | ServiceTokenV3AuthData | ServiceTokenAuthData;
|
||||
export type AuthData = UserAuthData | IdentityAuthData | ServiceTokenAuthData;
|
@ -39,8 +39,9 @@ export const requestErrorHandler: ErrorRequestHandler = async (
|
||||
|
||||
Sentry.captureException(error);
|
||||
|
||||
delete (<any>error).stacktrace // remove stack trace from being sent to client
|
||||
res.status((<RequestError>error).statusCode).json(error); // revise json part here
|
||||
res.status((<RequestError>error).statusCode).send(
|
||||
await error.format(req)
|
||||
);
|
||||
|
||||
next();
|
||||
};
|
||||
|
@ -50,7 +50,7 @@ const requireAuth = ({
|
||||
case AuthMode.SERVICE_TOKEN:
|
||||
req.serviceTokenData = authData.authPayload;
|
||||
break;
|
||||
case AuthMode.SERVICE_ACCESS_TOKEN:
|
||||
case AuthMode.IDENTITY_ACCESS_TOKEN:
|
||||
req.serviceTokenData = authData.authPayload;
|
||||
break;
|
||||
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 "./workspace";
|
||||
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 "./apiKeyDataV2";
|
||||
export * from "./loginSRPDetail";
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 {
|
||||
environmentSlug: string;
|
||||
@ -11,7 +11,7 @@ export interface IMembership {
|
||||
user: Types.ObjectId;
|
||||
inviteEmail?: string;
|
||||
workspace: Types.ObjectId;
|
||||
role: "admin" | "member" | "viewer" | "custom";
|
||||
role: "admin" | "member" | "viewer" | "no-access" | "custom";
|
||||
customRole: Types.ObjectId;
|
||||
deniedPermissions: IMembershipPermission[];
|
||||
}
|
||||
@ -44,7 +44,7 @@ const membershipSchema = new Schema<IMembership>(
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
enum: [ADMIN, MEMBER, VIEWER, CUSTOM],
|
||||
enum: [ADMIN, MEMBER, VIEWER, NO_ACCESS, CUSTOM],
|
||||
required: true
|
||||
},
|
||||
customRole: {
|
||||
|
@ -1,12 +1,12 @@
|
||||
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 {
|
||||
_id: Types.ObjectId;
|
||||
user: Types.ObjectId;
|
||||
inviteEmail: string;
|
||||
organization: Types.ObjectId;
|
||||
role: "owner" | "admin" | "member" | "custom";
|
||||
role: "admin" | "member" | "no-access" | "custom";
|
||||
customRole: Types.ObjectId;
|
||||
status: "invited" | "accepted";
|
||||
}
|
||||
@ -26,7 +26,7 @@ const membershipOrgSchema = new Schema(
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
enum: [ADMIN, MEMBER, CUSTOM],
|
||||
enum: [ADMIN, MEMBER, NO_ACCESS, CUSTOM],
|
||||
required: true
|
||||
},
|
||||
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 bot from "./bot";
|
||||
import auth from "./auth";
|
||||
import universalAuth from "./universalAuth";
|
||||
import user from "./user";
|
||||
import userAction from "./userAction";
|
||||
import organization from "./organization";
|
||||
@ -23,6 +24,7 @@ import admin from "./admin";
|
||||
export {
|
||||
signup,
|
||||
auth,
|
||||
universalAuth,
|
||||
bot,
|
||||
user,
|
||||
userAction,
|
||||
|
@ -156,12 +156,20 @@ router.get(
|
||||
integrationAuthController.getIntegrationAuthTeamCityBuildConfigs
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.deleteIntegrationAuths
|
||||
);
|
||||
|
||||
router.delete(
|
||||
"/:integrationAuthId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.deleteIntegrationAuth
|
||||
integrationAuthController.deleteIntegrationAuthById
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
|
||||
router.post(
|
||||
"/",
|
||||
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
|
||||
);
|
||||
@ -15,7 +15,7 @@ router.post(
|
||||
router.put(
|
||||
"/:id",
|
||||
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
|
||||
);
|
||||
@ -23,7 +23,7 @@ router.put(
|
||||
router.delete(
|
||||
"/:id",
|
||||
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
|
||||
);
|
||||
@ -31,7 +31,7 @@ router.delete(
|
||||
router.get(
|
||||
"/",
|
||||
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
|
||||
);
|
||||
|
@ -12,7 +12,7 @@ import { AuthMode } from "../../variables";
|
||||
router.post(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
createFolder
|
||||
);
|
||||
@ -20,7 +20,7 @@ router.post(
|
||||
router.patch(
|
||||
"/:folderName",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
updateFolderById
|
||||
);
|
||||
@ -28,7 +28,7 @@ router.patch(
|
||||
router.delete(
|
||||
"/:folderName",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
deleteFolder
|
||||
);
|
||||
@ -36,7 +36,7 @@ router.delete(
|
||||
router.get(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.SERVICE_TOKEN, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
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(
|
||||
"/:workspaceId/environments",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
environmentController.createWorkspaceEnvironment
|
||||
);
|
||||
@ -15,7 +15,7 @@ router.post(
|
||||
router.put(
|
||||
"/:workspaceId/environments",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
environmentController.renameWorkspaceEnvironment
|
||||
);
|
||||
@ -23,7 +23,7 @@ router.put(
|
||||
router.patch(
|
||||
"/:workspaceId/environments",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
environmentController.reorderWorkspaceEnvironments
|
||||
);
|
||||
@ -31,7 +31,7 @@ router.patch(
|
||||
router.delete(
|
||||
"/:workspaceId/environments",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
environmentController.deleteWorkspaceEnvironment
|
||||
);
|
||||
|
@ -9,7 +9,7 @@ import { organizationsController } from "../../controllers/v2";
|
||||
router.get(
|
||||
"/:organizationId/memberships",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
organizationsController.getOrganizationMemberships
|
||||
);
|
||||
@ -17,7 +17,7 @@ router.get(
|
||||
router.patch(
|
||||
"/:organizationId/memberships/:membershipId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
organizationsController.updateOrganizationMembership
|
||||
);
|
||||
@ -25,7 +25,7 @@ router.patch(
|
||||
router.delete(
|
||||
"/:organizationId/memberships/:membershipId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
organizationsController.deleteOrganizationMembership
|
||||
);
|
||||
@ -33,7 +33,7 @@ router.delete(
|
||||
router.get(
|
||||
"/:organizationId/workspaces",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
organizationsController.getOrganizationWorkspaces
|
||||
);
|
||||
@ -54,4 +54,12 @@ router.delete(
|
||||
organizationsController.deleteOrganizationById
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:organizationId/identity-memberships",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
organizationsController.getOrganizationIdentityMemberships
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
import { AuthMode } from "../../variables";
|
||||
import { serviceTokenDataController } from "../../controllers/v2";
|
||||
|
||||
router.get( // TODO: deprecate (moving to ST V3)
|
||||
router.get( // TODO: deprecate (moving to identity)
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.SERVICE_TOKEN]
|
||||
@ -14,7 +14,7 @@ router.get( // TODO: deprecate (moving to ST V3)
|
||||
serviceTokenDataController.getServiceTokenData
|
||||
);
|
||||
|
||||
router.post( // TODO: deprecate (moving to ST V3)
|
||||
router.post( // TODO: deprecate (moving to identity)
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
@ -22,7 +22,7 @@ router.post( // TODO: deprecate (moving to ST V3)
|
||||
serviceTokenDataController.createServiceTokenData
|
||||
);
|
||||
|
||||
router.delete( // TODO: deprecate (moving to ST V3)
|
||||
router.delete( // TODO: deprecate (moving to identity)
|
||||
"/:serviceTokenDataId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
|
@ -62,7 +62,7 @@ router.get(
|
||||
// new - TODO: rewire dashboard to this route
|
||||
"/:workspaceId/memberships",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
workspaceController.getWorkspaceMemberships
|
||||
);
|
||||
@ -71,7 +71,7 @@ router.patch(
|
||||
// TODO - rewire dashboard to this route
|
||||
"/:workspaceId/memberships/:membershipId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
workspaceController.updateWorkspaceMembership
|
||||
);
|
||||
@ -80,7 +80,7 @@ router.delete(
|
||||
// TODO - rewire dashboard to this route
|
||||
"/:workspaceId/memberships/:membershipId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]
|
||||
}),
|
||||
workspaceController.deleteWorkspaceMembership
|
||||
);
|
||||
@ -93,4 +93,37 @@ router.patch(
|
||||
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;
|
||||
|
@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
|
||||
router.get(
|
||||
"/raw",
|
||||
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
|
||||
);
|
||||
@ -15,7 +15,7 @@ router.get(
|
||||
router.get(
|
||||
"/raw/:secretName",
|
||||
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({
|
||||
locationWorkspaceId: "query"
|
||||
@ -29,7 +29,7 @@ router.get(
|
||||
router.post(
|
||||
"/raw/:secretName",
|
||||
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({
|
||||
locationWorkspaceId: "body"
|
||||
@ -43,7 +43,7 @@ router.post(
|
||||
router.patch(
|
||||
"/raw/:secretName",
|
||||
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({
|
||||
locationWorkspaceId: "body"
|
||||
@ -57,7 +57,7 @@ router.patch(
|
||||
router.delete(
|
||||
"/raw/:secretName",
|
||||
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({
|
||||
locationWorkspaceId: "body"
|
||||
@ -71,7 +71,7 @@ router.delete(
|
||||
router.get(
|
||||
"/",
|
||||
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({
|
||||
locationWorkspaceId: "query"
|
||||
@ -116,7 +116,7 @@ router.delete(
|
||||
router.post(
|
||||
"/:secretName",
|
||||
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({
|
||||
locationWorkspaceId: "body"
|
||||
@ -127,7 +127,7 @@ router.post(
|
||||
router.get(
|
||||
"/:secretName",
|
||||
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({
|
||||
locationWorkspaceId: "query"
|
||||
@ -138,7 +138,7 @@ router.get(
|
||||
router.patch(
|
||||
"/:secretName",
|
||||
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({
|
||||
locationWorkspaceId: "body"
|
||||
@ -149,7 +149,7 @@ router.patch(
|
||||
router.delete(
|
||||
"/:secretName",
|
||||
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({
|
||||
locationWorkspaceId: "body"
|
||||
|
@ -34,12 +34,4 @@ router.post(
|
||||
|
||||
// --
|
||||
|
||||
router.get(
|
||||
"/:workspaceId/service-token",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
workspacesController.getWorkspaceServiceTokenData
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -8,12 +8,12 @@ import {
|
||||
getTelemetryEnabled,
|
||||
} from "../config";
|
||||
import {
|
||||
Identity,
|
||||
ServiceTokenData,
|
||||
User,
|
||||
User
|
||||
} from "../models";
|
||||
import {
|
||||
AccountNotFoundError,
|
||||
BadRequestError,
|
||||
} from "../utils/errors";
|
||||
|
||||
class Telemetry {
|
||||
@ -22,7 +22,7 @@ class Telemetry {
|
||||
*/
|
||||
static logTelemetryMessage = async () => {
|
||||
|
||||
if(!(await getTelemetryEnabled())){
|
||||
if (!(await getTelemetryEnabled())) {
|
||||
[
|
||||
"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.",
|
||||
@ -42,8 +42,8 @@ class Telemetry {
|
||||
postHogClient = new PostHog(await getPostHogProjectApiKey(), {
|
||||
host: await getPostHogHost(),
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return postHogClient;
|
||||
}
|
||||
|
||||
@ -52,6 +52,7 @@ class Telemetry {
|
||||
}: {
|
||||
authData: AuthData;
|
||||
}) => {
|
||||
|
||||
let distinctId = "";
|
||||
if (authData.authPayload instanceof User) {
|
||||
distinctId = authData.authPayload.email;
|
||||
@ -59,14 +60,14 @@ class Telemetry {
|
||||
if (authData.authPayload.user) {
|
||||
const user = await User.findById(authData.authPayload.user, "email");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
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 "./jwt";
|
||||
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 {
|
||||
Identity,
|
||||
ServiceTokenData,
|
||||
ServiceTokenDataV3,
|
||||
User
|
||||
} from "../../../models";
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
return { serviceTokenDataId: authData.authPayload._id };
|
||||
}
|
||||
|
||||
if (authData.authPayload instanceof ServiceTokenDataV3) {
|
||||
if (authData.authPayload instanceof Identity) {
|
||||
return { serviceTokenDataId: authData.authPayload._id };
|
||||
}
|
||||
};
|
||||
@ -38,7 +38,7 @@ export const getAuthDataPayloadUserObj = (authData: AuthData) => {
|
||||
return { user: authData.authPayload.user };
|
||||
}
|
||||
|
||||
if (authData.authPayload instanceof ServiceTokenDataV3) {
|
||||
return { user: authData.authPayload.user };
|
||||
if (authData.authPayload instanceof Identity) {
|
||||
return {};
|
||||
}
|
||||
}
|
@ -7,9 +7,9 @@ import { UnauthorizedRequestError } from "../../errors";
|
||||
import {
|
||||
validateAPIKey,
|
||||
validateAPIKeyV2,
|
||||
validateIdentity,
|
||||
validateJWT,
|
||||
validateServiceTokenV2,
|
||||
validateServiceTokenV3
|
||||
validateServiceTokenV2
|
||||
} from "../authModeValidators";
|
||||
import { getUserAgentType } from "../../posthog";
|
||||
|
||||
@ -36,7 +36,7 @@ interface GetAuthDataParams {
|
||||
* - SERVICE_TOKEN
|
||||
* - API_KEY
|
||||
* - JWT
|
||||
* - SERVICE_ACCESS_TOKEN (from ST V3)
|
||||
* - IDENTITY_ACCESS_TOKEN (from identity)
|
||||
* - API_KEY_V2
|
||||
* @param {Object} params
|
||||
* @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 };
|
||||
case AuthTokenType.API_KEY:
|
||||
return { authMode: AuthMode.API_KEY_V2, authTokenValue };
|
||||
case AuthTokenType.SERVICE_ACCESS_TOKEN:
|
||||
return { authMode: AuthMode.SERVICE_ACCESS_TOKEN, authTokenValue };
|
||||
case AuthTokenType.IDENTITY_ACCESS_TOKEN:
|
||||
return { authMode: AuthMode.IDENTITY_ACCESS_TOKEN, authTokenValue };
|
||||
default:
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed to authenticate unknown authentication method"
|
||||
@ -115,20 +115,21 @@ export const getAuthData = async ({
|
||||
userAgentType
|
||||
}
|
||||
}
|
||||
case AuthMode.SERVICE_ACCESS_TOKEN: {
|
||||
const serviceTokenData = await validateServiceTokenV3({
|
||||
authTokenValue
|
||||
case AuthMode.IDENTITY_ACCESS_TOKEN: {
|
||||
const identity = await validateIdentity({
|
||||
authTokenValue,
|
||||
ipAddress
|
||||
});
|
||||
|
||||
return {
|
||||
actor: {
|
||||
type: ActorType.SERVICE_V3,
|
||||
type: ActorType.IDENTITY,
|
||||
metadata: {
|
||||
serviceId: serviceTokenData._id.toString(),
|
||||
name: serviceTokenData.name
|
||||
identityId: identity._id.toString(),
|
||||
name: identity.name
|
||||
}
|
||||
},
|
||||
authPayload: serviceTokenData,
|
||||
authPayload: identity,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
userAgentType
|
||||
|
@ -75,7 +75,7 @@ export const initializeSamlStrategy = async () => {
|
||||
const organization = await Organization.findById(req.ssoConfig.organization);
|
||||
|
||||
if (!organization) return done(OrganizationNotFoundError());
|
||||
|
||||
|
||||
const email = profile.email;
|
||||
const firstName = profile.firstName;
|
||||
const lastName = profile.lastName;
|
||||
@ -154,6 +154,7 @@ export const initializeSamlStrategy = async () => {
|
||||
firstName,
|
||||
lastName,
|
||||
organizationName: organization?.name,
|
||||
organizationId: organization?._id,
|
||||
authMethod: req.ssoConfig.authProvider,
|
||||
isUserCompleted,
|
||||
...(req.body.RelayState ? {
|
||||
|
@ -7,8 +7,14 @@ export const getUserAgentType = function (userAgent: string | undefined) {
|
||||
return UserAgentType.CLI;
|
||||
} else if (userAgent == UserAgentType.K8_OPERATOR) {
|
||||
return UserAgentType.K8_OPERATOR;
|
||||
} else if (userAgent == UserAgentType.TERRAFORM) {
|
||||
return UserAgentType.TERRAFORM;
|
||||
} else if (userAgent.toLowerCase().includes("mozilla")) {
|
||||
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 {
|
||||
return UserAgentType.OTHER;
|
||||
}
|
||||
|
@ -53,9 +53,10 @@ export default class RequestError extends Error {
|
||||
){
|
||||
|
||||
super(message)
|
||||
this._logLevel = logLevel || LogLevel.INFO
|
||||
this._logLevel = logLevel || LogLevel.INFO;
|
||||
this._logName = LogLevel[this._logLevel];
|
||||
this.statusCode = statusCode
|
||||
this.statusCode = statusCode;
|
||||
this.message = message;
|
||||
this.type = type
|
||||
this.context = context || {}
|
||||
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" }]),
|
||||
accessTokenTrustedIps: z
|
||||
.object({
|
||||
ipAddress: z.string().trim(),
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.default([{ ipAddress: "0.0.0.0/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({
|
||||
body: z.object({
|
||||
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 "./secrets";
|
||||
export * from "./serviceTokenData";
|
||||
export * from "./serviceTokenDataV3";
|
||||
export * from "./identities";
|
||||
export * from "./apiKeyDataV3";
|
||||
|
@ -58,9 +58,9 @@ const validateClientForIntegrationAuth = async ({
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service token authorization for integration authorization"
|
||||
});
|
||||
case ActorType.SERVICE_V3:
|
||||
case ActorType.IDENTITY:
|
||||
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({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
|
@ -46,9 +46,9 @@ export const validateClientForOrganization = async ({
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service token authorization for organization"
|
||||
});
|
||||
case ActorType.SERVICE_V3:
|
||||
case ActorType.IDENTITY:
|
||||
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({
|
||||
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() })
|
||||
});
|
@ -158,7 +158,7 @@ export const CreateServiceTokenV2 = z.object({
|
||||
encryptedKey: z.string().trim(),
|
||||
iv: z.string().trim(),
|
||||
tag: z.string().trim(),
|
||||
expiresIn: z.number(),
|
||||
expiresIn: z.number().nullable().optional(),
|
||||
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 { EventType, UserAgentType } from "../ee/models";
|
||||
import { UnauthorizedRequestError } from "../utils/errors";
|
||||
import { NO_ACCESS } from "../variables";
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for workspace with id [workspaceId] based
|
||||
@ -59,9 +60,9 @@ export const validateClientForWorkspace = async ({
|
||||
requiredPermissions
|
||||
});
|
||||
return { membership, workspace };
|
||||
case ActorType.SERVICE_V3:
|
||||
case ActorType.IDENTITY:
|
||||
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({
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
@ -304,9 +338,3 @@ export const NameWorkspaceSecretsV3 = z.object({
|
||||
.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
|
||||
PROVIDER_TOKEN = "providerToken", // TODO: remove in favor of claim
|
||||
API_KEY = "apiKey",
|
||||
SERVICE_ACCESS_TOKEN = "serviceAccessToken",
|
||||
SERVICE_REFRESH_TOKEN = "serviceRefreshToken"
|
||||
IDENTITY_ACCESS_TOKEN = "identityAccessToken",
|
||||
}
|
||||
|
||||
export enum AuthMode {
|
||||
JWT = "jwt",
|
||||
SERVICE_TOKEN = "serviceToken",
|
||||
SERVICE_ACCESS_TOKEN = "serviceAccessToken",
|
||||
IDENTITY_ACCESS_TOKEN = "identityAccessToken",
|
||||
API_KEY = "apiKey",
|
||||
API_KEY_V2 = "apiKeyV2"
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ export const OWNER = "owner"; // depreciated
|
||||
export const ADMIN = "admin";
|
||||
export const MEMBER = "member";
|
||||
export const VIEWER = "viewer";
|
||||
export const NO_ACCESS = "no-access";
|
||||
export const CUSTOM = "custom";
|
||||
|
||||
// membership statuses
|
||||
|
@ -30,7 +30,7 @@ const generateOpenAPISpec = async () => {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
bearerFormat: "JWT",
|
||||
description: "A service token in Infisical"
|
||||
description: "An access token in Infisical"
|
||||
},
|
||||
apiKeyAuth: {
|
||||
type: "apiKey",
|
||||
@ -52,6 +52,41 @@ const generateOpenAPISpec = async () => {
|
||||
updatedAt: "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: {
|
||||
user: {
|
||||
_id: "",
|
||||
@ -79,6 +114,25 @@ const generateOpenAPISpec = async () => {
|
||||
role: "owner",
|
||||
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: {
|
||||
_id: "",
|
||||
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"]
|
@ -1,17 +0,0 @@
|
||||
infisical:
|
||||
address: "http://localhost:8080"
|
||||
auth:
|
||||
type: "token"
|
||||
config:
|
||||
token-path: "./role-id"
|
||||
sinks:
|
||||
- type: "file"
|
||||
config:
|
||||
path: "/Users/maidulislam/Desktop/test/infisical-token"
|
||||
- type: "file"
|
||||
config:
|
||||
path: "access-token"
|
||||
- type: "file"
|
||||
config:
|
||||
path: "maiduls-access-token"
|
||||
templates:
|
16
cli/go.mod
16
cli/go.mod
@ -21,8 +21,9 @@ require (
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.8.1
|
||||
github.com/stretchr/testify v1.8.1
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
golang.org/x/term v0.11.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/term v0.13.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
@ -56,18 +57,17 @@ require (
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
|
||||
go.mongodb.org/mongo-driver v1.10.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/go-resty/resty/v2 v2.10.0
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible
|
||||
github.com/manifoldco/promptui v0.9.0
|
||||
|
46
cli/go.sum
46
cli/go.sum
@ -105,8 +105,8 @@ github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02E
|
||||
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
|
||||
github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o=
|
||||
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo=
|
||||
github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@ -354,6 +354,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
@ -376,9 +377,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -414,6 +417,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -451,10 +456,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -477,8 +484,10 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -528,11 +537,18 @@ golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -542,11 +558,15 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@ -599,6 +619,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -425,24 +425,44 @@ func CallCreateServiceToken(httpClient *resty.Client, request CreateServiceToken
|
||||
return createServiceTokenResponse, nil
|
||||
}
|
||||
|
||||
func CallServiceTokenV3Refresh(httpClient *resty.Client, request ServiceTokenV3RefreshTokenRequest) (ServiceTokenV3RefreshTokenResponse, error) {
|
||||
var serviceTokenV3RefreshTokenResponse ServiceTokenV3RefreshTokenResponse
|
||||
func CallUniversalAuthLogin(httpClient *resty.Client, request UniversalAuthLoginRequest) (UniversalAuthLoginResponse, error) {
|
||||
var universalAuthLoginResponse UniversalAuthLoginResponse
|
||||
response, err := httpClient.
|
||||
R().
|
||||
SetResult(&serviceTokenV3RefreshTokenResponse).
|
||||
SetResult(&universalAuthLoginResponse).
|
||||
SetHeader("User-Agent", USER_AGENT).
|
||||
SetBody(request).
|
||||
Post(fmt.Sprintf("%v/v3/service-token/me/token", config.INFISICAL_URL))
|
||||
Post(fmt.Sprintf("%v/v1/auth/universal-auth/login/", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return ServiceTokenV3RefreshTokenResponse{}, fmt.Errorf("CallServiceTokenV3Refresh: Unable to complete api request [err=%s]", err)
|
||||
return UniversalAuthLoginResponse{}, fmt.Errorf("CallUniversalAuthLogin: Unable to complete api request [err=%s]", err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return ServiceTokenV3RefreshTokenResponse{}, fmt.Errorf("CallServiceTokenV3Refresh: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
return UniversalAuthLoginResponse{}, fmt.Errorf("CallUniversalAuthLogin: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
}
|
||||
|
||||
return serviceTokenV3RefreshTokenResponse, nil
|
||||
return universalAuthLoginResponse, nil
|
||||
}
|
||||
|
||||
func CallUniversalAuthRefreshAccessToken(httpClient *resty.Client, request UniversalAuthRefreshRequest) (UniversalAuthRefreshResponse, error) {
|
||||
var universalAuthRefreshResponse UniversalAuthRefreshResponse
|
||||
response, err := httpClient.
|
||||
R().
|
||||
SetResult(&universalAuthRefreshResponse).
|
||||
SetHeader("User-Agent", USER_AGENT).
|
||||
SetBody(request).
|
||||
Post(fmt.Sprintf("%v/v1/auth/token/renew", config.INFISICAL_URL))
|
||||
|
||||
if err != nil {
|
||||
return UniversalAuthRefreshResponse{}, fmt.Errorf("CallUniversalAuthRefreshAccessToken: Unable to complete api request [err=%s]", err)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return UniversalAuthRefreshResponse{}, fmt.Errorf("CallUniversalAuthRefreshAccessToken: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
}
|
||||
|
||||
return universalAuthRefreshResponse, nil
|
||||
}
|
||||
|
||||
func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Request) (GetRawSecretsV3Response, error) {
|
||||
@ -454,6 +474,7 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
|
||||
SetBody(request).
|
||||
SetQueryParam("workspaceId", request.WorkspaceId).
|
||||
SetQueryParam("environment", request.Environment).
|
||||
SetQueryParam("secretPath", request.SecretPath).
|
||||
SetQueryParam("include_imports", "false").
|
||||
Get(fmt.Sprintf("%v/v3/secrets/raw", config.INFISICAL_URL))
|
||||
|
||||
@ -461,12 +482,12 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
|
||||
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unable to complete api request [err=%w]", err)
|
||||
}
|
||||
|
||||
if response.IsError() && strings.Contains(response.String(), "Failed to find bot key") {
|
||||
if response.IsError() && strings.Contains(response.String(), "bot_not_found_error") {
|
||||
return GetRawSecretsV3Response{}, fmt.Errorf("project with id %s is a legacy project type, please navigate to project settings and disable end to end encryption then try again", request.WorkspaceId)
|
||||
}
|
||||
|
||||
if response.IsError() {
|
||||
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v]", response.Request.Method, response.Request.URL, response.StatusCode())
|
||||
return GetRawSecretsV3Response{}, fmt.Errorf("CallGetRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||
}
|
||||
|
||||
return getRawSecretsV3Response, nil
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user