mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-24 00:15:26 +00:00
Compare commits
261 Commits
infisica-a
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
68eb0f8dd9 | |||
5941e8e836 | |||
80e50d13ec | |||
99c8dda4e1 | |||
14c8e3fa3b | |||
7aa3cb53a2 | |||
567309e848 | |||
f264340903 | |||
51b788cc5b | |||
8e0f424249 | |||
f3767d3963 | |||
51cbfdbc46 | |||
f5a580eb72 | |||
460ebf3296 | |||
7f7f11c970 | |||
f799e224a0 | |||
8a87277fe6 | |||
32805c726a | |||
6c4a6d31e4 | |||
e7b89b645f | |||
b60cf2eb07 | |||
cf5a79995f | |||
c51f09fd3a | |||
f9444c5205 | |||
7dd0943b2d | |||
31a9f032b3 | |||
9c55d1906d | |||
ff54a20ace | |||
8bf7eba07b | |||
bb75ea550a | |||
344f7276d2 | |||
c375662411 | |||
cc4ad1df4b | |||
c92c0f7288 | |||
fbe0cf006f | |||
d2f959558e | |||
e50c89e326 | |||
6cda14328b | |||
b551ee50e7 | |||
93aeacc6b6 | |||
f940f8b79d | |||
72ac2c04b8 | |||
bb3d591f21 | |||
763ce1b206 | |||
1f97ac5192 | |||
5f29562fad | |||
f3e8ef1537 | |||
544d37bbc4 | |||
4f6adb50d1 | |||
444ce9508d | |||
aabd896c37 | |||
50ef23e8a0 | |||
b87f51a044 | |||
1233d9c1a0 | |||
ff0b4d7f2b | |||
ef61bc6a40 | |||
13ee8c4e13 | |||
6ea9fc7134 | |||
00e1742a30 | |||
5055b5402c | |||
ff9418c0c7 | |||
d03921eef3 | |||
602afdefc3 | |||
5eb505326b | |||
fcf4153d87 | |||
097282c5e1 | |||
0eeef9a66c | |||
df0bec8a68 | |||
13014b5345 | |||
66d0cae066 | |||
8e82222fc5 | |||
f822bcd10f | |||
89d0c0e3c3 | |||
a4f6b828ad | |||
0fb2056b8b | |||
c51f8c5838 | |||
ec5cf97f18 | |||
69b57817d6 | |||
aafbe40c02 | |||
9d9b83f909 | |||
ea1f144b54 | |||
591f33ffbe | |||
855158d0bb | |||
87e997e7a0 | |||
3c449214d1 | |||
d813f0716f | |||
6787c0eaaa | |||
377a79f17d | |||
c91f6521c1 | |||
0ebd1d3d81 | |||
d257a449bb | |||
6a744c96e5 | |||
2a768a7bc4 | |||
28b617fd89 | |||
8b1eaad7b5 | |||
c917cf8a18 | |||
282830e7a2 | |||
3d6f04b94e | |||
60a5092947 | |||
69dae1f0b2 | |||
4b41664fa4 | |||
735cf093f0 | |||
98906f190c | |||
5f80e2f432 | |||
afd6a7736a | |||
057fcb164d | |||
b575c0e207 | |||
372afa2111 | |||
6557d7668e | |||
33017b50f0 | |||
f5de501348 | |||
758c3a2423 | |||
d01c6e4df9 | |||
ed94d218fd | |||
adc7be2e84 | |||
697485f8ed | |||
19e2523d0e | |||
8851987ac4 | |||
64d862ebe9 | |||
70d1cc0e06 | |||
aee91a9558 | |||
d6218eaa82 | |||
8886e57d4f | |||
6efb58da1a | |||
f187cc2c26 | |||
d5c5495475 | |||
8e33692cda | |||
6c31e70f4f | |||
d7026cbbfa | |||
df0e0bf988 | |||
4ec0c9cdbf | |||
75cd3bfa35 | |||
1db7d50a09 | |||
b4980b4a53 | |||
1351ba936f | |||
57bccaefba | |||
86af452888 | |||
6cef7532da | |||
74e33144a7 | |||
a9776eaeb5 | |||
3c2a66f722 | |||
d92b1d3cd8 | |||
f560242f1d | |||
33fc968055 | |||
f289b99cf1 | |||
019b477d2d | |||
9ad2d9d218 | |||
a575530ddf | |||
33ea019d70 | |||
5ae3b66e2e | |||
20210d7471 | |||
77e3d10a64 | |||
814b71052d | |||
6579b3c93f | |||
99c41bb63b | |||
63df0dba64 | |||
4e050cfe7a | |||
32f5c96dd2 | |||
d7262d4291 | |||
7a9221769d | |||
5e5761424a | |||
5b923c25b5 | |||
46c76e3984 | |||
b212681d09 | |||
be67b9b341 | |||
29016fbb23 | |||
0c0139ac8f | |||
180274be34 | |||
595a26a366 | |||
41c41a647f | |||
c3d2b7d3fc | |||
84e32faac9 | |||
87984a704a | |||
33e4104e98 | |||
b5d5eb87a7 | |||
bcfb14ca86 | |||
87e2844499 | |||
ae4b8ca9b2 | |||
0cff39f918 | |||
53ac05694c | |||
097a8cae89 | |||
597e1e1ca8 | |||
4757ceb938 | |||
e3d536ef58 | |||
89ae3070ce | |||
f3895b70ee | |||
3478d71e99 | |||
1f5e458b64 | |||
6f8373c977 | |||
c55a36b291 | |||
e4a04bdf0a | |||
4db9a5279f | |||
c37ff79927 | |||
98e299c2ac | |||
9d9e830d73 | |||
d909ff6a97 | |||
5301bcc91f | |||
77438f9282 | |||
122a1e32e1 | |||
9d2a08dbec | |||
87c2e417d2 | |||
341a745843 | |||
085ddb2c48 | |||
6734ce50a5 | |||
94f893017b | |||
fca5ae9172 | |||
829f65cdb7 | |||
7785fbafbd | |||
35cff782e1 | |||
a35643bf6e | |||
85de985321 | |||
40f5bbbc07 | |||
85254ba984 | |||
4cb586996c | |||
3ea20328dc | |||
2aa46f5a65 | |||
f0ca059b17 | |||
b1369d66c2 | |||
151ba2ffc9 | |||
152feba1fa | |||
16125157f3 | |||
6df678067c | |||
b97dc7f599 | |||
96db649cbc | |||
80ce695355 | |||
93a1725da6 |
backend
package-lock.jsonpackage.jsonspec.json
src
config
controllers
v1
authController.tsbotController.tsindex.tsintegrationAuthController.tsintegrationController.tskeyController.tsmembershipController.tsmembershipOrgController.tsorganizationController.tssecretImpsController.tssecretScanningController.tssecretsFolderController.tsuniversalAuthController.tswebhookController.tsworkspaceController.ts
v2
authController.tsenvironmentController.tsmembershipController.tsorganizationsController.tssecretsController.tsserviceTokenDataController.tstagController.tsworkspaceController.ts
v3
ee
controllers
v1
identitiesController.tsindex.tsorganizationsController.tsroleController.tssecretApprovalPolicyController.tssecretApprovalRequestsController.tssecretController.tssecretRotationController.tssecretRotationProviderController.tssecretSnapshotController.tsssoController.tsworkspaceController.ts
v3
models/auditLog
routes
services
helpers
index.tsintegrations
interfaces
middleware
models
identity.tsidentityAccessToken.tsidentityMembership.tsidentityMembershipOrg.tsidentityUniversalAuth.tsidentityUniversalAuthClientSecret.tsindex.ts
integration
integrationAuth
membership.tsmembershipOrg.tssecret.tsserviceTokenDataV3.tsserviceTokenDataV3Key.tsqueues/reminders
routes
v1
v2
v3
services
templates
utils
validation
auth.tsidentities.tsindex.tsintegrationAuth.tsorganization.tssecrets.tsserviceTokenData.tsserviceTokenDataV3.tsworkspace.ts
variables
swagger
tsconfig.jsoncli
docker-compose.ymldocs
api-reference
endpoints
audit-logs
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
cli/commands
contributing
documentation
getting-started
platform
images
getting-started/api
org-create-project-1.pngorg-create-project-2.pngproject-create-secret.pngproject-dashboard.pngproject-explore-env.png
integrations/cloudflare
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
pr-workflows
rbac
self-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
overview.mdxplatforms
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
public/data
src
components
context
hooks/api
layouts/AppLayout
pages/integrations/cloudflare-workers
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
MembersPage
MembersPage.tsx
components
IdentityTab
MemberListTab
ProjectRoleListTab
ServiceTokenTab
index.tsxSecretApprovalPage/components/SecretApprovalRequest
SecretMainPage/components
SecretDropzone
SecretListView
SecretOverviewPage
Settings/ProjectSettingsPage
Signup
helm-charts/secrets-operator
k8-operator
2755
backend/package-lock.json
generated
2755
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,13 +8,14 @@
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.77.0",
|
||||
"@sentry/tracing": "^7.48.0",
|
||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
"@ucast/mongo2js": "^1.3.4",
|
||||
"ajv": "^8.12.0",
|
||||
"argon2": "^0.30.3",
|
||||
"aws-sdk": "^2.1364.0",
|
||||
"axios": "^1.3.5",
|
||||
"axios": "^1.6.0",
|
||||
"axios-retry": "^3.4.0",
|
||||
"bcrypt": "^5.1.0",
|
||||
"bigint-conversion": "^2.4.0",
|
||||
@ -71,7 +72,7 @@
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "node build/index.js",
|
||||
"dev": "nodemon index.js | pino-pretty --colorize",
|
||||
"dev": "nodemon index.js",
|
||||
"swagger-autogen": "node ./swagger/index.ts",
|
||||
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build",
|
||||
"lint": "eslint . --ext .ts",
|
||||
@ -96,6 +97,8 @@
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^29.3.1",
|
||||
"@posthog/plugin-scaffold": "^1.3.4",
|
||||
"@swc/core": "^1.3.99",
|
||||
"@swc/helpers": "^0.5.3",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/bull": "^4.10.0",
|
||||
@ -126,6 +129,7 @@
|
||||
"nodemon": "^2.0.19",
|
||||
"npm": "^8.19.3",
|
||||
"pino-pretty": "^10.2.3",
|
||||
"regenerator-runtime": "^0.14.0",
|
||||
"smee-client": "^1.2.3",
|
||||
"supertest": "^6.3.3",
|
||||
"swagger-autogen": "^2.23.5",
|
||||
|
1522
backend/spec.json
1522
backend/spec.json
File diff suppressed because it is too large
Load Diff
@ -89,6 +89,21 @@ export const getClientSecretGitLabLogin = async () =>
|
||||
export const getUrlGitLabLogin = async () =>
|
||||
(await client.getSecret("URL_GITLAB_LOGIN")).secretValue || GITLAB_URL;
|
||||
|
||||
export const getAwsCloudWatchLog = async () => {
|
||||
const logGroupName =
|
||||
(await client.getSecret("AWS_CLOUDWATCH_LOG_GROUP_NAME")).secretValue || "infisical-log-stream";
|
||||
const region = (await client.getSecret("AWS_CLOUDWATCH_LOG_REGION")).secretValue;
|
||||
const accessKeyId = (await client.getSecret("AWS_CLOUDWATCH_LOG_ACCESS_KEY_ID")).secretValue;
|
||||
const accessKeySecret = (await client.getSecret("AWS_CLOUDWATCH_LOG_ACCESS_KEY_SECRET"))
|
||||
.secretValue;
|
||||
const interval = parseInt(
|
||||
(await client.getSecret("AWS_CLOUDWATCH_LOG_INTERVAL")).secretValue || 1000,
|
||||
10
|
||||
);
|
||||
if (!region || !accessKeyId || !accessKeySecret) return;
|
||||
return { logGroupName, region, accessKeySecret, accessKeyId, interval };
|
||||
};
|
||||
|
||||
export const getPostHogHost = async () =>
|
||||
(await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com";
|
||||
export const getPostHogProjectApiKey = async () =>
|
||||
|
@ -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)}`);
|
||||
};
|
||||
};
|
@ -7,7 +7,7 @@ import * as reqValidator from "../../validation/bot";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
@ -28,7 +28,11 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetBotByWorkspaceIdV1, req);
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -70,7 +74,11 @@ export const setBotActiveState = async (req: Request, res: Response) => {
|
||||
}
|
||||
const userId = req.user._id;
|
||||
|
||||
const { permission } = await getUserProjectPermissions(userId, bot.workspace.toString());
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: bot.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Integrations
|
||||
|
@ -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,
|
||||
|
@ -25,7 +25,7 @@ import * as reqValidator from "../../validation/integrationAuth";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { getIntegrationAuthAccessHelper } from "../../helpers";
|
||||
@ -40,15 +40,15 @@ export const getIntegrationAuth = async (req: Request, res: Response) => {
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||
|
||||
if (!integrationAuth)
|
||||
return res.status(400).send({
|
||||
message: "Failed to find integration authorization"
|
||||
});
|
||||
if (!integrationAuth) return res.status(400).send({
|
||||
message: "Failed to find integration authorization"
|
||||
});
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -79,7 +79,11 @@ export const oAuthExchange = async (req: Request, res: Response) => {
|
||||
} = await validateRequest(reqValidator.OauthExchangeV1, req);
|
||||
if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration");
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -131,7 +135,11 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
|
||||
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
|
||||
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -224,11 +232,12 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
||||
const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -263,10 +272,11 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -299,10 +309,11 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -360,10 +371,11 @@ export const getIntegrationAuthChecklyGroups = async (req: Request, res: Respons
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -413,10 +425,11 @@ export const getIntegrationAuthQoveryOrgs = async (req: Request, res: Response)
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -465,10 +478,11 @@ export const getIntegrationAuthQoveryProjects = async (req: Request, res: Respon
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -526,10 +540,11 @@ export const getIntegrationAuthQoveryEnvironments = async (req: Request, res: Re
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -587,10 +602,11 @@ export const getIntegrationAuthQoveryApps = async (req: Request, res: Response)
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -648,10 +664,11 @@ export const getIntegrationAuthQoveryContainers = async (req: Request, res: Resp
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -709,10 +726,11 @@ export const getIntegrationAuthQoveryJobs = async (req: Request, res: Response)
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -771,10 +789,11 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -864,10 +883,11 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -988,10 +1008,11 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -1044,10 +1065,11 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -1132,10 +1154,11 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -1201,10 +1224,11 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
||||
integrationAuthId: new Types.ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.Integrations
|
||||
|
@ -13,7 +13,7 @@ import * as reqValidator from "../../validation/integration";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
@ -51,11 +51,12 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw BadRequestError({ message: "Integration auth not found" });
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace._id.toString()
|
||||
);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integrationAuth.workspace._id
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -164,10 +165,11 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
||||
const integration = await Integration.findById(integrationId);
|
||||
if (!integration) throw BadRequestError({ message: "Integration not found" });
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integration.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integration.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -234,10 +236,11 @@ export const deleteIntegration = async (req: Request, res: Response) => {
|
||||
const integration = await Integration.findById(integrationId);
|
||||
if (!integration) throw BadRequestError({ message: "Integration not found" });
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integration.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: integration.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -285,7 +288,11 @@ export const manualSync = async (req: Request, res: Response) => {
|
||||
body: { workspaceId, environment }
|
||||
} = await validateRequest(reqValidator.ManualSyncV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Integrations
|
||||
|
@ -9,7 +9,7 @@ import * as reqValidator from "../../validation/key";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
@ -26,7 +26,11 @@ export const uploadKey = async (req: Request, res: Response) => {
|
||||
body: { key }
|
||||
} = await validateRequest(reqValidator.UploadKeyV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Member
|
||||
|
@ -4,15 +4,15 @@ 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 {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
@ -63,11 +63,12 @@ export const deleteMembership = async (req: Request, res: Response) => {
|
||||
if (!membershipToDelete) {
|
||||
throw new Error("Failed to delete workspace membership that doesn't exist");
|
||||
}
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
membershipToDelete.workspace.toString()
|
||||
);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: membershipToDelete.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.Member
|
||||
@ -118,16 +119,17 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
|
||||
throw new Error("Failed to find membership to change role");
|
||||
}
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
membershipToChangeRole.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: membershipToChangeRole.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
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,
|
||||
@ -135,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
|
||||
@ -191,7 +200,12 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
|
||||
params: { workspaceId },
|
||||
body: { email }
|
||||
} = await validateRequest(InviteUserToWorkspaceV1, req);
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.Member
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,6 @@
|
||||
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { isValidScope } from "../../helpers";
|
||||
import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models";
|
||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||
@ -15,7 +17,7 @@ import * as reqValidator from "../../validation/secretImports";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
@ -105,7 +107,11 @@ export const createSecretImp = async (req: Request, res: Response) => {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
} else {
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
|
||||
@ -313,10 +319,11 @@ export const updateSecretImport = async (req: Request, res: Response) => {
|
||||
}
|
||||
} else {
|
||||
// non token entry check
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
importSecDoc.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: importSecDoc.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
@ -442,10 +449,11 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
} else {
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
importSecDoc.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: importSecDoc.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
@ -550,7 +558,11 @@ export const getSecretImports = async (req: Request, res: Response) => {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
} else {
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
@ -604,7 +616,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
} else {
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
@ -657,10 +673,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
||||
permissionCheckFn = (env: string, secPath: string) =>
|
||||
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
|
||||
} else {
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
importSecDoc.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
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
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import * as reqValidator from "../../validation/folders";
|
||||
@ -125,7 +125,11 @@ export const createFolder = async (req: Request, res: Response) => {
|
||||
}
|
||||
} else {
|
||||
// user check
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
|
||||
@ -332,7 +336,11 @@ export const updateFolderById = async (req: Request, res: Response) => {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
} else {
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
|
||||
@ -502,7 +510,11 @@ export const deleteFolder = async (req: Request, res: Response) => {
|
||||
}
|
||||
} else {
|
||||
// check that user is a member of the workspace
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
|
||||
@ -649,7 +661,10 @@ export const getFolders = async (req: Request, res: Response) => {
|
||||
}
|
||||
} else {
|
||||
// check that user is a member of the workspace
|
||||
await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
}
|
||||
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
|
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
@ -16,7 +16,7 @@ import * as reqValidator from "../../validation/webhooks";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
||||
@ -26,7 +26,11 @@ export const createWebhook = async (req: Request, res: Response) => {
|
||||
body: { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath }
|
||||
} = await validateRequest(reqValidator.CreateWebhookV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.Webhooks
|
||||
@ -98,11 +102,11 @@ export const updateWebhook = async (req: Request, res: Response) => {
|
||||
if (!webhook) {
|
||||
throw BadRequestError({ message: "Webhook not found!!" });
|
||||
}
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
webhook.workspace.toString()
|
||||
);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: webhook.workspace
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Webhooks
|
||||
@ -146,10 +150,11 @@ export const deleteWebhook = async (req: Request, res: Response) => {
|
||||
throw ResourceNotFoundError({ message: "Webhook not found!!" });
|
||||
}
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
webhook.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: webhook.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.Webhooks
|
||||
@ -193,10 +198,11 @@ export const testWebhook = async (req: Request, res: Response) => {
|
||||
throw BadRequestError({ message: "Webhook not found!!" });
|
||||
}
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
webhook.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: webhook.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Webhooks
|
||||
@ -236,8 +242,12 @@ export const listWebhooks = async (req: Request, res: Response) => {
|
||||
const {
|
||||
query: { environment, workspaceId, secretPath }
|
||||
} = await validateRequest(reqValidator.ListWebhooksV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Webhooks
|
||||
|
@ -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";
|
||||
@ -25,7 +25,7 @@ import * as reqValidator from "../../validation";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
|
||||
/**
|
||||
@ -39,7 +39,11 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetWorkspacePublicKeysV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Member
|
||||
@ -72,7 +76,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetWorkspaceMembershipsV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Member
|
||||
@ -144,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
|
||||
@ -195,7 +206,11 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.DeleteWorkspaceV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.Workspace
|
||||
@ -223,7 +238,11 @@ export const changeWorkspaceName = async (req: Request, res: Response) => {
|
||||
body: { name }
|
||||
} = await validateRequest(reqValidator.ChangeWorkspaceNameV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Workspace
|
||||
@ -257,7 +276,12 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetWorkspaceIntegrationsV1, req);
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -283,7 +307,11 @@ export const getWorkspaceIntegrationAuthorizations = async (req: Request, res: R
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetWorkspaceIntegrationAuthorizationsV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
@ -309,7 +337,11 @@ export const getWorkspaceServiceTokens = async (req: Request, res: Response) =>
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetWorkspaceServiceTokensV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.ServiceTokens
|
||||
|
@ -204,20 +204,16 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
* @param res
|
||||
*/
|
||||
export const sendMfaToken = async (req: Request, res: Response) => {
|
||||
const {
|
||||
body: { email }
|
||||
} = await validateRequest(reqValidator.SendMfaTokenV2, req);
|
||||
|
||||
const code = await TokenService.createToken({
|
||||
type: TOKEN_EMAIL_MFA,
|
||||
email
|
||||
email: req.user.email
|
||||
});
|
||||
|
||||
// send MFA code [code] to [email]
|
||||
await sendMail({
|
||||
template: "emailMfa.handlebars",
|
||||
subjectLine: "Infisical MFA code",
|
||||
recipients: [email],
|
||||
recipients: [req.user.email],
|
||||
substitutions: {
|
||||
code
|
||||
}
|
||||
@ -236,17 +232,17 @@ export const sendMfaToken = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const verifyMfaToken = async (req: Request, res: Response) => {
|
||||
const {
|
||||
body: { email, mfaToken }
|
||||
body: { mfaToken }
|
||||
} = await validateRequest(reqValidator.VerifyMfaTokenV2, req);
|
||||
|
||||
await TokenService.validateToken({
|
||||
type: TOKEN_EMAIL_MFA,
|
||||
email,
|
||||
email: req.user.email,
|
||||
token: mfaToken
|
||||
});
|
||||
|
||||
const user = await User.findOne({
|
||||
email
|
||||
email: req.user.email
|
||||
}).select(
|
||||
"+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"
|
||||
);
|
||||
|
@ -12,14 +12,12 @@ import {
|
||||
import { EventType, SecretVersion } from "../../ee/models";
|
||||
import { EEAuditLogService, EELicenseService } from "../../ee/services";
|
||||
import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors";
|
||||
import _ from "lodash";
|
||||
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
import * as reqValidator from "../../validation/environments";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { SecretImport } from "../../models";
|
||||
@ -114,7 +112,11 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
|
||||
body: { environmentName, environmentSlug }
|
||||
} = await validateRequest(reqValidator.CreateWorkspaceEnvironmentV2, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.Environments
|
||||
@ -191,7 +193,11 @@ export const reorderWorkspaceEnvironments = async (req: Request, res: Response)
|
||||
body: { environmentName, environmentSlug, otherEnvironmentSlug, otherEnvironmentName }
|
||||
} = await validateRequest(reqValidator.ReorderWorkspaceEnvironmentsV2, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Environments
|
||||
@ -322,7 +328,11 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
|
||||
body: { environmentName, environmentSlug, oldEnvironmentSlug }
|
||||
} = await validateRequest(reqValidator.UpdateWorkspaceEnvironmentV2, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Environments
|
||||
@ -511,7 +521,11 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
|
||||
body: { environmentSlug }
|
||||
} = await validateRequest(reqValidator.DeleteWorkspaceEnvironmentV2, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.Environments
|
||||
@ -587,98 +601,4 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
|
||||
workspace: workspaceId,
|
||||
environment: environmentSlug
|
||||
});
|
||||
};
|
||||
|
||||
// TODO(akhilmhdh) after rbac this can be completely removed
|
||||
export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res: Response) => {
|
||||
/*
|
||||
#swagger.summary = 'Get all accessible environments of a workspace'
|
||||
#swagger.description = 'Fetch all environments that the user has access to in a specified workspace'
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of the workspace",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"in": "path"
|
||||
}
|
||||
|
||||
#swagger.responses[200] = {
|
||||
content: {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accessibleEnvironments": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Development"
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"example": "development"
|
||||
},
|
||||
"isWriteDenied": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"isReadDenied": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "List of environments the user has access to in the specified workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
const {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetAllAccessibileEnvironmentsOfWorkspaceV2, req);
|
||||
|
||||
const { membership: workspacesUserIsMemberOf } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
workspaceId
|
||||
);
|
||||
|
||||
const accessibleEnvironments: any = [];
|
||||
const deniedPermission = workspacesUserIsMemberOf.deniedPermissions;
|
||||
|
||||
const relatedWorkspace = await Workspace.findById(workspaceId);
|
||||
if (!relatedWorkspace) {
|
||||
throw BadRequestError();
|
||||
}
|
||||
relatedWorkspace.environments.forEach((environment) => {
|
||||
const isReadBlocked = _.some(deniedPermission, {
|
||||
environmentSlug: environment.slug,
|
||||
ability: PERMISSION_READ_SECRETS
|
||||
});
|
||||
const isWriteBlocked = _.some(deniedPermission, {
|
||||
environmentSlug: environment.slug,
|
||||
ability: PERMISSION_WRITE_SECRETS
|
||||
});
|
||||
if (isReadBlocked && isWriteBlocked) {
|
||||
return;
|
||||
} else {
|
||||
accessibleEnvironments.push({
|
||||
name: environment.name,
|
||||
slug: environment.slug,
|
||||
isWriteDenied: isWriteBlocked,
|
||||
isReadDenied: isReadBlocked
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
res.json({ accessibleEnvironments });
|
||||
};
|
||||
};
|
@ -8,7 +8,7 @@ import { EEAuditLogService } from "../../ee/services";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { sendMail } from "../../helpers";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
@ -27,7 +27,11 @@ export const addUserToWorkspace = async (req: Request, res: Response) => {
|
||||
if (!workspace) throw new Error("Failed to find workspace");
|
||||
|
||||
// check permission
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.Member
|
||||
|
@ -1,31 +1,29 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IdentityMembershipOrg,
|
||||
Membership,
|
||||
MembershipOrg,
|
||||
Workspace
|
||||
MembershipOrg,
|
||||
Workspace
|
||||
} from "../../models";
|
||||
import { Role } from "../../ee/models";
|
||||
import { deleteMembershipOrg } from "../../helpers/membershipOrg";
|
||||
import {
|
||||
import {
|
||||
createOrganization as create,
|
||||
deleteOrganization,
|
||||
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";
|
||||
|
||||
/**
|
||||
@ -35,11 +33,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'] = {
|
||||
@ -71,7 +70,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
|
||||
@ -93,11 +95,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'] = {
|
||||
@ -149,16 +152,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", "owner"].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,
|
||||
@ -197,11 +216,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'] = {
|
||||
@ -235,7 +255,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
|
||||
@ -299,7 +330,11 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
||||
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
|
||||
@ -336,7 +371,7 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const createOrganization = async (req: Request, res: Response) => {
|
||||
export const createOrganization = async (req: Request, res: Response) => {
|
||||
const {
|
||||
body: { name }
|
||||
} = await validateRequest(reqValidator.CreateOrgv2, req);
|
||||
@ -361,27 +396,90 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
||||
|
||||
/**
|
||||
* Delete organization with id [organizationId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const deleteOrganizationById = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { organizationId }
|
||||
} = await validateRequest(reqValidator.DeleteOrgv2, req);
|
||||
|
||||
|
||||
const membershipOrg = await MembershipOrg.findOne({
|
||||
user: req.user._id,
|
||||
organization: new Types.ObjectId(organizationId),
|
||||
role: ADMIN
|
||||
});
|
||||
|
||||
|
||||
if (!membershipOrg) throw UnauthorizedRequestError();
|
||||
|
||||
|
||||
const organization = await deleteOrganization({
|
||||
organizationId: new Types.ObjectId(organizationId)
|
||||
});
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
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
|
||||
});
|
||||
}
|
@ -39,7 +39,7 @@ import {
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
@ -159,7 +159,11 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
// not using service token using auth
|
||||
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (createSecrets.length)
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
|
@ -11,7 +11,7 @@ import * as reqValidator from "../../validation/serviceTokenData";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { Types } from "mongoose";
|
||||
@ -75,7 +75,12 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
const {
|
||||
body: { workspaceId, permissions, tag, encryptedKey, scopes, name, expiresIn, iv }
|
||||
} = await validateRequest(reqValidator.CreateServiceTokenV2, req);
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.ServiceTokens
|
||||
@ -151,10 +156,11 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
||||
let serviceTokenData = await ServiceTokenData.findById(serviceTokenDataId);
|
||||
if (!serviceTokenData) throw BadRequestError({ message: "Service token not found" });
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
serviceTokenData.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: serviceTokenData.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.ServiceTokens
|
||||
|
@ -7,7 +7,7 @@ import { validateRequest } from "../../helpers/validation";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import * as reqValidator from "../../validation/tags";
|
||||
|
||||
@ -17,7 +17,11 @@ export const createWorkspaceTag = async (req: Request, res: Response) => {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.CreateWorkspaceTagsV2, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.Tags
|
||||
@ -45,10 +49,11 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => {
|
||||
throw BadRequestError();
|
||||
}
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
tagFromDB.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: tagFromDB.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.Tags
|
||||
@ -66,7 +71,12 @@ export const getWorkspaceTags = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetWorkspaceTagsV2, req);
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Tags
|
||||
|
@ -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,
|
||||
getUserProjectPermissions
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -272,7 +286,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetWorkspaceMembershipsV2, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Member
|
||||
@ -295,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": {
|
||||
@ -323,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",
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -335,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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -352,7 +371,11 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
|
||||
body: { role }
|
||||
} = await validateRequest(reqValidator.UpdateWorkspaceMembershipsV2, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Member
|
||||
@ -381,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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -420,7 +444,11 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
|
||||
params: { workspaceId, membershipId }
|
||||
} = await validateRequest(reqValidator.DeleteWorkspaceMembershipsV2, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.Member
|
||||
@ -452,7 +480,11 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
|
||||
body: { autoCapitalization }
|
||||
} = await validateRequest(reqValidator.ToggleAutoCapitalizationV2, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Settings
|
||||
@ -475,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
|
||||
});
|
||||
}
|
@ -3,11 +3,10 @@ import { Types } from "mongoose";
|
||||
import { EventService, SecretService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
import { BotService } from "../../services";
|
||||
import { containsGlobPatterns, isValidScopeV3, repackageSecretToRaw } from "../../helpers/secrets";
|
||||
import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets";
|
||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||
import { Folder, IMembership, IServiceTokenData, IServiceTokenDataV3 } from "../../models";
|
||||
import { Permission } from "../../models/serviceTokenDataV3";
|
||||
import { Folder, IServiceTokenData, Membership, ServiceTokenData, User } from "../../models";
|
||||
import { getFolderByPath } from "../../services/FolderService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
@ -15,13 +14,10 @@ import * as reqValidator from "../../validation/secrets";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../ee/services/ProjectRoleService";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import {
|
||||
validateServiceTokenDataClientForWorkspace,
|
||||
validateServiceTokenDataV3ClientForWorkspace
|
||||
} from "../../validation";
|
||||
import { validateServiceTokenDataClientForWorkspace } from "../../validation";
|
||||
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
|
||||
import { ActorType } from "../../ee/models";
|
||||
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||
@ -31,7 +27,8 @@ import {
|
||||
getSecretPolicyOfBoard
|
||||
} from "../../ee/services/SecretApprovalService";
|
||||
import { CommitType } from "../../ee/models/secretApprovalRequest";
|
||||
import { IRole } from "../../ee/models/role";
|
||||
import { logger } from "../../utils/logging";
|
||||
import { createReminder, deleteReminder } from "../../helpers/reminder";
|
||||
|
||||
const checkSecretsPermission = async ({
|
||||
authData,
|
||||
@ -47,36 +44,31 @@ const checkSecretsPermission = async ({
|
||||
secretAction: ProjectPermissionActions; // CRUD
|
||||
}): Promise<{
|
||||
authVerifier: (env: string, secPath: string) => boolean;
|
||||
membership?: Omit<IMembership, "customRole"> & { customRole: IRole };
|
||||
}> => {
|
||||
let STV2RequiredPermissions = [];
|
||||
let STV3RequiredPermissions: Permission[] = [];
|
||||
|
||||
switch (secretAction) {
|
||||
case ProjectPermissionActions.Create:
|
||||
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
||||
STV3RequiredPermissions = [Permission.WRITE];
|
||||
break;
|
||||
case ProjectPermissionActions.Read:
|
||||
STV2RequiredPermissions = [PERMISSION_READ_SECRETS];
|
||||
STV3RequiredPermissions = [Permission.READ];
|
||||
break;
|
||||
case ProjectPermissionActions.Edit:
|
||||
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
||||
STV3RequiredPermissions = [Permission.WRITE];
|
||||
break;
|
||||
case ProjectPermissionActions.Delete:
|
||||
STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS];
|
||||
STV3RequiredPermissions = [Permission.WRITE];
|
||||
break;
|
||||
}
|
||||
|
||||
switch (authData.actor.type) {
|
||||
case ActorType.USER: {
|
||||
const { permission, membership } = await getUserProjectPermissions(
|
||||
authData.actor.metadata.userId,
|
||||
workspaceId
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
secretAction,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
@ -89,8 +81,7 @@ const checkSecretsPermission = async ({
|
||||
environment: env,
|
||||
secretPath: secPath
|
||||
})
|
||||
),
|
||||
membership
|
||||
)
|
||||
};
|
||||
}
|
||||
case ActorType.SERVICE: {
|
||||
@ -103,23 +94,25 @@ const checkSecretsPermission = async ({
|
||||
});
|
||||
return { authVerifier: () => true };
|
||||
}
|
||||
case ActorType.SERVICE_V3: {
|
||||
await validateServiceTokenDataV3ClientForWorkspace({
|
||||
case ActorType.IDENTITY: {
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData,
|
||||
serviceTokenData: authData.authPayload as IServiceTokenDataV3,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
secretPath,
|
||||
requiredPermissions: STV3RequiredPermissions
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
secretAction,
|
||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||
);
|
||||
return {
|
||||
authVerifier: (env: string, secPath: string) =>
|
||||
isValidScopeV3({
|
||||
authPayload: authData.authPayload as IServiceTokenDataV3,
|
||||
environment: env,
|
||||
secretPath: secPath,
|
||||
requiredPermissions: STV3RequiredPermissions
|
||||
})
|
||||
permission.can(
|
||||
secretAction,
|
||||
subject(ProjectPermissionSub.Secrets, {
|
||||
environment: env,
|
||||
secretPath: secPath
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
default: {
|
||||
@ -199,17 +192,23 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
|
||||
query: { include_imports: includeImports }
|
||||
} = validatedData;
|
||||
|
||||
// if the service token has single scope, it will get all secrets for that scope by default
|
||||
const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData;
|
||||
if (
|
||||
serviceTokenDetails &&
|
||||
serviceTokenDetails.scopes.length == 1 &&
|
||||
!containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath)
|
||||
) {
|
||||
const scope = serviceTokenDetails.scopes[0];
|
||||
secretPath = scope.secretPath;
|
||||
environment = scope.environment;
|
||||
workspaceId = serviceTokenDetails.workspace.toString();
|
||||
logger.info(
|
||||
`getSecretsRaw: fetch raw secrets [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [includeImports=${includeImports}]`
|
||||
);
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
// if the service token has single scope, it will get all secrets for that scope by default
|
||||
const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData;
|
||||
if (
|
||||
serviceTokenDetails &&
|
||||
serviceTokenDetails.scopes.length == 1 &&
|
||||
!containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath)
|
||||
) {
|
||||
const scope = serviceTokenDetails.scopes[0];
|
||||
secretPath = scope.secretPath;
|
||||
environment = scope.environment;
|
||||
workspaceId = serviceTokenDetails.workspace.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!environment || !workspaceId)
|
||||
@ -353,6 +352,10 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
params: { secretName }
|
||||
} = await validateRequest(reqValidator.GetSecretByNameRawV3, req);
|
||||
|
||||
logger.info(
|
||||
`getSecretByNameRaw: fetch raw secret by name [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}] [include_imports=${include_imports}]`
|
||||
);
|
||||
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
@ -476,6 +479,10 @@ export const createSecretRaw = async (req: Request, res: Response) => {
|
||||
}
|
||||
} = await validateRequest(reqValidator.CreateSecretRawV3, req);
|
||||
|
||||
logger.info(
|
||||
`createSecretRaw: create a secret raw by name and value [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}] [skipMultilineEncoding=${skipMultilineEncoding}]`
|
||||
);
|
||||
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
@ -621,6 +628,10 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
body: { workspaceId, environment, secretValue, secretPath, type, skipMultilineEncoding }
|
||||
} = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req);
|
||||
|
||||
logger.info(
|
||||
`updateSecretByNameRaw: update raw secret by name [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}] [skipMultilineEncoding=${skipMultilineEncoding}]`
|
||||
);
|
||||
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
@ -743,6 +754,10 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
|
||||
body: { environment, secretPath, type, workspaceId }
|
||||
} = await validateRequest(reqValidator.DeleteSecretByNameRawV3, req);
|
||||
|
||||
logger.info(
|
||||
`deleteSecretByNameRaw: delete a secret by name [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}]`
|
||||
);
|
||||
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
@ -796,6 +811,10 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
query: { secretPath }
|
||||
} = validatedData;
|
||||
|
||||
logger.info(
|
||||
`getSecrets: fetch encrypted secrets [environment=${environment}] [workspaceId=${workspaceId}] [includeImports=${includeImports}]`
|
||||
);
|
||||
|
||||
const { authVerifier: permissionCheckFn } = await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
@ -850,6 +869,10 @@ export const getSecretByName = async (req: Request, res: Response) => {
|
||||
params: { secretName }
|
||||
} = await validateRequest(reqValidator.GetSecretByNameV3, req);
|
||||
|
||||
logger.info(
|
||||
`getSecretByName: get a single secret by name [environment=${environment}] [workspaceId=${workspaceId}] [include_imports=${include_imports}] [type=${type}]`
|
||||
);
|
||||
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
@ -900,7 +923,11 @@ export const createSecret = async (req: Request, res: Response) => {
|
||||
params: { secretName }
|
||||
} = await validateRequest(reqValidator.CreateSecretV3, req);
|
||||
|
||||
const { membership } = await checkSecretsPermission({
|
||||
logger.info(
|
||||
`createSecret: create an encrypted secret [environment=${environment}] [workspaceId=${workspaceId}] [skipMultilineEncoding=${skipMultilineEncoding}] [type=${type}]`
|
||||
);
|
||||
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
environment,
|
||||
@ -908,35 +935,46 @@ export const createSecret = async (req: Request, res: Response) => {
|
||||
secretAction: ProjectPermissionActions.Create
|
||||
});
|
||||
|
||||
if (membership && type !== "personal") {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
if (req.authData.authPayload instanceof User) {
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (membership && type !== "personal") {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
authData: req.authData,
|
||||
data: {
|
||||
[CommitType.CREATE]: [
|
||||
{
|
||||
secretName,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentCiphertext,
|
||||
skipMultilineEncoding,
|
||||
secretKeyTag,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
secretPath
|
||||
);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
authData: req.authData,
|
||||
data: {
|
||||
[CommitType.CREATE]: [
|
||||
{
|
||||
secretName,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentCiphertext,
|
||||
skipMultilineEncoding,
|
||||
secretKeyTag,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1000,16 +1038,22 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
secretKeyIV,
|
||||
secretKeyTag,
|
||||
secretKeyCiphertext,
|
||||
skipMultilineEncoding
|
||||
skipMultilineEncoding,
|
||||
secretReminderRepeatDays,
|
||||
secretReminderNote
|
||||
},
|
||||
params: { secretName }
|
||||
} = await validateRequest(reqValidator.UpdateSecretByNameV3, req);
|
||||
|
||||
logger.info(
|
||||
`updateSecretByName: update a encrypted secret by name [environment=${environment}] [workspaceId=${workspaceId}] [skipMultilineEncoding=${skipMultilineEncoding}] [type=${type}]`
|
||||
);
|
||||
|
||||
if (newSecretName && (!secretKeyIV || !secretKeyTag || !secretKeyCiphertext)) {
|
||||
throw BadRequestError({ message: "Missing encrypted key" });
|
||||
}
|
||||
|
||||
const { membership } = await checkSecretsPermission({
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
environment,
|
||||
@ -1017,37 +1061,83 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
secretAction: ProjectPermissionActions.Edit
|
||||
});
|
||||
|
||||
if (membership && type !== "personal") {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
if (req.authData.authPayload instanceof User) {
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (membership && type !== "personal") {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
authData: req.authData,
|
||||
data: {
|
||||
[CommitType.UPDATE]: [
|
||||
{
|
||||
secretName,
|
||||
newSecretName,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
tags,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentCiphertext,
|
||||
skipMultilineEncoding,
|
||||
secretKeyTag,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
secretPath
|
||||
);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
authData: req.authData,
|
||||
data: {
|
||||
[CommitType.UPDATE]: [
|
||||
{
|
||||
secretName,
|
||||
newSecretName,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
tags,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentCiphertext,
|
||||
skipMultilineEncoding,
|
||||
secretKeyTag,
|
||||
secretKeyCiphertext,
|
||||
secretKeyIV
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type !== "personal") {
|
||||
const existingSecret = await SecretService.getSecret({
|
||||
secretName,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
secretPath,
|
||||
authData: req.authData
|
||||
});
|
||||
|
||||
if (secretReminderRepeatDays !== undefined) {
|
||||
if (
|
||||
(secretReminderRepeatDays &&
|
||||
existingSecret.secretReminderRepeatDays !== secretReminderRepeatDays) ||
|
||||
(secretReminderNote && existingSecret.secretReminderNote !== secretReminderNote)
|
||||
) {
|
||||
await createReminder(existingSecret, {
|
||||
_id: existingSecret._id,
|
||||
secretReminderRepeatDays,
|
||||
secretReminderNote,
|
||||
workspace: existingSecret.workspace
|
||||
});
|
||||
} else if (
|
||||
secretReminderRepeatDays === null &&
|
||||
secretReminderNote === null &&
|
||||
existingSecret.secretReminderRepeatDays
|
||||
) {
|
||||
await deleteReminder({
|
||||
_id: existingSecret._id,
|
||||
secretReminderRepeatDays: existingSecret.secretReminderRepeatDays
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1061,6 +1151,8 @@ export const updateSecretByName = async (req: Request, res: Response) => {
|
||||
newSecretName,
|
||||
secretValueCiphertext,
|
||||
secretValueIV,
|
||||
secretReminderRepeatDays,
|
||||
secretReminderNote,
|
||||
secretValueTag,
|
||||
secretPath,
|
||||
tags,
|
||||
@ -1097,7 +1189,11 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||
params: { secretName }
|
||||
} = await validateRequest(reqValidator.DeleteSecretByNameV3, req);
|
||||
|
||||
const { membership } = await checkSecretsPermission({
|
||||
logger.info(
|
||||
`deleteSecretByName: delete a encrypted secret by name [environment=${environment}] [workspaceId=${workspaceId}] [type=${type}]`
|
||||
);
|
||||
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
environment,
|
||||
@ -1105,25 +1201,36 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
|
||||
secretAction: ProjectPermissionActions.Delete
|
||||
});
|
||||
|
||||
if (membership && type !== "personal") {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
if (req.authData.authPayload instanceof User) {
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (membership && type !== "personal") {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
authData: req.authData,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
data: {
|
||||
[CommitType.DELETE]: [
|
||||
{
|
||||
secretName
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
secretPath
|
||||
);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
authData: req.authData,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
data: {
|
||||
[CommitType.DELETE]: [
|
||||
{
|
||||
secretName
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1155,7 +1262,11 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
body: { secrets, secretPath, environment, workspaceId }
|
||||
} = await validateRequest(reqValidator.CreateSecretByNameBatchV3, req);
|
||||
|
||||
const { membership } = await checkSecretsPermission({
|
||||
logger.info(
|
||||
`createSecretByNameBatch: create a list of secrets by their names [environment=${environment}] [workspaceId=${workspaceId}] [secretsLength=${secrets?.length}]`
|
||||
);
|
||||
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
environment,
|
||||
@ -1163,21 +1274,32 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
secretAction: ProjectPermissionActions.Create
|
||||
});
|
||||
|
||||
if (membership) {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
if (req.authData.authPayload instanceof User) {
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (membership) {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
authData: req.authData,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
data: {
|
||||
[CommitType.CREATE]: secrets.filter(({ type }) => type === "shared")
|
||||
}
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
secretPath
|
||||
);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
authData: req.authData,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
data: {
|
||||
[CommitType.CREATE]: secrets.filter(({ type }) => type === "shared")
|
||||
}
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1207,7 +1329,11 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
body: { secrets, secretPath, environment, workspaceId }
|
||||
} = await validateRequest(reqValidator.UpdateSecretByNameBatchV3, req);
|
||||
|
||||
const { membership } = await checkSecretsPermission({
|
||||
logger.info(
|
||||
`updateSecretByNameBatch: update a list of secrets by their names [environment=${environment}] [workspaceId=${workspaceId}] [secretsLength=${secrets?.length}]`
|
||||
);
|
||||
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
environment,
|
||||
@ -1215,21 +1341,32 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
secretAction: ProjectPermissionActions.Edit
|
||||
});
|
||||
|
||||
if (membership) {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
if (req.authData.authPayload instanceof User) {
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (membership) {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
data: {
|
||||
[CommitType.UPDATE]: secrets.filter(({ type }) => type === "shared")
|
||||
},
|
||||
authData: req.authData
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
secretPath
|
||||
);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
data: {
|
||||
[CommitType.UPDATE]: secrets.filter(({ type }) => type === "shared")
|
||||
},
|
||||
authData: req.authData
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1259,7 +1396,11 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
body: { secrets, secretPath, environment, workspaceId }
|
||||
} = await validateRequest(reqValidator.DeleteSecretByNameBatchV3, req);
|
||||
|
||||
const { membership } = await checkSecretsPermission({
|
||||
logger.info(
|
||||
`deleteSecretByNameBatch: delete a list of secrets by their names [environment=${environment}] [workspaceId=${workspaceId}] [secretsLength=${secrets?.length}]`
|
||||
);
|
||||
|
||||
await checkSecretsPermission({
|
||||
authData: req.authData,
|
||||
workspaceId,
|
||||
environment,
|
||||
@ -1267,21 +1408,32 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
secretAction: ProjectPermissionActions.Delete
|
||||
});
|
||||
|
||||
if (membership) {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
if (req.authData.authPayload instanceof User) {
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (membership) {
|
||||
const secretApprovalPolicy = await getSecretPolicyOfBoard(
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
data: {
|
||||
[CommitType.DELETE]: secrets.filter(({ type }) => type === "shared")
|
||||
},
|
||||
authData: req.authData
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
secretPath
|
||||
);
|
||||
if (secretApprovalPolicy) {
|
||||
const secretApprovalRequest = await generateSecretApprovalRequest({
|
||||
workspaceId,
|
||||
environment,
|
||||
secretPath,
|
||||
policy: secretApprovalPolicy,
|
||||
commiterMembershipId: membership._id.toString(),
|
||||
data: {
|
||||
[CommitType.DELETE]: secrets.filter(({ type }) => type === "shared")
|
||||
},
|
||||
authData: req.authData
|
||||
});
|
||||
return res.send({ approval: secretApprovalRequest });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1304,4 +1456,4 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
secrets: deletedSecrets
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { validateRequest } from "../../helpers/validation";
|
||||
import { Secret, ServiceTokenDataV3 } from "../../models";
|
||||
import { Membership, Secret, User } from "../../models";
|
||||
import { SecretService } from "../../services";
|
||||
import { getUserProjectPermissions } from "../../ee/services/ProjectRoleService";
|
||||
import { getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService";
|
||||
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||
import * as reqValidator from "../../validation/workspace";
|
||||
|
||||
@ -19,9 +19,22 @@ export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response)
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetWorkspaceBlinkIndexStatusV3, req);
|
||||
|
||||
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
if (membership.role !== "admin")
|
||||
throw UnauthorizedRequestError({ message: "User must be an admin" });
|
||||
await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (req.authData.authPayload instanceof User) {
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
if (membership.role !== "admin")
|
||||
throw UnauthorizedRequestError({ message: "User must be an admin" });
|
||||
}
|
||||
|
||||
const secretsWithoutBlindIndex = await Secret.countDocuments({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
@ -41,9 +54,22 @@ export const getWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetWorkspaceSecretsV3, req);
|
||||
|
||||
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
if (membership.role !== "admin")
|
||||
throw UnauthorizedRequestError({ message: "User must be an admin" });
|
||||
await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (req.authData.authPayload instanceof User) {
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
if (membership.role !== "admin")
|
||||
throw UnauthorizedRequestError({ message: "User must be an admin" });
|
||||
}
|
||||
|
||||
const secrets = await Secret.find({
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
@ -65,9 +91,22 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
|
||||
body: { secretsToUpdate }
|
||||
} = await validateRequest(reqValidator.NameWorkspaceSecretsV3, req);
|
||||
|
||||
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
if (membership.role !== "admin")
|
||||
throw UnauthorizedRequestError({ message: "User must be an admin" });
|
||||
await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (req.authData.authPayload instanceof User) {
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
if (membership.role !== "admin")
|
||||
throw UnauthorizedRequestError({ message: "User must be an admin" });
|
||||
}
|
||||
|
||||
// get secret blind index salt
|
||||
const salt = await SecretService.getSecretBlindIndexSalt({
|
||||
@ -101,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)
|
||||
});
|
||||
|
||||
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
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { Membership, User } from "../../../models";
|
||||
import {
|
||||
CreateRoleSchema,
|
||||
DeleteRoleSchema,
|
||||
@ -11,16 +13,19 @@ import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
adminProjectPermissions,
|
||||
getUserProjectPermissions,
|
||||
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";
|
||||
@ -34,12 +39,19 @@ 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." });
|
||||
}
|
||||
} else {
|
||||
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
if (permission.cannot(ProjectPermissionActions.Create, ProjectPermissionSub.Role)) {
|
||||
throw BadRequestError({ message: "User doesn't have the permission." });
|
||||
}
|
||||
@ -75,14 +87,21 @@ 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." });
|
||||
}
|
||||
} else {
|
||||
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (permission.cannot(ProjectPermissionActions.Edit, ProjectPermissionSub.Role)) {
|
||||
throw BadRequestError({ message: "User doesn't have the workspace permission." });
|
||||
}
|
||||
@ -129,12 +148,19 @@ 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." });
|
||||
}
|
||||
} else {
|
||||
const { permission } = await getUserProjectPermissions(req.user.id, role.workspace.toString());
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: role.workspace
|
||||
});
|
||||
|
||||
if (permission.cannot(ProjectPermissionActions.Delete, ProjectPermissionSub.Role)) {
|
||||
throw BadRequestError({ message: "User doesn't have the workspace permission." });
|
||||
}
|
||||
@ -157,12 +183,19 @@ 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." });
|
||||
}
|
||||
} else {
|
||||
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (permission.cannot(ProjectPermissionActions.Read, ProjectPermissionSub.Role)) {
|
||||
throw BadRequestError({ message: "User doesn't have the workspace permission." });
|
||||
}
|
||||
@ -178,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",
|
||||
@ -212,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({
|
||||
@ -227,7 +267,19 @@ export const getUserWorkspacePermissions = async (req: Request, res: Response) =
|
||||
const {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(GetUserProjectPermission, req);
|
||||
const { permission, membership } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
let membership;
|
||||
if (req.authData.authPayload instanceof User) {
|
||||
membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
})
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
data: {
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Types } from "mongoose";
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { Request, Response } from "express";
|
||||
import { nanoid } from "nanoid";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../services/ProjectRoleService";
|
||||
import { validateRequest } from "../../../helpers/validation";
|
||||
import { SecretApprovalPolicy } from "../../models/secretApprovalPolicy";
|
||||
@ -19,7 +20,11 @@ export const createSecretApprovalPolicy = async (req: Request, res: Response) =>
|
||||
body: { approvals, secretPath, approvers, environment, workspaceId, name }
|
||||
} = await validateRequest(reqValidator.CreateSecretApprovalRule, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.SecretApproval
|
||||
@ -49,10 +54,11 @@ export const updateSecretApprovalPolicy = async (req: Request, res: Response) =>
|
||||
const secretApproval = await SecretApprovalPolicy.findById(id);
|
||||
if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND;
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
secretApproval.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: secretApproval.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.SecretApproval
|
||||
@ -78,10 +84,11 @@ export const deleteSecretApprovalPolicy = async (req: Request, res: Response) =>
|
||||
const secretApproval = await SecretApprovalPolicy.findById(id);
|
||||
if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND;
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
secretApproval.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: secretApproval.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.SecretApproval
|
||||
@ -99,7 +106,11 @@ export const getSecretApprovalPolicy = async (req: Request, res: Response) => {
|
||||
query: { workspaceId }
|
||||
} = await validateRequest(reqValidator.GetSecretApprovalRuleList, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.SecretApproval
|
||||
@ -117,7 +128,11 @@ export const getSecretApprovalPolicyOfBoard = async (req: Request, res: Response
|
||||
query: { workspaceId, environment, secretPath }
|
||||
} = await validateRequest(reqValidator.GetSecretApprovalPolicyOfABoard, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
subject(ProjectPermissionSub.Secrets, { secretPath, environment })
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Request, Response } from "express";
|
||||
import { getUserProjectPermissions } from "../../services/ProjectRoleService";
|
||||
import { validateRequest } from "../../../helpers/validation";
|
||||
import { Folder } from "../../../models";
|
||||
import { Folder, Membership, User } from "../../../models";
|
||||
import { ApprovalStatus, SecretApprovalRequest } from "../../models/secretApprovalRequest";
|
||||
import * as reqValidator from "../../validation/secretApprovalRequest";
|
||||
import { getFolderWithPathFromId } from "../../../services/FolderService";
|
||||
@ -17,7 +16,15 @@ export const getSecretApprovalRequestCount = async (req: Request, res: Response)
|
||||
query: { workspaceId }
|
||||
} = await validateRequest(reqValidator.getSecretApprovalRequestCount, req);
|
||||
|
||||
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
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([
|
||||
{
|
||||
$match: {
|
||||
@ -65,7 +72,14 @@ export const getSecretApprovalRequests = async (req: Request, res: Response) =>
|
||||
query: { status, committer, workspaceId, environment, limit, offset }
|
||||
} = await validateRequest(reqValidator.getSecretApprovalRequests, req);
|
||||
|
||||
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
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 = {
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
@ -148,14 +162,19 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons
|
||||
if (!secretApprovalRequest)
|
||||
throw BadRequestError({ message: "Secret approval request not found" });
|
||||
|
||||
const { membership } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
secretApprovalRequest.workspace.toString()
|
||||
);
|
||||
if (!(req.authData.authPayload instanceof User)) return;
|
||||
|
||||
const membership = await Membership.findOne({
|
||||
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()
|
||||
)
|
||||
@ -190,10 +209,15 @@ export const updateSecretApprovalReviewStatus = async (req: Request, res: Respon
|
||||
if (!secretApprovalRequest)
|
||||
throw BadRequestError({ message: "Secret approval request not found" });
|
||||
|
||||
const { membership } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
secretApprovalRequest.workspace.toString()
|
||||
);
|
||||
if (!(req.authData.authPayload instanceof User)) return;
|
||||
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: secretApprovalRequest.workspace
|
||||
});
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
if (
|
||||
membership.role !== "admin" &&
|
||||
secretApprovalRequest.committer !== membership.id &&
|
||||
@ -227,10 +251,15 @@ export const mergeSecretApprovalRequest = async (req: Request, res: Response) =>
|
||||
if (!secretApprovalRequest)
|
||||
throw BadRequestError({ message: "Secret approval request not found" });
|
||||
|
||||
const { membership } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
secretApprovalRequest.workspace.toString()
|
||||
);
|
||||
if (!(req.authData.authPayload instanceof User)) return;
|
||||
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: secretApprovalRequest.workspace
|
||||
});
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
if (
|
||||
membership.role !== "admin" &&
|
||||
secretApprovalRequest.committer !== membership.id &&
|
||||
@ -272,10 +301,14 @@ export const updateSecretApprovalRequestStatus = async (req: Request, res: Respo
|
||||
if (!secretApprovalRequest)
|
||||
throw BadRequestError({ message: "Secret approval request not found" });
|
||||
|
||||
const { membership } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
secretApprovalRequest.workspace.toString()
|
||||
);
|
||||
if (!(req.authData.authPayload instanceof User)) return;
|
||||
|
||||
const membership = await Membership.findOne({
|
||||
user: req.authData.authPayload._id,
|
||||
workspace: secretApprovalRequest.workspace
|
||||
});
|
||||
|
||||
if (!membership) throw UnauthorizedRequestError();
|
||||
|
||||
if (
|
||||
membership.role !== "admin" &&
|
||||
|
@ -5,7 +5,7 @@ import { Folder, Secret } from "../../../models";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../services/ProjectRoleService";
|
||||
import { BadRequestError } from "../../../utils/errors";
|
||||
import * as reqValidator from "../../../validation";
|
||||
@ -74,7 +74,11 @@ export const getSecretVersions = async (req: Request, res: Response) => {
|
||||
throw BadRequestError({ message: "Failed to find secret" });
|
||||
}
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, secret.workspace.toString());
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: secret.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.SecretRollback
|
||||
@ -157,10 +161,12 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
|
||||
if (!toBeUpdatedSec) {
|
||||
throw BadRequestError({ message: "Failed to find secret" });
|
||||
}
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
toBeUpdatedSec.workspace.toString()
|
||||
);
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: toBeUpdatedSec.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.SecretRollback
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { validateRequest } from "../../../helpers/validation";
|
||||
import * as reqValidator from "../../validation/secretRotation";
|
||||
import * as secretRotationService from "../../secretRotation/service";
|
||||
import {
|
||||
getUserProjectPermissions,
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub
|
||||
ProjectPermissionSub,
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
@ -23,7 +24,11 @@ export const createSecretRotation = async (req: Request, res: Response) => {
|
||||
}
|
||||
} = await validateRequest(reqValidator.createSecretRotationV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.SecretRotation
|
||||
@ -49,7 +54,12 @@ export const restartSecretRotations = async (req: Request, res: Response) => {
|
||||
} = await validateRequest(reqValidator.restartSecretRotationV1, req);
|
||||
|
||||
const doc = await secretRotationService.getSecretRotationById({ id });
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, doc.workspace.toString());
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: doc.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.SecretRotation
|
||||
@ -65,7 +75,12 @@ export const deleteSecretRotations = async (req: Request, res: Response) => {
|
||||
} = await validateRequest(reqValidator.removeSecretRotationV1, req);
|
||||
|
||||
const doc = await secretRotationService.getSecretRotationById({ id });
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, doc.workspace.toString());
|
||||
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: doc.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.SecretRotation
|
||||
@ -80,7 +95,11 @@ export const getSecretRotations = async (req: Request, res: Response) => {
|
||||
query: { workspaceId }
|
||||
} = await validateRequest(reqValidator.getSecretRotationV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.SecretRotation
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { validateRequest } from "../../../helpers/validation";
|
||||
import * as reqValidator from "../../validation/secretRotationProvider";
|
||||
import * as secretRotationProviderService from "../../secretRotation/service";
|
||||
import {
|
||||
getUserProjectPermissions,
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub
|
||||
ProjectPermissionSub,
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
@ -14,7 +15,11 @@ export const getProviderTemplates = async (req: Request, res: Response) => {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(reqValidator.getSecretRotationProvidersV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.SecretRotation
|
||||
|
@ -4,7 +4,7 @@ import { validateRequest } from "../../../helpers/validation";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../services/ProjectRoleService";
|
||||
import * as reqValidator from "../../../validation/secretSnapshot";
|
||||
import { ISecretVersion, SecretSnapshot, TFolderRootVersionSchema } from "../../models";
|
||||
@ -33,10 +33,11 @@ export const getSecretSnapshot = async (req: Request, res: Response) => {
|
||||
|
||||
if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
secretSnapshot.workspace.toString()
|
||||
);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: secretSnapshot.workspace
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.SecretRollback
|
||||
|
@ -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,17 +18,16 @@ import {
|
||||
FolderVersion,
|
||||
IPType,
|
||||
ISecretVersion,
|
||||
IdentityActor,
|
||||
SecretSnapshot,
|
||||
SecretVersion,
|
||||
ServiceActor,
|
||||
ServiceActorV3,
|
||||
TFolderRootVersionSchema,
|
||||
TrustedIP,
|
||||
UserActor
|
||||
} from "../../models";
|
||||
import { EESecretService } from "../../services";
|
||||
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
|
||||
// import Folder, { TFolderSchema } from "../../../models/folder";
|
||||
import { getFolderByPath, searchByFolderId } from "../../../services/FolderService";
|
||||
import { EEAuditLogService, EELicenseService } from "../../services";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
||||
@ -46,7 +46,7 @@ import {
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
getAuthDataProjectPermissions
|
||||
} from "../../services/ProjectRoleService";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { BadRequestError } from "../../../utils/errors";
|
||||
@ -62,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,
|
||||
@ -107,7 +122,11 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) =
|
||||
query: { environment, directory, offset, limit }
|
||||
} = await validateRequest(GetWorkspaceSecretSnapshotsV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.SecretRollback
|
||||
@ -148,7 +167,11 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon
|
||||
query: { environment, directory }
|
||||
} = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.SecretRollback
|
||||
@ -187,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"
|
||||
}
|
||||
@ -203,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",
|
||||
@ -238,7 +270,11 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
body: { directory, environment, version }
|
||||
} = await validateRequest(RollbackWorkspaceSecretSnapshotV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.SecretRollback
|
||||
@ -567,17 +603,112 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
|
||||
* @param res
|
||||
*/
|
||||
export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
||||
/*
|
||||
#swagger.summary = 'Return audit logs'
|
||||
#swagger.description = 'Return audit logs'
|
||||
|
||||
#swagger.security = [{
|
||||
"apiKeyAuth": []
|
||||
}]
|
||||
|
||||
#swagger.parameters['workspaceId'] = {
|
||||
"description": "ID of the workspace where to get folders from",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"in": "path"
|
||||
}
|
||||
|
||||
#swagger.parameters['offset'] = {
|
||||
"description": "Number of logs to skip before starting to return logs for pagination",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.parameters['limit'] = {
|
||||
"description": "Maximum number of logs to return for pagination",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.parameters['startDate'] = {
|
||||
"description": "Filter logs from this date in ISO-8601 format",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.parameters['endDate'] = {
|
||||
"description": "Filter logs till this date in ISO-8601 format",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.parameters['eventType'] = {
|
||||
"description": "Filter by type of event such as get-secrets, get-secret, create-secret, update-secret, delete-secret, etc.",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
}
|
||||
|
||||
#swagger.parameters['userAgentType'] = {
|
||||
"description": "Filter by type of user agent such as web, cli, k8-operator, or other",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
}
|
||||
|
||||
#swagger.parameters['actor'] = {
|
||||
"description": "Filter by actor such as user or service",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
}
|
||||
|
||||
#swagger.responses[200] = {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"auditLogs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
$ref: "#/components/schemas/AuditLog",
|
||||
},
|
||||
"description": "List of audit log"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
const {
|
||||
query: { limit, offset, endDate, eventType, startDate, userAgentType, actor },
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(GetWorkspaceAuditLogsV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
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
|
||||
@ -593,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
|
||||
@ -611,14 +738,11 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
||||
}
|
||||
: {})
|
||||
};
|
||||
|
||||
|
||||
const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit);
|
||||
|
||||
const totalCount = await AuditLog.countDocuments(query);
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
auditLogs,
|
||||
totalCount
|
||||
auditLogs
|
||||
});
|
||||
};
|
||||
|
||||
@ -632,7 +756,11 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(GetWorkspaceAuditLogActorFilterOptsV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.AuditLogs
|
||||
@ -641,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: {
|
||||
@ -666,24 +795,26 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
|
||||
name: serviceTokenData.name
|
||||
}
|
||||
}));
|
||||
|
||||
const identityIds = await IdentityMembership.distinct("identity", {
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
const serviceV3Actors: ServiceActorV3[] = (
|
||||
await ServiceTokenDataV3.find({
|
||||
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
|
||||
@ -700,7 +831,11 @@ export const getWorkspaceTrustedIps = async (req: Request, res: Response) => {
|
||||
params: { workspaceId }
|
||||
} = await validateRequest(GetWorkspaceTrustedIpsV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.IpAllowList
|
||||
@ -726,7 +861,11 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
body: { comment, isActive, ipAddress: ip }
|
||||
} = await validateRequest(AddWorkspaceTrustedIpV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.IpAllowList
|
||||
@ -792,7 +931,11 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
body: { ipAddress: ip, comment }
|
||||
} = await validateRequest(UpdateWorkspaceTrustedIpV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.IpAllowList
|
||||
@ -884,7 +1027,11 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
params: { workspaceId, trustedIpId }
|
||||
} = await validateRequest(DeleteWorkspaceTrustedIpV1, req);
|
||||
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
const { permission } = await getAuthDataProjectPermissions({
|
||||
authData: req.authData,
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
ProjectPermissionSub.IpAllowList
|
||||
|
@ -1,7 +1,5 @@
|
||||
import * as serviceTokenDataController from "./serviceTokenDataController";
|
||||
import * as apiKeyDataController from "./apiKeyDataController";
|
||||
|
||||
export {
|
||||
serviceTokenDataController,
|
||||
apiKeyDataController
|
||||
}
|
@ -1,431 +0,0 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IServiceTokenDataV3,
|
||||
IUser,
|
||||
ServiceTokenDataV3,
|
||||
ServiceTokenDataV3Key,
|
||||
Workspace
|
||||
} from "../../../models";
|
||||
import {
|
||||
IServiceTokenV3Scope,
|
||||
IServiceTokenV3TrustedIp
|
||||
} from "../../../models/serviceTokenDataV3";
|
||||
import {
|
||||
ActorType,
|
||||
EventType
|
||||
} from "../../models";
|
||||
import { validateRequest } from "../../../helpers/validation";
|
||||
import * as reqValidator from "../../../validation/serviceTokenDataV3";
|
||||
import { createToken } from "../../../helpers/auth";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
getUserProjectPermissions
|
||||
} 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 { AuthTokenType } 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,
|
||||
scopes,
|
||||
trustedIps,
|
||||
expiresIn,
|
||||
accessTokenTTL,
|
||||
isRefreshTokenRotationEnabled,
|
||||
encryptedKey, // for ServiceTokenDataV3Key
|
||||
nonce, // for ServiceTokenDataV3Key
|
||||
}
|
||||
} = await validateRequest(reqValidator.CreateServiceTokenV3, req);
|
||||
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.ServiceTokens
|
||||
);
|
||||
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
if (!workspace) throw BadRequestError({ message: "Workspace 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,
|
||||
scopes,
|
||||
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,
|
||||
scopes: scopes as Array<IServiceTokenV3Scope>,
|
||||
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,
|
||||
scopes,
|
||||
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 getUserProjectPermissions(
|
||||
req.user._id,
|
||||
serviceTokenData.workspace.toString()
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.ServiceTokens
|
||||
);
|
||||
|
||||
const workspace = await Workspace.findById(serviceTokenData.workspace);
|
||||
if (!workspace) throw BadRequestError({ message: "Workspace 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,
|
||||
scopes,
|
||||
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,
|
||||
scopes: scopes as Array<IServiceTokenV3Scope>,
|
||||
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 getUserProjectPermissions(
|
||||
req.user._id,
|
||||
serviceTokenData.workspace.toString()
|
||||
);
|
||||
|
||||
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,
|
||||
scopes: serviceTokenData.scopes as Array<IServiceTokenV3Scope>,
|
||||
trustedIps: serviceTokenData.trustedIps as Array<IServiceTokenV3TrustedIp>,
|
||||
expiresAt: serviceTokenData.expiresAt
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: serviceTokenData.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
serviceTokenData
|
||||
});
|
||||
}
|
@ -1,76 +1,70 @@
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
import {
|
||||
ActorType,
|
||||
EventType,
|
||||
UserAgentType
|
||||
} from "./enums";
|
||||
import {
|
||||
Actor,
|
||||
Event
|
||||
} from "./types";
|
||||
import { ActorType, EventType, UserAgentType } from "./enums";
|
||||
import { Actor, Event } from "./types";
|
||||
|
||||
export interface IAuditLog {
|
||||
actor: Actor;
|
||||
organization: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
ipAddress: string;
|
||||
event: Event;
|
||||
userAgent: string;
|
||||
userAgentType: UserAgentType;
|
||||
expiresAt: Date;
|
||||
actor: Actor;
|
||||
organization: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
ipAddress: string;
|
||||
event: Event;
|
||||
userAgent: string;
|
||||
userAgentType: UserAgentType;
|
||||
expiresAt?: Date;
|
||||
}
|
||||
|
||||
const auditLogSchema = new Schema<IAuditLog>(
|
||||
{
|
||||
actor: {
|
||||
type: {
|
||||
type: String,
|
||||
enum: ActorType,
|
||||
required: true
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
}
|
||||
},
|
||||
organization: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: false
|
||||
},
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: false
|
||||
},
|
||||
ipAddress: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
event: {
|
||||
type: {
|
||||
type: String,
|
||||
enum: EventType,
|
||||
required: true
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
}
|
||||
},
|
||||
userAgent: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
userAgentType: {
|
||||
type: String,
|
||||
enum: UserAgentType,
|
||||
required: true
|
||||
},
|
||||
expiresAt: {
|
||||
type: Date,
|
||||
expires: 0
|
||||
}
|
||||
{
|
||||
actor: {
|
||||
type: {
|
||||
type: String,
|
||||
enum: ActorType,
|
||||
required: true
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
organization: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: false
|
||||
},
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: false,
|
||||
index: true
|
||||
},
|
||||
ipAddress: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
event: {
|
||||
type: {
|
||||
type: String,
|
||||
enum: EventType,
|
||||
required: true
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
}
|
||||
},
|
||||
userAgent: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
userAgentType: {
|
||||
type: String,
|
||||
enum: UserAgentType,
|
||||
required: true
|
||||
},
|
||||
expiresAt: {
|
||||
type: Date,
|
||||
expires: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
export const AuditLog = model<IAuditLog>("AuditLog", auditLogSchema);
|
||||
export const AuditLog = model<IAuditLog>("AuditLog", auditLogSchema);
|
||||
|
@ -1,8 +1,7 @@
|
||||
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 {
|
||||
@ -32,9 +31,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 { IServiceTokenV3Scope, 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 | MachineActor;
|
||||
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;
|
||||
scopes: Array<IServiceTokenV3Scope>;
|
||||
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;
|
||||
scopes?: Array<IServiceTokenV3Scope>;
|
||||
trustedIps?: Array<IServiceTokenV3TrustedIp>;
|
||||
expiresAt?: Date;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteServiceTokenV3Event {
|
||||
type: EventType.DELETE_SERVICE_TOKEN_V3;
|
||||
interface DeleteIdentityEvent {
|
||||
type: EventType.DELETE_IDENTITY;
|
||||
metadata: {
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
scopes: Array<IServiceTokenV3Scope>;
|
||||
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) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
AbilityBuilder,
|
||||
ForcedSubject,
|
||||
@ -6,11 +7,19 @@ import {
|
||||
buildMongoQueryMatcher,
|
||||
createMongoAbility
|
||||
} from "@casl/ability";
|
||||
import { Membership } from "../../models";
|
||||
import { IRole } from "../models/role";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
|
||||
import picomatch from "picomatch";
|
||||
import { AuthData } from "../../interfaces/middleware";
|
||||
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",
|
||||
@ -51,7 +60,8 @@ export enum ProjectPermissionSub {
|
||||
Secrets = "secrets",
|
||||
SecretRollback = "secret-rollback",
|
||||
SecretApproval = "secret-approval",
|
||||
SecretRotation = "secret-rotation"
|
||||
SecretRotation = "secret-rotation",
|
||||
Identity = "identity"
|
||||
}
|
||||
|
||||
type SubjectFields = {
|
||||
@ -76,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]
|
||||
@ -122,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);
|
||||
@ -187,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);
|
||||
@ -227,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);
|
||||
@ -239,31 +261,155 @@ const buildViewerPermission = () => {
|
||||
|
||||
export const viewerProjectPermission = buildViewerPermission();
|
||||
|
||||
export const getUserProjectPermissions = async (userId: string, workspaceId: string) => {
|
||||
// TODO(akhilmhdh): speed this up by pulling from cache later
|
||||
const membership = await Membership.findOne({
|
||||
user: userId,
|
||||
workspace: workspaceId
|
||||
})
|
||||
.populate<{
|
||||
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
||||
}>("customRole")
|
||||
.exec();
|
||||
const buildNoAccessProjectPermission = () => {
|
||||
const { build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||
return build({ conditionsMatcher });
|
||||
}
|
||||
|
||||
if (!membership || (membership.role === "custom" && !membership.customRole)) {
|
||||
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
||||
export const noAccessProjectPermissions = buildNoAccessProjectPermission();
|
||||
|
||||
/**
|
||||
* Return permissions for user/service pertaining to workspace with id [workspaceId]
|
||||
*
|
||||
* Note: should not rely on this function for ST V2 authorization logic
|
||||
* b/c ST V2 does not support role-based access control
|
||||
*/
|
||||
export const getAuthDataProjectPermissions = async ({
|
||||
authData,
|
||||
workspaceId
|
||||
}: {
|
||||
authData: AuthData;
|
||||
workspaceId: Types.ObjectId;
|
||||
}) => {
|
||||
let role: "admin" | "member" | "viewer" | "no-access" | "custom";
|
||||
let customRole;
|
||||
|
||||
switch (authData.actor.type) {
|
||||
case ActorType.USER: {
|
||||
const membership = await Membership.findOne({
|
||||
user: authData.authPayload._id,
|
||||
workspace: workspaceId
|
||||
})
|
||||
.populate<{
|
||||
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
|
||||
}>("customRole")
|
||||
.exec();
|
||||
|
||||
if (!membership || (membership.role === "custom" && !membership.customRole)) {
|
||||
throw UnauthorizedRequestError();
|
||||
}
|
||||
|
||||
role = membership.role;
|
||||
customRole = membership.customRole;
|
||||
break;
|
||||
}
|
||||
case ActorType.SERVICE: {
|
||||
const serviceTokenData = await ServiceTokenData.findById(authData.authPayload._id);
|
||||
if (!serviceTokenData || !serviceTokenData.workspace.equals(workspaceId)) throw UnauthorizedRequestError();
|
||||
role = "viewer";
|
||||
break;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
role = identityMembership.role;
|
||||
customRole = identityMembership.customRole;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw UnauthorizedRequestError();
|
||||
}
|
||||
|
||||
if (membership.role === "admin") return { permission: adminProjectPermissions, membership };
|
||||
if (membership.role === "member") return { permission: memberProjectPermissions, membership };
|
||||
if (membership.role === "viewer") return { permission: viewerProjectPermission, membership };
|
||||
switch (role) {
|
||||
case ADMIN:
|
||||
return { permission: adminProjectPermissions };
|
||||
case MEMBER:
|
||||
return { permission: memberProjectPermissions };
|
||||
case VIEWER:
|
||||
return { permission: viewerProjectPermission };
|
||||
case NO_ACCESS:
|
||||
return { permission: noAccessProjectPermissions };
|
||||
case CUSTOM: {
|
||||
if (!customRole) throw UnauthorizedRequestError();
|
||||
return {
|
||||
permission: createMongoAbility<ProjectPermissionSet>(
|
||||
customRole.permissions,
|
||||
{ conditionsMatcher }
|
||||
)
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw UnauthorizedRequestError();
|
||||
}
|
||||
}
|
||||
|
||||
if (membership.role === "custom") {
|
||||
const permission = createMongoAbility<ProjectPermissionSet>(membership.customRole.permissions, {
|
||||
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
|
||||
});
|
||||
return { permission, membership };
|
||||
}
|
||||
|
||||
throw BadRequestError({ message: "User role not found" });
|
||||
};
|
||||
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) => {
|
||||
|
58
backend/src/helpers/reminder.ts
Normal file
58
backend/src/helpers/reminder.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { ISecret } from "../models";
|
||||
import {
|
||||
createRecurringSecretReminder,
|
||||
deleteRecurringSecretReminder,
|
||||
updateRecurringSecretReminder
|
||||
} from "../queues/reminders/sendSecretReminders";
|
||||
|
||||
type TPartialSecret = Pick<
|
||||
ISecret,
|
||||
"_id" | "secretReminderRepeatDays" | "secretReminderNote" | "workspace"
|
||||
>;
|
||||
type TPartialSecretDeleteReminder = Pick<ISecret, "_id" | "secretReminderRepeatDays">;
|
||||
|
||||
export const createReminder = async (oldSecret: TPartialSecret, newSecret: TPartialSecret) => {
|
||||
if (oldSecret._id !== newSecret._id) {
|
||||
throw new Error("Secret id's don't match");
|
||||
}
|
||||
|
||||
if (!newSecret.secretReminderRepeatDays) {
|
||||
throw new Error("No repeat days provided");
|
||||
}
|
||||
|
||||
const secretId = oldSecret._id.toString();
|
||||
const workspaceId = oldSecret.workspace.toString();
|
||||
|
||||
if (oldSecret.secretReminderRepeatDays) {
|
||||
// This will first delete the existing recurring job, and then create a new one.
|
||||
await updateRecurringSecretReminder({
|
||||
workspaceId,
|
||||
secretId,
|
||||
repeatDays: newSecret.secretReminderRepeatDays,
|
||||
note: newSecret.secretReminderNote
|
||||
});
|
||||
} else {
|
||||
// This will create a new recurring job.
|
||||
await createRecurringSecretReminder({
|
||||
workspaceId,
|
||||
secretId,
|
||||
repeatDays: newSecret.secretReminderRepeatDays,
|
||||
note: newSecret.secretReminderNote
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteReminder = async (secret: TPartialSecretDeleteReminder) => {
|
||||
if (!secret._id) {
|
||||
throw new Error("No secret id provided");
|
||||
}
|
||||
|
||||
if (!secret.secretReminderRepeatDays) {
|
||||
throw new Error("No repeat days provided");
|
||||
}
|
||||
|
||||
await deleteRecurringSecretReminder({
|
||||
secretId: secret._id.toString(),
|
||||
repeatDays: secret.secretReminderRepeatDays
|
||||
});
|
||||
};
|
@ -13,13 +13,11 @@ import {
|
||||
Folder,
|
||||
ISecret,
|
||||
IServiceTokenData,
|
||||
IServiceTokenDataV3,
|
||||
Secret,
|
||||
SecretBlindIndexData,
|
||||
ServiceTokenData,
|
||||
TFolderRootSchema
|
||||
} from "../models";
|
||||
import { Permission } from "../models/serviceTokenDataV3";
|
||||
import { EventType, SecretVersion } from "../ee/models";
|
||||
import {
|
||||
BadRequestError,
|
||||
@ -51,42 +49,6 @@ import picomatch from "picomatch";
|
||||
import path from "path";
|
||||
import { getAnImportedSecret } from "../services/SecretImportService";
|
||||
|
||||
/**
|
||||
* Validate scope for service token v3
|
||||
* @param authPayload
|
||||
* @param environment
|
||||
* @param secretPath
|
||||
* @returns
|
||||
*/
|
||||
export const isValidScopeV3 = ({
|
||||
authPayload,
|
||||
environment,
|
||||
secretPath,
|
||||
requiredPermissions
|
||||
}: {
|
||||
authPayload: IServiceTokenDataV3;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
requiredPermissions: Permission[];
|
||||
}) => {
|
||||
const { scopes } = authPayload;
|
||||
|
||||
const validScope = scopes.find(
|
||||
(scope) =>
|
||||
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
|
||||
scope.environment === environment
|
||||
);
|
||||
|
||||
if (
|
||||
validScope &&
|
||||
!requiredPermissions.every((permission) => validScope.permissions.includes(permission))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(validScope);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate scope for service token v2
|
||||
* @param authPayload
|
||||
@ -612,18 +574,20 @@ export const getSecretsHelper = async ({
|
||||
const approximateForNoneCapturedEvents = secrets.length * 10;
|
||||
|
||||
if (shouldCapture) {
|
||||
postHogClient.capture({
|
||||
event: "secrets pulled",
|
||||
distinctId: await TelemetryService.getDistinctId({ authData }),
|
||||
properties: {
|
||||
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.userAgentType,
|
||||
userAgent: authData.userAgent
|
||||
}
|
||||
});
|
||||
if (workspaceId.toString() != "650e71fbae3e6c8572f436d4") {
|
||||
postHogClient.capture({
|
||||
event: "secrets pulled",
|
||||
distinctId: await TelemetryService.getDistinctId({ authData }),
|
||||
properties: {
|
||||
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.userAgentType,
|
||||
userAgent: authData.userAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -755,6 +719,8 @@ export const updateSecretHelper = async ({
|
||||
secretValueIV,
|
||||
secretValueTag,
|
||||
secretPath,
|
||||
secretReminderRepeatDays,
|
||||
secretReminderNote,
|
||||
tags,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
@ -819,6 +785,10 @@ export const updateSecretHelper = async ({
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretCommentCiphertext,
|
||||
|
||||
secretReminderRepeatDays,
|
||||
secretReminderNote,
|
||||
|
||||
skipMultilineEncoding,
|
||||
secretBlindIndex: newSecretNameBlindIndex,
|
||||
secretKeyIV,
|
||||
|
@ -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
|
||||
});
|
||||
|
||||
|
@ -2,10 +2,10 @@ import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
import express from "express";
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require("express-async-errors");
|
||||
import "express-async-errors";
|
||||
import helmet from "helmet";
|
||||
import cors from "cors";
|
||||
import { logger } from "./utils/logging";
|
||||
import { initLogger, logger } from "./utils/logging";
|
||||
import httpLogger from "pino-http";
|
||||
import { DatabaseService } from "./services";
|
||||
import { EELicenseService, GithubSecretScanningService } from "./ee/services";
|
||||
@ -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,
|
||||
@ -97,13 +98,17 @@ import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPu
|
||||
const SmeeClient = require("smee-client"); // eslint-disable-line
|
||||
import path from "path";
|
||||
import { serverConfigInit } from "./config/serverConfig";
|
||||
import { initRedis } from "./services/RedisService";
|
||||
|
||||
let handler: null | any = null;
|
||||
|
||||
const main = async () => {
|
||||
await initLogger();
|
||||
|
||||
const port = await getPort();
|
||||
|
||||
// initializing the database connection
|
||||
// initializing the database connection + redis
|
||||
await initRedis()
|
||||
await DatabaseService.initDatabase(await getMongoURL());
|
||||
const serverCfg = await serverConfigInit();
|
||||
await setup();
|
||||
@ -194,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);
|
||||
@ -201,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);
|
||||
@ -216,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
|
||||
@ -243,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)
|
||||
|
@ -10,6 +10,8 @@ import {
|
||||
INTEGRATION_CIRCLECI_API_URL,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUDFLARE_PAGES_API_URL,
|
||||
INTEGRATION_CLOUDFLARE_WORKERS,
|
||||
INTEGRATION_CLOUDFLARE_WORKERS_API_URL,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CLOUD_66_API_URL,
|
||||
INTEGRATION_CODEFRESH,
|
||||
@ -186,6 +188,12 @@ const getApps = async ({
|
||||
accountId: accessId
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_CLOUDFLARE_WORKERS:
|
||||
apps = await getAppsCloudflareWorkers({
|
||||
accessToken,
|
||||
accountId: accessId
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_NORTHFLANK:
|
||||
apps = await getAppsNorthflank({
|
||||
accessToken
|
||||
@ -968,6 +976,40 @@ const getAppsCloudflarePages = async ({
|
||||
return apps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of projects for the Cloudflare Workers integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - api key for the Cloudflare API
|
||||
* @returns {Object[]} apps - Cloudflare Workers projects
|
||||
* @returns {String} apps.id - Id of Cloudflare Workers project
|
||||
* @returns {String} apps.name - Id of Cloudflare Workers project (Cloudflare workers API does not return the name)
|
||||
*/
|
||||
const getAppsCloudflareWorkers = async ({
|
||||
accessToken,
|
||||
accountId
|
||||
}: {
|
||||
accessToken: string;
|
||||
accountId?: string;
|
||||
}) => {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accountId}/workers/services`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const apps = data.result.map((a: any) => {
|
||||
return {
|
||||
name: a.id,
|
||||
appId: a.id
|
||||
};
|
||||
});
|
||||
return apps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of repositories for the BitBucket integration based on provided BitBucket workspace
|
||||
* @param {Object} obj
|
||||
|
@ -18,6 +18,8 @@ import {
|
||||
INTEGRATION_CIRCLECI_API_URL,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUDFLARE_PAGES_API_URL,
|
||||
INTEGRATION_CLOUDFLARE_WORKERS,
|
||||
INTEGRATION_CLOUDFLARE_WORKERS_API_URL,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CLOUD_66_API_URL,
|
||||
INTEGRATION_CODEFRESH,
|
||||
@ -262,6 +264,14 @@ const syncSecrets = async ({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_CLOUDFLARE_WORKERS:
|
||||
await syncSecretsCloudflareWorkers({
|
||||
integration,
|
||||
secrets,
|
||||
accessId,
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_CODEFRESH:
|
||||
await syncSecretsCodefresh({
|
||||
integration,
|
||||
@ -2746,6 +2756,98 @@ const syncSecretsCloudflarePages = async ({
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to Cloudflare Workers project with name [integration.app]
|
||||
* @param {Object} obj
|
||||
* @param {IIntegration} obj.integration - integration details
|
||||
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||
* @param {String} obj.accessToken - API token for Cloudflare workers
|
||||
*/
|
||||
const syncSecretsCloudflareWorkers = async ({
|
||||
integration,
|
||||
secrets,
|
||||
accessId,
|
||||
accessToken
|
||||
}: {
|
||||
integration: IIntegration;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
// get secrets from cloudflare workers
|
||||
const getSecretsRes = (
|
||||
await standardRequest.get(
|
||||
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accessId}/workers/scripts/${integration.app}/secrets`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
)
|
||||
).data.result;
|
||||
|
||||
const secretsObj: any = getSecretKeyValuePair(secrets);
|
||||
|
||||
for (const [key, val] of Object.entries(secretsObj)) {
|
||||
secretsObj[key] = { type: "secret_text", value: val };
|
||||
}
|
||||
|
||||
// get deleted secrets list
|
||||
const deletedSecretKeys: string[] = [];
|
||||
if (getSecretsRes) {
|
||||
getSecretsRes.forEach((secretRes: any) => {
|
||||
if (!(Object.keys(secrets).includes(secretRes.name))) {
|
||||
deletedSecretKeys.push(secretRes.name);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deletedSecretKeys.forEach(async (secretKey) => {
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accessId}/workers/scripts/${integration.app}/secrets/${secretKey}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
interface ConvertedSecret {
|
||||
name: string;
|
||||
text: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface SecretsObj {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
const data: ConvertedSecret[] = Object.entries(secretsObj as SecretsObj).map(([name, secret]) => ({
|
||||
name,
|
||||
text: secret.value,
|
||||
type: "secret_text"
|
||||
}));
|
||||
|
||||
data.forEach(async (secret) => {
|
||||
await standardRequest.put(
|
||||
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accessId}/workers/scripts/${integration.app}/secrets`,
|
||||
secret,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to BitBucket repo with name [integration.app]
|
||||
* @param {Object} obj
|
||||
|
@ -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;
|
@ -58,6 +58,10 @@ export interface UpdateSecretParams {
|
||||
secretCommentCiphertext?: string;
|
||||
secretCommentIV?: string;
|
||||
secretCommentTag?: string;
|
||||
|
||||
secretReminderRepeatDays?: number | null;
|
||||
secretReminderNote?: string | null;
|
||||
|
||||
skipMultilineEncoding?: boolean;
|
||||
tags?: string[];
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ export const requestErrorHandler: ErrorRequestHandler = async (
|
||||
if (res.headersSent) return next();
|
||||
|
||||
let error: RequestError;
|
||||
|
||||
|
||||
switch (true) {
|
||||
case err instanceof TokenExpiredError:
|
||||
error = UnauthorizedRequestError({ stack: err.stack, message: "Token expired" });
|
||||
@ -31,7 +31,7 @@ export const requestErrorHandler: ErrorRequestHandler = async (
|
||||
break;
|
||||
}
|
||||
|
||||
logger[mapToPinoLogLevel(error.level)](error);
|
||||
logger[mapToPinoLogLevel(error.level)]({ msg: error });
|
||||
|
||||
if (req.user) {
|
||||
Sentry.setUser({ email: (req.user as any).email });
|
||||
@ -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";
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUDFLARE_WORKERS,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
@ -71,6 +72,7 @@ export interface IIntegration {
|
||||
| "teamcity"
|
||||
| "hashicorp-vault"
|
||||
| "cloudflare-pages"
|
||||
| "cloudflare-workers"
|
||||
| "bitbucket"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
@ -179,6 +181,7 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUDFLARE_WORKERS,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUDFLARE_WORKERS,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
@ -55,6 +56,7 @@ export interface IIntegrationAuth extends Document {
|
||||
| "checkly"
|
||||
| "qovery"
|
||||
| "cloudflare-pages"
|
||||
| "cloudflare-workers"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
| "bitbucket"
|
||||
@ -113,6 +115,7 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUDFLARE_WORKERS,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
|
@ -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: {
|
||||
|
@ -27,6 +27,12 @@ export interface ISecret {
|
||||
secretCommentIV?: string;
|
||||
secretCommentTag?: string;
|
||||
secretCommentHash?: string;
|
||||
|
||||
// ? NOTE: This works great for workspace-level reminders.
|
||||
// ? If we want to do it on a user-basis, we should ideally have a seperate model for reminders.
|
||||
secretReminderRepeatDays?: number | null;
|
||||
secretReminderNote?: string | null;
|
||||
|
||||
skipMultilineEncoding?: boolean;
|
||||
algorithm: "aes-256-gcm";
|
||||
keyEncoding: "utf8" | "base64";
|
||||
@ -118,10 +124,23 @@ const secretSchema = new Schema<ISecret>(
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
|
||||
secretReminderRepeatDays: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
secretReminderNote: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
|
||||
skipMultilineEncoding: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
|
||||
algorithm: {
|
||||
// the encryption algorithm used
|
||||
type: String,
|
||||
|
@ -1,159 +0,0 @@
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
import { IPType } from "../ee/models";
|
||||
|
||||
export enum Permission {
|
||||
READ = "read",
|
||||
WRITE = "write"
|
||||
}
|
||||
|
||||
export interface IServiceTokenV3Scope {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
permissions: Permission[];
|
||||
}
|
||||
|
||||
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;
|
||||
scopes: Array<IServiceTokenV3Scope>;
|
||||
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
|
||||
},
|
||||
scopes: {
|
||||
type: [
|
||||
{
|
||||
environment: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
secretPath: {
|
||||
type: String,
|
||||
default: "/",
|
||||
required: true
|
||||
},
|
||||
permissions: {
|
||||
type: [String],
|
||||
enum: [Permission.READ, Permission.WRITE],
|
||||
default: [Permission.READ],
|
||||
required: true
|
||||
}
|
||||
}
|
||||
],
|
||||
required: true
|
||||
},
|
||||
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);
|
83
backend/src/queues/reminders/sendSecretReminders.ts
Normal file
83
backend/src/queues/reminders/sendSecretReminders.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import Queue, { Job } from "bull";
|
||||
import { IUser, Membership, Organization, Workspace } from "../../models";
|
||||
import { Types } from "mongoose";
|
||||
import { sendMail } from "../../helpers";
|
||||
|
||||
type TSendSecretReminders = {
|
||||
workspaceId: string;
|
||||
secretId: string;
|
||||
repeatDays: number;
|
||||
note: string | undefined | null;
|
||||
};
|
||||
|
||||
type TDeleteSecretReminder = {
|
||||
secretId: string;
|
||||
repeatDays: number;
|
||||
};
|
||||
|
||||
const DAY_IN_MS = 86400000;
|
||||
|
||||
export const sendSecretReminders = new Queue(
|
||||
"send-secret-reminders",
|
||||
process.env.REDIS_URL as string
|
||||
);
|
||||
|
||||
sendSecretReminders.process(async (job: Job<TSendSecretReminders>) => {
|
||||
const { workspaceId }: TSendSecretReminders = job.data;
|
||||
|
||||
const workspace = await Workspace.findById(new Types.ObjectId(workspaceId));
|
||||
const organization = await Organization.findById(new Types.ObjectId(workspace?.organization));
|
||||
|
||||
if (!workspace) {
|
||||
throw new Error("Workspace for reminder not found");
|
||||
}
|
||||
if (!organization) {
|
||||
throw new Error("Organization for reminder not found");
|
||||
}
|
||||
|
||||
const memberships = await Membership.find({
|
||||
workspace: workspaceId
|
||||
}).populate<{ user: IUser }>("user");
|
||||
|
||||
await sendMail({
|
||||
template: "secretReminder.handlebars",
|
||||
subjectLine: "Infisical secret reminder",
|
||||
recipients: [...memberships.map((membership) => membership.user.email)],
|
||||
substitutions: {
|
||||
reminderNote: job.data.note, // May not be present.
|
||||
workspaceName: workspace.name,
|
||||
organizationName: organization.name
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export const createRecurringSecretReminder = (jobDetails: TSendSecretReminders) => {
|
||||
const repeat = jobDetails.repeatDays * DAY_IN_MS;
|
||||
|
||||
return sendSecretReminders.add(jobDetails, {
|
||||
delay: repeat,
|
||||
repeat: {
|
||||
every: repeat
|
||||
},
|
||||
jobId: `reminder-${jobDetails.secretId}`,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: {
|
||||
count: 20
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteRecurringSecretReminder = (jobDetails: TDeleteSecretReminder) => {
|
||||
const repeat = jobDetails.repeatDays * DAY_IN_MS;
|
||||
|
||||
return sendSecretReminders.removeRepeatable({
|
||||
every: repeat,
|
||||
jobId: `reminder-${jobDetails.secretId}`
|
||||
});
|
||||
};
|
||||
|
||||
export const updateRecurringSecretReminder = async (jobDetails: TSendSecretReminders) => {
|
||||
// We need to delete the potentially existing reminder job first, or the new one won't be created.
|
||||
await deleteRecurringSecretReminder(jobDetails);
|
||||
await createRecurringSecretReminder(jobDetails);
|
||||
};
|
@ -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,
|
||||
|
@ -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;
|
@ -26,7 +26,7 @@ router.post(
|
||||
);
|
||||
|
||||
//remove above ones after depreciation
|
||||
router.post("/mfa/send", authLimiter, authController.sendMfaToken);
|
||||
router.post("/mfa/send", authLimiter, requireMfaAuth, authController.sendMfaToken);
|
||||
|
||||
router.post("/mfa/verify", authLimiter, requireMfaAuth, authController.verifyMfaToken);
|
||||
|
||||
|
@ -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,17 +31,9 @@ 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
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:workspaceId/environments",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||
}),
|
||||
environmentController.getAllAccessibleEnvironmentsOfWorkspace
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
@ -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;
|
||||
|
@ -3,11 +3,14 @@ import { logger } from "../utils/logging";
|
||||
|
||||
let redisClient: TRedis | null;
|
||||
|
||||
if (process.env.REDIS_URL) {
|
||||
redisClient = new Redis(process.env.REDIS_URL as string);
|
||||
} else {
|
||||
logger.warn("Redis URL not set, skipping Redis initialization.");
|
||||
redisClient = null;
|
||||
export const initRedis = async () => {
|
||||
if (process.env.REDIS_URL) {
|
||||
redisClient = new Redis(process.env.REDIS_URL as string);
|
||||
} else {
|
||||
logger.warn("Redis URL not set, skipping Redis initialization.");
|
||||
redisClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export { redisClient };
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
19
backend/src/templates/secretReminder.handlebars
Normal file
19
backend/src/templates/secretReminder.handlebars
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>Secret Reminder</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Infisical</h2>
|
||||
<h2>You have a new secret reminder!</h2>
|
||||
<p>You have a new secret reminder from workspace "{{workspaceName}}", in {{organizationName}}</p>
|
||||
{{#if reminderNote}}
|
||||
<p>Here's the note included with the reminder: {{reminderNote}}</p>
|
||||
{{/if}}
|
||||
</body>
|
||||
|
||||
</html>
|
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";
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user