mirror of
https://github.com/Infisical/infisical.git
synced 2025-04-05 17:32:31 +00:00
Compare commits
270 Commits
maidul98-p
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
6bca7dcc58 | |||
a9f062b469 | |||
43caccad9f | |||
9cef1d3b10 | |||
8e6b0ca0a9 | |||
a288a0bf2a | |||
9dd77ae36e | |||
1e20715511 | |||
d07b2dafc3 | |||
04548313ab | |||
86bf2ddd89 | |||
2ad663c021 | |||
56317a3f53 | |||
ad0bc4efdc | |||
bf74b75c4a | |||
7f543b635c | |||
353dfeb2a9 | |||
16196e6343 | |||
3f2b74a28a | |||
4a603da425 | |||
0d9ce70000 | |||
9fa1e415c8 | |||
09b22f36c0 | |||
43c8c42249 | |||
d4a7ad713c | |||
9afad7df32 | |||
3f61a24ef1 | |||
f342c345b7 | |||
6dd46885f8 | |||
41774fa97c | |||
32b26c331c | |||
011507b8e0 | |||
4adb2a623e | |||
1d410c8420 | |||
35f3d6c776 | |||
5e24b517cc | |||
bf95415a0d | |||
4025063732 | |||
e3ecfbaaa5 | |||
a7b3d12844 | |||
f5145c6c39 | |||
d9abe671af | |||
37ae05fa2a | |||
086ce0d2a6 | |||
06dec29773 | |||
ed8e942a5d | |||
e770bdde24 | |||
a84dab1219 | |||
02d9d7b6a4 | |||
f21eb3b7c8 | |||
219e3884e7 | |||
41cd8b7408 | |||
f6be86a26b | |||
85e5822ece | |||
5c9e89a8e2 | |||
46a77d5e58 | |||
a6e9643464 | |||
affa2ee695 | |||
dc0d577cbb | |||
9e8ddd2956 | |||
b40b876fb2 | |||
2ba6a65da4 | |||
76cf79d201 | |||
a79c6227b1 | |||
f1f64e6ff5 | |||
d72ddfe315 | |||
f924d0c02c | |||
ef1b75d890 | |||
d8094b2ab1 | |||
ad61fa845c | |||
6bb5e7078f | |||
a07ddb806d | |||
6e7d3d6912 | |||
84a866eb88 | |||
9416fca832 | |||
2ea518b107 | |||
62399dd293 | |||
16f1360550 | |||
a99751eb72 | |||
9ea414fb25 | |||
a9fa3ebab2 | |||
293a62b632 | |||
a1f08b064e | |||
50977cf788 | |||
fccec083a9 | |||
63af7d4a15 | |||
ab3533ce1c | |||
4d6a8f0476 | |||
688cf91eb7 | |||
8ee6710e9b | |||
14fc78eaaf | |||
9fa28f5b5e | |||
368855a44e | |||
ae375916e8 | |||
21f1648998 | |||
88695a2f8c | |||
77114e02cf | |||
3ac1795a5b | |||
8d6f59b253 | |||
7fd77b14ff | |||
8d3d7d98e3 | |||
6cac879ed0 | |||
ac66834daa | |||
0616f24923 | |||
4e1abc6eba | |||
8f57377130 | |||
2d7c7f075e | |||
c342b22d49 | |||
b8120f7512 | |||
ca18883bd3 | |||
8b381b2b80 | |||
6bcf5cb54c | |||
51b425dceb | |||
7ec00475c6 | |||
84840bddb5 | |||
93640c9d69 | |||
ec856f0bcc | |||
3e46bec6f7 | |||
25fc508d5e | |||
ea262da505 | |||
954806d950 | |||
2960f86647 | |||
b2888272f2 | |||
d6d3302659 | |||
e5c87442e5 | |||
be08417c8b | |||
61e44e152c | |||
52c4f64655 | |||
81743d55ab | |||
3e36adcf5c | |||
1f60a3d73e | |||
00089a6bba | |||
026ea29847 | |||
1242d88acb | |||
f47a119474 | |||
0b359cd797 | |||
c5ae402787 | |||
e288402ec4 | |||
196beb8355 | |||
d6222d5cee | |||
e855d4a0ba | |||
20f34b4764 | |||
0eb21919fb | |||
fbeb210965 | |||
0d1aa713ea | |||
9a1b453c86 | |||
534d96ffb6 | |||
5b342409e3 | |||
a9f54009b8 | |||
82947e183c | |||
eb7ef2196a | |||
ad3801ce36 | |||
b7aac1a465 | |||
e28ced8eed | |||
4a95f936ea | |||
85a39c60bb | |||
66ea3ba172 | |||
01d91c0dc7 | |||
dedd27a781 | |||
57a6d1fff6 | |||
554f0c79a4 | |||
2af88d4c99 | |||
fc8b567352 | |||
ec234e198a | |||
6e1cc12e3a | |||
1b4b7a967b | |||
e47d6b7f2f | |||
45a13d06b5 | |||
4a48c088df | |||
2b65f65063 | |||
065e150847 | |||
ab72eb1178 | |||
816099a8b4 | |||
b5f672cc61 | |||
ddc7be18eb | |||
c0ce92cf3d | |||
0073fe459e | |||
a7f52a9298 | |||
29c0d8ab57 | |||
d7b26cbf04 | |||
767abe51ef | |||
5ac1816392 | |||
c5b1e7298e | |||
3436e6be0e | |||
b000a78f74 | |||
cb42db3de4 | |||
11bb0d648f | |||
90517258a2 | |||
d78b37c632 | |||
4a6fc9e84f | |||
8030104c02 | |||
3825269cbb | |||
baa907dbb6 | |||
83465dff2d | |||
67a8211cb0 | |||
bc108a82b6 | |||
05be5910d0 | |||
2341ec0e11 | |||
9652d534b6 | |||
dd8f55804c | |||
95d25b114e | |||
c0f3aecad3 | |||
f650cd3925 | |||
8a514e329f | |||
dbd55441f2 | |||
01e613301a | |||
de7bd27b4b | |||
a4cdd14014 | |||
632c78f401 | |||
051f4501e8 | |||
69605a1a54 | |||
e47912edd7 | |||
a4edf6bd0c | |||
b11cd29943 | |||
395b51c265 | |||
27f56be466 | |||
dfe95ac773 | |||
2dba7847b6 | |||
78802409bd | |||
9963724a6a | |||
b49ef9efc9 | |||
a31ffe9617 | |||
18beed7540 | |||
0a538ac1a7 | |||
a75ad5ef26 | |||
b47f61f1ad | |||
2a1665a2c3 | |||
e993bd048e | |||
11833ccf0f | |||
37d52432d0 | |||
04b7e04d98 | |||
57a3384f32 | |||
c813c91aec | |||
91947df5f6 | |||
8330890087 | |||
221e601173 | |||
28c24fc8c1 | |||
a9389643b8 | |||
58854e6b81 | |||
7ae859e9ae | |||
ff6e07bdcf | |||
fc9393b77f | |||
0cad823267 | |||
97a0728f02 | |||
6cb8cf53f8 | |||
1ac607b42e | |||
ec21e35f8c | |||
2591161272 | |||
be86e4176c | |||
2067c021ed | |||
648968c453 | |||
dc3f2c78c1 | |||
b4dbdbabac | |||
681255187f | |||
bde30049bc | |||
0a140f5333 | |||
3a9bf5409b | |||
04fdccc45d | |||
5604232aea | |||
373dfff8e0 | |||
b9ce448bed | |||
142fcf0a01 | |||
49bcd8839f | |||
d5f6e20c78 | |||
00030f2231 | |||
24d23e89d0 | |||
3fe592686a | |||
9cba0970be | |||
9d57b1db87 | |||
bb466dbe1c |
@ -25,6 +25,9 @@ JWT_PROVIDER_AUTH_LIFETIME=
|
||||
# Required
|
||||
MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
||||
# Optional credentials for MongoDB container instance and Mongo-Express
|
||||
MONGO_USERNAME=root
|
||||
MONGO_PASSWORD=example
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -8,7 +8,7 @@ assignees: ''
|
||||
---
|
||||
|
||||
### Feature description
|
||||
A clear and concise description of what the the feature should be.
|
||||
A clear and concise description of what the feature should be.
|
||||
|
||||
### Why would it be useful?
|
||||
Why would this feature be useful for Infisical users?
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -57,3 +57,6 @@ yarn-error.log*
|
||||
|
||||
# Infisical init
|
||||
.infisical.json
|
||||
|
||||
# Editor specific
|
||||
.vscode/*
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,3 +0,0 @@
|
||||
{
|
||||
"workbench.editor.wrapTabs": true
|
||||
}
|
@ -34,7 +34,7 @@
|
||||
<img src="https://img.shields.io/github/commit-activity/m/infisical/infisical" alt="git commit activity" />
|
||||
</a>
|
||||
<a href="https://cloudsmith.io/~infisical/repos/">
|
||||
<img src="https://img.shields.io/badge/Downloads-395.8k-orange" alt="Cloudsmith downloads" />
|
||||
<img src="https://img.shields.io/badge/Downloads-821.8k-orange" alt="Cloudsmith downloads" />
|
||||
</a>
|
||||
<a href="https://infisical.com/slack">
|
||||
<img src="https://img.shields.io/badge/chat-on%20Slack-blueviolet" alt="Slack community channel" />
|
||||
@ -116,7 +116,7 @@ Lean about Infisical's code scanning feature [here](https://infisical.com/docs/c
|
||||
|
||||
This repo available under the [MIT expat license](https://github.com/Infisical/infisical/blob/main/LICENSE), with the exception of the `ee` directory which will contain premium enterprise features requiring a Infisical license.
|
||||
|
||||
If you are interested in managed Infisical Cloud of self-hosted Enterprise Offering, take a look at [our webiste](https://infisical.com/) or [book a meeting with us](https://cal.com/vmatsiiako/infisical-demo):
|
||||
If you are interested in managed Infisical Cloud of self-hosted Enterprise Offering, take a look at [our website](https://infisical.com/) or [book a meeting with us](https://cal.com/vmatsiiako/infisical-demo):
|
||||
|
||||
<a href="https://cal.com/vmatsiiako/infisical-demo"><img alt="Schedule a meeting" src="https://cal.com/book-with-cal-dark.svg" /></a>
|
||||
|
||||
|
@ -14,6 +14,8 @@ FROM node:16-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV npm_config_cache /home/node/.npm
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only-production
|
||||
|
||||
@ -21,11 +23,11 @@ COPY --from=build /app .
|
||||
|
||||
RUN apk add --no-cache bash curl && curl -1sLf \
|
||||
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
|
||||
&& apk add infisical=0.8.1
|
||||
&& apk add infisical=0.8.1 && apk add --no-cache git
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
||||
CMD node healthcheck.js
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
CMD ["npm", "run", "start"]
|
||||
|
4289
backend/package-lock.json
generated
4289
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -50,7 +50,7 @@
|
||||
"typescript": "^4.9.3",
|
||||
"utility-types": "^3.10.0",
|
||||
"winston": "^3.8.2",
|
||||
"winston-loki": "^6.0.6"
|
||||
"winston-loki": "^6.0.7"
|
||||
},
|
||||
"name": "infisical-api",
|
||||
"version": "1.0.0",
|
||||
@ -84,6 +84,7 @@
|
||||
"@posthog/plugin-scaffold": "^1.3.4",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/bull": "^4.10.0",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.14",
|
||||
|
@ -3203,6 +3203,9 @@
|
||||
"name": {
|
||||
"example": "any"
|
||||
},
|
||||
"tagColor": {
|
||||
"example": "any"
|
||||
},
|
||||
"slug": {
|
||||
"example": "any"
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID
|
||||
export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
|
||||
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
|
||||
export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue;
|
||||
export const getClientIdGCPSecretManager = async () => (await client.getSecret("CLIENT_ID_GCP_SECRET_MANAGER")).secretValue;
|
||||
export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue;
|
||||
export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue;
|
||||
export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue;
|
||||
@ -44,6 +45,7 @@ export const getClientSecretNetlify = async () => (await client.getSecret("CLIEN
|
||||
export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue;
|
||||
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue;
|
||||
export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue;
|
||||
export const getClientSecretGCPSecretManager = async () => (await client.getSecret("CLIENT_SECRET_GCP_SECRET_MANAGER")).secretValue;
|
||||
export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
|
||||
|
||||
export const getClientIdGoogleLogin = async () => (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue;
|
||||
@ -68,6 +70,8 @@ export const getSecretScanningWebhookSecret = async () => (await client.getSecre
|
||||
export const getSecretScanningGitAppId = async () => (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue;
|
||||
export const getSecretScanningPrivateKey = async () => (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue;
|
||||
|
||||
export const getRedisUrl = async () => (await client.getSecret("REDIS_URL")).secretValue;
|
||||
|
||||
export const getLicenseKey = async () => {
|
||||
const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue;
|
||||
return secretValue === "" ? undefined : secretValue;
|
||||
|
@ -1,34 +1,22 @@
|
||||
import { Request, Response } from "express";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
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 {
|
||||
ACTION_LOGIN,
|
||||
ACTION_LOGOUT,
|
||||
AUTH_MODE_JWT,
|
||||
} from "../../variables";
|
||||
import {
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError,
|
||||
} from "../../utils/errors";
|
||||
import { ACTION_LOGIN, ACTION_LOGOUT } from "../../variables";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { EELogService } from "../../ee/services";
|
||||
import { getChannelFromUserAgent } from "../../utils/posthog";
|
||||
import { getUserAgentType } from "../../utils/posthog";
|
||||
import {
|
||||
getHttpsEnabled,
|
||||
getJwtAuthLifetime,
|
||||
getJwtAuthSecret,
|
||||
getJwtRefreshSecret,
|
||||
getJwtRefreshSecret
|
||||
} from "../../config";
|
||||
import { ActorType } from "../../ee/models";
|
||||
|
||||
declare module "jsonwebtoken" {
|
||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||
@ -44,13 +32,10 @@ declare module "jsonwebtoken" {
|
||||
* @returns
|
||||
*/
|
||||
export const login1 = async (req: Request, res: Response) => {
|
||||
const {
|
||||
email,
|
||||
clientPublicKey,
|
||||
}: { email: string; clientPublicKey: string } = req.body;
|
||||
const { email, clientPublicKey }: { email: string; clientPublicKey: string } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
email,
|
||||
email
|
||||
}).select("+salt +verifier");
|
||||
|
||||
if (!user) throw new Error("Failed to find user");
|
||||
@ -59,21 +44,25 @@ export const login1 = async (req: Request, res: Response) => {
|
||||
server.init(
|
||||
{
|
||||
salt: user.salt,
|
||||
verifier: user.verifier,
|
||||
verifier: user.verifier
|
||||
},
|
||||
async () => {
|
||||
// generate server-side public key
|
||||
const serverPublicKey = server.getPublicKey();
|
||||
|
||||
await LoginSRPDetail.findOneAndReplace({ email: email }, {
|
||||
email: email,
|
||||
clientPublicKey: clientPublicKey,
|
||||
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
||||
}, { upsert: true, returnNewDocument: false })
|
||||
await LoginSRPDetail.findOneAndReplace(
|
||||
{ email: email },
|
||||
{
|
||||
email: email,
|
||||
clientPublicKey: clientPublicKey,
|
||||
serverBInt: bigintConversion.bigintToBuf(server.bInt)
|
||||
},
|
||||
{ upsert: true, returnNewDocument: false }
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
serverPublicKey,
|
||||
salt: user.salt,
|
||||
salt: user.salt
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -89,15 +78,19 @@ export const login1 = async (req: Request, res: Response) => {
|
||||
export const login2 = async (req: Request, res: Response) => {
|
||||
const { email, clientProof } = req.body;
|
||||
const user = await User.findOne({
|
||||
email,
|
||||
email
|
||||
}).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag");
|
||||
|
||||
if (!user) throw new Error("Failed to find user");
|
||||
|
||||
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email })
|
||||
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email });
|
||||
|
||||
if (!loginSRPDetailFromDB) {
|
||||
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
|
||||
return BadRequestError(
|
||||
Error(
|
||||
"It looks like some details from the first login are not found. Please try login one again"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const server = new jsrp.server();
|
||||
@ -105,7 +98,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
{
|
||||
salt: user.salt,
|
||||
verifier: user.verifier,
|
||||
b: loginSRPDetailFromDB.serverBInt,
|
||||
b: loginSRPDetailFromDB.serverBInt
|
||||
},
|
||||
async () => {
|
||||
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
|
||||
@ -117,13 +110,13 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
await checkUserDevice({
|
||||
user,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
userAgent: req.headers["user-agent"] ?? ""
|
||||
});
|
||||
|
||||
const tokens = await issueAuthTokens({
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
userAgent: req.headers["user-agent"] ?? ""
|
||||
});
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
@ -131,20 +124,21 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: await getHttpsEnabled(),
|
||||
secure: await getHttpsEnabled()
|
||||
});
|
||||
|
||||
const loginAction = await EELogService.createAction({
|
||||
name: ACTION_LOGIN,
|
||||
userId: user._id,
|
||||
userId: user._id
|
||||
});
|
||||
|
||||
loginAction && await EELogService.createLog({
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getChannelFromUserAgent(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP,
|
||||
});
|
||||
loginAction &&
|
||||
(await EELogService.createLog({
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
// return (access) token in response
|
||||
return res.status(200).send({
|
||||
@ -152,12 +146,12 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
publicKey: user.publicKey,
|
||||
encryptedPrivateKey: user.encryptedPrivateKey,
|
||||
iv: user.iv,
|
||||
tag: user.tag,
|
||||
tag: user.tag
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(400).send({
|
||||
message: "Failed to authenticate. Try again?",
|
||||
message: "Failed to authenticate. Try again?"
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -170,8 +164,8 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const logout = async (req: Request, res: Response) => {
|
||||
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User && req.authData.tokenVersionId) {
|
||||
await clearTokens(req.authData.tokenVersionId)
|
||||
if (req.authData.actor.type === ActorType.USER && req.authData.tokenVersionId) {
|
||||
await clearTokens(req.authData.tokenVersionId);
|
||||
}
|
||||
|
||||
// clear httpOnly cookie
|
||||
@ -179,49 +173,44 @@ export const logout = async (req: Request, res: Response) => {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: (await getHttpsEnabled()) as boolean,
|
||||
secure: (await getHttpsEnabled()) as boolean
|
||||
});
|
||||
|
||||
const logoutAction = await EELogService.createAction({
|
||||
name: ACTION_LOGOUT,
|
||||
userId: req.user._id,
|
||||
userId: req.user._id
|
||||
});
|
||||
|
||||
logoutAction && await EELogService.createLog({
|
||||
userId: req.user._id,
|
||||
actions: [logoutAction],
|
||||
channel: getChannelFromUserAgent(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP,
|
||||
});
|
||||
logoutAction &&
|
||||
(await EELogService.createLog({
|
||||
userId: req.user._id,
|
||||
actions: [logoutAction],
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully logged out.",
|
||||
message: "Successfully logged out."
|
||||
});
|
||||
};
|
||||
|
||||
export const getCommonPasswords = async (req: Request, res: Response) => {
|
||||
const commonPasswords = fs.readFileSync(
|
||||
path.resolve(__dirname, "../../data/" + "common_passwords.txt"),
|
||||
"utf8"
|
||||
).split("\n");
|
||||
|
||||
return res.status(200).send(commonPasswords);
|
||||
}
|
||||
|
||||
export const revokeAllSessions = async (req: Request, res: Response) => {
|
||||
await TokenVersion.updateMany({
|
||||
user: req.user._id,
|
||||
}, {
|
||||
$inc: {
|
||||
refreshVersion: 1,
|
||||
accessVersion: 1,
|
||||
await TokenVersion.updateMany(
|
||||
{
|
||||
user: req.user._id
|
||||
},
|
||||
});
|
||||
{
|
||||
$inc: {
|
||||
refreshVersion: 1,
|
||||
accessVersion: 1
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully revoked all sessions.",
|
||||
});
|
||||
}
|
||||
message: "Successfully revoked all sessions."
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return user is authenticated
|
||||
@ -231,9 +220,9 @@ export const revokeAllSessions = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const checkAuth = async (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
message: "Authenticated",
|
||||
message: "Authenticated"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return new JWT access token by first validating the refresh token
|
||||
@ -244,47 +233,47 @@ export const checkAuth = async (req: Request, res: Response) => {
|
||||
export const getNewToken = async (req: Request, res: Response) => {
|
||||
const refreshToken = req.cookies.jid;
|
||||
|
||||
if (!refreshToken) throw BadRequestError({
|
||||
message: "Failed to find refresh token in request cookies"
|
||||
});
|
||||
if (!refreshToken)
|
||||
throw BadRequestError({
|
||||
message: "Failed to find refresh token in request cookies"
|
||||
});
|
||||
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(refreshToken, await getJwtRefreshSecret())
|
||||
);
|
||||
const decodedToken = <jwt.UserIDJwtPayload>jwt.verify(refreshToken, await getJwtRefreshSecret());
|
||||
|
||||
const user = await User.findOne({
|
||||
_id: decodedToken.userId,
|
||||
_id: decodedToken.userId
|
||||
}).select("+publicKey +refreshVersion +accessVersion");
|
||||
|
||||
if (!user) throw new Error("Failed to authenticate unfound user");
|
||||
if (!user?.publicKey)
|
||||
throw new Error("Failed to authenticate not fully set up account");
|
||||
|
||||
if (!user?.publicKey) throw new Error("Failed to authenticate not fully set up account");
|
||||
|
||||
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
|
||||
|
||||
if (!tokenVersion) throw UnauthorizedRequestError({
|
||||
message: "Failed to validate refresh token",
|
||||
});
|
||||
if (!tokenVersion)
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed to validate refresh token"
|
||||
});
|
||||
|
||||
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({
|
||||
message: "Failed to validate refresh token",
|
||||
});
|
||||
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion)
|
||||
throw BadRequestError({
|
||||
message: "Failed to validate refresh token"
|
||||
});
|
||||
|
||||
const token = createToken({
|
||||
payload: {
|
||||
userId: decodedToken.userId,
|
||||
tokenVersionId: tokenVersion._id.toString(),
|
||||
accessVersion: tokenVersion.refreshVersion,
|
||||
accessVersion: tokenVersion.refreshVersion
|
||||
},
|
||||
expiresIn: await getJwtAuthLifetime(),
|
||||
secret: await getJwtAuthSecret(),
|
||||
secret: await getJwtAuthSecret()
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
token,
|
||||
token
|
||||
});
|
||||
};
|
||||
|
||||
export const handleAuthProviderCallback = (req: Request, res: Response) => {
|
||||
res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`);
|
||||
}
|
||||
};
|
||||
|
@ -3,17 +3,21 @@ import { Types } from "mongoose";
|
||||
import { standardRequest } from "../../config/request";
|
||||
import { getApps, getTeams, revokeAccess } from "../../integrations";
|
||||
import { Bot, IntegrationAuth } from "../../models";
|
||||
import { EventType } from "../../ee/models";
|
||||
import { IntegrationService } from "../../services";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_BITBUCKET_API_URL,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_NORTHFLANK_API_URL,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
getIntegrationOptions as getIntegrationOptionsFunc
|
||||
} from "../../variables";
|
||||
import { exchangeRefresh } from "../../integrations";
|
||||
|
||||
/***
|
||||
* Return integration authorization with id [integrationAuthId]
|
||||
@ -47,7 +51,12 @@ export const getIntegrationOptions = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const oAuthExchange = async (req: Request, res: Response) => {
|
||||
const { workspaceId, code, integration } = req.body;
|
||||
const {
|
||||
workspaceId,
|
||||
code,
|
||||
integration,
|
||||
url
|
||||
} = req.body;
|
||||
if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration");
|
||||
|
||||
const environments = req.membership.workspace?.environments || [];
|
||||
@ -59,9 +68,23 @@ export const oAuthExchange = async (req: Request, res: Response) => {
|
||||
workspaceId,
|
||||
integration,
|
||||
code,
|
||||
environment: environments[0].slug
|
||||
environment: environments[0].slug,
|
||||
url
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.AUTHORIZE_INTEGRATION,
|
||||
metadata: {
|
||||
integration: integrationAuth.integration
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: integrationAuth.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
integrationAuth
|
||||
});
|
||||
@ -73,7 +96,7 @@ export const oAuthExchange = async (req: Request, res: Response) => {
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const saveIntegrationAccessToken = async (req: Request, res: Response) => {
|
||||
export const saveIntegrationToken = async (req: Request, res: Response) => {
|
||||
// TODO: refactor
|
||||
// TODO: check if access token is valid for each integration
|
||||
|
||||
@ -81,14 +104,16 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) =>
|
||||
const {
|
||||
workspaceId,
|
||||
accessId,
|
||||
refreshToken,
|
||||
accessToken,
|
||||
url,
|
||||
namespace,
|
||||
integration
|
||||
}: {
|
||||
workspaceId: string;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
accessId: string | undefined;
|
||||
refreshToken: string | undefined;
|
||||
accessToken: string | undefined;
|
||||
url: string;
|
||||
namespace: string;
|
||||
integration: string;
|
||||
@ -112,23 +137,51 @@ export const saveIntegrationAccessToken = async (req: Request, res: Response) =>
|
||||
url,
|
||||
namespace,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
...(integration === INTEGRATION_GCP_SECRET_MANAGER ? {
|
||||
metadata: {
|
||||
authMethod: "serviceAccount"
|
||||
}
|
||||
} : {})
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
|
||||
// encrypt and save integration access details
|
||||
if (refreshToken) {
|
||||
await exchangeRefresh({
|
||||
integrationAuth,
|
||||
refreshToken
|
||||
});
|
||||
}
|
||||
|
||||
// encrypt and save integration access details
|
||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt: undefined
|
||||
});
|
||||
if (accessId || accessToken) {
|
||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId,
|
||||
accessToken,
|
||||
accessExpiresAt: undefined
|
||||
});
|
||||
}
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to save integration access token");
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.AUTHORIZE_INTEGRATION,
|
||||
metadata: {
|
||||
integration: integrationAuth.integration
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: integrationAuth.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
integrationAuth
|
||||
@ -519,6 +572,57 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of build configs for TeamCity project with id [appId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res: Response) => {
|
||||
const appId = req.query.appId as string;
|
||||
|
||||
interface TeamCityBuildConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
projectName: string;
|
||||
projectId: string;
|
||||
href: string;
|
||||
webUrl: string;
|
||||
}
|
||||
|
||||
interface GetTeamCityBuildConfigsRes {
|
||||
count: number;
|
||||
href: string;
|
||||
buildType: TeamCityBuildConfig[];
|
||||
}
|
||||
|
||||
|
||||
if (appId && appId !== "") {
|
||||
const { data: { buildType } } = (
|
||||
await standardRequest.get<GetTeamCityBuildConfigsRes>(`${req.integrationAuth.url}/app/rest/buildTypes`, {
|
||||
params: {
|
||||
locator: `project:${appId}`
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${req.accessToken}`,
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
buildConfigs: buildType.map((buildConfig) => ({
|
||||
name: buildConfig.name,
|
||||
buildConfigId: buildConfig.id
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
buildConfigs: []
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete integration authorization with id [integrationAuthId]
|
||||
* @param req
|
||||
@ -530,6 +634,23 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
||||
integrationAuth: req.integrationAuth,
|
||||
accessToken: req.accessToken
|
||||
});
|
||||
|
||||
if (!integrationAuth) return res.status(400).send({
|
||||
message: "Failed to find integration authorization"
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UNAUTHORIZE_INTEGRATION,
|
||||
metadata: {
|
||||
integration: integrationAuth.integration
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: integrationAuth.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
integrationAuth
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { Integration } from "../../models";
|
||||
import { Folder, Integration } from "../../models";
|
||||
import { EventService } from "../../services";
|
||||
import { eventStartIntegration } from "../../events";
|
||||
import Folder from "../../models/folder";
|
||||
import { getFolderByPath } from "../../services/FolderService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
import { EventType } from "../../ee/models";
|
||||
import { syncSecretsToActiveIntegrationsQueue } from "../../queues/integrations/syncSecretsToThirdPartyServices";
|
||||
|
||||
/**
|
||||
* Create/initialize an (empty) integration for integration authorization
|
||||
@ -27,7 +29,8 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
owner,
|
||||
path,
|
||||
region,
|
||||
secretPath
|
||||
secretPath,
|
||||
metadata
|
||||
} = req.body;
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
@ -62,7 +65,8 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
region,
|
||||
secretPath,
|
||||
integration: req.integrationAuth.integration,
|
||||
integrationAuth: new Types.ObjectId(integrationAuthId)
|
||||
integrationAuth: new Types.ObjectId(integrationAuthId),
|
||||
metadata
|
||||
}).save();
|
||||
|
||||
if (integration) {
|
||||
@ -75,6 +79,31 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
});
|
||||
}
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_INTEGRATION,
|
||||
metadata: {
|
||||
integrationId: integration._id.toString(),
|
||||
integration: integration.integration,
|
||||
environment: integration.environment,
|
||||
secretPath,
|
||||
url: integration.url,
|
||||
app: integration.app,
|
||||
appId: integration.appId,
|
||||
targetEnvironment: integration.targetEnvironment,
|
||||
targetEnvironmentId: integration.targetEnvironmentId,
|
||||
targetService: integration.targetService,
|
||||
targetServiceId: integration.targetServiceId,
|
||||
path: integration.path,
|
||||
region: integration.region
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: integration.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
integration
|
||||
});
|
||||
@ -148,8 +177,7 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete integration with id [integrationId] and deactivate bot if there are
|
||||
* no integrations left
|
||||
* Delete integration with id [integrationId]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
@ -163,7 +191,44 @@ export const deleteIntegration = async (req: Request, res: Response) => {
|
||||
|
||||
if (!integration) throw new Error("Failed to find integration");
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.DELETE_INTEGRATION,
|
||||
metadata: {
|
||||
integrationId: integration._id.toString(),
|
||||
integration: integration.integration,
|
||||
environment: integration.environment,
|
||||
secretPath: integration.secretPath,
|
||||
url: integration.url,
|
||||
app: integration.app,
|
||||
appId: integration.appId,
|
||||
targetEnvironment: integration.targetEnvironment,
|
||||
targetEnvironmentId: integration.targetEnvironmentId,
|
||||
targetService: integration.targetService,
|
||||
targetServiceId: integration.targetServiceId,
|
||||
path: integration.path,
|
||||
region: integration.region
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: integration.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
integration
|
||||
});
|
||||
};
|
||||
|
||||
// Will trigger sync for all integrations within the given env and workspace id
|
||||
export const manualSync = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment } = req.body;
|
||||
syncSecretsToActiveIntegrationsQueue({
|
||||
workspaceId,
|
||||
environment
|
||||
})
|
||||
|
||||
res.status(200).send()
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Request, Response } from "express";
|
||||
import { Key } from "../../models";
|
||||
import { findMembership } from "../../helpers/membership";
|
||||
import { EventType } from "../../ee/models";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
|
||||
/**
|
||||
* Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with
|
||||
@ -44,7 +47,7 @@ export const uploadKey = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const getLatestKey = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
|
||||
// get latest key
|
||||
const latestKey = await Key.find({
|
||||
workspace: workspaceId,
|
||||
@ -58,6 +61,18 @@ export const getLatestKey = async (req: Request, res: Response) => {
|
||||
|
||||
if (latestKey.length > 0) {
|
||||
resObj["latestKey"] = latestKey[0];
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.GET_WORKSPACE_KEY,
|
||||
metadata: {
|
||||
keyId: latestKey[0]._id.toString()
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return res.status(200).send(resObj);
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Key, Membership, MembershipOrg, User } from "../../models";
|
||||
import { Types } from "mongoose";
|
||||
import { IUser, Key, Membership, MembershipOrg, User } from "../../models";
|
||||
import { EventType } from "../../ee/models";
|
||||
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
|
||||
import { sendMail } from "../../helpers/nodemailer";
|
||||
import { ACCEPTED, ADMIN, MEMBER } from "../../variables";
|
||||
import { getSiteURL } from "../../config";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
|
||||
/**
|
||||
* Check that user is a member of workspace with id [workspaceId]
|
||||
@ -36,11 +39,11 @@ export const validateMembership = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const deleteMembership = async (req: Request, res: Response) => {
|
||||
const { membershipId } = req.params;
|
||||
|
||||
|
||||
// check if membership to delete exists
|
||||
const membershipToDelete = await Membership.findOne({
|
||||
_id: membershipId
|
||||
}).populate("user");
|
||||
}).populate<{ user: IUser }>("user");
|
||||
|
||||
if (!membershipToDelete) {
|
||||
throw new Error("Failed to delete workspace membership that doesn't exist");
|
||||
@ -66,6 +69,20 @@ export const deleteMembership = async (req: Request, res: Response) => {
|
||||
const deletedMembership = await deleteMember({
|
||||
membershipId: membershipToDelete._id.toString()
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.REMOVE_WORKSPACE_MEMBER,
|
||||
metadata: {
|
||||
userId: membershipToDelete.user._id.toString(),
|
||||
email: membershipToDelete.user.email
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: membership.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
deletedMembership
|
||||
@ -87,9 +104,9 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// validate target membership
|
||||
const membershipToChangeRole = await findMembership({
|
||||
_id: membershipId
|
||||
});
|
||||
const membershipToChangeRole = await Membership
|
||||
.findById(membershipId)
|
||||
.populate<{ user: IUser }>("user");
|
||||
|
||||
if (!membershipToChangeRole) {
|
||||
throw new Error("Failed to find membership to change role");
|
||||
@ -110,9 +127,27 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
|
||||
// user is not an admin member of the workspace
|
||||
throw new Error("Insufficient role for changing member roles");
|
||||
}
|
||||
|
||||
const oldRole = membershipToChangeRole.role;
|
||||
|
||||
membershipToChangeRole.role = role;
|
||||
await membershipToChangeRole.save();
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UPDATE_USER_WORKSPACE_ROLE,
|
||||
metadata: {
|
||||
userId: membershipToChangeRole.user._id.toString(),
|
||||
email: membershipToChangeRole.user.email,
|
||||
oldRole,
|
||||
newRole: membershipToChangeRole.role
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: membershipToChangeRole.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
membership: membershipToChangeRole
|
||||
@ -140,7 +175,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
|
||||
const inviteeMembership = await Membership.findOne({
|
||||
user: invitee._id,
|
||||
workspace: workspaceId
|
||||
});
|
||||
}).populate<{ user: IUser }>("user");
|
||||
|
||||
if (inviteeMembership) throw new Error("Failed to add existing member of workspace");
|
||||
|
||||
@ -181,6 +216,20 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.ADD_WORKSPACE_MEMBER,
|
||||
metadata: {
|
||||
userId: invitee._id.toString(),
|
||||
email: invitee.email
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
invitee,
|
||||
latestKey
|
||||
|
@ -103,14 +103,14 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
// validate membership
|
||||
const membershipOrg = await MembershipOrg.findOne({
|
||||
user: req.user._id,
|
||||
organization: organizationId
|
||||
organization: new Types.ObjectId(organizationId)
|
||||
});
|
||||
|
||||
if (!membershipOrg) {
|
||||
throw new Error("Failed to validate organization membership");
|
||||
}
|
||||
|
||||
const plan = await EELicenseService.getPlan(organizationId);
|
||||
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
|
||||
|
||||
const ssoConfig = await SSOConfig.findOne({
|
||||
organization: new Types.ObjectId(organizationId)
|
||||
|
@ -260,22 +260,6 @@ export const createOrganizationPortalSession = async (
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return organization subscriptions
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const getOrganizationSubscriptions = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
) => {
|
||||
return res.status(200).send({
|
||||
subscriptions: []
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Given a org id, return the projects each member of the org belongs to
|
||||
* @param req
|
||||
|
@ -5,7 +5,7 @@ import * as bigintConversion from "bigint-conversion";
|
||||
import { BackupPrivateKey, LoginSRPDetail, User } from "../../models";
|
||||
import { clearTokens, createToken, sendMail } from "../../helpers";
|
||||
import { TokenService } from "../../services";
|
||||
import { AUTH_MODE_JWT, TOKEN_EMAIL_PASSWORD_RESET } from "../../variables";
|
||||
import { TOKEN_EMAIL_PASSWORD_RESET } from "../../variables";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import {
|
||||
getHttpsEnabled,
|
||||
@ -13,6 +13,7 @@ import {
|
||||
getJwtSignupSecret,
|
||||
getSiteURL
|
||||
} from "../../config";
|
||||
import { ActorType } from "../../ee/models";
|
||||
|
||||
/**
|
||||
* Password reset step 1: Send email verification link to email [email]
|
||||
@ -208,8 +209,7 @@ export const changePassword = async (req: Request, res: Response) => {
|
||||
);
|
||||
|
||||
if (
|
||||
req.authData.authMode === AUTH_MODE_JWT &&
|
||||
req.authData.authPayload instanceof User &&
|
||||
req.authData.actor.type === ActorType.USER &&
|
||||
req.authData.tokenVersionId
|
||||
) {
|
||||
await clearTokens(req.authData.tokenVersionId);
|
||||
|
@ -1,17 +1,47 @@
|
||||
import { Request, Response } from "express";
|
||||
import { validateMembership } from "../../helpers";
|
||||
import SecretImport from "../../models/secretImports";
|
||||
import { isValidScope, validateMembership } from "../../helpers";
|
||||
import { Folder, SecretImport, ServiceTokenData } from "../../models";
|
||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { getFolderWithPathFromId } from "../../services/FolderService";
|
||||
import { BadRequestError, ResourceNotFoundError,UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { ADMIN, MEMBER } from "../../variables";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
import { EventType } from "../../ee/models";
|
||||
|
||||
export const createSecretImport = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment, folderId, secretImport } = req.body;
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
workspace: workspaceId,
|
||||
environment
|
||||
}).lean();
|
||||
|
||||
if (!folders && folderId !== "root") {
|
||||
throw ResourceNotFoundError({
|
||||
message: "Failed to find folder"
|
||||
});
|
||||
}
|
||||
|
||||
let secretPath = "/";
|
||||
if (folders) {
|
||||
const { folderPath } = getFolderWithPathFromId(folders.nodes, folderId);
|
||||
secretPath = folderPath;
|
||||
}
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
// root check
|
||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
const importSecDoc = await SecretImport.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
folderId
|
||||
});
|
||||
|
||||
const importToSecretPath = folders?getFolderWithPathFromId(folders.nodes, folderId).folderPath:"/";
|
||||
|
||||
if (!importSecDoc) {
|
||||
const doc = new SecretImport({
|
||||
@ -20,7 +50,25 @@ export const createSecretImport = async (req: Request, res: Response) => {
|
||||
folderId,
|
||||
imports: [{ environment: secretImport.environment, secretPath: secretImport.secretPath }]
|
||||
});
|
||||
|
||||
await doc.save();
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
secretImportId: doc._id.toString(),
|
||||
folderId: doc.folderId.toString(),
|
||||
importFromEnvironment: secretImport.environment,
|
||||
importFromSecretPath: secretImport.secretPath,
|
||||
importToEnvironment: environment,
|
||||
importToSecretPath
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: doc.workspace
|
||||
}
|
||||
);
|
||||
return res.status(200).json({ message: "successfully created secret import" });
|
||||
}
|
||||
|
||||
@ -30,11 +78,30 @@ export const createSecretImport = async (req: Request, res: Response) => {
|
||||
if (doesImportExist) {
|
||||
throw BadRequestError({ message: "Secret import already exist" });
|
||||
}
|
||||
|
||||
importSecDoc.imports.push({
|
||||
environment: secretImport.environment,
|
||||
secretPath: secretImport.secretPath
|
||||
});
|
||||
await importSecDoc.save();
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
secretImportId: importSecDoc._id.toString(),
|
||||
folderId: importSecDoc.folderId.toString(),
|
||||
importFromEnvironment: secretImport.environment,
|
||||
importFromSecretPath: secretImport.secretPath,
|
||||
importToEnvironment: environment,
|
||||
importToSecretPath
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: importSecDoc.workspace
|
||||
}
|
||||
);
|
||||
return res.status(200).json({ message: "successfully created secret import" });
|
||||
};
|
||||
|
||||
@ -48,14 +115,68 @@ export const updateSecretImport = async (req: Request, res: Response) => {
|
||||
throw BadRequestError({ message: "Import not found" });
|
||||
}
|
||||
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: importSecDoc.workspace,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: importSecDoc.workspace,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
} else {
|
||||
// check for service token validity
|
||||
const folders = await Folder.findOne({
|
||||
workspace: importSecDoc.workspace,
|
||||
environment: importSecDoc.environment
|
||||
}).lean();
|
||||
|
||||
let secretPath = "/";
|
||||
if (folders) {
|
||||
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
|
||||
secretPath = folderPath;
|
||||
}
|
||||
|
||||
const isValidScopeAccess = isValidScope(
|
||||
req.authData.authPayload,
|
||||
importSecDoc.environment,
|
||||
secretPath
|
||||
);
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
const orderBefore = importSecDoc.imports;
|
||||
importSecDoc.imports = secretImports;
|
||||
|
||||
await importSecDoc.save();
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
workspace: importSecDoc.workspace,
|
||||
environment: importSecDoc.environment,
|
||||
}).lean();
|
||||
|
||||
if (!folders) throw ResourceNotFoundError({
|
||||
message: "Failed to find folder"
|
||||
});
|
||||
|
||||
const importToSecretPath = folders?getFolderWithPathFromId(folders.nodes, importSecDoc.folderId).folderPath:"/";
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UPDATE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
importToEnvironment: importSecDoc.environment,
|
||||
importToSecretPath,
|
||||
secretImportId: importSecDoc._id.toString(),
|
||||
folderId: importSecDoc.folderId.toString(),
|
||||
orderBefore,
|
||||
orderAfter: secretImports
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: importSecDoc.workspace
|
||||
}
|
||||
);
|
||||
return res.status(200).json({ message: "successfully updated secret import" });
|
||||
};
|
||||
|
||||
@ -67,16 +188,69 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
|
||||
throw BadRequestError({ message: "Import not found" });
|
||||
}
|
||||
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: importSecDoc.workspace,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId: importSecDoc.workspace,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
} else {
|
||||
// check for service token validity
|
||||
const folders = await Folder.findOne({
|
||||
workspace: importSecDoc.workspace,
|
||||
environment: importSecDoc.environment
|
||||
}).lean();
|
||||
|
||||
let secretPath = "/";
|
||||
if (folders) {
|
||||
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
|
||||
secretPath = folderPath;
|
||||
}
|
||||
|
||||
const isValidScopeAccess = isValidScope(
|
||||
req.authData.authPayload,
|
||||
importSecDoc.environment,
|
||||
secretPath
|
||||
);
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
importSecDoc.imports = importSecDoc.imports.filter(
|
||||
({ environment, secretPath }) =>
|
||||
!(environment === secretImportEnv && secretPath === secretImportPath)
|
||||
);
|
||||
await importSecDoc.save();
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
workspace: importSecDoc.workspace,
|
||||
environment: importSecDoc.environment,
|
||||
}).lean();
|
||||
|
||||
if (!folders) throw ResourceNotFoundError({
|
||||
message: "Failed to find folder"
|
||||
});
|
||||
|
||||
const importToSecretPath = folders?getFolderWithPathFromId(folders.nodes, importSecDoc.folderId).folderPath:"/";
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.DELETE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
secretImportId: importSecDoc._id.toString(),
|
||||
folderId: importSecDoc.folderId.toString(),
|
||||
importFromEnvironment: secretImportEnv,
|
||||
importFromSecretPath: secretImportPath,
|
||||
importToEnvironment: importSecDoc.environment,
|
||||
importToSecretPath
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: importSecDoc.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).json({ message: "successfully delete secret import" });
|
||||
};
|
||||
|
||||
@ -92,6 +266,29 @@ export const getSecretImports = async (req: Request, res: Response) => {
|
||||
return res.status(200).json({ secretImport: {} });
|
||||
}
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
// check for service token validity
|
||||
const folders = await Folder.findOne({
|
||||
workspace: importSecDoc.workspace,
|
||||
environment: importSecDoc.environment
|
||||
}).lean();
|
||||
|
||||
let secretPath = "/";
|
||||
if (folders) {
|
||||
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
|
||||
secretPath = folderPath;
|
||||
}
|
||||
|
||||
const isValidScopeAccess = isValidScope(
|
||||
req.authData.authPayload,
|
||||
importSecDoc.environment,
|
||||
secretPath
|
||||
);
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json({ secretImport: importSecDoc });
|
||||
};
|
||||
|
||||
@ -111,6 +308,45 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
||||
return res.status(200).json({ secrets: [] });
|
||||
}
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
// check for service token validity
|
||||
const folders = await Folder.findOne({
|
||||
workspace: importSecDoc.workspace,
|
||||
environment: importSecDoc.environment
|
||||
}).lean();
|
||||
|
||||
let secretPath = "/";
|
||||
if (folders) {
|
||||
const { folderPath } = getFolderWithPathFromId(folders.nodes, importSecDoc.folderId);
|
||||
secretPath = folderPath;
|
||||
}
|
||||
|
||||
const isValidScopeAccess = isValidScope(
|
||||
req.authData.authPayload,
|
||||
importSecDoc.environment,
|
||||
secretPath
|
||||
);
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.GET_SECRET_IMPORTS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretImportId: importSecDoc._id.toString(),
|
||||
folderId,
|
||||
numberOfImports: importSecDoc.imports.length
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: importSecDoc.workspace
|
||||
}
|
||||
);
|
||||
|
||||
const secrets = await getAllImportedSecrets(workspaceId, environment, folderId);
|
||||
return res.status(200).json({ secrets });
|
||||
};
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { Request, Response } from "express";
|
||||
import GitAppInstallationSession from "../../models/gitAppInstallationSession";
|
||||
import GitAppInstallationSession from "../../ee/models/gitAppInstallationSession";
|
||||
import crypto from "crypto";
|
||||
import { Types } from "mongoose";
|
||||
import { UnauthorizedRequestError } from "../../utils/errors";
|
||||
import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation";
|
||||
import GitAppOrganizationInstallation from "../../ee/models/gitAppOrganizationInstallation";
|
||||
import { MembershipOrg } from "../../models";
|
||||
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../models/gitRisks";
|
||||
import { scanGithubFullRepoForSecretLeaks } from "../../queues/secret-scanning/githubScanFullRepository"
|
||||
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
|
||||
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../ee/models/gitRisks";
|
||||
import { ProbotOctokit } from "probot";
|
||||
|
||||
export const createInstallationSession = async (req: Request, res: Response) => {
|
||||
const sessionId = crypto.randomBytes(16).toString("hex");
|
||||
@ -47,6 +50,18 @@ export const linkInstallationToOrganization = async (req: Request, res: Response
|
||||
upsert: true
|
||||
}).lean()
|
||||
|
||||
const octokit = new ProbotOctokit({
|
||||
auth: {
|
||||
appId: await getSecretScanningGitAppId(),
|
||||
privateKey: await getSecretScanningPrivateKey(),
|
||||
installationId: installationId.toString()
|
||||
},
|
||||
});
|
||||
|
||||
const { data: { repositories }}= await octokit.apps.listReposAccessibleToInstallation()
|
||||
for (const repository of repositories) {
|
||||
scanGithubFullRepoForSecretLeaks({organizationId: installationSession.organization.toString(), installationId, repository: {id: repository.id, fullName: repository.full_name}})
|
||||
}
|
||||
res.json(installationLink)
|
||||
}
|
||||
|
||||
|
@ -1,39 +1,48 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Secret } from "../../models";
|
||||
import Folder from "../../models/folder";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { Types } from "mongoose";
|
||||
import { EventType, FolderVersion } from "../../ee/models";
|
||||
import { EEAuditLogService, EESecretService } from "../../ee/services";
|
||||
import { validateMembership } from "../../helpers/membership";
|
||||
import { isValidScope } from "../../helpers/secrets";
|
||||
import { Folder, Secret, ServiceTokenData } from "../../models";
|
||||
import {
|
||||
appendFolder,
|
||||
deleteFolderById,
|
||||
generateFolderId,
|
||||
getAllFolderIds,
|
||||
getFolderByPath,
|
||||
getFolderWithPathFromId,
|
||||
getParentFromFolderId,
|
||||
searchByFolderId,
|
||||
searchByFolderIdWithDir,
|
||||
validateFolderName,
|
||||
validateFolderName
|
||||
} from "../../services/FolderService";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { ADMIN, MEMBER } from "../../variables";
|
||||
import { validateMembership } from "../../helpers/membership";
|
||||
import { FolderVersion } from "../../ee/models";
|
||||
import { EESecretService } from "../../ee/services";
|
||||
|
||||
// TODO
|
||||
// verify workspace id/environment
|
||||
export const createFolder = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment, folderName, parentFolderId } = req.body;
|
||||
if (!validateFolderName(folderName)) {
|
||||
throw BadRequestError({
|
||||
message: "Folder name cannot contain spaces. Only underscore and dashes",
|
||||
message: "Folder name cannot contain spaces. Only underscore and dashes"
|
||||
});
|
||||
}
|
||||
|
||||
const folders = await Folder.findOne({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
environment
|
||||
}).lean();
|
||||
|
||||
// space has no folders initialized
|
||||
|
||||
if (!folders) {
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
// root check
|
||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, "/");
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
const id = generateFolderId();
|
||||
const folder = new Folder({
|
||||
workspace: workspaceId,
|
||||
@ -42,39 +51,93 @@ export const createFolder = async (req: Request, res: Response) => {
|
||||
id: "root",
|
||||
name: "root",
|
||||
version: 1,
|
||||
children: [{ id, name: folderName, children: [], version: 1 }],
|
||||
},
|
||||
children: [{ id, name: folderName, children: [], version: 1 }]
|
||||
}
|
||||
});
|
||||
await folder.save();
|
||||
const folderVersion = new FolderVersion({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
nodes: folder.nodes,
|
||||
nodes: folder.nodes
|
||||
});
|
||||
await folderVersion.save();
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId,
|
||||
environment,
|
||||
environment
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_FOLDER,
|
||||
metadata: {
|
||||
environment,
|
||||
folderId: id,
|
||||
folderName,
|
||||
folderPath: `root/${folderName}`
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
}
|
||||
);
|
||||
|
||||
return res.json({ folder: { id, name: folderName } });
|
||||
}
|
||||
|
||||
const folder = appendFolder(folders.nodes, { folderName, parentFolderId });
|
||||
|
||||
await Folder.findByIdAndUpdate(folders._id, folders);
|
||||
|
||||
const { folder: parentFolder, folderPath: parentFolderPath } = getFolderWithPathFromId(
|
||||
folders.nodes,
|
||||
parentFolderId || "root"
|
||||
);
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
// root check
|
||||
const isValidScopeAccess = isValidScope(
|
||||
req.authData.authPayload,
|
||||
environment,
|
||||
parentFolderPath
|
||||
);
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
await Folder.findByIdAndUpdate(folders._id, folders);
|
||||
|
||||
const parentFolder = searchByFolderId(folders.nodes, parentFolderId);
|
||||
const folderVersion = new FolderVersion({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
nodes: parentFolder,
|
||||
nodes: parentFolder
|
||||
});
|
||||
await folderVersion.save();
|
||||
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId,
|
||||
environment,
|
||||
folderId: parentFolderId,
|
||||
folderId: parentFolderId
|
||||
});
|
||||
|
||||
const {folderPath} = getFolderWithPathFromId(folders.nodes, folder.id);
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_FOLDER,
|
||||
metadata: {
|
||||
environment,
|
||||
folderId: folder.id,
|
||||
folderName,
|
||||
folderPath
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
}
|
||||
);
|
||||
|
||||
return res.json({ folder });
|
||||
};
|
||||
@ -82,28 +145,46 @@ export const createFolder = async (req: Request, res: Response) => {
|
||||
export const updateFolderById = async (req: Request, res: Response) => {
|
||||
const { folderId } = req.params;
|
||||
const { name, workspaceId, environment } = req.body;
|
||||
if (!validateFolderName(name)) {
|
||||
throw BadRequestError({
|
||||
message: "Folder name cannot contain spaces. Only underscore and dashes"
|
||||
});
|
||||
}
|
||||
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
if (!folders) {
|
||||
throw BadRequestError({ message: "The folder doesn't exist" });
|
||||
}
|
||||
|
||||
// check that user is a member of the workspace
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
});
|
||||
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
|
||||
// check that user is a member of the workspace
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
}
|
||||
|
||||
const parentFolder = getParentFromFolderId(folders.nodes, folderId);
|
||||
if (!parentFolder) {
|
||||
throw BadRequestError({ message: "The folder doesn't exist" });
|
||||
}
|
||||
const folder = parentFolder.children.find(({ id }) => id === folderId);
|
||||
|
||||
if (!folder) {
|
||||
throw BadRequestError({ message: "The folder doesn't exist" });
|
||||
}
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const { folderPath: secretPath } = getFolderWithPathFromId(folders.nodes, parentFolder.id);
|
||||
// root check
|
||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
const oldFolderName = folder.name;
|
||||
parentFolder.version += 1;
|
||||
folder.name = name;
|
||||
|
||||
@ -111,19 +192,38 @@ export const updateFolderById = async (req: Request, res: Response) => {
|
||||
const folderVersion = new FolderVersion({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
nodes: parentFolder,
|
||||
nodes: parentFolder
|
||||
});
|
||||
await folderVersion.save();
|
||||
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId,
|
||||
environment,
|
||||
folderId: parentFolder.id,
|
||||
folderId: parentFolder.id
|
||||
});
|
||||
|
||||
const {folderPath} = getFolderWithPathFromId(folders.nodes, folder.id);
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UPDATE_FOLDER,
|
||||
metadata: {
|
||||
environment,
|
||||
folderId: folder.id,
|
||||
oldFolderName,
|
||||
newFolderName: name,
|
||||
folderPath
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
}
|
||||
);
|
||||
|
||||
return res.json({
|
||||
message: "Successfully updated folder",
|
||||
folder: { name: folder.name, id: folder.id },
|
||||
folder: { name: folder.name, id: folder.id }
|
||||
});
|
||||
};
|
||||
|
||||
@ -136,12 +236,16 @@ export const deleteFolder = async (req: Request, res: Response) => {
|
||||
throw BadRequestError({ message: "The folder doesn't exist" });
|
||||
}
|
||||
|
||||
// check that user is a member of the workspace
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
});
|
||||
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
|
||||
// check that user is a member of the workspace
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
}
|
||||
|
||||
const {folderPath} = getFolderWithPathFromId(folders.nodes, folderId);
|
||||
|
||||
const delOp = deleteFolderById(folders.nodes, folderId);
|
||||
if (!delOp) {
|
||||
@ -149,6 +253,14 @@ export const deleteFolder = async (req: Request, res: Response) => {
|
||||
}
|
||||
const { deletedNode: delFolder, parent: parentFolder } = delOp;
|
||||
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const { folderPath: secretPath } = getFolderWithPathFromId(folders.nodes, parentFolder.id);
|
||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, secretPath);
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
parentFolder.version += 1;
|
||||
const delFolderIds = getAllFolderIds(delFolder);
|
||||
|
||||
@ -156,35 +268,50 @@ export const deleteFolder = async (req: Request, res: Response) => {
|
||||
const folderVersion = new FolderVersion({
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
nodes: parentFolder,
|
||||
nodes: parentFolder
|
||||
});
|
||||
await folderVersion.save();
|
||||
if (delFolderIds.length) {
|
||||
await Secret.deleteMany({
|
||||
folder: { $in: delFolderIds.map(({ id }) => id) },
|
||||
workspace: workspaceId,
|
||||
environment,
|
||||
environment
|
||||
});
|
||||
}
|
||||
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId,
|
||||
environment,
|
||||
folderId: parentFolder.id,
|
||||
folderId: parentFolder.id
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.DELETE_FOLDER ,
|
||||
metadata: {
|
||||
environment,
|
||||
folderId,
|
||||
folderName: delFolder.name,
|
||||
folderPath
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
}
|
||||
);
|
||||
|
||||
res.send({ message: "successfully deleted folders", folders: delFolderIds });
|
||||
};
|
||||
|
||||
// TODO: validate workspace
|
||||
export const getFolders = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment, parentFolderId, parentFolderPath } =
|
||||
req.query as {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
parentFolderId?: string;
|
||||
parentFolderPath?: string;
|
||||
};
|
||||
const { workspaceId, environment, parentFolderId, parentFolderPath } = req.query as {
|
||||
workspaceId: string;
|
||||
environment: string;
|
||||
parentFolderId?: string;
|
||||
parentFolderPath?: string;
|
||||
};
|
||||
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
if (!folders) {
|
||||
@ -192,16 +319,29 @@ export const getFolders = async (req: Request, res: Response) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// check that user is a member of the workspace
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
});
|
||||
if (!(req.authData.authPayload instanceof ServiceTokenData)) {
|
||||
// check that user is a member of the workspace
|
||||
await validateMembership({
|
||||
userId: req.user._id.toString(),
|
||||
workspaceId,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
}
|
||||
|
||||
// if instead of parentFolderId given a path like /folder1/folder2
|
||||
if (parentFolderPath) {
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const isValidScopeAccess = isValidScope(
|
||||
req.authData.authPayload,
|
||||
environment,
|
||||
parentFolderPath
|
||||
);
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
const folder = getFolderByPath(folders.nodes, parentFolderPath);
|
||||
|
||||
if (!folder) {
|
||||
res.send({ folders: [], dir: [] });
|
||||
return;
|
||||
@ -209,27 +349,36 @@ export const getFolders = async (req: Request, res: Response) => {
|
||||
// dir is not needed at present as this is only used in overview section of secrets
|
||||
res.send({
|
||||
folders: folder.children.map(({ id, name }) => ({ id, name })),
|
||||
dir: [{ name: folder.name, id: folder.id }],
|
||||
dir: [{ name: folder.name, id: folder.id }]
|
||||
});
|
||||
}
|
||||
|
||||
if (!parentFolderId) {
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, "/");
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
|
||||
const rootFolders = folders.nodes.children.map(({ id, name }) => ({
|
||||
id,
|
||||
name,
|
||||
name
|
||||
}));
|
||||
res.send({ folders: rootFolders });
|
||||
return;
|
||||
}
|
||||
|
||||
const folderBySearch = searchByFolderIdWithDir(folders.nodes, parentFolderId);
|
||||
if (!folderBySearch) {
|
||||
throw BadRequestError({ message: "The folder doesn't exist" });
|
||||
const { folder, folderPath, dir } = getFolderWithPathFromId(folders.nodes, parentFolderId);
|
||||
if (req.authData.authPayload instanceof ServiceTokenData) {
|
||||
const isValidScopeAccess = isValidScope(req.authData.authPayload, environment, folderPath);
|
||||
if (!isValidScopeAccess) {
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
const { folder, dir } = folderBySearch;
|
||||
|
||||
res.send({
|
||||
folders: folder.children.map(({ id, name }) => ({ id, name })),
|
||||
dir,
|
||||
dir
|
||||
});
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Request, Response } from "express";
|
||||
import { User } from "../../models";
|
||||
import { AuthMethod, User } from "../../models";
|
||||
import { checkEmailVerification, sendEmailVerification } from "../../helpers/signup";
|
||||
import { createToken } from "../../helpers/auth";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
@ -81,7 +81,8 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
|
||||
|
||||
if (!user) {
|
||||
user = await new User({
|
||||
email
|
||||
email,
|
||||
authMethods: [AuthMethod.EMAIL]
|
||||
}).save();
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,11 @@ import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { client, getRootEncryptionKey } from "../../config";
|
||||
import { validateMembership } from "../../helpers";
|
||||
import Webhook from "../../models/webhooks";
|
||||
import { Webhook } from "../../models";
|
||||
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
import { EventType } from "../../ee/models";
|
||||
import { ADMIN, ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, MEMBER } from "../../variables";
|
||||
|
||||
export const createWebhook = async (req: Request, res: Response) => {
|
||||
@ -27,6 +29,23 @@ export const createWebhook = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
await webhook.save();
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_WEBHOOK,
|
||||
metadata: {
|
||||
webhookId: webhook._id.toString(),
|
||||
environment,
|
||||
secretPath,
|
||||
webhookUrl,
|
||||
isDisabled: false
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
webhook,
|
||||
@ -54,6 +73,23 @@ export const updateWebhook = async (req: Request, res: Response) => {
|
||||
}
|
||||
await webhook.save();
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UPDATE_WEBHOOK_STATUS,
|
||||
metadata: {
|
||||
webhookId: webhook._id.toString(),
|
||||
environment: webhook.environment,
|
||||
secretPath: webhook.secretPath,
|
||||
webhookUrl: webhook.url,
|
||||
isDisabled
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: webhook.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
webhook,
|
||||
message: "successfully updated webhook"
|
||||
@ -62,9 +98,10 @@ export const updateWebhook = async (req: Request, res: Response) => {
|
||||
|
||||
export const deleteWebhook = async (req: Request, res: Response) => {
|
||||
const { webhookId } = req.params;
|
||||
const webhook = await Webhook.findById(webhookId);
|
||||
let webhook = await Webhook.findById(webhookId);
|
||||
|
||||
if (!webhook) {
|
||||
throw BadRequestError({ message: "Webhook not found!!" });
|
||||
throw ResourceNotFoundError({ message: "Webhook not found!!" });
|
||||
}
|
||||
|
||||
await validateMembership({
|
||||
@ -72,8 +109,29 @@ export const deleteWebhook = async (req: Request, res: Response) => {
|
||||
workspaceId: webhook.workspace,
|
||||
acceptedRoles: [ADMIN, MEMBER]
|
||||
});
|
||||
|
||||
webhook = await Webhook.findByIdAndDelete(webhookId);
|
||||
|
||||
await webhook.deleteOne();
|
||||
if (!webhook) {
|
||||
throw ResourceNotFoundError({ message: "Webhook not found!!" });
|
||||
}
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.DELETE_WEBHOOK,
|
||||
metadata: {
|
||||
webhookId: webhook._id.toString(),
|
||||
environment: webhook.environment,
|
||||
secretPath: webhook.secretPath,
|
||||
webhookUrl: webhook.url,
|
||||
isDisabled: webhook.isDisabled
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: webhook.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
message: "successfully removed webhook"
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Request, Response } from "express";
|
||||
import {
|
||||
IUser,
|
||||
@ -108,14 +109,14 @@ export const createWorkspace = async (req: Request, res: Response) => {
|
||||
// validate organization membership
|
||||
const membershipOrg = await MembershipOrg.findOne({
|
||||
user: req.user._id,
|
||||
organization: organizationId,
|
||||
organization: new Types.ObjectId(organizationId),
|
||||
});
|
||||
|
||||
if (!membershipOrg) {
|
||||
throw new Error("Failed to validate organization membership");
|
||||
}
|
||||
|
||||
const plan = await EELicenseService.getPlan(organizationId);
|
||||
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
|
||||
|
||||
if (plan.workspaceLimit !== null) {
|
||||
// case: limit imposed on number of workspaces allowed
|
||||
@ -134,7 +135,7 @@ export const createWorkspace = async (req: Request, res: Response) => {
|
||||
// create workspace and add user as member
|
||||
const workspace = await create({
|
||||
name: workspaceName,
|
||||
organizationId,
|
||||
organizationId: new Types.ObjectId(organizationId),
|
||||
});
|
||||
|
||||
await addMemberships({
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
ACTION_LOGIN,
|
||||
TOKEN_EMAIL_MFA,
|
||||
} from "../../variables";
|
||||
import { getChannelFromUserAgent } from "../../utils/posthog"; // TODO: move this
|
||||
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
|
||||
import {
|
||||
getHttpsEnabled,
|
||||
getJwtMfaLifetime,
|
||||
@ -203,7 +203,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
loginAction && await EELogService.createLog({
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getChannelFromUserAgent(req.headers["user-agent"]),
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
ipAddress: req.ip,
|
||||
});
|
||||
|
||||
@ -336,7 +336,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
|
||||
loginAction && await EELogService.createLog({
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getChannelFromUserAgent(req.headers["user-agent"]),
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP,
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
Integration,
|
||||
Membership,
|
||||
@ -7,8 +8,8 @@ import {
|
||||
ServiceTokenData,
|
||||
Workspace,
|
||||
} from "../../models";
|
||||
import { SecretVersion } from "../../ee/models";
|
||||
import { EELicenseService } from "../../ee/services";
|
||||
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";
|
||||
@ -30,7 +31,7 @@ export const createWorkspaceEnvironment = async (
|
||||
|
||||
if (!workspace) throw WorkspaceNotFoundError();
|
||||
|
||||
const plan = await EELicenseService.getPlan(workspace.organization.toString());
|
||||
const plan = await EELicenseService.getPlan(workspace.organization);
|
||||
|
||||
if (plan.environmentLimit !== null) {
|
||||
// case: limit imposed on number of environments allowed
|
||||
@ -58,7 +59,21 @@ export const createWorkspaceEnvironment = async (
|
||||
});
|
||||
await workspace.save();
|
||||
|
||||
await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId);
|
||||
await EELicenseService.refreshPlan(workspace.organization, new Types.ObjectId(workspaceId));
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_ENVIRONMENT,
|
||||
metadata: {
|
||||
name: environmentName,
|
||||
slug: environmentSlug
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: workspace._id
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully created new environment",
|
||||
@ -70,6 +85,43 @@ export const createWorkspaceEnvironment = async (
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Swaps the ordering of two environments in the database. This is purely for aesthetic purposes.
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const reorderWorkspaceEnvironments = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
) => {
|
||||
const { workspaceId } = req.params;
|
||||
const { environmentSlug, environmentName, otherEnvironmentSlug, otherEnvironmentName } = req.body;
|
||||
|
||||
// atomic update the env to avoid conflict
|
||||
const workspace = await Workspace.findById(workspaceId).exec();
|
||||
if (!workspace) {
|
||||
throw BadRequestError({message: "Couldn't load workspace"});
|
||||
}
|
||||
|
||||
const environmentIndex = workspace.environments.findIndex((env) => env.name === environmentName && env.slug === environmentSlug)
|
||||
const otherEnvironmentIndex = workspace.environments.findIndex((env) => env.name === otherEnvironmentName && env.slug === otherEnvironmentSlug)
|
||||
|
||||
if (environmentIndex === -1 || otherEnvironmentIndex === -1) {
|
||||
throw BadRequestError({message: "environment or otherEnvironment couldn't be found"})
|
||||
}
|
||||
|
||||
// swap the order of the environments
|
||||
[workspace.environments[environmentIndex], workspace.environments[otherEnvironmentIndex]] = [workspace.environments[otherEnvironmentIndex], workspace.environments[environmentIndex]]
|
||||
|
||||
await workspace.save()
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully reordered environments",
|
||||
workspace: workspaceId,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Rename workspace environment with new name and slug of a workspace with [workspaceId]
|
||||
* Old slug [oldEnvironmentSlug] must be provided
|
||||
@ -110,6 +162,8 @@ export const renameWorkspaceEnvironment = async (
|
||||
throw new Error("Invalid environment given");
|
||||
}
|
||||
|
||||
const oldEnvironment = workspace.environments[envIndex];
|
||||
|
||||
workspace.environments[envIndex].name = environmentName;
|
||||
workspace.environments[envIndex].slug = environmentSlug.toLowerCase();
|
||||
|
||||
@ -141,8 +195,23 @@ export const renameWorkspaceEnvironment = async (
|
||||
},
|
||||
{ $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } },
|
||||
{ arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] }
|
||||
)
|
||||
);
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UPDATE_ENVIRONMENT,
|
||||
metadata: {
|
||||
oldName: oldEnvironment.name,
|
||||
newName: environmentName,
|
||||
oldSlug: oldEnvironment.slug,
|
||||
newSlug: environmentSlug.toLowerCase()
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: workspace._id
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully update environment",
|
||||
@ -179,6 +248,8 @@ export const deleteWorkspaceEnvironment = async (
|
||||
throw new Error("Invalid environment given");
|
||||
}
|
||||
|
||||
const oldEnvironment = workspace.environments[envIndex];
|
||||
|
||||
workspace.environments.splice(envIndex, 1);
|
||||
await workspace.save();
|
||||
|
||||
@ -215,7 +286,21 @@ export const deleteWorkspaceEnvironment = async (
|
||||
{ $pull: { deniedPermissions: { environmentSlug: environmentSlug } } }
|
||||
);
|
||||
|
||||
await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId);
|
||||
await EELicenseService.refreshPlan(workspace.organization, new Types.ObjectId(workspaceId));
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.DELETE_ENVIRONMENT,
|
||||
metadata: {
|
||||
name: oldEnvironment.name,
|
||||
slug: oldEnvironment.slug
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: workspace._id
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
message: "Successfully deleted environment",
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Request, Response } from "express";
|
||||
import mongoose, { Types } from "mongoose";
|
||||
import Secret, { ISecret } from "../../models/secret";
|
||||
import {
|
||||
CreateSecretRequestBody,
|
||||
ModifySecretRequestBody,
|
||||
@ -20,7 +19,7 @@ import {
|
||||
SECRET_SHARED
|
||||
} from "../../variables";
|
||||
import { TelemetryService } from "../../services";
|
||||
import { User } from "../../models";
|
||||
import { ISecret, Secret, User } from "../../models";
|
||||
import { AccountNotFoundError } from "../../utils/errors";
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Request, Response } from "express";
|
||||
import { ISecret, Secret, ServiceTokenData } from "../../models";
|
||||
import { IAction, SecretVersion } from "../../ee/models";
|
||||
import { Folder, ISecret, Secret, ServiceTokenData, Tag } from "../../models";
|
||||
import { AuditLog, EventType, IAction, SecretVersion } from "../../ee/models";
|
||||
import {
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
@ -9,24 +9,23 @@ import {
|
||||
ACTION_UPDATE_SECRETS,
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
K8_USER_AGENT_NAME,
|
||||
SECRET_PERSONAL
|
||||
} from "../../variables";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { EventService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
import { EELogService, EESecretService } from "../../ee/services";
|
||||
import { EEAuditLogService, EELogService, EESecretService } from "../../ee/services";
|
||||
import { SecretService, TelemetryService } from "../../services";
|
||||
import { getChannelFromUserAgent } from "../../utils/posthog";
|
||||
import { getUserAgentType } from "../../utils/posthog";
|
||||
import { PERMISSION_WRITE_SECRETS } from "../../variables";
|
||||
import {
|
||||
userHasNoAbility,
|
||||
userHasWorkspaceAccess,
|
||||
userHasWriteOnlyAbility
|
||||
} from "../../ee/helpers/checkMembershipPermissions";
|
||||
import Tag from "../../models/tag";
|
||||
import _ from "lodash";
|
||||
import { BatchSecret, BatchSecretRequest } from "../../types/secret";
|
||||
import Folder from "../../models/folder";
|
||||
import {
|
||||
getFolderByPath,
|
||||
getFolderIdFromServiceToken,
|
||||
@ -44,7 +43,7 @@ import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||
* @param res
|
||||
*/
|
||||
export const batchSecrets = async (req: Request, res: Response) => {
|
||||
const channel = getChannelFromUserAgent(req.headers["user-agent"]);
|
||||
const channel = getUserAgentType(req.headers["user-agent"]);
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
const {
|
||||
@ -56,12 +55,13 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
environment: string;
|
||||
requests: BatchSecretRequest[];
|
||||
} = req.body;
|
||||
|
||||
let secretPath = req.body.secretPath as string;
|
||||
let folderId = req.body.folderId as string;
|
||||
|
||||
const createSecrets: BatchSecret[] = [];
|
||||
const updateSecrets: BatchSecret[] = [];
|
||||
const deleteSecrets: Types.ObjectId[] = [];
|
||||
const deleteSecrets: { _id: Types.ObjectId, secretName: string; }[] = [];
|
||||
const actions: IAction[] = [];
|
||||
|
||||
// get secret blind index salt
|
||||
@ -133,7 +133,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
});
|
||||
break;
|
||||
case "DELETE":
|
||||
deleteSecrets.push(new Types.ObjectId(request.secret._id));
|
||||
deleteSecrets.push({ _id: new Types.ObjectId(request.secret._id), secretName: request.secret.secretName });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -154,6 +154,30 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
})
|
||||
});
|
||||
|
||||
const auditLogs = await Promise.all(
|
||||
createdSecrets.map((secret, index) => {
|
||||
return EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_SECRET,
|
||||
metadata: {
|
||||
environment: secret.environment,
|
||||
secretPath: secretPath ?? "/",
|
||||
secretId: secret._id.toString(),
|
||||
secretKey: createSecrets[index].secretName,
|
||||
secretVersion: secret.version
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: secret.workspace
|
||||
},
|
||||
false
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
await AuditLog.insertMany(auditLogs);
|
||||
|
||||
const addAction = (await EELogService.createAction({
|
||||
name: ACTION_ADD_SECRETS,
|
||||
userId: req.user?._id,
|
||||
@ -209,6 +233,9 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
$inc: {
|
||||
version: 1
|
||||
},
|
||||
$unset: {
|
||||
"metadata.source": true as const
|
||||
},
|
||||
...u,
|
||||
_id: new Types.ObjectId(u._id)
|
||||
}
|
||||
@ -253,6 +280,30 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
|
||||
const auditLogs = await Promise.all(
|
||||
updateSecrets.map((secret) => {
|
||||
return EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UPDATE_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath: secretPath ?? "/",
|
||||
secretId: secret._id.toString(),
|
||||
secretKey: secret.secretName,
|
||||
secretVersion: listedSecretsObj[secret._id.toString()].version
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
},
|
||||
false
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
await AuditLog.insertMany(auditLogs);
|
||||
|
||||
const updateAction = (await EELogService.createAction({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
userId: req.user._id,
|
||||
@ -279,21 +330,60 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// handle delete secrets
|
||||
if (deleteSecrets.length > 0) {
|
||||
const deleteSecretIds: Types.ObjectId[] = deleteSecrets.map((s) => s._id);
|
||||
|
||||
const deletedSecretsObj = (await Secret.find({
|
||||
_id: {
|
||||
$in: deleteSecretIds
|
||||
}
|
||||
}))
|
||||
.reduce(
|
||||
(obj: any, secret: ISecret) => ({
|
||||
...obj,
|
||||
[secret._id.toString()]: secret
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
await Secret.deleteMany({
|
||||
_id: {
|
||||
$in: deleteSecrets
|
||||
$in: deleteSecretIds
|
||||
}
|
||||
});
|
||||
|
||||
await EESecretService.markDeletedSecretVersions({
|
||||
secretIds: deleteSecrets
|
||||
secretIds: deleteSecretIds
|
||||
});
|
||||
|
||||
const auditLogs = await Promise.all(
|
||||
deleteSecrets.map((secret) => {
|
||||
return EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.DELETE_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath: secretPath ?? "/",
|
||||
secretId: secret._id.toString(),
|
||||
secretKey: secret.secretName,
|
||||
secretVersion: deletedSecretsObj[secret._id.toString()].version
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
},
|
||||
false
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
await AuditLog.insertMany(auditLogs);
|
||||
|
||||
const deleteAction = (await EELogService.createAction({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
userId: req.user._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: deleteSecrets
|
||||
secretIds: deleteSecretIds
|
||||
})) as IAction;
|
||||
actions.push(deleteAction);
|
||||
|
||||
@ -351,7 +441,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
if (deleteSecrets.length > 0) {
|
||||
resObj["deletedSecrets"] = deleteSecrets.map((d) => d.toString());
|
||||
resObj["deletedSecrets"] = deleteSecrets.map((d) => d._id.toString());
|
||||
}
|
||||
|
||||
return res.status(200).send(resObj);
|
||||
@ -416,7 +506,7 @@ export const createSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
*/
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers["user-agent"]);
|
||||
const channel = getUserAgentType(req.headers["user-agent"]);
|
||||
const {
|
||||
workspaceId,
|
||||
environment,
|
||||
@ -697,10 +787,16 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
const environment = req.query.environment as string;
|
||||
|
||||
const folders = await Folder.findOne({ workspace: workspaceId, environment });
|
||||
if ((!folders && folderId && folderId !== "root") || (!folders && secretPath)) {
|
||||
|
||||
if (
|
||||
// if no folders and asking for a non root folder id or non root secret path
|
||||
(!folders && folderId && folderId !== "root") ||
|
||||
(!folders && secretPath && secretPath !== "/")
|
||||
) {
|
||||
res.send({ secrets: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
if (folders && folderId !== "root") {
|
||||
const folder = searchByFolderId(folders.nodes, folderId as string);
|
||||
if (!folder) {
|
||||
@ -834,7 +930,7 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId as string);
|
||||
}
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers["user-agent"]);
|
||||
const channel = getUserAgentType(req.headers["user-agent"]);
|
||||
|
||||
const readAction = await EELogService.createAction({
|
||||
name: ACTION_READ_SECRETS,
|
||||
@ -856,22 +952,52 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
ipAddress: req.realIP
|
||||
}));
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: "secrets pulled",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.GET_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
workspaceId,
|
||||
channel,
|
||||
folderId,
|
||||
userAgent: req.headers?.["user-agent"]
|
||||
secretPath: (secretPath as string) ?? "/",
|
||||
numberOfSecrets: secrets.length
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId as string)
|
||||
}
|
||||
);
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
// reduce the number of events captured
|
||||
let shouldRecordK8Event = false
|
||||
if (req.authData.userAgent == K8_USER_AGENT_NAME) {
|
||||
const randomNumber = Math.random();
|
||||
if (randomNumber > 0.9) {
|
||||
shouldRecordK8Event = true
|
||||
}
|
||||
}
|
||||
|
||||
if (postHogClient) {
|
||||
const shouldCapture = req.authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event;
|
||||
const approximateForNoneCapturedEvents = secrets.length * 10
|
||||
|
||||
if (shouldCapture) {
|
||||
postHogClient.capture({
|
||||
event: "secrets pulled",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData: req.authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: req.authData.userAgentType,
|
||||
userAgent: req.authData.userAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
@ -978,10 +1104,10 @@ export const updateSecrets = async (req: Request, res: Response) => {
|
||||
tags,
|
||||
...(secretCommentCiphertext !== undefined && secretCommentIV && secretCommentTag
|
||||
? {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
}
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}
|
||||
@ -1170,7 +1296,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
*/
|
||||
|
||||
const channel = getChannelFromUserAgent(req.headers["user-agent"]);
|
||||
const channel = getUserAgentType(req.headers["user-agent"]);
|
||||
const toDelete = req.secrets.map((s: any) => s._id);
|
||||
|
||||
await Secret.deleteMany({
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Request, Response } from "express";
|
||||
import crypto from "crypto";
|
||||
import bcrypt from "bcrypt";
|
||||
import { ServiceAccount, ServiceTokenData, User } from "../../models";
|
||||
import { AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT } from "../../variables";
|
||||
import { ServiceTokenData } from "../../models";
|
||||
import { getSaltRounds } from "../../config";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { ActorType, EventType } from "../../ee/models";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
|
||||
/**
|
||||
* Return service token data associated with service token on request
|
||||
@ -73,24 +74,16 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
|
||||
}
|
||||
|
||||
let user, serviceAccount;
|
||||
|
||||
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
|
||||
let user;
|
||||
|
||||
if (req.authData.actor.type === ActorType.USER) {
|
||||
user = req.authData.authPayload._id;
|
||||
}
|
||||
|
||||
if (
|
||||
req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
|
||||
req.authData.authPayload instanceof ServiceAccount
|
||||
) {
|
||||
serviceAccount = req.authData.authPayload._id;
|
||||
}
|
||||
|
||||
serviceTokenData = await new ServiceTokenData({
|
||||
name,
|
||||
workspace: workspaceId,
|
||||
user,
|
||||
serviceAccount,
|
||||
scopes,
|
||||
lastUsed: new Date(),
|
||||
expiresAt,
|
||||
@ -107,6 +100,20 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
|
||||
if (!serviceTokenData) throw new Error("Failed to find service token data");
|
||||
|
||||
const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_SERVICE_TOKEN,
|
||||
metadata: {
|
||||
name,
|
||||
scopes
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
serviceToken,
|
||||
@ -124,6 +131,24 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
|
||||
const { serviceTokenDataId } = req.params;
|
||||
|
||||
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
|
||||
|
||||
if (!serviceTokenData) return res.status(200).send({
|
||||
message: "Failed to delete service token"
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.DELETE_SERVICE_TOKEN,
|
||||
metadata: {
|
||||
name: serviceTokenData.name,
|
||||
scopes: serviceTokenData?.scopes
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: serviceTokenData.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
serviceTokenData
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { Membership, Secret } from "../../models";
|
||||
import Tag from "../../models/tag";
|
||||
import { Membership, Secret, Tag } from "../../models";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
|
||||
export const createWorkspaceTag = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
const { name, slug } = req.body;
|
||||
const { name, slug, tagColor } = req.body;
|
||||
|
||||
const tagToCreate = {
|
||||
name,
|
||||
tagColor,
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
slug,
|
||||
user: new Types.ObjectId(req.user._id),
|
||||
|
@ -4,7 +4,7 @@ import crypto from "crypto";
|
||||
import bcrypt from "bcrypt";
|
||||
import {
|
||||
APIKeyData,
|
||||
AuthProvider,
|
||||
AuthMethod,
|
||||
MembershipOrg,
|
||||
TokenVersion,
|
||||
User
|
||||
@ -113,21 +113,26 @@ export const updateName = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update auth provider of the current user to [authProvider]
|
||||
* Update auth method of the current user to [authMethods]
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
export const updateAuthProvider = async (req: Request, res: Response) => {
|
||||
export const updateAuthMethods = async (req: Request, res: Response) => {
|
||||
const {
|
||||
authProvider
|
||||
authMethods
|
||||
} = req.body;
|
||||
|
||||
if (
|
||||
req.user?.authProvider === AuthProvider.OKTA_SAML
|
||||
|| req.user?.authProvider === AuthProvider.AZURE_SAML
|
||||
|| req.user?.authProvider === AuthProvider.JUMPCLOUD_SAML
|
||||
) {
|
||||
const hasSamlEnabled = req.user.authMethods
|
||||
.some(
|
||||
(authMethod: AuthMethod) => [
|
||||
AuthMethod.OKTA_SAML,
|
||||
AuthMethod.AZURE_SAML,
|
||||
AuthMethod.JUMPCLOUD_SAML
|
||||
].includes(authMethod)
|
||||
);
|
||||
|
||||
if (hasSamlEnabled) {
|
||||
return res.status(400).send({
|
||||
message: "Failed to update user authentication method because SAML SSO is enforced"
|
||||
});
|
||||
@ -136,7 +141,7 @@ export const updateAuthProvider = async (req: Request, res: Response) => {
|
||||
const user = await User.findByIdAndUpdate(
|
||||
req.user._id.toString(),
|
||||
{
|
||||
authProvider
|
||||
authMethods
|
||||
},
|
||||
{
|
||||
new: true
|
||||
@ -148,6 +153,7 @@ export const updateAuthProvider = async (req: Request, res: Response) => {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return organizations that the current user is part of.
|
||||
* @param req
|
||||
|
@ -9,6 +9,8 @@ import {
|
||||
import { pushKeys } from "../../helpers/key";
|
||||
import { EventService, TelemetryService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
import { EventType } from "../../ee/models";
|
||||
|
||||
interface V2PushSecret {
|
||||
type: string; // personal or shared
|
||||
@ -180,16 +182,30 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
||||
}
|
||||
*/
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
|
||||
const key = await Key.findOne({
|
||||
workspace: workspaceId,
|
||||
receiver: req.user._id
|
||||
}).populate("sender", "+publicKey");
|
||||
|
||||
if (!key) throw new Error("Failed to find workspace key");
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.GET_WORKSPACE_KEY,
|
||||
metadata: {
|
||||
keyId: key._id.toString()
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).json(key);
|
||||
};
|
||||
|
||||
export const getWorkspaceServiceTokenData = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import { Request, Response } from "express";
|
||||
import jwt from "jsonwebtoken";
|
||||
import * as Sentry from "@sentry/node";
|
||||
import * as bigintConversion from "bigint-conversion";
|
||||
const jsrp = require("jsrp");
|
||||
import { LoginSRPDetail, User } from "../../models";
|
||||
@ -15,19 +14,19 @@ import {
|
||||
ACTION_LOGIN,
|
||||
TOKEN_EMAIL_MFA,
|
||||
} from "../../variables";
|
||||
import { getChannelFromUserAgent } from "../../utils/posthog"; // TODO: move this
|
||||
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
|
||||
import {
|
||||
getHttpsEnabled,
|
||||
getJwtMfaLifetime,
|
||||
getJwtMfaSecret,
|
||||
} from "../../config";
|
||||
import { AuthProvider } from "../../models/user";
|
||||
import { AuthMethod } from "../../models/user";
|
||||
|
||||
declare module "jsonwebtoken" {
|
||||
export interface ProviderAuthJwtPayload extends jwt.JwtPayload {
|
||||
userId: string;
|
||||
email: string;
|
||||
authProvider: AuthProvider;
|
||||
authProvider: AuthMethod;
|
||||
isUserCompleted: boolean,
|
||||
}
|
||||
}
|
||||
@ -39,62 +38,53 @@ declare module "jsonwebtoken" {
|
||||
* @returns
|
||||
*/
|
||||
export const login1 = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const {
|
||||
const {
|
||||
email,
|
||||
providerAuthToken,
|
||||
clientPublicKey,
|
||||
}: {
|
||||
email: string;
|
||||
clientPublicKey: string,
|
||||
providerAuthToken?: string;
|
||||
} = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
email,
|
||||
}).select("+salt +verifier");
|
||||
|
||||
if (!user) throw new Error("Failed to find user");
|
||||
|
||||
if (!user.authMethods.includes(AuthMethod.EMAIL)) {
|
||||
await validateProviderAuthToken({
|
||||
email,
|
||||
providerAuthToken,
|
||||
clientPublicKey,
|
||||
}: {
|
||||
email: string;
|
||||
clientPublicKey: string,
|
||||
providerAuthToken?: string;
|
||||
} = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
email,
|
||||
}).select("+salt +verifier");
|
||||
|
||||
if (!user) throw new Error("Failed to find user");
|
||||
|
||||
if (user.authProvider && user.authProvider !== AuthProvider.EMAIL) {
|
||||
await validateProviderAuthToken({
|
||||
email,
|
||||
user,
|
||||
providerAuthToken,
|
||||
})
|
||||
}
|
||||
|
||||
const server = new jsrp.server();
|
||||
server.init(
|
||||
{
|
||||
salt: user.salt,
|
||||
verifier: user.verifier,
|
||||
},
|
||||
async () => {
|
||||
// generate server-side public key
|
||||
const serverPublicKey = server.getPublicKey();
|
||||
await LoginSRPDetail.findOneAndReplace({
|
||||
email: email,
|
||||
}, {
|
||||
email,
|
||||
userId: user.id,
|
||||
clientPublicKey: clientPublicKey,
|
||||
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
||||
}, { upsert: true, returnNewDocument: false });
|
||||
|
||||
return res.status(200).send({
|
||||
serverPublicKey,
|
||||
salt: user.salt,
|
||||
});
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: "Failed to start authentication process",
|
||||
});
|
||||
}
|
||||
|
||||
const server = new jsrp.server();
|
||||
server.init(
|
||||
{
|
||||
salt: user.salt,
|
||||
verifier: user.verifier,
|
||||
},
|
||||
async () => {
|
||||
// generate server-side public key
|
||||
const serverPublicKey = server.getPublicKey();
|
||||
await LoginSRPDetail.findOneAndReplace({
|
||||
email: email,
|
||||
}, {
|
||||
email,
|
||||
userId: user.id,
|
||||
clientPublicKey: clientPublicKey,
|
||||
serverBInt: bigintConversion.bigintToBuf(server.bInt),
|
||||
}, { upsert: true, returnNewDocument: false });
|
||||
|
||||
return res.status(200).send({
|
||||
serverPublicKey,
|
||||
salt: user.salt,
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -105,159 +95,150 @@ export const login1 = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const login2 = async (req: Request, res: Response) => {
|
||||
try {
|
||||
if (!req.headers["user-agent"]) throw InternalServerError({ message: "User-Agent header is required" });
|
||||
if (!req.headers["user-agent"]) throw InternalServerError({ message: "User-Agent header is required" });
|
||||
|
||||
const { email, clientProof, providerAuthToken } = req.body;
|
||||
const { email, clientProof, providerAuthToken } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
const user = await User.findOne({
|
||||
email,
|
||||
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
|
||||
|
||||
if (!user) throw new Error("Failed to find user");
|
||||
|
||||
if (!user.authMethods.includes(AuthMethod.EMAIL)) {
|
||||
await validateProviderAuthToken({
|
||||
email,
|
||||
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
|
||||
providerAuthToken,
|
||||
})
|
||||
}
|
||||
|
||||
if (!user) throw new Error("Failed to find user");
|
||||
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
|
||||
|
||||
if (user.authProvider && user.authProvider !== AuthProvider.EMAIL) {
|
||||
await validateProviderAuthToken({
|
||||
email,
|
||||
user,
|
||||
providerAuthToken,
|
||||
})
|
||||
}
|
||||
if (!loginSRPDetail) {
|
||||
return BadRequestError(Error("Failed to find login details for SRP"))
|
||||
}
|
||||
|
||||
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
|
||||
const server = new jsrp.server();
|
||||
server.init(
|
||||
{
|
||||
salt: user.salt,
|
||||
verifier: user.verifier,
|
||||
b: loginSRPDetail.serverBInt,
|
||||
},
|
||||
async () => {
|
||||
server.setClientPublicKey(loginSRPDetail.clientPublicKey);
|
||||
|
||||
if (!loginSRPDetail) {
|
||||
return BadRequestError(Error("Failed to find login details for SRP"))
|
||||
}
|
||||
// compare server and client shared keys
|
||||
if (server.checkClientProof(clientProof)) {
|
||||
|
||||
const server = new jsrp.server();
|
||||
server.init(
|
||||
{
|
||||
salt: user.salt,
|
||||
verifier: user.verifier,
|
||||
b: loginSRPDetail.serverBInt,
|
||||
},
|
||||
async () => {
|
||||
server.setClientPublicKey(loginSRPDetail.clientPublicKey);
|
||||
if (user.isMfaEnabled) {
|
||||
// case: user has MFA enabled
|
||||
|
||||
// compare server and client shared keys
|
||||
if (server.checkClientProof(clientProof)) {
|
||||
|
||||
if (user.isMfaEnabled) {
|
||||
// case: user has MFA enabled
|
||||
|
||||
// generate temporary MFA token
|
||||
const token = createToken({
|
||||
payload: {
|
||||
userId: user._id.toString(),
|
||||
},
|
||||
expiresIn: await getJwtMfaLifetime(),
|
||||
secret: await getJwtMfaSecret(),
|
||||
});
|
||||
|
||||
const code = await TokenService.createToken({
|
||||
type: TOKEN_EMAIL_MFA,
|
||||
email,
|
||||
});
|
||||
|
||||
// send MFA code [code] to [email]
|
||||
await sendMail({
|
||||
template: "emailMfa.handlebars",
|
||||
subjectLine: "Infisical MFA code",
|
||||
recipients: [user.email],
|
||||
substitutions: {
|
||||
code,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
mfaEnabled: true,
|
||||
token,
|
||||
});
|
||||
}
|
||||
|
||||
await checkUserDevice({
|
||||
user,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
// generate temporary MFA token
|
||||
const token = createToken({
|
||||
payload: {
|
||||
userId: user._id.toString(),
|
||||
},
|
||||
expiresIn: await getJwtMfaLifetime(),
|
||||
secret: await getJwtMfaSecret(),
|
||||
});
|
||||
|
||||
// issue tokens
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
});
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
res.cookie("jid", tokens.refreshToken, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: await getHttpsEnabled(),
|
||||
const code = await TokenService.createToken({
|
||||
type: TOKEN_EMAIL_MFA,
|
||||
email,
|
||||
});
|
||||
|
||||
// case: user does not have MFA enablgged
|
||||
// return (access) token in response
|
||||
|
||||
interface ResponseData {
|
||||
mfaEnabled: boolean;
|
||||
encryptionVersion: any;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
protectedKeyTag?: string;
|
||||
token: string;
|
||||
publicKey?: string;
|
||||
encryptedPrivateKey?: string;
|
||||
iv?: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
const response: ResponseData = {
|
||||
mfaEnabled: false,
|
||||
encryptionVersion: user.encryptionVersion,
|
||||
token: tokens.token,
|
||||
publicKey: user.publicKey,
|
||||
encryptedPrivateKey: user.encryptedPrivateKey,
|
||||
iv: user.iv,
|
||||
tag: user.tag,
|
||||
}
|
||||
|
||||
if (
|
||||
user?.protectedKey &&
|
||||
user?.protectedKeyIV &&
|
||||
user?.protectedKeyTag
|
||||
) {
|
||||
response.protectedKey = user.protectedKey;
|
||||
response.protectedKeyIV = user.protectedKeyIV
|
||||
response.protectedKeyTag = user.protectedKeyTag;
|
||||
}
|
||||
|
||||
const loginAction = await EELogService.createAction({
|
||||
name: ACTION_LOGIN,
|
||||
userId: user._id,
|
||||
// send MFA code [code] to [email]
|
||||
await sendMail({
|
||||
template: "emailMfa.handlebars",
|
||||
subjectLine: "Infisical MFA code",
|
||||
recipients: [user.email],
|
||||
substitutions: {
|
||||
code,
|
||||
},
|
||||
});
|
||||
|
||||
loginAction && await EELogService.createLog({
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getChannelFromUserAgent(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP,
|
||||
return res.status(200).send({
|
||||
mfaEnabled: true,
|
||||
token,
|
||||
});
|
||||
|
||||
return res.status(200).send(response);
|
||||
}
|
||||
|
||||
return res.status(400).send({
|
||||
message: "Failed to authenticate. Try again?",
|
||||
await checkUserDevice({
|
||||
user,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
});
|
||||
|
||||
// issue tokens
|
||||
const tokens = await issueAuthTokens({
|
||||
userId: user._id,
|
||||
ip: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
});
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
res.cookie("jid", tokens.refreshToken, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
sameSite: "strict",
|
||||
secure: await getHttpsEnabled(),
|
||||
});
|
||||
|
||||
// case: user does not have MFA enablgged
|
||||
// return (access) token in response
|
||||
|
||||
interface ResponseData {
|
||||
mfaEnabled: boolean;
|
||||
encryptionVersion: any;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
protectedKeyTag?: string;
|
||||
token: string;
|
||||
publicKey?: string;
|
||||
encryptedPrivateKey?: string;
|
||||
iv?: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
const response: ResponseData = {
|
||||
mfaEnabled: false,
|
||||
encryptionVersion: user.encryptionVersion,
|
||||
token: tokens.token,
|
||||
publicKey: user.publicKey,
|
||||
encryptedPrivateKey: user.encryptedPrivateKey,
|
||||
iv: user.iv,
|
||||
tag: user.tag,
|
||||
}
|
||||
|
||||
if (
|
||||
user?.protectedKey &&
|
||||
user?.protectedKeyIV &&
|
||||
user?.protectedKeyTag
|
||||
) {
|
||||
response.protectedKey = user.protectedKey;
|
||||
response.protectedKeyIV = user.protectedKeyIV
|
||||
response.protectedKeyTag = user.protectedKeyTag;
|
||||
}
|
||||
|
||||
const loginAction = await EELogService.createAction({
|
||||
name: ACTION_LOGIN,
|
||||
userId: user._id,
|
||||
});
|
||||
|
||||
loginAction && await EELogService.createLog({
|
||||
userId: user._id,
|
||||
actions: [loginAction],
|
||||
channel: getUserAgentType(req.headers["user-agent"]),
|
||||
ipAddress: req.realIP,
|
||||
});
|
||||
|
||||
return res.status(200).send(response);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: "Failed to authenticate. Try again?",
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(400).send({
|
||||
message: "Failed to authenticate. Try again?",
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -6,10 +6,9 @@ import { BotService } from "../../services";
|
||||
import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets";
|
||||
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
|
||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||
import Folder from "../../models/folder";
|
||||
import { Folder, IServiceTokenData } from "../../models";
|
||||
import { getFolderByPath } from "../../services/FolderService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { IServiceTokenData } from "../../models";
|
||||
import { requireWorkspaceAuth } from "../../middleware";
|
||||
import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables";
|
||||
|
||||
@ -23,6 +22,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
|
||||
let workspaceId = req.query.workspaceId as string;
|
||||
let environment = req.query.environment as string;
|
||||
let secretPath = req.query.secretPath as string;
|
||||
const folderId = req.query.folderId as string | undefined;
|
||||
const includeImports = req.query.include_imports as string;
|
||||
|
||||
// if the service token has single scope, it will get all secrets for that scope by default
|
||||
@ -47,6 +47,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => {
|
||||
const secrets = await SecretService.getSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folderId,
|
||||
secretPath,
|
||||
authData: req.authData
|
||||
});
|
||||
@ -284,11 +285,13 @@ export const getSecrets = async (req: Request, res: Response) => {
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
const environment = req.query.environment as string;
|
||||
const secretPath = req.query.secretPath as string;
|
||||
const folderId = req.query.folderId as string | undefined;
|
||||
const includeImports = req.query.include_imports as string;
|
||||
|
||||
const secrets = await SecretService.getSecrets({
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folderId,
|
||||
secretPath,
|
||||
authData: req.authData
|
||||
});
|
||||
@ -362,7 +365,8 @@ export const createSecret = async (req: Request, res: Response) => {
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretPath = "/"
|
||||
secretPath = "/",
|
||||
metadata
|
||||
} = req.body;
|
||||
|
||||
const secret = await SecretService.createSecret({
|
||||
@ -380,7 +384,8 @@ export const createSecret = async (req: Request, res: Response) => {
|
||||
secretPath,
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag
|
||||
secretCommentTag,
|
||||
metadata
|
||||
});
|
||||
|
||||
await EventService.handleEvent({
|
||||
|
@ -12,7 +12,7 @@ import { standardRequest } from "../../config/request";
|
||||
import { getHttpsEnabled, getJwtSignupSecret, getLoopsApiKey } from "../../config";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { TelemetryService } from "../../services";
|
||||
import { AuthProvider } from "../../models";
|
||||
import { AuthMethod } from "../../models";
|
||||
|
||||
/**
|
||||
* Complete setting up user by adding their personal and auth information as part of the
|
||||
@ -71,8 +71,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
if (providerAuthToken) {
|
||||
await validateProviderAuthToken({
|
||||
email,
|
||||
providerAuthToken,
|
||||
user,
|
||||
providerAuthToken
|
||||
});
|
||||
} else {
|
||||
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers["authorization"]?.split(" ", 2) ?? [null, null]
|
||||
@ -117,7 +116,9 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
||||
if (!user)
|
||||
throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
|
||||
|
||||
if (user.authProvider !== AuthProvider.OKTA_SAML) {
|
||||
const hasSamlEnabled = user.authMethods.some((authMethod: AuthMethod) => [AuthMethod.OKTA_SAML, AuthMethod.AZURE_SAML, AuthMethod.JUMPCLOUD_SAML].includes(authMethod));
|
||||
|
||||
if (!hasSamlEnabled) { // TODO: modify this part
|
||||
// initialize default organization and workspace
|
||||
await initializeDefaultOrg({
|
||||
organizationName,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,12 @@
|
||||
import { Request, Response } from "express";
|
||||
import { Membership, Workspace } from "../../../models";
|
||||
import { IUser, Membership, Workspace } from "../../../models";
|
||||
import { EventType } from "../../../ee/models";
|
||||
import { IMembershipPermission } from "../../../models/membership";
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../../utils/errors";
|
||||
import { ADMIN, MEMBER } from "../../../variables/organization";
|
||||
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../../variables";
|
||||
import _ from "lodash";
|
||||
import { EEAuditLogService } from "../../services";
|
||||
|
||||
export const denyMembershipPermissions = async (req: Request, res: Response) => {
|
||||
const { membershipId } = req.params;
|
||||
@ -51,12 +53,33 @@ export const denyMembershipPermissions = async (req: Request, res: Response) =>
|
||||
{ _id: membershipToModify._id },
|
||||
{ $set: { deniedPermissions: sanitizedMembershipPermissionsUnique } },
|
||||
{ new: true }
|
||||
)
|
||||
).populate<{ user: IUser }>("user");
|
||||
|
||||
if (!updatedMembershipWithPermissions) {
|
||||
throw BadRequestError({ message: "The resource has been removed before it can be modified" })
|
||||
}
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS,
|
||||
metadata: {
|
||||
userId: updatedMembershipWithPermissions.user._id.toString(),
|
||||
email: updatedMembershipWithPermissions.user.email,
|
||||
deniedPermissions: updatedMembershipWithPermissions.deniedPermissions.map(({
|
||||
environmentSlug,
|
||||
ability
|
||||
}) => ({
|
||||
environmentSlug,
|
||||
ability
|
||||
}))
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: updatedMembershipWithPermissions.workspace
|
||||
}
|
||||
);
|
||||
|
||||
res.send({
|
||||
permissionsDenied: updatedMembershipWithPermissions.deniedPermissions,
|
||||
})
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Request, Response } from "express";
|
||||
import { getLicenseServerUrl } from "../../../config";
|
||||
import { licenseServerKeyRequest } from "../../../config/request";
|
||||
@ -20,7 +21,7 @@ export const getOrganizationPlan = async (req: Request, res: Response) => {
|
||||
const { organizationId } = req.params;
|
||||
const workspaceId = req.query.workspaceId as string;
|
||||
|
||||
const plan = await EELicenseService.getPlan(organizationId, workspaceId);
|
||||
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId), new Types.ObjectId(workspaceId));
|
||||
|
||||
return res.status(200).send({
|
||||
plan,
|
||||
@ -44,7 +45,7 @@ export const startOrganizationTrial = async (req: Request, res: Response) => {
|
||||
}
|
||||
);
|
||||
|
||||
EELicenseService.delPlan(organizationId);
|
||||
EELicenseService.delPlan(new Types.ObjectId(organizationId));
|
||||
|
||||
return res.status(200).send({
|
||||
url
|
||||
|
@ -3,6 +3,7 @@ import { Types } from "mongoose";
|
||||
import { BotOrgService } from "../../../services";
|
||||
import { SSOConfig } from "../../models";
|
||||
import {
|
||||
AuthMethod,
|
||||
MembershipOrg,
|
||||
User
|
||||
} from "../../../models";
|
||||
@ -59,7 +60,7 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
|
||||
cert,
|
||||
} = req.body;
|
||||
|
||||
const plan = await EELicenseService.getPlan(organizationId);
|
||||
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
|
||||
|
||||
if (!plan.samlSSO) return res.status(400).send({
|
||||
message: "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
|
||||
@ -156,7 +157,7 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
|
||||
}
|
||||
},
|
||||
{
|
||||
authProvider: ssoConfig.authProvider
|
||||
authMethods: [ssoConfig.authProvider],
|
||||
}
|
||||
);
|
||||
} else {
|
||||
@ -167,9 +168,7 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
|
||||
}
|
||||
},
|
||||
{
|
||||
$unset: {
|
||||
authProvider: 1
|
||||
}
|
||||
authMethods: [AuthMethod.EMAIL],
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -194,7 +193,7 @@ export const createSSOConfig = async (req: Request, res: Response) => {
|
||||
cert
|
||||
} = req.body;
|
||||
|
||||
const plan = await EELicenseService.getPlan(organizationId);
|
||||
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
|
||||
|
||||
if (!plan.samlSSO) return res.status(400).send({
|
||||
message: "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration."
|
||||
|
@ -8,6 +8,6 @@ import { Request, Response } from "express";
|
||||
*/
|
||||
export const getMyIp = (req: Request, res: Response) => {
|
||||
return res.status(200).send({
|
||||
ip: req.authData.authIP
|
||||
ip: req.authData.ipAddress
|
||||
});
|
||||
}
|
@ -1,21 +1,26 @@
|
||||
import { Request, Response } from "express";
|
||||
import { PipelineStage, Types } from "mongoose";
|
||||
import { Secret } from "../../../models";
|
||||
import { Folder, Membership, Secret, ServiceTokenData, TFolderSchema, User } from "../../../models";
|
||||
import {
|
||||
ActorType,
|
||||
AuditLog,
|
||||
EventType,
|
||||
FolderVersion,
|
||||
IPType,
|
||||
ISecretVersion,
|
||||
Log,
|
||||
SecretSnapshot,
|
||||
SecretVersion,
|
||||
ServiceActor,
|
||||
TFolderRootVersionSchema,
|
||||
TrustedIP
|
||||
TrustedIP,
|
||||
UserActor
|
||||
} from "../../models";
|
||||
import { EESecretService } from "../../services";
|
||||
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
|
||||
import Folder, { TFolderSchema } from "../../../models/folder";
|
||||
// import Folder, { TFolderSchema } from "../../../models/folder";
|
||||
import { searchByFolderId } from "../../../services/FolderService";
|
||||
import { EELicenseService } from "../../services";
|
||||
import { EEAuditLogService, EELicenseService } from "../../services";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
|
||||
|
||||
/**
|
||||
@ -593,6 +598,101 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return audit logs for workspace with id [workspaceId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
const eventType = req.query.eventType;
|
||||
const userAgentType = req.query.userAgentType;
|
||||
const actor = req.query.actor as string | undefined;
|
||||
const offset: number = parseInt(req.query.offset as string);
|
||||
const limit: number = parseInt(req.query.limit as string);
|
||||
|
||||
const startDate = req.query.startDate as string;
|
||||
const endDate = req.query.endDate as string;
|
||||
|
||||
const query = {
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
...(eventType ? {
|
||||
"event.type": eventType
|
||||
} : {}),
|
||||
...(userAgentType ? {
|
||||
userAgentType
|
||||
} : {}),
|
||||
...(actor ? {
|
||||
"actor.type": actor.split("-", 2)[0],
|
||||
...(actor.split("-", 2)[0] === ActorType.USER ? {
|
||||
"actor.metadata.userId": actor.split("-", 2)[1]
|
||||
} : {
|
||||
"actor.metadata.serviceId": actor.split("-", 2)[1]
|
||||
})
|
||||
} : {}),
|
||||
...(startDate || endDate ? {
|
||||
createdAt: {
|
||||
...(startDate && { $gte: new Date(startDate) }),
|
||||
...(endDate && { $lte: new Date(endDate) })
|
||||
}
|
||||
} : {})
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return audit log actor filter options for workspace with id [workspaceId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
const userIds = await Membership.distinct("user", {
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
const userActors: UserActor[] = (await User.find({
|
||||
_id: {
|
||||
$in: userIds
|
||||
}
|
||||
})
|
||||
.select("email"))
|
||||
.map((user) => ({
|
||||
type: ActorType.USER,
|
||||
metadata: {
|
||||
userId: user._id.toString(),
|
||||
email: user.email
|
||||
}
|
||||
}));
|
||||
|
||||
const serviceActors: ServiceActor[] = (await ServiceTokenData.find({
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
})
|
||||
.select("name"))
|
||||
.map((serviceTokenData) => ({
|
||||
type: ActorType.SERVICE,
|
||||
metadata: {
|
||||
serviceId: serviceTokenData._id.toString(),
|
||||
name: serviceTokenData.name
|
||||
}
|
||||
}));
|
||||
|
||||
return res.status(200).send({
|
||||
actors: [...userActors, ...serviceActors]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return trusted ips for workspace with id [workspaceId]
|
||||
* @param req
|
||||
@ -623,7 +723,7 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
isActive
|
||||
} = req.body;
|
||||
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization);
|
||||
|
||||
if (!plan.ipAllowlisting) return res.status(400).send({
|
||||
message: "Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range."
|
||||
@ -645,6 +745,21 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
isActive,
|
||||
comment,
|
||||
}).save();
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.ADD_TRUSTED_IP,
|
||||
metadata: {
|
||||
trustedIpId: trustedIp._id.toString(),
|
||||
ipAddress: trustedIp.ipAddress,
|
||||
prefix: trustedIp.prefix
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: trustedIp.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
trustedIp
|
||||
@ -663,7 +778,7 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
comment
|
||||
} = req.body;
|
||||
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization);
|
||||
|
||||
if (!plan.ipAllowlisting) return res.status(400).send({
|
||||
message: "Failed to update IP access range due to plan restriction. Upgrade plan to update IP access range."
|
||||
@ -708,6 +823,25 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
}
|
||||
);
|
||||
|
||||
if (!trustedIp) return res.status(400).send({
|
||||
message: "Failed to update trusted IP"
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UPDATE_TRUSTED_IP,
|
||||
metadata: {
|
||||
trustedIpId: trustedIp._id.toString(),
|
||||
ipAddress: trustedIp.ipAddress,
|
||||
prefix: trustedIp.prefix
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: trustedIp.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
trustedIp
|
||||
});
|
||||
@ -721,7 +855,7 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
const { workspaceId, trustedIpId } = req.params;
|
||||
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
|
||||
const plan = await EELicenseService.getPlan(req.workspace.organization);
|
||||
|
||||
if (!plan.ipAllowlisting) return res.status(400).send({
|
||||
message: "Failed to delete IP access range due to plan restriction. Upgrade plan to delete IP access range."
|
||||
@ -731,6 +865,25 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
|
||||
_id: new Types.ObjectId(trustedIpId),
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
if (!trustedIp) return res.status(400).send({
|
||||
message: "Failed to delete trusted IP"
|
||||
});
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.DELETE_TRUSTED_IP,
|
||||
metadata: {
|
||||
trustedIpId: trustedIp._id.toString(),
|
||||
ipAddress: trustedIp.ipAddress,
|
||||
prefix: trustedIp.prefix
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: trustedIp.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).send({
|
||||
trustedIp
|
||||
|
76
backend/src/ee/models/auditLog/auditLog.ts
Normal file
76
backend/src/ee/models/auditLog/auditLog.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true
|
||||
}
|
||||
);
|
||||
|
||||
export const AuditLog = model<IAuditLog>("AuditLog", auditLogSchema);
|
47
backend/src/ee/models/auditLog/enums.ts
Normal file
47
backend/src/ee/models/auditLog/enums.ts
Normal file
@ -0,0 +1,47 @@
|
||||
export enum ActorType {
|
||||
USER = "user",
|
||||
SERVICE = "service"
|
||||
}
|
||||
|
||||
export enum UserAgentType {
|
||||
WEB = "web",
|
||||
CLI = "cli",
|
||||
K8_OPERATOR = "k8-operator",
|
||||
OTHER = "other"
|
||||
}
|
||||
|
||||
export enum EventType {
|
||||
GET_SECRETS = "get-secrets",
|
||||
GET_SECRET = "get-secret",
|
||||
REVEAL_SECRET = "reveal-secret",
|
||||
CREATE_SECRET = "create-secret",
|
||||
UPDATE_SECRET = "update-secret",
|
||||
DELETE_SECRET = "delete-secret",
|
||||
GET_WORKSPACE_KEY = "get-workspace-key",
|
||||
AUTHORIZE_INTEGRATION = "authorize-integration",
|
||||
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
|
||||
CREATE_INTEGRATION = "create-integration",
|
||||
DELETE_INTEGRATION = "delete-integration",
|
||||
ADD_TRUSTED_IP = "add-trusted-ip",
|
||||
UPDATE_TRUSTED_IP = "update-trusted-ip",
|
||||
DELETE_TRUSTED_IP = "delete-trusted-ip",
|
||||
CREATE_SERVICE_TOKEN = "create-service-token",
|
||||
DELETE_SERVICE_TOKEN = "delete-service-token",
|
||||
CREATE_ENVIRONMENT = "create-environment",
|
||||
UPDATE_ENVIRONMENT = "update-environment",
|
||||
DELETE_ENVIRONMENT = "delete-environment",
|
||||
ADD_WORKSPACE_MEMBER = "add-workspace-member",
|
||||
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
|
||||
CREATE_FOLDER = "create-folder",
|
||||
UPDATE_FOLDER = "update-folder",
|
||||
DELETE_FOLDER = "delete-folder",
|
||||
CREATE_WEBHOOK = "create-webhook",
|
||||
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
|
||||
DELETE_WEBHOOK = "delete-webhook",
|
||||
GET_SECRET_IMPORTS = "get-secret-imports",
|
||||
CREATE_SECRET_IMPORT = "create-secret-import",
|
||||
UPDATE_SECRET_IMPORT = "update-secret-import",
|
||||
DELETE_SECRET_IMPORT = "delete-secret-import",
|
||||
UPDATE_USER_WORKSPACE_ROLE = "update-user-workspace-role",
|
||||
UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS = "update-user-workspace-denied-permissions"
|
||||
}
|
3
backend/src/ee/models/auditLog/index.ts
Normal file
3
backend/src/ee/models/auditLog/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./auditLog";
|
||||
export * from "./enums";
|
||||
export * from "./types";
|
403
backend/src/ee/models/auditLog/types.ts
Normal file
403
backend/src/ee/models/auditLog/types.ts
Normal file
@ -0,0 +1,403 @@
|
||||
import {
|
||||
ActorType,
|
||||
EventType
|
||||
} from "./enums";
|
||||
|
||||
interface UserActorMetadata {
|
||||
userId: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface ServiceActorMetadata {
|
||||
serviceId: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UserActor {
|
||||
type: ActorType.USER;
|
||||
metadata: UserActorMetadata;
|
||||
}
|
||||
|
||||
export interface ServiceActor {
|
||||
type: ActorType.SERVICE;
|
||||
metadata: ServiceActorMetadata;
|
||||
}
|
||||
|
||||
export type Actor =
|
||||
| UserActor
|
||||
| ServiceActor;
|
||||
|
||||
interface GetSecretsEvent {
|
||||
type: EventType.GET_SECRETS;
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
numberOfSecrets: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetSecretEvent {
|
||||
type: EventType.GET_SECRET;
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
secretId: string;
|
||||
secretKey: string;
|
||||
secretVersion: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateSecretEvent {
|
||||
type: EventType.CREATE_SECRET;
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
secretId: string;
|
||||
secretKey: string;
|
||||
secretVersion: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateSecretEvent {
|
||||
type: EventType.UPDATE_SECRET;
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
secretId: string;
|
||||
secretKey: string;
|
||||
secretVersion: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteSecretEvent {
|
||||
type: EventType.DELETE_SECRET;
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
secretId: string;
|
||||
secretKey: string;
|
||||
secretVersion: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface GetWorkspaceKeyEvent {
|
||||
type: EventType.GET_WORKSPACE_KEY,
|
||||
metadata: {
|
||||
keyId: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface AuthorizeIntegrationEvent {
|
||||
type: EventType.AUTHORIZE_INTEGRATION;
|
||||
metadata: {
|
||||
integration: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface UnauthorizeIntegrationEvent {
|
||||
type: EventType.UNAUTHORIZE_INTEGRATION;
|
||||
metadata: {
|
||||
integration: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateIntegrationEvent {
|
||||
type: EventType.CREATE_INTEGRATION;
|
||||
metadata: {
|
||||
integrationId: string;
|
||||
integration: string; // TODO: fix type
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
url?: string;
|
||||
app?: string;
|
||||
appId?: string;
|
||||
targetEnvironment?: string;
|
||||
targetEnvironmentId?: string;
|
||||
targetService?: string;
|
||||
targetServiceId?: string;
|
||||
path?: string;
|
||||
region?: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteIntegrationEvent {
|
||||
type: EventType.DELETE_INTEGRATION;
|
||||
metadata: {
|
||||
integrationId: string;
|
||||
integration: string; // TODO: fix type
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
url?: string;
|
||||
app?: string;
|
||||
appId?: string;
|
||||
targetEnvironment?: string;
|
||||
targetEnvironmentId?: string;
|
||||
targetService?: string;
|
||||
targetServiceId?: string;
|
||||
path?: string;
|
||||
region?: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface AddTrustedIPEvent {
|
||||
type: EventType.ADD_TRUSTED_IP;
|
||||
metadata: {
|
||||
trustedIpId: string;
|
||||
ipAddress: string;
|
||||
prefix?: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateTrustedIPEvent {
|
||||
type: EventType.UPDATE_TRUSTED_IP;
|
||||
metadata: {
|
||||
trustedIpId: string;
|
||||
ipAddress: string;
|
||||
prefix?: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteTrustedIPEvent {
|
||||
type: EventType.DELETE_TRUSTED_IP;
|
||||
metadata: {
|
||||
trustedIpId: string;
|
||||
ipAddress: string;
|
||||
prefix?: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateServiceTokenEvent {
|
||||
type: EventType.CREATE_SERVICE_TOKEN;
|
||||
metadata: {
|
||||
name: string;
|
||||
scopes: Array<{
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteServiceTokenEvent {
|
||||
type: EventType.DELETE_SERVICE_TOKEN;
|
||||
metadata: {
|
||||
name: string;
|
||||
scopes: Array<{
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateEnvironmentEvent {
|
||||
type: EventType.CREATE_ENVIRONMENT;
|
||||
metadata: {
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateEnvironmentEvent {
|
||||
type: EventType.UPDATE_ENVIRONMENT;
|
||||
metadata: {
|
||||
oldName: string;
|
||||
newName: string;
|
||||
oldSlug: string;
|
||||
newSlug: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteEnvironmentEvent {
|
||||
type: EventType.DELETE_ENVIRONMENT;
|
||||
metadata: {
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface AddWorkspaceMemberEvent {
|
||||
type: EventType.ADD_WORKSPACE_MEMBER;
|
||||
metadata: {
|
||||
userId: string;
|
||||
email: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface RemoveWorkspaceMemberEvent {
|
||||
type: EventType.REMOVE_WORKSPACE_MEMBER;
|
||||
metadata: {
|
||||
userId: string;
|
||||
email: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateFolderEvent {
|
||||
type: EventType.CREATE_FOLDER;
|
||||
metadata: {
|
||||
environment: string;
|
||||
folderId: string;
|
||||
folderName: string;
|
||||
folderPath: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateFolderEvent {
|
||||
type: EventType.UPDATE_FOLDER;
|
||||
metadata: {
|
||||
environment: string;
|
||||
folderId: string;
|
||||
oldFolderName: string;
|
||||
newFolderName: string;
|
||||
folderPath: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteFolderEvent {
|
||||
type: EventType.DELETE_FOLDER;
|
||||
metadata: {
|
||||
environment: string;
|
||||
folderId: string;
|
||||
folderName: string;
|
||||
folderPath: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateWebhookEvent {
|
||||
type: EventType.CREATE_WEBHOOK,
|
||||
metadata: {
|
||||
webhookId: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
webhookUrl: string;
|
||||
isDisabled: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateWebhookStatusEvent {
|
||||
type: EventType.UPDATE_WEBHOOK_STATUS,
|
||||
metadata: {
|
||||
webhookId: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
webhookUrl: string;
|
||||
isDisabled: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteWebhookEvent {
|
||||
type: EventType.DELETE_WEBHOOK,
|
||||
metadata: {
|
||||
webhookId: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
webhookUrl: string;
|
||||
isDisabled: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
interface GetSecretImportsEvent {
|
||||
type: EventType.GET_SECRET_IMPORTS,
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretImportId: string;
|
||||
folderId: string;
|
||||
numberOfImports: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateSecretImportEvent {
|
||||
type: EventType.CREATE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
secretImportId: string;
|
||||
folderId: string;
|
||||
importFromEnvironment: string;
|
||||
importFromSecretPath: string;
|
||||
importToEnvironment: string;
|
||||
importToSecretPath: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateSecretImportEvent {
|
||||
type: EventType.UPDATE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
secretImportId: string;
|
||||
folderId: string;
|
||||
importToEnvironment: string;
|
||||
importToSecretPath: string;
|
||||
orderBefore: {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}[],
|
||||
orderAfter: {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteSecretImportEvent {
|
||||
type: EventType.DELETE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
secretImportId: string;
|
||||
folderId: string;
|
||||
importFromEnvironment: string;
|
||||
importFromSecretPath: string;
|
||||
importToEnvironment: string;
|
||||
importToSecretPath: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateUserRole {
|
||||
type: EventType.UPDATE_USER_WORKSPACE_ROLE,
|
||||
metadata: {
|
||||
userId: string;
|
||||
email: string;
|
||||
oldRole: string;
|
||||
newRole: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateUserDeniedPermissions {
|
||||
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS,
|
||||
metadata: {
|
||||
userId: string;
|
||||
email: string;
|
||||
deniedPermissions: {
|
||||
environmentSlug: string;
|
||||
ability: string;
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export type Event =
|
||||
| GetSecretsEvent
|
||||
| GetSecretEvent
|
||||
| CreateSecretEvent
|
||||
| UpdateSecretEvent
|
||||
| DeleteSecretEvent
|
||||
| GetWorkspaceKeyEvent
|
||||
| AuthorizeIntegrationEvent
|
||||
| UnauthorizeIntegrationEvent
|
||||
| CreateIntegrationEvent
|
||||
| DeleteIntegrationEvent
|
||||
| AddTrustedIPEvent
|
||||
| UpdateTrustedIPEvent
|
||||
| DeleteTrustedIPEvent
|
||||
| CreateServiceTokenEvent
|
||||
| DeleteServiceTokenEvent
|
||||
| CreateEnvironmentEvent
|
||||
| UpdateEnvironmentEvent
|
||||
| DeleteEnvironmentEvent
|
||||
| AddWorkspaceMemberEvent
|
||||
| RemoveWorkspaceMemberEvent
|
||||
| CreateFolderEvent
|
||||
| UpdateFolderEvent
|
||||
| DeleteFolderEvent
|
||||
| CreateWebhookEvent
|
||||
| UpdateWebhookStatusEvent
|
||||
| DeleteWebhookEvent
|
||||
| GetSecretImportsEvent
|
||||
| CreateSecretImportEvent
|
||||
| UpdateSecretImportEvent
|
||||
| DeleteSecretImportEvent
|
||||
| UpdateUserRole
|
||||
| UpdateUserDeniedPermissions;
|
@ -4,4 +4,8 @@ export * from "./folderVersion";
|
||||
export * from "./log";
|
||||
export * from "./action";
|
||||
export * from "./ssoConfig";
|
||||
export * from "./trustedIp";
|
||||
export * from "./trustedIp";
|
||||
export * from "./auditLog";
|
||||
export * from "./gitRisks";
|
||||
export * from "./gitAppOrganizationInstallation";
|
||||
export * from "./gitAppInstallationSession";
|
||||
|
@ -117,7 +117,7 @@ const secretVersionSchema = new Schema<ISecretVersion>(
|
||||
ref: "Tag",
|
||||
type: [Schema.Types.ObjectId],
|
||||
default: [],
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
@ -6,11 +6,12 @@ import {
|
||||
} from "../../../middleware";
|
||||
import { query } from "express-validator";
|
||||
import { cloudProductsController } from "../../controllers/v1";
|
||||
import { AuthMode } from "../../../variables";
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt", "apiKey"],
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||
}),
|
||||
query("billing-cycle").exists().isIn(["monthly", "yearly"]),
|
||||
validateRequest,
|
||||
|
@ -6,6 +6,7 @@ import users from "./users";
|
||||
import workspace from "./workspace";
|
||||
import action from "./action";
|
||||
import cloudProducts from "./cloudProducts";
|
||||
import secretScanning from "./secretScanning";
|
||||
|
||||
export {
|
||||
secret,
|
||||
@ -16,4 +17,5 @@ export {
|
||||
workspace,
|
||||
action,
|
||||
cloudProducts,
|
||||
secretScanning
|
||||
}
|
@ -8,13 +8,13 @@ import {
|
||||
import { body, param, query } from "express-validator";
|
||||
import { organizationsController } from "../../controllers/v1";
|
||||
import {
|
||||
ACCEPTED, ADMIN, MEMBER, OWNER,
|
||||
ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER
|
||||
} from "../../../variables";
|
||||
|
||||
router.get(
|
||||
"/:organizationId/plans/table",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -29,7 +29,7 @@ router.get(
|
||||
router.get(
|
||||
"/:organizationId/plan",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -44,7 +44,7 @@ router.get(
|
||||
router.post(
|
||||
"/:organizationId/session/trial",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -59,7 +59,7 @@ router.post(
|
||||
router.get(
|
||||
"/:organizationId/plan/billing",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -74,7 +74,7 @@ router.get(
|
||||
router.get(
|
||||
"/:organizationId/plan/table",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -89,7 +89,7 @@ router.get(
|
||||
router.get(
|
||||
"/:organizationId/billing-details",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -103,7 +103,7 @@ router.get(
|
||||
router.patch(
|
||||
"/:organizationId/billing-details",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -119,7 +119,7 @@ router.patch(
|
||||
router.get(
|
||||
"/:organizationId/billing-details/payment-methods",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -133,7 +133,7 @@ router.get(
|
||||
router.post(
|
||||
"/:organizationId/billing-details/payment-methods",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -149,7 +149,7 @@ router.post(
|
||||
router.delete(
|
||||
"/:organizationId/billing-details/payment-methods/:pmtMethodId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -164,7 +164,7 @@ router.delete(
|
||||
router.get(
|
||||
"/:organizationId/billing-details/tax-ids",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -178,7 +178,7 @@ router.get(
|
||||
router.post(
|
||||
"/:organizationId/billing-details/tax-ids",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -194,7 +194,7 @@ router.post(
|
||||
router.delete(
|
||||
"/:organizationId/billing-details/tax-ids/:taxId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -209,7 +209,7 @@ router.delete(
|
||||
router.get(
|
||||
"/:organizationId/invoices",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||
@ -223,7 +223,7 @@ router.get(
|
||||
router.get(
|
||||
"/:organizationId/licenses",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN],
|
||||
|
@ -9,15 +9,16 @@ import { body, param, query } from "express-validator";
|
||||
import { secretController } from "../../controllers/v1";
|
||||
import {
|
||||
ADMIN,
|
||||
AuthMode,
|
||||
MEMBER,
|
||||
PERMISSION_READ_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS,
|
||||
PERMISSION_WRITE_SECRETS
|
||||
} from "../../../variables";
|
||||
|
||||
router.get(
|
||||
"/:secretId/secret-versions",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt", "apiKey"],
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||
}),
|
||||
requireSecretAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@ -33,7 +34,7 @@ router.get(
|
||||
router.post(
|
||||
"/:secretId/secret-versions/rollback",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt", "apiKey"],
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||
}),
|
||||
requireSecretAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
|
@ -4,15 +4,15 @@ import {
|
||||
requireAuth,
|
||||
requireOrganizationAuth,
|
||||
validateRequest,
|
||||
} from "../../middleware";
|
||||
} from "../../../middleware";
|
||||
import { body, param } from "express-validator";
|
||||
import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../controllers/v1/secretScanningController";
|
||||
import { ACCEPTED, ADMIN, MEMBER, OWNER } from "../../variables";
|
||||
import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../../controllers/v1/secretScanningController";
|
||||
import { ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER } from "../../../variables";
|
||||
|
||||
router.post(
|
||||
"/create-installation-session/organization/:organizationId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
param("organizationId").exists().trim(),
|
||||
requireOrganizationAuth({
|
||||
@ -26,7 +26,7 @@ router.post(
|
||||
router.post(
|
||||
"/link-installation",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
body("installationId").exists().trim(),
|
||||
body("sessionId").exists().trim(),
|
||||
@ -37,7 +37,7 @@ router.post(
|
||||
router.get(
|
||||
"/installation-status/organization/:organizationId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
param("organizationId").exists().trim(),
|
||||
requireOrganizationAuth({
|
||||
@ -51,7 +51,7 @@ router.get(
|
||||
router.get(
|
||||
"/organization/:organizationId/risks",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
param("organizationId").exists().trim(),
|
||||
requireOrganizationAuth({
|
||||
@ -65,7 +65,7 @@ router.get(
|
||||
router.post(
|
||||
"/organization/:organizationId/risks/:riskId/status",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
param("organizationId").exists().trim(),
|
||||
param("riskId").exists().trim(),
|
@ -8,13 +8,13 @@ import {
|
||||
validateRequest,
|
||||
} from "../../../middleware";
|
||||
import { param } from "express-validator";
|
||||
import { ADMIN, MEMBER } from "../../../variables";
|
||||
import { ADMIN, AuthMode, MEMBER } from "../../../variables";
|
||||
import { secretSnapshotController } from "../../controllers/v1";
|
||||
|
||||
router.get(
|
||||
"/:secretSnapshotId",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireSecretSnapshotAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
|
@ -15,6 +15,7 @@ import { authLimiter } from "../../../helpers/rateLimiter";
|
||||
import {
|
||||
ACCEPTED,
|
||||
ADMIN,
|
||||
AuthMode,
|
||||
OWNER
|
||||
} from "../../../variables";
|
||||
|
||||
@ -90,7 +91,7 @@ router.post("/saml2/:ssoIdentifier",
|
||||
router.get(
|
||||
"/config",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN],
|
||||
@ -105,7 +106,7 @@ router.get(
|
||||
router.post(
|
||||
"/config",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN],
|
||||
@ -125,7 +126,7 @@ router.post(
|
||||
router.patch(
|
||||
"/config",
|
||||
requireAuth({
|
||||
acceptedAuthModes: ["jwt"],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireOrganizationAuth({
|
||||
acceptedRoles: [OWNER, ADMIN],
|
||||
|
@ -3,13 +3,13 @@ const router = express.Router();
|
||||
import {
|
||||
requireAuth
|
||||
} from "../../../middleware";
|
||||
import { AUTH_MODE_API_KEY, AUTH_MODE_JWT } from "../../../variables";
|
||||
import { AuthMode } from "../../../variables";
|
||||
import { usersController } from "../../controllers/v1";
|
||||
|
||||
router.get(
|
||||
"/me/ip",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||
}),
|
||||
usersController.getMyIp
|
||||
);
|
||||
|
@ -8,16 +8,16 @@ import {
|
||||
import { body, param, query } from "express-validator";
|
||||
import {
|
||||
ADMIN,
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_JWT,
|
||||
AuthMode,
|
||||
MEMBER
|
||||
} from "../../../variables";
|
||||
import { workspaceController } from "../../controllers/v1";
|
||||
import { EventType, UserAgentType } from "../../models";
|
||||
|
||||
router.get(
|
||||
"/:workspaceId/secret-snapshots",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@ -35,7 +35,7 @@ router.get(
|
||||
router.get(
|
||||
"/:workspaceId/secret-snapshots/count",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@ -51,7 +51,7 @@ router.get(
|
||||
router.post(
|
||||
"/:workspaceId/secret-snapshots/rollback",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@ -68,7 +68,7 @@ router.post(
|
||||
router.get(
|
||||
"/:workspaceId/logs",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT, AUTH_MODE_API_KEY],
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@ -84,11 +84,46 @@ router.get(
|
||||
workspaceController.getWorkspaceLogs
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:workspaceId/audit-logs",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "params",
|
||||
}),
|
||||
param("workspaceId").exists().trim(),
|
||||
query("eventType").isString().isIn(Object.values(EventType)).optional({ nullable: true }),
|
||||
query("userAgentType").isString().isIn(Object.values(UserAgentType)).optional({ nullable: true }),
|
||||
query("actor").optional({ nullable: true }),
|
||||
query("startDate").isISO8601().withMessage("Invalid start date format").optional({ nullable: true }),
|
||||
query("endDate").isISO8601().withMessage("Invalid end date format").optional({ nullable: true }),
|
||||
query("offset"),
|
||||
query("limit"),
|
||||
validateRequest,
|
||||
workspaceController.getWorkspaceAuditLogs
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:workspaceId/audit-logs/filters/actors",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
locationWorkspaceId: "params",
|
||||
}),
|
||||
param("workspaceId").exists().trim(),
|
||||
validateRequest,
|
||||
workspaceController.getWorkspaceAuditLogActorFilterOpts
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:workspaceId/trusted-ips",
|
||||
param("workspaceId").exists().isString().trim(),
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN, MEMBER],
|
||||
@ -105,7 +140,7 @@ router.post(
|
||||
body("isActive").exists().isBoolean(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN],
|
||||
@ -122,7 +157,7 @@ router.patch(
|
||||
body("comment").default("").isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN],
|
||||
@ -137,7 +172,7 @@ router.delete(
|
||||
param("trustedIpId").exists().isString().trim(),
|
||||
validateRequest,
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AUTH_MODE_JWT],
|
||||
acceptedAuthModes: [AuthMode.JWT],
|
||||
}),
|
||||
requireWorkspaceAuth({
|
||||
acceptedRoles: [ADMIN],
|
||||
|
50
backend/src/ee/services/EEAuditLogService.ts
Normal file
50
backend/src/ee/services/EEAuditLogService.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { Types } from "mongoose";
|
||||
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;
|
||||
organizationId?: Types.ObjectId;
|
||||
}
|
||||
|
||||
type ValidEventScope =
|
||||
| Required<Pick<EventScope, "workspaceId">>
|
||||
| Required<Pick<EventScope, "organizationId">>
|
||||
| Required<EventScope>
|
||||
|
||||
export default class EEAuditLogService {
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
const auditLog = await new AuditLog({
|
||||
actor: authData.actor,
|
||||
organization: organizationId,
|
||||
workspace: ("workspaceId" in eventScope) ? eventScope.workspaceId : undefined,
|
||||
ipAddress: authData.ipAddress,
|
||||
event,
|
||||
userAgent: authData.userAgent,
|
||||
userAgentType: authData.userAgentType,
|
||||
expiresAt: new Date(Date.now() + ttl)
|
||||
});
|
||||
|
||||
if (shouldSave) {
|
||||
await auditLog.save();
|
||||
}
|
||||
|
||||
return auditLog;
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { Types } from "mongoose";
|
||||
import * as Sentry from "@sentry/node";
|
||||
import NodeCache from "node-cache";
|
||||
import {
|
||||
@ -31,6 +32,7 @@ interface FeatureSet {
|
||||
customRateLimits: boolean;
|
||||
customAlerts: boolean;
|
||||
auditLogs: boolean;
|
||||
auditLogsRetentionDays: number;
|
||||
samlSSO: boolean;
|
||||
status: "incomplete" | "incomplete_expired" | "trialing" | "active" | "past_due" | "canceled" | "unpaid" | null;
|
||||
trial_end: number | null;
|
||||
@ -63,9 +65,10 @@ class EELicenseService {
|
||||
pitRecovery: false,
|
||||
ipAllowlisting: false,
|
||||
rbac: true,
|
||||
customRateLimits: true,
|
||||
customAlerts: true,
|
||||
customRateLimits: false,
|
||||
customAlerts: false,
|
||||
auditLogs: false,
|
||||
auditLogsRetentionDays: 0,
|
||||
samlSSO: false,
|
||||
status: null,
|
||||
trial_end: null,
|
||||
@ -81,10 +84,10 @@ class EELicenseService {
|
||||
});
|
||||
}
|
||||
|
||||
public async getPlan(organizationId: string, workspaceId?: string): Promise<FeatureSet> {
|
||||
public async getPlan(organizationId: Types.ObjectId, workspaceId?: Types.ObjectId): Promise<FeatureSet> {
|
||||
try {
|
||||
if (this.instanceType === "cloud") {
|
||||
const cachedPlan = this.localFeatureSet.get<FeatureSet>(`${organizationId}-${workspaceId ?? ""}`);
|
||||
const cachedPlan = this.localFeatureSet.get<FeatureSet>(`${organizationId.toString()}-${workspaceId?.toString() ?? ""}`);
|
||||
if (cachedPlan) {
|
||||
return cachedPlan;
|
||||
}
|
||||
@ -101,7 +104,7 @@ class EELicenseService {
|
||||
const { data: { currentPlan } } = await licenseServerKeyRequest.get(url);
|
||||
|
||||
// cache fetched plan for organization
|
||||
this.localFeatureSet.set(`${organizationId}-${workspaceId ?? ""}`, currentPlan);
|
||||
this.localFeatureSet.set(`${organizationId.toString()}-${workspaceId?.toString() ?? ""}`, currentPlan);
|
||||
|
||||
return currentPlan;
|
||||
}
|
||||
@ -112,16 +115,16 @@ class EELicenseService {
|
||||
return this.globalFeatureSet;
|
||||
}
|
||||
|
||||
public async refreshPlan(organizationId: string, workspaceId?: string) {
|
||||
public async refreshPlan(organizationId: Types.ObjectId, workspaceId?: Types.ObjectId) {
|
||||
if (this.instanceType === "cloud") {
|
||||
this.localFeatureSet.del(`${organizationId}-${workspaceId ?? ""}`);
|
||||
this.localFeatureSet.del(`${organizationId.toString()}-${workspaceId?.toString() ?? ""}`);
|
||||
await this.getPlan(organizationId, workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
public async delPlan(organizationId: string) {
|
||||
public async delPlan(organizationId: Types.ObjectId) {
|
||||
if (this.instanceType === "cloud") {
|
||||
this.localFeatureSet.del(`${organizationId}-`);
|
||||
this.localFeatureSet.del(`${organizationId.toString()}-`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import EELicenseService from "./EELicenseService";
|
||||
/**
|
||||
* Class to handle Enterprise Edition secret actions
|
||||
*/
|
||||
class EESecretService {
|
||||
export default class EESecretService {
|
||||
/**
|
||||
* Save a secret snapshot that is a copy of the current state of secrets in workspace with id
|
||||
* [workspaceId] under a new snapshot with incremented version under the
|
||||
@ -71,5 +71,3 @@ class EESecretService {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default EESecretService;
|
||||
|
@ -0,0 +1,45 @@
|
||||
import { Probot } from "probot";
|
||||
import GitRisks from "../../models/gitRisks";
|
||||
import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation";
|
||||
import { scanGithubPushEventForSecretLeaks } from "../../../queues/secret-scanning/githubScanPushEvent";
|
||||
export default async (app: Probot) => {
|
||||
app.on("installation.deleted", async (context) => {
|
||||
const { payload } = context;
|
||||
const { installation, repositories } = payload;
|
||||
if (repositories) {
|
||||
for (const repository of repositories) {
|
||||
await GitRisks.deleteMany({ repositoryId: repository.id })
|
||||
}
|
||||
await GitAppOrganizationInstallation.deleteOne({ installationId: installation.id })
|
||||
}
|
||||
})
|
||||
|
||||
app.on("installation", async (context) => {
|
||||
const { payload } = context;
|
||||
payload.repositories
|
||||
const { installation, repositories } = payload;
|
||||
// TODO: start full repo scans
|
||||
})
|
||||
|
||||
app.on("push", async (context) => {
|
||||
const { payload } = context;
|
||||
const { commits, repository, installation, pusher } = payload;
|
||||
|
||||
if (!commits || !repository || !installation || !pusher) {
|
||||
return
|
||||
}
|
||||
|
||||
const installationLinkToOrgExists = await GitAppOrganizationInstallation.findOne({ installationId: installation?.id }).lean()
|
||||
if (!installationLinkToOrgExists) {
|
||||
return
|
||||
}
|
||||
|
||||
scanGithubPushEventForSecretLeaks({
|
||||
commits: commits,
|
||||
pusher: { name: pusher.name, email: pusher.email },
|
||||
repository: { fullName: repository.full_name, id: repository.id },
|
||||
organizationId: installationLinkToOrgExists.organizationId,
|
||||
installationId: installation.id
|
||||
})
|
||||
});
|
||||
};
|
142
backend/src/ee/services/GithubSecretScanning/helper.ts
Normal file
142
backend/src/ee/services/GithubSecretScanning/helper.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { exec } from "child_process";
|
||||
import { mkdir, readFile, rm, writeFile } from "fs";
|
||||
import { tmpdir } from "os";
|
||||
import { join } from "path"
|
||||
import { SecretMatch } from "./types";
|
||||
|
||||
export async function scanFullRepoContentAndGetFindings(octokit: any, installationId: number, repositoryFullName: string): Promise<SecretMatch[]> {
|
||||
const tempFolder = await createTempFolder();
|
||||
const findingsPath = join(tempFolder, "findings.json");
|
||||
const repoPath = join(tempFolder, "repo.git")
|
||||
try {
|
||||
const { data: { token }} = await octokit.apps.createInstallationAccessToken({installation_id: installationId})
|
||||
await cloneRepo(token, repositoryFullName, repoPath)
|
||||
await runInfisicalScanOnRepo(repoPath, findingsPath);
|
||||
const findingsData = await readFindingsFile(findingsPath);
|
||||
return JSON.parse(findingsData);
|
||||
} finally {
|
||||
await deleteTempFolder(tempFolder);
|
||||
}
|
||||
}
|
||||
|
||||
export async function scanContentAndGetFindings(textContent: string): Promise<SecretMatch[]> {
|
||||
const tempFolder = await createTempFolder();
|
||||
const filePath = join(tempFolder, "content.txt");
|
||||
const findingsPath = join(tempFolder, "findings.json");
|
||||
|
||||
try {
|
||||
await writeTextToFile(filePath, textContent);
|
||||
await runInfisicalScan(filePath, findingsPath);
|
||||
const findingsData = await readFindingsFile(findingsPath);
|
||||
return JSON.parse(findingsData);
|
||||
} finally {
|
||||
await deleteTempFolder(tempFolder);
|
||||
}
|
||||
}
|
||||
|
||||
export function createTempFolder(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tempDir = tmpdir()
|
||||
const tempFolderName = Math.random().toString(36).substring(2);
|
||||
const tempFolderPath = join(tempDir, tempFolderName);
|
||||
|
||||
mkdir(tempFolderPath, (err: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(tempFolderPath);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function writeTextToFile(filePath: string, content: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
writeFile(filePath, content, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function cloneRepo(installationAcccessToken: string, repositoryFullName: string, repoPath: string): Promise<void> {
|
||||
const cloneUrl = `https://x-access-token:${installationAcccessToken}@github.com/${repositoryFullName}.git`;
|
||||
const command = `git clone ${cloneUrl} ${repoPath} --bare`
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(command, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export function runInfisicalScanOnRepo(repoPath: string, outputPath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const command = `cd ${repoPath} && infisical scan --exit-code=77 -r "${outputPath}"`;
|
||||
exec(command, (error) => {
|
||||
if (error && error.code != 77) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function runInfisicalScan(inputPath: string, outputPath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const command = `cat "${inputPath}" | infisical scan --exit-code=77 --pipe -r "${outputPath}"`;
|
||||
exec(command, (error) => {
|
||||
if (error && error.code != 77) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function readFindingsFile(filePath: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
readFile(filePath, "utf8", (err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteTempFolder(folderPath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
rm(folderPath, { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function convertKeysToLowercase<T>(obj: T): T {
|
||||
const convertedObj = {} as T;
|
||||
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const lowercaseKey = key.charAt(0).toLowerCase() + key.slice(1);
|
||||
convertedObj[lowercaseKey as keyof T] = obj[key];
|
||||
}
|
||||
}
|
||||
|
||||
return convertedObj;
|
||||
}
|
21
backend/src/ee/services/GithubSecretScanning/types.ts
Normal file
21
backend/src/ee/services/GithubSecretScanning/types.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export type SecretMatch = {
|
||||
Description: string;
|
||||
StartLine: number;
|
||||
EndLine: number;
|
||||
StartColumn: number;
|
||||
EndColumn: number;
|
||||
Match: string;
|
||||
Secret: string;
|
||||
File: string;
|
||||
SymlinkFile: string;
|
||||
Commit: string;
|
||||
Entropy: number;
|
||||
Author: string;
|
||||
Email: string;
|
||||
Date: string;
|
||||
Message: string;
|
||||
Tags: string[];
|
||||
RuleID: string;
|
||||
Fingerprint: string;
|
||||
FingerPrintWithoutCommitId: string
|
||||
};
|
@ -1,9 +1,13 @@
|
||||
import EELicenseService from "./EELicenseService";
|
||||
import EESecretService from "./EESecretService";
|
||||
import EELogService from "./EELogService";
|
||||
import EEAuditLogService from "./EEAuditLogService";
|
||||
import GithubSecretScanningService from "./GithubSecretScanning/GithubSecretScanningService"
|
||||
|
||||
export {
|
||||
EELicenseService,
|
||||
EESecretService,
|
||||
EELogService,
|
||||
EEAuditLogService,
|
||||
GithubSecretScanningService
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { Request } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import jwt from "jsonwebtoken";
|
||||
import bcrypt from "bcrypt";
|
||||
@ -5,7 +6,6 @@ import {
|
||||
APIKeyData,
|
||||
ITokenVersion,
|
||||
IUser,
|
||||
ServiceAccount,
|
||||
ServiceTokenData,
|
||||
TokenVersion,
|
||||
User,
|
||||
@ -14,7 +14,6 @@ import {
|
||||
APIKeyDataNotFoundError,
|
||||
AccountNotFoundError,
|
||||
BadRequestError,
|
||||
ServiceAccountNotFoundError,
|
||||
ServiceTokenDataNotFoundError,
|
||||
UnauthorizedRequestError,
|
||||
} from "../utils/errors";
|
||||
@ -26,11 +25,15 @@ import {
|
||||
getJwtRefreshSecret,
|
||||
} from "../config";
|
||||
import {
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
AuthMode
|
||||
} from "../variables";
|
||||
import {
|
||||
ServiceTokenAuthData,
|
||||
UserAuthData
|
||||
} from "../interfaces/middleware";
|
||||
|
||||
import { ActorType } from "../ee/models";
|
||||
import { getUserAgentType } from "../utils/posthog";
|
||||
|
||||
/**
|
||||
*
|
||||
@ -42,7 +45,7 @@ export const validateAuthMode = ({
|
||||
acceptedAuthModes,
|
||||
}: {
|
||||
headers: { [key: string]: string | string[] | undefined },
|
||||
acceptedAuthModes: string[]
|
||||
acceptedAuthModes: AuthMode[]
|
||||
}) => {
|
||||
const apiKey = headers["x-api-key"];
|
||||
const authHeader = headers["authorization"];
|
||||
@ -55,7 +58,7 @@ export const validateAuthMode = ({
|
||||
|
||||
if (typeof apiKey === "string") {
|
||||
// case: treat request authentication type as via X-API-KEY (i.e. API Key)
|
||||
authMode = AUTH_MODE_API_KEY;
|
||||
authMode = AuthMode.API_KEY;
|
||||
authTokenValue = apiKey;
|
||||
}
|
||||
|
||||
@ -71,13 +74,10 @@ export const validateAuthMode = ({
|
||||
|
||||
switch (tokenValue.split(".", 1)[0]) {
|
||||
case "st":
|
||||
authMode = AUTH_MODE_SERVICE_TOKEN;
|
||||
break;
|
||||
case "sa":
|
||||
authMode = AUTH_MODE_SERVICE_ACCOUNT;
|
||||
authMode = AuthMode.SERVICE_TOKEN;
|
||||
break;
|
||||
default:
|
||||
authMode = AUTH_MODE_JWT;
|
||||
authMode = AuthMode.JWT;
|
||||
}
|
||||
|
||||
authTokenValue = tokenValue;
|
||||
@ -100,10 +100,12 @@ export const validateAuthMode = ({
|
||||
* @returns {User} user - user corresponding to JWT token
|
||||
*/
|
||||
export const getAuthUserPayload = async ({
|
||||
req,
|
||||
authTokenValue,
|
||||
}: {
|
||||
req: Request,
|
||||
authTokenValue: string;
|
||||
}) => {
|
||||
}): Promise<UserAuthData> => {
|
||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||
jwt.verify(authTokenValue, await getJwtAuthSecret())
|
||||
);
|
||||
@ -130,11 +132,25 @@ export const getAuthUserPayload = async ({
|
||||
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({
|
||||
message: "Failed to validate access token",
|
||||
});
|
||||
|
||||
return ({
|
||||
user,
|
||||
tokenVersionId: tokenVersion._id,
|
||||
});
|
||||
|
||||
return {
|
||||
actor: {
|
||||
type: ActorType.USER,
|
||||
metadata: {
|
||||
userId: user._id.toString(),
|
||||
email: user.email
|
||||
}
|
||||
},
|
||||
authPayload: user,
|
||||
ipAddress: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
userAgentType: getUserAgentType(req.headers["user-agent"])
|
||||
}
|
||||
|
||||
// return ({
|
||||
// user,
|
||||
// tokenVersionId: tokenVersion._id, // what to do with this? // move this out
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,10 +160,12 @@ export const getAuthUserPayload = async ({
|
||||
* @returns {ServiceTokenData} serviceTokenData - service token data
|
||||
*/
|
||||
export const getAuthSTDPayload = async ({
|
||||
req,
|
||||
authTokenValue,
|
||||
}: {
|
||||
req: Request,
|
||||
authTokenValue: string;
|
||||
}) => {
|
||||
}): Promise<ServiceTokenAuthData> => {
|
||||
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
|
||||
|
||||
const serviceTokenData = await ServiceTokenData
|
||||
@ -180,36 +198,21 @@ export const getAuthSTDPayload = async ({
|
||||
|
||||
if (!serviceTokenDataToReturn) throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" });
|
||||
|
||||
return serviceTokenDataToReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return service account access key payload
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.authTokenValue - service account access token value
|
||||
* @returns {ServiceAccount} serviceAccount
|
||||
*/
|
||||
export const getAuthSAAKPayload = async ({
|
||||
authTokenValue,
|
||||
}: {
|
||||
authTokenValue: string;
|
||||
}) => {
|
||||
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
|
||||
|
||||
const serviceAccount = await ServiceAccount.findById(
|
||||
Buffer.from(TOKEN_IDENTIFIER, "base64").toString("hex")
|
||||
).select("+secretHash");
|
||||
|
||||
if (!serviceAccount) {
|
||||
throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
|
||||
return {
|
||||
actor: {
|
||||
type: ActorType.SERVICE,
|
||||
metadata: {
|
||||
serviceId: serviceTokenDataToReturn._id.toString(),
|
||||
name: serviceTokenDataToReturn.name
|
||||
}
|
||||
},
|
||||
authPayload: serviceTokenDataToReturn,
|
||||
ipAddress: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
userAgentType: getUserAgentType(req.headers["user-agent"])
|
||||
}
|
||||
|
||||
const result = await bcrypt.compare(TOKEN_SECRET, serviceAccount.secretHash);
|
||||
if (!result) throw UnauthorizedRequestError({
|
||||
message: "Failed to authenticate service account access key",
|
||||
});
|
||||
|
||||
return serviceAccount;
|
||||
// return serviceTokenDataToReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,10 +222,12 @@ export const getAuthSAAKPayload = async ({
|
||||
* @returns {APIKeyData} apiKeyData - API key data
|
||||
*/
|
||||
export const getAuthAPIKeyPayload = async ({
|
||||
req,
|
||||
authTokenValue,
|
||||
}: {
|
||||
req: Request,
|
||||
authTokenValue: string;
|
||||
}) => {
|
||||
}): Promise<UserAuthData> => {
|
||||
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
|
||||
|
||||
let apiKeyData = await APIKeyData
|
||||
@ -264,7 +269,19 @@ export const getAuthAPIKeyPayload = async ({
|
||||
});
|
||||
}
|
||||
|
||||
return user;
|
||||
return {
|
||||
actor: {
|
||||
type: ActorType.USER,
|
||||
metadata: {
|
||||
userId: user._id.toString(),
|
||||
email: user.email
|
||||
}
|
||||
},
|
||||
authPayload: user,
|
||||
ipAddress: req.realIP,
|
||||
userAgent: req.headers["user-agent"] ?? "",
|
||||
userAgentType: getUserAgentType(req.headers["user-agent"])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -375,11 +392,9 @@ export const createToken = ({
|
||||
|
||||
export const validateProviderAuthToken = async ({
|
||||
email,
|
||||
user,
|
||||
providerAuthToken,
|
||||
}: {
|
||||
email: string;
|
||||
user: IUser,
|
||||
providerAuthToken?: string;
|
||||
}) => {
|
||||
if (!providerAuthToken) {
|
||||
@ -390,10 +405,7 @@ export const validateProviderAuthToken = async ({
|
||||
jwt.verify(providerAuthToken, await getJwtProviderAuthSecret())
|
||||
);
|
||||
|
||||
if (
|
||||
decodedToken.authProvider !== user.authProvider ||
|
||||
decodedToken.email !== email
|
||||
) {
|
||||
if (decodedToken.email !== email) {
|
||||
throw new Error("Invalid authentication credentials.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
} from "../variables";
|
||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||
import { InternalServerError } from "../utils/errors";
|
||||
import Folder from "../models/folder";
|
||||
import { Folder } from "../models";
|
||||
import { getFolderByPath } from "../services/FolderService";
|
||||
import { getAllImportedSecrets } from "../services/SecretImportService";
|
||||
import { expandSecrets } from "./secrets";
|
||||
|
@ -32,7 +32,7 @@ export const handleEventHelper = async ({ event }: { event: Event }) => {
|
||||
switch (event.name) {
|
||||
case EVENT_PUSH_SECRETS:
|
||||
if (bot) {
|
||||
await IntegrationService.syncIntegrations({
|
||||
IntegrationService.syncIntegrations({
|
||||
workspaceId,
|
||||
environment
|
||||
});
|
||||
|
@ -1,21 +1,23 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Bot, Integration, IntegrationAuth } from "../models";
|
||||
import { exchangeCode, exchangeRefresh, syncSecrets } from "../integrations";
|
||||
import { Bot, IIntegrationAuth, IntegrationAuth } from "../models";
|
||||
import { exchangeCode, exchangeRefresh } from "../integrations";
|
||||
import { BotService } from "../services";
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_VERCEL
|
||||
INTEGRATION_VERCEL,
|
||||
} from "../variables";
|
||||
import { UnauthorizedRequestError } from "../utils/errors";
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { IntegrationAuthMetadata } from "../models/integrationAuth/types";
|
||||
|
||||
interface Update {
|
||||
workspace: string;
|
||||
integration: string;
|
||||
teamId?: string;
|
||||
accountId?: string;
|
||||
metadata?: IntegrationAuthMetadata
|
||||
}
|
||||
|
||||
/**
|
||||
@ -34,12 +36,14 @@ export const handleOAuthExchangeHelper = async ({
|
||||
workspaceId,
|
||||
integration,
|
||||
code,
|
||||
environment
|
||||
environment,
|
||||
url
|
||||
}: {
|
||||
workspaceId: string;
|
||||
integration: string;
|
||||
code: string;
|
||||
environment: string;
|
||||
url?: string;
|
||||
}) => {
|
||||
const bot = await Bot.findOne({
|
||||
workspace: workspaceId,
|
||||
@ -51,7 +55,8 @@ export const handleOAuthExchangeHelper = async ({
|
||||
// exchange code for access and refresh tokens
|
||||
const res = await exchangeCode({
|
||||
integration,
|
||||
code
|
||||
code,
|
||||
url
|
||||
});
|
||||
|
||||
const update: Update = {
|
||||
@ -66,6 +71,11 @@ export const handleOAuthExchangeHelper = async ({
|
||||
case INTEGRATION_NETLIFY:
|
||||
update.accountId = res.accountId;
|
||||
break;
|
||||
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||
update.metadata = {
|
||||
authMethod: "oauth2"
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||
@ -94,7 +104,6 @@ export const handleOAuthExchangeHelper = async ({
|
||||
// set integration auth access token
|
||||
await setIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId: null,
|
||||
accessToken: res.accessToken,
|
||||
accessExpiresAt: res.accessExpiresAt
|
||||
});
|
||||
@ -102,69 +111,6 @@ export const handleOAuthExchangeHelper = async ({
|
||||
|
||||
return integrationAuth;
|
||||
};
|
||||
/**
|
||||
* Sync/push environment variables in workspace with id [workspaceId] to
|
||||
* all active integrations for that workspace
|
||||
* @param {Object} obj
|
||||
* @param {Object} obj.workspaceId - id of workspace
|
||||
*/
|
||||
export const syncIntegrationsHelper = async ({
|
||||
workspaceId,
|
||||
environment
|
||||
}: {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
}) => {
|
||||
try {
|
||||
const integrations = await Integration.find({
|
||||
workspace: workspaceId,
|
||||
...(environment
|
||||
? {
|
||||
environment
|
||||
}
|
||||
: {}),
|
||||
isActive: true,
|
||||
app: { $ne: null }
|
||||
});
|
||||
|
||||
// for each workspace integration, sync/push secrets
|
||||
// to that integration
|
||||
for await (const integration of integrations) {
|
||||
// get workspace, environment (shared) secrets
|
||||
const secrets = await BotService.getSecrets({
|
||||
workspaceId: integration.workspace,
|
||||
environment: integration.environment,
|
||||
secretPath: integration.secretPath
|
||||
});
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findById(integration.integrationAuth);
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||
|
||||
// get integration auth access token
|
||||
const access = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: integration.integrationAuth
|
||||
});
|
||||
|
||||
// sync secrets to integration
|
||||
await syncSecrets({
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets,
|
||||
accessId: access.accessId === undefined ? null : access.accessId,
|
||||
accessToken: access.accessToken
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
// eslint-disable-next-line
|
||||
console.log(
|
||||
`syncIntegrationsHelper: failed with [workspaceId=${workspaceId}] [environment=${environment}]`,
|
||||
err
|
||||
); // eslint-disable-line no-use-before-define
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return decrypted refresh token using the bot's copy
|
||||
@ -222,22 +168,24 @@ export const getIntegrationAuthAccessHelper = async ({
|
||||
message: "Failed to locate Integration Authentication credentials"
|
||||
});
|
||||
|
||||
accessToken = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.accessCiphertext as string,
|
||||
iv: integrationAuth.accessIV as string,
|
||||
tag: integrationAuth.accessTag as string
|
||||
});
|
||||
if (integrationAuth.accessCiphertext && integrationAuth.accessIV && integrationAuth.accessTag) {
|
||||
accessToken = await BotService.decryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
ciphertext: integrationAuth.accessCiphertext as string,
|
||||
iv: integrationAuth.accessIV as string,
|
||||
tag: integrationAuth.accessTag as string
|
||||
});
|
||||
}
|
||||
|
||||
if (integrationAuth?.accessExpiresAt && integrationAuth?.refreshCiphertext) {
|
||||
if (integrationAuth?.refreshCiphertext) {
|
||||
// there is a access token expiration date
|
||||
// and refresh token to exchange with the OAuth2 server
|
||||
const refreshToken = await getIntegrationAuthRefreshHelper({
|
||||
integrationAuthId
|
||||
});
|
||||
|
||||
if (integrationAuth.accessExpiresAt < new Date()) {
|
||||
if (integrationAuth?.accessExpiresAt && integrationAuth.accessExpiresAt < new Date()) {
|
||||
// access token is expired
|
||||
const refreshToken = await getIntegrationAuthRefreshHelper({
|
||||
integrationAuthId
|
||||
});
|
||||
accessToken = await exchangeRefresh({
|
||||
integrationAuth,
|
||||
refreshToken
|
||||
@ -258,6 +206,8 @@ export const getIntegrationAuthAccessHelper = async ({
|
||||
});
|
||||
}
|
||||
|
||||
if (!accessToken) throw InternalServerError();
|
||||
|
||||
return {
|
||||
accessId,
|
||||
accessToken
|
||||
@ -278,7 +228,7 @@ export const setIntegrationAuthRefreshHelper = async ({
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
refreshToken: string;
|
||||
}) => {
|
||||
}): Promise<IIntegrationAuth> => {
|
||||
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||
@ -303,6 +253,8 @@ export const setIntegrationAuthRefreshHelper = async ({
|
||||
new: true
|
||||
}
|
||||
);
|
||||
|
||||
if (!integrationAuth) throw InternalServerError();
|
||||
|
||||
return integrationAuth;
|
||||
};
|
||||
@ -323,20 +275,24 @@ export const setIntegrationAuthAccessHelper = async ({
|
||||
accessExpiresAt
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
accessId: string | null;
|
||||
accessToken: string;
|
||||
accessId?: string;
|
||||
accessToken?: string;
|
||||
accessExpiresAt: Date | undefined;
|
||||
}) => {
|
||||
let integrationAuth = await IntegrationAuth.findById(integrationAuthId);
|
||||
|
||||
if (!integrationAuth) throw new Error("Failed to find integration auth");
|
||||
|
||||
const encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: accessToken
|
||||
});
|
||||
|
||||
|
||||
let encryptedAccessTokenObj;
|
||||
let encryptedAccessIdObj;
|
||||
|
||||
if (accessToken) {
|
||||
encryptedAccessTokenObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
plaintext: accessToken
|
||||
});
|
||||
}
|
||||
|
||||
if (accessId) {
|
||||
encryptedAccessIdObj = await BotService.encryptSymmetric({
|
||||
workspaceId: integrationAuth.workspace,
|
||||
@ -350,11 +306,11 @@ export const setIntegrationAuthAccessHelper = async ({
|
||||
},
|
||||
{
|
||||
accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
|
||||
accessIdIV: encryptedAccessIdObj?.iv ?? undefined,
|
||||
accessIdTag: encryptedAccessIdObj?.tag ?? undefined,
|
||||
accessCiphertext: encryptedAccessTokenObj.ciphertext,
|
||||
accessIV: encryptedAccessTokenObj.iv,
|
||||
accessTag: encryptedAccessTokenObj.tag,
|
||||
accessIdIV: encryptedAccessIdObj?.iv,
|
||||
accessIdTag: encryptedAccessIdObj?.tag,
|
||||
accessCiphertext: encryptedAccessTokenObj?.ciphertext,
|
||||
accessIV: encryptedAccessTokenObj?.iv,
|
||||
accessTag: encryptedAccessTokenObj?.tag,
|
||||
accessExpiresAt,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
|
@ -115,5 +115,5 @@ export const updateSubscriptionOrgQuantity = async ({
|
||||
);
|
||||
}
|
||||
|
||||
await EELicenseService.refreshPlan(organizationId);
|
||||
await EELicenseService.refreshPlan(new Types.ObjectId(organizationId));
|
||||
};
|
@ -7,13 +7,15 @@ import {
|
||||
UpdateSecretParams
|
||||
} from "../interfaces/services/SecretService";
|
||||
import {
|
||||
Folder,
|
||||
ISecret,
|
||||
IServiceTokenData,
|
||||
Secret,
|
||||
SecretBlindIndexData,
|
||||
ServiceTokenData
|
||||
ServiceTokenData,
|
||||
TFolderRootSchema
|
||||
} from "../models";
|
||||
import { SecretVersion } from "../ee/models";
|
||||
import { EventType, SecretVersion } from "../ee/models";
|
||||
import {
|
||||
BadRequestError,
|
||||
InternalServerError,
|
||||
@ -29,6 +31,7 @@ import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
K8_USER_AGENT_NAME,
|
||||
SECRET_PERSONAL,
|
||||
SECRET_SHARED
|
||||
} from "../variables";
|
||||
@ -40,12 +43,11 @@ import {
|
||||
} from "../utils/crypto";
|
||||
import { TelemetryService } from "../services";
|
||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||
import { EELogService, EESecretService } from "../ee/services";
|
||||
import { EEAuditLogService, EELogService, EESecretService } from "../ee/services";
|
||||
import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/auth";
|
||||
import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService";
|
||||
import picomatch from "picomatch";
|
||||
import path from "path";
|
||||
import Folder, { TFolderRootSchema } from "../models/folder";
|
||||
|
||||
export const isValidScope = (
|
||||
authPayload: IServiceTokenData,
|
||||
@ -326,7 +328,8 @@ export const createSecretHelper = async ({
|
||||
secretCommentCiphertext,
|
||||
secretCommentIV,
|
||||
secretCommentTag,
|
||||
secretPath = "/"
|
||||
secretPath = "/",
|
||||
metadata
|
||||
}: CreateSecretParams) => {
|
||||
const secretBlindIndex = await generateSecretBlindIndexHelper({
|
||||
secretName,
|
||||
@ -392,7 +395,8 @@ export const createSecretHelper = async ({
|
||||
secretCommentTag,
|
||||
folder: folderId,
|
||||
algorithm: ALGORITHM_AES_256_GCM,
|
||||
keyEncoding: ENCODING_SCHEME_UTF8
|
||||
keyEncoding: ENCODING_SCHEME_UTF8,
|
||||
metadata
|
||||
}).save();
|
||||
|
||||
const secretVersion = new SecretVersion({
|
||||
@ -433,10 +437,27 @@ export const createSecretHelper = async ({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
channel: authData.userAgentType,
|
||||
ipAddress: authData.ipAddress
|
||||
}));
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
authData,
|
||||
{
|
||||
type: EventType.CREATE_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
secretId: secret._id.toString(),
|
||||
secretKey: secretName,
|
||||
secretVersion: secret.version
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId
|
||||
}
|
||||
);
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId,
|
||||
@ -446,7 +467,7 @@ export const createSecretHelper = async ({
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
if (postHogClient) {
|
||||
if (postHogClient && metadata?.source !== "signup") {
|
||||
postHogClient.capture({
|
||||
event: "secrets added",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
@ -457,8 +478,8 @@ export const createSecretHelper = async ({
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent
|
||||
channel: authData.userAgentType,
|
||||
userAgent: authData.userAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -478,6 +499,7 @@ export const getSecretsHelper = async ({
|
||||
workspaceId,
|
||||
environment,
|
||||
authData,
|
||||
folderId,
|
||||
secretPath = "/"
|
||||
}: GetSecretsParams) => {
|
||||
let secrets: ISecret[] = [];
|
||||
@ -487,7 +509,10 @@ export const getSecretsHelper = async ({
|
||||
throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
|
||||
}
|
||||
}
|
||||
const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
|
||||
if (!folderId) {
|
||||
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath);
|
||||
}
|
||||
|
||||
// get personal secrets first
|
||||
secrets = await Secret.find({
|
||||
@ -528,27 +553,57 @@ export const getSecretsHelper = async ({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
channel: authData.userAgentType,
|
||||
ipAddress: authData.ipAddress
|
||||
}));
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
authData,
|
||||
{
|
||||
type: EventType.GET_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
numberOfSecrets: secrets.length
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId
|
||||
}
|
||||
);
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: "secrets pulled",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData
|
||||
}),
|
||||
properties: {
|
||||
numberOfSecrets: secrets.length,
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent
|
||||
}
|
||||
});
|
||||
// reduce the number of events captured
|
||||
let shouldRecordK8Event = false
|
||||
if (authData.userAgent == K8_USER_AGENT_NAME) {
|
||||
const randomNumber = Math.random();
|
||||
if (randomNumber > 0.9) {
|
||||
shouldRecordK8Event = true
|
||||
}
|
||||
}
|
||||
|
||||
const numberOfSignupSecrets = (secrets.filter((secret) => secret?.metadata?.source === "signup")).length;
|
||||
const atLeastOneNonSignUpSecret = (secrets.length - numberOfSignupSecrets > 0)
|
||||
|
||||
if (postHogClient && atLeastOneNonSignUpSecret) {
|
||||
const shouldCapture = authData.userAgent !== K8_USER_AGENT_NAME || shouldRecordK8Event;
|
||||
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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return secrets;
|
||||
@ -622,15 +677,32 @@ export const getSecretHelper = async ({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
channel: authData.userAgentType,
|
||||
ipAddress: authData.ipAddress
|
||||
}));
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
authData,
|
||||
{
|
||||
type: EventType.GET_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
secretId: secret._id.toString(),
|
||||
secretKey: secretName,
|
||||
secretVersion: secret.version
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId
|
||||
}
|
||||
);
|
||||
|
||||
const postHogClient = await TelemetryService.getPostHogClient();
|
||||
|
||||
if (postHogClient) {
|
||||
postHogClient.capture({
|
||||
event: "secrets pull",
|
||||
event: "secrets pulled",
|
||||
distinctId: await TelemetryService.getDistinctId({
|
||||
authData
|
||||
}),
|
||||
@ -639,8 +711,8 @@ export const getSecretHelper = async ({
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent
|
||||
channel: authData.userAgentType,
|
||||
userAgent: authData.userAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -771,10 +843,27 @@ export const updateSecretHelper = async ({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
channel: authData.userAgentType,
|
||||
ipAddress: authData.ipAddress
|
||||
}));
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
authData,
|
||||
{
|
||||
type: EventType.UPDATE_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
secretId: secret._id.toString(),
|
||||
secretKey: secretName,
|
||||
secretVersion: secret.version
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId
|
||||
}
|
||||
);
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId,
|
||||
@ -795,8 +884,8 @@ export const updateSecretHelper = async ({
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent
|
||||
channel: authData.userAgentType,
|
||||
userAgent: authData.userAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -841,14 +930,14 @@ export const deleteSecretHelper = async ({
|
||||
if (type === SECRET_SHARED) {
|
||||
secrets = await Secret.find({
|
||||
secretBlindIndex,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
folder: folderId
|
||||
}).lean();
|
||||
|
||||
secret = await Secret.findOneAndDelete({
|
||||
secretBlindIndex,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
folder: folderId
|
||||
@ -864,7 +953,7 @@ export const deleteSecretHelper = async ({
|
||||
secret = await Secret.findOneAndDelete({
|
||||
secretBlindIndex,
|
||||
folder: folderId,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
environment,
|
||||
type,
|
||||
...getAuthDataPayloadUserObj(authData)
|
||||
@ -894,10 +983,27 @@ export const deleteSecretHelper = async ({
|
||||
...getAuthDataPayloadIdObj(authData),
|
||||
workspaceId,
|
||||
actions: [action],
|
||||
channel: authData.authChannel,
|
||||
ipAddress: authData.authIP
|
||||
channel: authData.userAgentType,
|
||||
ipAddress: authData.ipAddress
|
||||
}));
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
authData,
|
||||
{
|
||||
type: EventType.DELETE_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
secretId: secret._id.toString(),
|
||||
secretKey: secretName,
|
||||
secretVersion: secret.version
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId
|
||||
}
|
||||
);
|
||||
|
||||
// (EE) take a secret snapshot
|
||||
await EESecretService.takeSecretSnapshot({
|
||||
workspaceId,
|
||||
@ -918,8 +1024,8 @@ export const deleteSecretHelper = async ({
|
||||
environment,
|
||||
workspaceId,
|
||||
folderId,
|
||||
channel: authData.authChannel,
|
||||
userAgent: authData.authUserAgent
|
||||
channel: authData.userAgentType,
|
||||
userAgent: authData.userAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1004,7 +1110,8 @@ const recursivelyExpandSecret = async (
|
||||
|
||||
let interpolatedValue = interpolatedSec[key];
|
||||
if (!interpolatedValue) {
|
||||
throw new Error(`Couldn't find referenced value - ${key}`);
|
||||
console.error(`Couldn't find referenced value - ${key}`);
|
||||
return "";
|
||||
}
|
||||
|
||||
const refs = interpolatedValue.match(INTERPOLATION_SYNTAX_REG);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
Bot,
|
||||
Key,
|
||||
@ -25,7 +26,7 @@ export const createWorkspace = async ({
|
||||
organizationId,
|
||||
}: {
|
||||
name: string;
|
||||
organizationId: string;
|
||||
organizationId: Types.ObjectId;
|
||||
}) => {
|
||||
// create workspace
|
||||
const workspace = await new Workspace({
|
||||
|
@ -5,8 +5,8 @@ import express from "express";
|
||||
require("express-async-errors");
|
||||
import helmet from "helmet";
|
||||
import cors from "cors";
|
||||
import { DatabaseService, GithubSecretScanningService } from "./services";
|
||||
import { EELicenseService } from "./ee/services";
|
||||
import { DatabaseService } from "./services";
|
||||
import { EELicenseService, GithubSecretScanningService } from "./ee/services";
|
||||
import { setUpHealthEndpoint } from "./services/health";
|
||||
import cookieParser from "cookie-parser";
|
||||
import swaggerUi = require("swagger-ui-express");
|
||||
@ -24,6 +24,7 @@ import {
|
||||
secretSnapshot as eeSecretSnapshotRouter,
|
||||
users as eeUsersRouter,
|
||||
workspace as eeWorkspaceRouter,
|
||||
secretScanning as v1SecretScanningRouter
|
||||
} from "./ee/routes/v1";
|
||||
import {
|
||||
auth as v1AuthRouter,
|
||||
@ -38,7 +39,6 @@ import {
|
||||
password as v1PasswordRouter,
|
||||
secretImport as v1SecretImportRouter,
|
||||
secret as v1SecretRouter,
|
||||
secretScanning as v1SecretScanningRouter,
|
||||
secretsFolder as v1SecretsFolder,
|
||||
serviceToken as v1ServiceTokenRouter,
|
||||
signup as v1SignupRouter,
|
||||
@ -58,7 +58,7 @@ import {
|
||||
signup as v2SignupRouter,
|
||||
tags as v2TagsRouter,
|
||||
users as v2UsersRouter,
|
||||
workspace as v2WorkspaceRouter,
|
||||
workspace as v2WorkspaceRouter
|
||||
} from "./routes/v2";
|
||||
import {
|
||||
auth as v3AuthRouter,
|
||||
@ -70,12 +70,21 @@ import { healthCheck } from "./routes/status";
|
||||
import { getLogger } from "./utils/logger";
|
||||
import { RouteNotFoundError } from "./utils/errors";
|
||||
import { requestErrorHandler } from "./middleware/requestErrorHandler";
|
||||
import { getNodeEnv, getPort, getSecretScanningGitAppId, getSecretScanningPrivateKey, getSecretScanningWebhookProxy, getSecretScanningWebhookSecret, getSiteURL } from "./config";
|
||||
import {
|
||||
getNodeEnv,
|
||||
getPort,
|
||||
getSecretScanningGitAppId,
|
||||
getSecretScanningPrivateKey,
|
||||
getSecretScanningWebhookProxy,
|
||||
getSecretScanningWebhookSecret,
|
||||
getSiteURL
|
||||
} from "./config";
|
||||
import { setup } from "./utils/setup";
|
||||
const SmeeClient = require('smee-client') // eslint-disable-line
|
||||
import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices";
|
||||
import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent";
|
||||
const SmeeClient = require("smee-client"); // eslint-disable-line
|
||||
|
||||
const main = async () => {
|
||||
|
||||
await setup();
|
||||
|
||||
await EELicenseService.initGlobalFeatureSet();
|
||||
@ -92,11 +101,15 @@ const main = async () => {
|
||||
})
|
||||
);
|
||||
|
||||
if (await getSecretScanningGitAppId() && await getSecretScanningWebhookSecret() && await getSecretScanningPrivateKey()) {
|
||||
if (
|
||||
(await getSecretScanningGitAppId()) &&
|
||||
(await getSecretScanningWebhookSecret()) &&
|
||||
(await getSecretScanningPrivateKey())
|
||||
) {
|
||||
const probot = new Probot({
|
||||
appId: await getSecretScanningGitAppId(),
|
||||
privateKey: await getSecretScanningPrivateKey(),
|
||||
secret: await getSecretScanningWebhookSecret(),
|
||||
secret: await getSecretScanningWebhookSecret()
|
||||
});
|
||||
|
||||
if ((await getNodeEnv()) != "production") {
|
||||
@ -104,12 +117,14 @@ const main = async () => {
|
||||
source: await getSecretScanningWebhookProxy(),
|
||||
target: "http://backend:4000/ss-webhook",
|
||||
logger: console
|
||||
})
|
||||
});
|
||||
|
||||
smee.start()
|
||||
smee.start();
|
||||
}
|
||||
|
||||
app.use(createNodeMiddleware(GithubSecretScanningService, { probot, webhooksPath: "/ss-webhook" })); // secret scanning webhook
|
||||
app.use(
|
||||
createNodeMiddleware(GithubSecretScanningService, { probot, webhooksPath: "/ss-webhook" })
|
||||
); // secret scanning webhook
|
||||
}
|
||||
|
||||
if ((await getNodeEnv()) === "production") {
|
||||
@ -205,6 +220,8 @@ const main = async () => {
|
||||
|
||||
server.on("close", async () => {
|
||||
await DatabaseService.closeDatabase();
|
||||
syncSecretsToThirdPartyServices.close();
|
||||
githubPushEventSecretScan.close();
|
||||
});
|
||||
|
||||
return server;
|
||||
|
@ -18,6 +18,10 @@ import {
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_FLYIO_API_URL,
|
||||
INTEGRATION_GCP_API_URL,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME,
|
||||
INTEGRATION_GCP_SERVICE_USAGE_URL,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_GITLAB_API_URL,
|
||||
@ -79,6 +83,11 @@ const getApps = async ({
|
||||
}) => {
|
||||
let apps: App[] = [];
|
||||
switch (integrationAuth.integration) {
|
||||
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||
apps = await getAppsGCPSecretManager({
|
||||
accessToken,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_AZURE_KEY_VAULT:
|
||||
apps = [];
|
||||
break;
|
||||
@ -210,6 +219,96 @@ const getApps = async ({
|
||||
return apps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of apps for GCP secret manager integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.accessToken - access token for GCP API
|
||||
* @returns {Object[]} apps - list of GCP projects
|
||||
* @returns {String} apps.name - name of GCP project
|
||||
* @returns {String} apps.appId - id of GCP project
|
||||
*/
|
||||
const getAppsGCPSecretManager = async ({ accessToken }: { accessToken: string }) => {
|
||||
|
||||
interface GCPApp {
|
||||
projectNumber: string;
|
||||
projectId: string;
|
||||
lifecycleState: "ACTIVE" | "LIFECYCLE_STATE_UNSPECIFIED" | "DELETE_REQUESTED" | "DELETE_IN_PROGRESS";
|
||||
name: string;
|
||||
createTime: string;
|
||||
parent: {
|
||||
type: "organization" | "folder" | "project";
|
||||
id: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface GCPGetProjectsRes {
|
||||
projects: GCPApp[];
|
||||
nextPageToken?: string;
|
||||
}
|
||||
|
||||
interface GCPGetServiceRes {
|
||||
name: string;
|
||||
parent: string;
|
||||
state: "ENABLED" | "DISABLED" | "STATE_UNSPECIFIED"
|
||||
}
|
||||
|
||||
let gcpApps: GCPApp[] = [];
|
||||
const apps: App[] = [];
|
||||
|
||||
const pageSize = 100;
|
||||
let pageToken: string | undefined;
|
||||
let hasMorePages = true;
|
||||
|
||||
while (hasMorePages) {
|
||||
const params = new URLSearchParams({
|
||||
pageSize: String(pageSize),
|
||||
...(pageToken ? { pageToken } : {})
|
||||
});
|
||||
|
||||
const res: GCPGetProjectsRes = (await standardRequest.get(`${INTEGRATION_GCP_API_URL}/v1/projects`, {
|
||||
params,
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
})
|
||||
)
|
||||
.data;
|
||||
|
||||
gcpApps = gcpApps.concat(res.projects);
|
||||
|
||||
if (!res.nextPageToken) {
|
||||
hasMorePages = false;
|
||||
}
|
||||
|
||||
pageToken = res.nextPageToken;
|
||||
}
|
||||
|
||||
for await (const gcpApp of gcpApps) {
|
||||
try {
|
||||
const res: GCPGetServiceRes = (await standardRequest.get(
|
||||
`${INTEGRATION_GCP_SERVICE_USAGE_URL}/v1/projects/${gcpApp.projectId}/services/${INTEGRATION_GCP_SECRET_MANAGER_SERVICE_NAME}`, {
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
)).data;
|
||||
|
||||
if (res.state === "ENABLED") {
|
||||
apps.push({
|
||||
name: gcpApp.name,
|
||||
appId: gcpApp.projectId
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return apps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of apps for Heroku integration
|
||||
* @param {Object} obj
|
||||
@ -751,7 +850,7 @@ const getAppsTeamCity = async ({
|
||||
},
|
||||
})
|
||||
).data.project.slice(1);
|
||||
|
||||
|
||||
const apps = res.map((a: any) => {
|
||||
return {
|
||||
name: a.name,
|
||||
|
@ -4,6 +4,8 @@ import {
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GCP_TOKEN_URL,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITHUB_TOKEN_URL,
|
||||
INTEGRATION_GITLAB,
|
||||
@ -13,17 +15,19 @@ import {
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_VERCEL_TOKEN_URL,
|
||||
INTEGRATION_VERCEL_TOKEN_URL
|
||||
} from "../variables";
|
||||
import {
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGCPSecretManager,
|
||||
getClientIdGitHub,
|
||||
getClientIdGitLab,
|
||||
getClientIdNetlify,
|
||||
getClientIdVercel,
|
||||
getClientSecretAzure,
|
||||
getClientSecretBitBucket,
|
||||
getClientSecretGCPSecretManager,
|
||||
getClientSecretGitHub,
|
||||
getClientSecretGitLab,
|
||||
getClientSecretHeroku,
|
||||
@ -42,6 +46,14 @@ interface ExchangeCodeAzureResponse {
|
||||
id_token: string;
|
||||
}
|
||||
|
||||
interface ExchangeCodeGCPResponse {
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scope: string;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
interface ExchangeCodeHerokuResponse {
|
||||
token_type: string;
|
||||
access_token: string;
|
||||
@ -106,13 +118,20 @@ interface ExchangeCodeBitBucketResponse {
|
||||
const exchangeCode = async ({
|
||||
integration,
|
||||
code,
|
||||
url
|
||||
}: {
|
||||
integration: string;
|
||||
code: string;
|
||||
url?: string;
|
||||
}) => {
|
||||
let obj = {} as any;
|
||||
|
||||
switch (integration) {
|
||||
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||
obj = await exchangeCodeGCP({
|
||||
code,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_AZURE_KEY_VAULT:
|
||||
obj = await exchangeCodeAzure({
|
||||
code,
|
||||
@ -141,6 +160,7 @@ const exchangeCode = async ({
|
||||
case INTEGRATION_GITLAB:
|
||||
obj = await exchangeCodeGitlab({
|
||||
code,
|
||||
url
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_BITBUCKET:
|
||||
@ -153,6 +173,40 @@ const exchangeCode = async ({
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return [accessToken] for GCP OAuth2 code-token exchange
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.code - code for code-token exchange
|
||||
* @returns {Object} obj2
|
||||
* @returns {String} obj2.accessToken - access token for GCP API
|
||||
* @returns {String} obj2.refreshToken - refresh token for GCP API
|
||||
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||
*/
|
||||
const exchangeCodeGCP = async ({ code }: { code: string }) => {
|
||||
const accessExpiresAt = new Date();
|
||||
|
||||
const res: ExchangeCodeGCPResponse = (
|
||||
await standardRequest.post(
|
||||
INTEGRATION_GCP_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code: code,
|
||||
client_id: await getClientIdGCPSecretManager(),
|
||||
client_secret: await getClientSecretGCPSecretManager(),
|
||||
redirect_uri: `${await getSiteURL()}/integrations/gcp-secret-manager/oauth2/callback`,
|
||||
} as any)
|
||||
)
|
||||
).data;
|
||||
|
||||
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + res.expires_in);
|
||||
|
||||
return {
|
||||
accessToken: res.access_token,
|
||||
refreshToken: res.refresh_token,
|
||||
accessExpiresAt,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return [accessToken] for Azure OAuth2 code-token exchange
|
||||
* @param param0
|
||||
@ -337,11 +391,17 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
|
||||
* @returns {String} obj2.refreshToken - refresh token for Gitlab API
|
||||
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
|
||||
*/
|
||||
const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
||||
const exchangeCodeGitlab = async ({
|
||||
code,
|
||||
url
|
||||
}: {
|
||||
code: string,
|
||||
url?: string;
|
||||
}) => {
|
||||
const accessExpiresAt = new Date();
|
||||
const res: ExchangeCodeGitlabResponse = (
|
||||
await standardRequest.post(
|
||||
INTEGRATION_GITLAB_TOKEN_URL,
|
||||
url ? `${url}/oauth/token` : INTEGRATION_GITLAB_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code: code,
|
||||
|
@ -2,7 +2,6 @@ import { exchangeCode } from "./exchange";
|
||||
import { exchangeRefresh } from "./refresh";
|
||||
import { getApps } from "./apps";
|
||||
import { getTeams } from "./teams";
|
||||
import { syncSecrets } from "./sync";
|
||||
import { revokeAccess } from "./revoke";
|
||||
|
||||
export {
|
||||
@ -10,6 +9,5 @@ export {
|
||||
exchangeRefresh,
|
||||
getApps,
|
||||
getTeams,
|
||||
syncSecrets,
|
||||
revokeAccess,
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import { standardRequest } from "../config/request";
|
||||
import { IIntegrationAuth } from "../models";
|
||||
import {
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_BITBUCKET_TOKEN_URL,
|
||||
INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GCP_TOKEN_URL,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_HEROKU
|
||||
} from "../variables";
|
||||
import {
|
||||
INTEGRATION_AZURE_TOKEN_URL,
|
||||
@ -16,9 +20,11 @@ import { IntegrationService } from "../services";
|
||||
import {
|
||||
getClientIdAzure,
|
||||
getClientIdBitBucket,
|
||||
getClientIdGCPSecretManager,
|
||||
getClientIdGitLab,
|
||||
getClientSecretAzure,
|
||||
getClientSecretBitBucket,
|
||||
getClientSecretGCPSecretManager,
|
||||
getClientSecretGitLab,
|
||||
getClientSecretHeroku,
|
||||
getSiteURL,
|
||||
@ -59,6 +65,19 @@ interface RefreshTokenBitBucketResponse {
|
||||
state: string;
|
||||
}
|
||||
|
||||
interface ServiceAccountAccessTokenGCPSecretManagerResponse {
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
interface RefreshTokenGCPSecretManagerResponse {
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
scope: string;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return new access token by exchanging refresh token [refreshToken] for integration
|
||||
* named [integration]
|
||||
@ -93,6 +112,7 @@ const exchangeRefresh = async ({
|
||||
break;
|
||||
case INTEGRATION_GITLAB:
|
||||
tokenDetails = await exchangeRefreshGitLab({
|
||||
integrationAuth,
|
||||
refreshToken,
|
||||
});
|
||||
break;
|
||||
@ -101,18 +121,23 @@ const exchangeRefresh = async ({
|
||||
refreshToken,
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||
tokenDetails = await exchangeRefreshGCPSecretManager({
|
||||
integrationAuth,
|
||||
refreshToken,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error("Failed to exchange token for incompatible integration");
|
||||
}
|
||||
|
||||
if (
|
||||
tokenDetails?.accessToken &&
|
||||
tokenDetails?.refreshToken &&
|
||||
tokenDetails?.accessExpiresAt
|
||||
tokenDetails.accessToken &&
|
||||
tokenDetails.refreshToken &&
|
||||
tokenDetails.accessExpiresAt
|
||||
) {
|
||||
await IntegrationService.setIntegrationAuthAccess({
|
||||
integrationAuthId: integrationAuth._id.toString(),
|
||||
accessId: null,
|
||||
accessToken: tokenDetails.accessToken,
|
||||
accessExpiresAt: tokenDetails.accessExpiresAt,
|
||||
});
|
||||
@ -202,17 +227,21 @@ const exchangeRefreshHeroku = async ({
|
||||
* @returns
|
||||
*/
|
||||
const exchangeRefreshGitLab = async ({
|
||||
integrationAuth,
|
||||
refreshToken,
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
refreshToken: string;
|
||||
}) => {
|
||||
const accessExpiresAt = new Date();
|
||||
const url = integrationAuth.url;
|
||||
|
||||
const {
|
||||
data,
|
||||
}: {
|
||||
data: RefreshTokenGitLabResponse;
|
||||
} = await standardRequest.post(
|
||||
INTEGRATION_GITLAB_TOKEN_URL,
|
||||
url ? `${url}/oauth/token` : INTEGRATION_GITLAB_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: refreshToken,
|
||||
@ -278,4 +307,76 @@ const exchangeRefreshBitBucket = async ({
|
||||
};
|
||||
};
|
||||
|
||||
export { exchangeRefresh };
|
||||
/**
|
||||
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||
* GCP Secret Manager integration
|
||||
* @param {Object} obj
|
||||
* @param {String} obj.refreshToken - refresh token to use to get new access token for GCP Secret Manager
|
||||
* @returns
|
||||
*/
|
||||
const exchangeRefreshGCPSecretManager = async ({
|
||||
integrationAuth,
|
||||
refreshToken,
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
refreshToken: string;
|
||||
}) => {
|
||||
const accessExpiresAt = new Date();
|
||||
|
||||
if (integrationAuth.metadata?.authMethod === "serviceAccount") {
|
||||
const serviceAccount = JSON.parse(refreshToken);
|
||||
|
||||
const payload = {
|
||||
iss: serviceAccount.client_email,
|
||||
aud: serviceAccount.token_uri,
|
||||
scope: INTEGRATION_GCP_CLOUD_PLATFORM_SCOPE,
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + 3600,
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, serviceAccount.private_key, { algorithm: "RS256" });
|
||||
|
||||
const { data }: { data: ServiceAccountAccessTokenGCPSecretManagerResponse } = await standardRequest.post(
|
||||
INTEGRATION_GCP_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
||||
assertion: token
|
||||
}).toString(),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
|
||||
|
||||
return {
|
||||
accessToken: data.access_token,
|
||||
refreshToken,
|
||||
accessExpiresAt
|
||||
};
|
||||
}
|
||||
|
||||
const { data }: { data: RefreshTokenGCPSecretManagerResponse } = (
|
||||
await standardRequest.post(
|
||||
INTEGRATION_GCP_TOKEN_URL,
|
||||
new URLSearchParams({
|
||||
client_id: await getClientIdGCPSecretManager(),
|
||||
client_secret: await getClientSecretGCPSecretManager(),
|
||||
refresh_token: refreshToken,
|
||||
grant_type: "refresh_token",
|
||||
} as any)
|
||||
)
|
||||
);
|
||||
|
||||
accessExpiresAt.setSeconds(accessExpiresAt.getSeconds() + data.expires_in);
|
||||
|
||||
return {
|
||||
accessToken: data.access_token,
|
||||
refreshToken,
|
||||
accessExpiresAt,
|
||||
};
|
||||
};
|
||||
|
||||
export { exchangeRefresh };
|
@ -26,6 +26,8 @@ import {
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_FLYIO_API_URL,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GCP_SECRET_MANAGER_URL,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_GITLAB_API_URL,
|
||||
@ -92,6 +94,13 @@ const syncSecrets = async ({
|
||||
accessToken: string;
|
||||
}) => {
|
||||
switch (integration.integration) {
|
||||
case INTEGRATION_GCP_SECRET_MANAGER:
|
||||
await syncSecretsGCPSecretManager({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_AZURE_KEY_VAULT:
|
||||
await syncSecretsAzureKeyVault({
|
||||
integration,
|
||||
@ -147,6 +156,7 @@ const syncSecrets = async ({
|
||||
break;
|
||||
case INTEGRATION_GITLAB:
|
||||
await syncSecretsGitLab({
|
||||
integrationAuth,
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
@ -245,7 +255,7 @@ const syncSecrets = async ({
|
||||
integrationAuth,
|
||||
integration,
|
||||
secrets,
|
||||
accessToken,
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_BITBUCKET:
|
||||
@ -286,6 +296,165 @@ const syncSecrets = async ({
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to GCP secret manager project
|
||||
* @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 - access token for GCP secret manager
|
||||
*/
|
||||
const syncSecretsGCPSecretManager = async ({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
}: {
|
||||
integration: IIntegration;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
interface GCPSecret {
|
||||
name: string;
|
||||
createTime: string;
|
||||
}
|
||||
|
||||
interface GCPSMListSecretsRes {
|
||||
secrets?: GCPSecret[];
|
||||
totalSize?: number;
|
||||
nextPageToken?: string;
|
||||
}
|
||||
|
||||
let gcpSecrets: GCPSecret[] = [];
|
||||
|
||||
const pageSize = 100;
|
||||
let pageToken: string | undefined;
|
||||
let hasMorePages = true;
|
||||
|
||||
while (hasMorePages) {
|
||||
const params = new URLSearchParams({
|
||||
pageSize: String(pageSize),
|
||||
...(pageToken ? { pageToken } : {})
|
||||
});
|
||||
|
||||
const res: GCPSMListSecretsRes = (await standardRequest.get(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets`,
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
"Authorization": `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
)).data;
|
||||
|
||||
if (res.secrets) {
|
||||
gcpSecrets = gcpSecrets.concat(res.secrets);
|
||||
}
|
||||
|
||||
if (!res.nextPageToken) {
|
||||
hasMorePages = false;
|
||||
}
|
||||
|
||||
pageToken = res.nextPageToken;
|
||||
}
|
||||
|
||||
const res: { [key: string]: string; } = {};
|
||||
|
||||
interface GCPLatestSecretVersionAccess {
|
||||
name: string;
|
||||
payload: {
|
||||
data: string;
|
||||
}
|
||||
}
|
||||
|
||||
for await (const gcpSecret of gcpSecrets) {
|
||||
const arr = gcpSecret.name.split("/");
|
||||
const key = arr[arr.length - 1];
|
||||
|
||||
const secretLatest: GCPLatestSecretVersionAccess = (await standardRequest.get(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}/versions/latest:access`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
)).data;
|
||||
|
||||
res[key] = Buffer.from(secretLatest.payload.data, "base64").toString("utf-8");
|
||||
}
|
||||
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
if (!(key in res)) {
|
||||
// case: create secret
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets`,
|
||||
{
|
||||
replication: {
|
||||
automatic: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
params: {
|
||||
secretId: key
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
||||
{
|
||||
payload: {
|
||||
data: Buffer.from(secrets[key].value).toString("base64")
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for await (const key of Object.keys(res)) {
|
||||
if (!(key in secrets)) {
|
||||
// case: delete secret
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// case: update secret
|
||||
if (secrets[key].value !== res[key]) {
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_GCP_SECRET_MANAGER_URL}/v1beta1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
||||
{
|
||||
payload: {
|
||||
data: Buffer.from(secrets[key].value).toString("base64")
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to Azure Key Vault with vault URI [integration.app]
|
||||
* @param {Object} obj
|
||||
@ -520,15 +689,17 @@ const syncSecretsAWSParameterStore = async ({
|
||||
const parameterList = (await ssm.getParametersByPath(params).promise()).Parameters;
|
||||
|
||||
let awsParameterStoreSecretsObj: {
|
||||
[key: string]: any; // TODO: fix type
|
||||
[key: string]: any;
|
||||
} = {};
|
||||
|
||||
if (parameterList) {
|
||||
awsParameterStoreSecretsObj = parameterList.reduce(
|
||||
(obj: any, secret: any) => ({
|
||||
...obj,
|
||||
[secret.Name.split("/").pop()]: secret
|
||||
}),
|
||||
(obj: any, secret: any) => {
|
||||
return ({
|
||||
...obj,
|
||||
[secret.Name.substring(integration.path.length)]: secret
|
||||
});
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
@ -1643,10 +1814,12 @@ const syncSecretsTravisCI = async ({
|
||||
* @param {String} obj.accessToken - access token for GitLab integration
|
||||
*/
|
||||
const syncSecretsGitLab = async ({
|
||||
integrationAuth,
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
integration: IIntegration;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
@ -1656,16 +1829,18 @@ const syncSecretsGitLab = async ({
|
||||
value: string;
|
||||
environment_scope: string;
|
||||
}
|
||||
|
||||
const gitLabApiUrl = integrationAuth.url ? `${integrationAuth.url}/api` : INTEGRATION_GITLAB_API_URL;
|
||||
|
||||
const getAllEnvVariables = async (integrationAppId: string, accessToken: string) => {
|
||||
const gitLabApiUrl = `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integrationAppId}/variables`;
|
||||
const headers = {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
"Accept-Encoding": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
|
||||
let allEnvVariables: GitLabSecret[] = [];
|
||||
let url: string | null = `${gitLabApiUrl}?per_page=100`;
|
||||
let url: string | null = `${gitLabApiUrl}/v4/projects/${integrationAppId}/variables?per_page=100`;
|
||||
|
||||
while (url) {
|
||||
const response: any = await standardRequest.get(url, { headers });
|
||||
@ -1693,7 +1868,7 @@ const syncSecretsGitLab = async ({
|
||||
const existingSecret = getSecretsRes.find((s: any) => s.key == key);
|
||||
if (!existingSecret) {
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`,
|
||||
`${gitLabApiUrl}/v4/projects/${integration?.appId}/variables`,
|
||||
{
|
||||
key: key,
|
||||
value: secrets[key].value,
|
||||
@ -1714,7 +1889,7 @@ const syncSecretsGitLab = async ({
|
||||
// update secret
|
||||
if (secrets[key].value !== existingSecret.value) {
|
||||
await standardRequest.put(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||
`${gitLabApiUrl}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||
{
|
||||
...existingSecret,
|
||||
value: secrets[existingSecret.key].value
|
||||
@ -1735,7 +1910,7 @@ const syncSecretsGitLab = async ({
|
||||
for await (const sec of getSecretsRes) {
|
||||
if (!(sec.key in secrets)) {
|
||||
await standardRequest.delete(
|
||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||
`${gitLabApiUrl}/v4/projects/${integration?.appId}/variables/${sec.key}?filter[environment_scope]=${integration.targetEnvironment}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
@ -1794,7 +1969,16 @@ const syncSecretsSupabase = async ({
|
||||
|
||||
const secretsToDelete: any = [];
|
||||
getSecretsRes?.forEach((secretObj: any) => {
|
||||
if (!(secretObj.name in secrets)) {
|
||||
if (
|
||||
!(secretObj.name in secrets) &&
|
||||
// supbase reserved secret ref: https://supabase.com/docs/guides/functions/secrets#default-secrets
|
||||
![
|
||||
"SUPABASE_ANON_KEY",
|
||||
"SUPABASE_SERVICE_ROLE_KEY",
|
||||
"SUPABASE_DB_URL",
|
||||
"SUPABASE_URL"
|
||||
].includes(secretObj.name)
|
||||
) {
|
||||
secretsToDelete.push(secretObj.name);
|
||||
}
|
||||
});
|
||||
@ -1828,7 +2012,7 @@ const syncSecretsCheckly = async ({
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
// get secrets from travis-ci
|
||||
|
||||
const getSecretsRes = (
|
||||
await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, {
|
||||
headers: {
|
||||
@ -1850,7 +2034,6 @@ const syncSecretsCheckly = async ({
|
||||
if (!(key in getSecretsRes)) {
|
||||
// case: secret does not exist in checkly
|
||||
// -> add secret
|
||||
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_CHECKLY_API_URL}/v1/variables`,
|
||||
{
|
||||
@ -2009,7 +2192,7 @@ const syncSecretsTerraformCloud = async ({
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to TeamCity project
|
||||
* Sync/push [secrets] to TeamCity project (and optionally build config)
|
||||
* @param {Object} obj
|
||||
* @param {IIntegration} obj.integration - integration details
|
||||
* @param {Object} obj.secrets - secrets to push to integration
|
||||
@ -2019,7 +2202,7 @@ const syncSecretsTeamCity = async ({
|
||||
integrationAuth,
|
||||
integration,
|
||||
secrets,
|
||||
accessToken,
|
||||
accessToken
|
||||
}: {
|
||||
integrationAuth: IIntegrationAuth;
|
||||
integration: IIntegration;
|
||||
@ -2031,62 +2214,124 @@ const syncSecretsTeamCity = async ({
|
||||
value: string;
|
||||
}
|
||||
|
||||
// get secrets from Teamcity
|
||||
const res = (
|
||||
await standardRequest.get(
|
||||
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
|
||||
interface TeamCityBuildConfigParameter {
|
||||
name: string;
|
||||
value: string;
|
||||
inherited: boolean;
|
||||
}
|
||||
interface GetTeamCityBuildConfigParametersRes {
|
||||
href: string;
|
||||
count: number;
|
||||
property: TeamCityBuildConfigParameter[];
|
||||
}
|
||||
|
||||
if (integration.targetEnvironment && integration.targetEnvironmentId) {
|
||||
// case: sync to specific build-config in TeamCity project
|
||||
const res = (await standardRequest.get<GetTeamCityBuildConfigParametersRes>(
|
||||
`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
)
|
||||
.data
|
||||
.property
|
||||
.reduce(
|
||||
(obj: any, secret: TeamCitySecret) => {
|
||||
const secretName = secret.name.replace(/^env\./, "");
|
||||
return ({
|
||||
}
|
||||
))
|
||||
.data
|
||||
.property
|
||||
.filter((parameter) => !parameter.inherited)
|
||||
.reduce((obj: any, secret: TeamCitySecret) => {
|
||||
const secretName = secret.name.replace(/^env\./, "");
|
||||
return {
|
||||
...obj,
|
||||
[secretName]: secret.value
|
||||
})
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
if (!(key in res) || (key in res && secrets[key] !== res[key])) {
|
||||
// case: secret does not exist in TeamCity or secret value has changed
|
||||
// -> create/update secret
|
||||
await standardRequest.post(
|
||||
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
|
||||
};
|
||||
}, {});
|
||||
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
if (!(key in res) || (key in res && secrets[key].value !== res[key])) {
|
||||
// case: secret does not exist in TeamCity or secret value has changed
|
||||
// -> create/update secret
|
||||
await standardRequest.post(`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters`,
|
||||
{
|
||||
name: `env.${key}`,
|
||||
value: secrets[key]
|
||||
name:`env.${key}`,
|
||||
value: secrets[key].value
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for await (const key of Object.keys(res)) {
|
||||
if (!(key in secrets)) {
|
||||
// delete secret
|
||||
await standardRequest.delete(
|
||||
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters/env.${key}`,
|
||||
for await (const key of Object.keys(res)) {
|
||||
if (!(key in secrets)) {
|
||||
// delete secret
|
||||
await standardRequest.delete(
|
||||
`${integrationAuth.url}/app/rest/buildTypes/${integration.targetEnvironmentId}/parameters/env.${key}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// case: sync to TeamCity project
|
||||
const res = (
|
||||
await standardRequest.get(
|
||||
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
)
|
||||
).data.property.reduce((obj: any, secret: TeamCitySecret) => {
|
||||
const secretName = secret.name.replace(/^env\./, "");
|
||||
return {
|
||||
...obj,
|
||||
[secretName]: secret.value
|
||||
};
|
||||
}, {});
|
||||
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
if (!(key in res) || (key in res && secrets[key] !== res[key])) {
|
||||
// case: secret does not exist in TeamCity or secret value has changed
|
||||
// -> create/update secret
|
||||
await standardRequest.post(
|
||||
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters`,
|
||||
{
|
||||
name: `env.${key}`,
|
||||
value: secrets[key].value
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for await (const key of Object.keys(res)) {
|
||||
if (!(key in secrets)) {
|
||||
// delete secret
|
||||
await standardRequest.delete(
|
||||
`${integrationAuth.url}/app/rest/projects/id:${integration.appId}/parameters/env.${key}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,15 +1,31 @@
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IServiceAccount,
|
||||
IServiceTokenData,
|
||||
IUser,
|
||||
} from "../../models";
|
||||
import {
|
||||
ServiceActor,
|
||||
UserActor,
|
||||
UserAgentType
|
||||
} from "../../ee/models";
|
||||
|
||||
export interface AuthData {
|
||||
authMode: string;
|
||||
authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
authChannel: string;
|
||||
authIP: string;
|
||||
authUserAgent: string;
|
||||
interface BaseAuthData {
|
||||
ipAddress: string;
|
||||
userAgent: string;
|
||||
userAgentType: UserAgentType;
|
||||
tokenVersionId?: Types.ObjectId;
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserAuthData extends BaseAuthData {
|
||||
actor: UserActor;
|
||||
authPayload: IUser;
|
||||
}
|
||||
|
||||
export interface ServiceTokenAuthData extends BaseAuthData {
|
||||
actor: ServiceActor;
|
||||
authPayload: IServiceTokenData;
|
||||
}
|
||||
|
||||
export type AuthData =
|
||||
| UserAuthData
|
||||
| ServiceTokenAuthData;
|
@ -17,11 +17,15 @@ export interface CreateSecretParams {
|
||||
secretCommentIV?: string;
|
||||
secretCommentTag?: string;
|
||||
secretPath: string;
|
||||
metadata?: {
|
||||
source?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface GetSecretsParams {
|
||||
workspaceId: Types.ObjectId;
|
||||
environment: string;
|
||||
folderId?: string;
|
||||
secretPath: string;
|
||||
authData: AuthData;
|
||||
}
|
||||
|
@ -1,25 +1,13 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import { Types } from "mongoose";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
import {
|
||||
getAuthAPIKeyPayload,
|
||||
getAuthSAAKPayload,
|
||||
getAuthSTDPayload,
|
||||
getAuthUserPayload,
|
||||
validateAuthMode,
|
||||
} from "../helpers/auth";
|
||||
import {
|
||||
IServiceAccount,
|
||||
IServiceTokenData,
|
||||
IUser,
|
||||
} from "../models";
|
||||
import {
|
||||
AUTH_MODE_API_KEY,
|
||||
AUTH_MODE_JWT,
|
||||
AUTH_MODE_SERVICE_ACCOUNT,
|
||||
AUTH_MODE_SERVICE_TOKEN,
|
||||
} from "../variables";
|
||||
import { getChannelFromUserAgent } from "../utils/posthog";
|
||||
import { AuthMode } from "../variables";
|
||||
import { AuthData } from "../interfaces/middleware";
|
||||
|
||||
declare module "jsonwebtoken" {
|
||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||
@ -38,9 +26,9 @@ declare module "jsonwebtoken" {
|
||||
* @returns
|
||||
*/
|
||||
const requireAuth = ({
|
||||
acceptedAuthModes = [AUTH_MODE_JWT],
|
||||
acceptedAuthModes = [AuthMode.JWT],
|
||||
}: {
|
||||
acceptedAuthModes: string[];
|
||||
acceptedAuthModes: AuthMode[];
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
|
||||
@ -50,55 +38,36 @@ const requireAuth = ({
|
||||
headers: req.headers,
|
||||
acceptedAuthModes,
|
||||
});
|
||||
|
||||
let authPayload: IUser | IServiceAccount | IServiceTokenData;
|
||||
let authUserPayload: {
|
||||
user: IUser;
|
||||
tokenVersionId: Types.ObjectId;
|
||||
};
|
||||
|
||||
let authData: AuthData;
|
||||
|
||||
switch (authMode) {
|
||||
case AUTH_MODE_SERVICE_ACCOUNT:
|
||||
authPayload = await getAuthSAAKPayload({
|
||||
case AuthMode.SERVICE_TOKEN:
|
||||
authData = await getAuthSTDPayload({
|
||||
req,
|
||||
authTokenValue,
|
||||
});
|
||||
req.serviceAccount = authPayload;
|
||||
req.serviceTokenData = authData.authPayload;
|
||||
break;
|
||||
case AUTH_MODE_SERVICE_TOKEN:
|
||||
authPayload = await getAuthSTDPayload({
|
||||
authTokenValue,
|
||||
case AuthMode.API_KEY:
|
||||
authData = await getAuthAPIKeyPayload({
|
||||
req,
|
||||
authTokenValue
|
||||
});
|
||||
req.serviceTokenData = authPayload;
|
||||
req.user = authData.authPayload;
|
||||
break;
|
||||
case AUTH_MODE_API_KEY:
|
||||
authPayload = await getAuthAPIKeyPayload({
|
||||
authTokenValue,
|
||||
case AuthMode.JWT:
|
||||
authData = await getAuthUserPayload({
|
||||
req,
|
||||
authTokenValue
|
||||
});
|
||||
req.user = authPayload;
|
||||
break;
|
||||
default:
|
||||
authUserPayload = await getAuthUserPayload({
|
||||
authTokenValue,
|
||||
});
|
||||
authPayload = authUserPayload.user;
|
||||
req.user = authUserPayload.user;
|
||||
req.tokenVersionId = authUserPayload.tokenVersionId;
|
||||
// authPayload = authUserPayload.user;
|
||||
req.user = authData.authPayload;
|
||||
// req.tokenVersionId = authUserPayload.tokenVersionId; // TODO
|
||||
break;
|
||||
}
|
||||
|
||||
req.requestData = {
|
||||
...req.params,
|
||||
...req.query,
|
||||
...req.body,
|
||||
}
|
||||
|
||||
req.authData = {
|
||||
authMode,
|
||||
authPayload, // User, ServiceAccount, ServiceTokenData
|
||||
authChannel: getChannelFromUserAgent(req.headers["user-agent"]),
|
||||
authIP: req.realIP,
|
||||
authUserAgent: req.headers["user-agent"] ?? "other",
|
||||
tokenVersionId: req.tokenVersionId,
|
||||
}
|
||||
|
||||
req.authData = authData;
|
||||
|
||||
return next();
|
||||
}
|
||||
|
@ -36,6 +36,4 @@ const apiKeyDataSchema = new Schema<IAPIKeyData>(
|
||||
}
|
||||
);
|
||||
|
||||
const APIKeyData = model<IAPIKeyData>("APIKeyData", apiKeyDataSchema);
|
||||
|
||||
export default APIKeyData;
|
||||
export const APIKeyData = model<IAPIKeyData>("APIKeyData", apiKeyDataSchema);
|
@ -68,9 +68,7 @@ const backupPrivateKeySchema = new Schema<IBackupPrivateKey>(
|
||||
}
|
||||
);
|
||||
|
||||
const BackupPrivateKey = model<IBackupPrivateKey>(
|
||||
export const BackupPrivateKey = model<IBackupPrivateKey>(
|
||||
"BackupPrivateKey",
|
||||
backupPrivateKeySchema
|
||||
);
|
||||
|
||||
export default BackupPrivateKey;
|
||||
|
@ -74,6 +74,4 @@ const botSchema = new Schema<IBot>(
|
||||
}
|
||||
);
|
||||
|
||||
const Bot = model<IBot>("Bot", botSchema);
|
||||
|
||||
export default Bot;
|
||||
export const Bot = model<IBot>("Bot", botSchema);
|
@ -40,6 +40,4 @@ const botKeySchema = new Schema<IBotKey>(
|
||||
}
|
||||
);
|
||||
|
||||
const BotKey = model<IBotKey>("BotKey", botKeySchema);
|
||||
|
||||
export default BotKey;
|
||||
export const BotKey = model<IBotKey>("BotKey", botKeySchema);
|
@ -93,6 +93,4 @@ const botOrgSchema = new Schema<IBotOrg>(
|
||||
}
|
||||
);
|
||||
|
||||
const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema);
|
||||
|
||||
export default BotOrg;
|
||||
export const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema);
|
@ -51,6 +51,4 @@ const folderRootSchema = new Schema<TFolderRootSchema>(
|
||||
}
|
||||
);
|
||||
|
||||
const Folder = model<TFolderRootSchema>("Folder", folderRootSchema);
|
||||
|
||||
export default Folder;
|
||||
export const Folder = model<TFolderRootSchema>("Folder", folderRootSchema);
|
@ -23,9 +23,7 @@ const incidentContactOrgSchema = new Schema<IIncidentContactOrg>(
|
||||
}
|
||||
);
|
||||
|
||||
const IncidentContactOrg = model<IIncidentContactOrg>(
|
||||
export const IncidentContactOrg = model<IIncidentContactOrg>(
|
||||
"IncidentContactOrg",
|
||||
incidentContactOrgSchema
|
||||
);
|
||||
|
||||
export default IncidentContactOrg;
|
||||
);
|
@ -1,85 +1,30 @@
|
||||
import BackupPrivateKey, { IBackupPrivateKey } from "./backupPrivateKey";
|
||||
import Bot, { IBot } from "./bot";
|
||||
import BotOrg, { IBotOrg } from "./botOrg";
|
||||
import BotKey, { IBotKey } from "./botKey";
|
||||
import IncidentContactOrg, { IIncidentContactOrg } from "./incidentContactOrg";
|
||||
import Integration, { IIntegration } from "./integration";
|
||||
import IntegrationAuth, { IIntegrationAuth } from "./integrationAuth";
|
||||
import Key, { IKey } from "./key";
|
||||
import Membership, { IMembership } from "./membership";
|
||||
import MembershipOrg, { IMembershipOrg } from "./membershipOrg";
|
||||
import Organization, { IOrganization } from "./organization";
|
||||
import Secret, { ISecret } from "./secret";
|
||||
import SecretBlindIndexData, { ISecretBlindIndexData } from "./secretBlindIndexData";
|
||||
import ServiceToken, { IServiceToken } from "./serviceToken";
|
||||
import ServiceAccount, { IServiceAccount } from "./serviceAccount"; // new
|
||||
import ServiceAccountKey, { IServiceAccountKey } from "./serviceAccountKey"; // new
|
||||
import ServiceAccountOrganizationPermission, { IServiceAccountOrganizationPermission } from "./serviceAccountOrganizationPermission"; // new
|
||||
import ServiceAccountWorkspacePermission, { IServiceAccountWorkspacePermission } from "./serviceAccountWorkspacePermission"; // new
|
||||
import TokenData, { ITokenData } from "./tokenData";
|
||||
import User, { AuthProvider, IUser } from "./user";
|
||||
import UserAction, { IUserAction } from "./userAction";
|
||||
import Workspace, { IWorkspace } from "./workspace";
|
||||
import ServiceTokenData, { IServiceTokenData } from "./serviceTokenData";
|
||||
import APIKeyData, { IAPIKeyData } from "./apiKeyData";
|
||||
import LoginSRPDetail, { ILoginSRPDetail } from "./loginSRPDetail";
|
||||
import TokenVersion, { ITokenVersion } from "./tokenVersion";
|
||||
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE } from "./gitRisks";
|
||||
|
||||
export {
|
||||
AuthProvider,
|
||||
BackupPrivateKey,
|
||||
IBackupPrivateKey,
|
||||
Bot,
|
||||
IBot,
|
||||
BotOrg,
|
||||
IBotOrg,
|
||||
BotKey,
|
||||
IBotKey,
|
||||
IncidentContactOrg,
|
||||
IIncidentContactOrg,
|
||||
Integration,
|
||||
IIntegration,
|
||||
IntegrationAuth,
|
||||
IIntegrationAuth,
|
||||
Key,
|
||||
IKey,
|
||||
Membership,
|
||||
IMembership,
|
||||
MembershipOrg,
|
||||
IMembershipOrg,
|
||||
Organization,
|
||||
IOrganization,
|
||||
Secret,
|
||||
ISecret,
|
||||
SecretBlindIndexData,
|
||||
ISecretBlindIndexData,
|
||||
ServiceToken,
|
||||
IServiceToken,
|
||||
ServiceAccount,
|
||||
IServiceAccount,
|
||||
ServiceAccountKey,
|
||||
IServiceAccountKey,
|
||||
ServiceAccountOrganizationPermission,
|
||||
IServiceAccountOrganizationPermission,
|
||||
ServiceAccountWorkspacePermission,
|
||||
IServiceAccountWorkspacePermission,
|
||||
TokenData,
|
||||
ITokenData,
|
||||
User,
|
||||
IUser,
|
||||
UserAction,
|
||||
IUserAction,
|
||||
Workspace,
|
||||
IWorkspace,
|
||||
ServiceTokenData,
|
||||
IServiceTokenData,
|
||||
APIKeyData,
|
||||
IAPIKeyData,
|
||||
LoginSRPDetail,
|
||||
ILoginSRPDetail,
|
||||
TokenVersion,
|
||||
ITokenVersion,
|
||||
GitRisks,
|
||||
STATUS_RESOLVED_FALSE_POSITIVE
|
||||
};
|
||||
export * from "./backupPrivateKey";
|
||||
export * from "./bot";
|
||||
export * from "./botOrg";
|
||||
export * from "./botKey";
|
||||
export * from "./incidentContactOrg";
|
||||
export * from "./integration/integration";
|
||||
export * from "./integrationAuth";
|
||||
export * from "./key";
|
||||
export * from "./membership";
|
||||
export * from "./membershipOrg";
|
||||
export * from "./organization";
|
||||
export * from "./secret";
|
||||
export * from "./tag";
|
||||
export * from "./folder";
|
||||
export * from "./secretImports";
|
||||
export * from "./secretBlindIndexData";
|
||||
export * from "./serviceToken";
|
||||
export * from "./serviceAccount";
|
||||
export * from "./serviceAccountKey";
|
||||
export * from "./serviceAccountOrganizationPermission";
|
||||
export * from "./serviceAccountWorkspacePermission";
|
||||
export * from "./tokenData";
|
||||
export * from "./user";
|
||||
export * from "./userAction";
|
||||
export * from "./workspace";
|
||||
export * from "./serviceTokenData";
|
||||
export * from "./apiKeyData";
|
||||
export * from "./loginSRPDetail";
|
||||
export * from "./tokenVersion";
|
||||
export * from "./webhooks";
|
1
backend/src/models/integration/index.ts
Normal file
1
backend/src/models/integration/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./integration";
|
@ -10,6 +10,7 @@ import {
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
@ -25,8 +26,9 @@ import {
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_WINDMILL
|
||||
} from "../variables";
|
||||
} from "../../variables";
|
||||
import { Schema, Types, model } from "mongoose";
|
||||
import { Metadata } from "./types";
|
||||
|
||||
export interface IIntegration {
|
||||
_id: Types.ObjectId;
|
||||
@ -70,8 +72,10 @@ export interface IIntegration {
|
||||
| "digital-ocean-app-platform"
|
||||
| "cloud-66"
|
||||
| "northflank"
|
||||
| "windmill";
|
||||
| "windmill"
|
||||
| "gcp-secret-manager";
|
||||
integrationAuth: Types.ObjectId;
|
||||
metadata: Metadata;
|
||||
}
|
||||
|
||||
const integrationSchema = new Schema<IIntegration>(
|
||||
@ -167,7 +171,8 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_GCP_SECRET_MANAGER
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
@ -180,6 +185,9 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
type: String,
|
||||
required: true,
|
||||
default: "/",
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -187,6 +195,4 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
}
|
||||
);
|
||||
|
||||
const Integration = model<IIntegration>("Integration", integrationSchema);
|
||||
|
||||
export default Integration;
|
||||
export const Integration = model<IIntegration>("Integration", integrationSchema);
|
3
backend/src/models/integration/types.ts
Normal file
3
backend/src/models/integration/types.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type Metadata = {
|
||||
secretSuffix?: string;
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_WINDMILL
|
||||
} from "../variables";
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
|
||||
export interface IIntegrationAuth extends Document {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
integration:
|
||||
| "heroku"
|
||||
| "vercel"
|
||||
| "netlify"
|
||||
| "github"
|
||||
| "gitlab"
|
||||
| "render"
|
||||
| "railway"
|
||||
| "flyio"
|
||||
| "azure-key-vault"
|
||||
| "laravel-forge"
|
||||
| "circleci"
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "aws-parameter-store"
|
||||
| "aws-secret-manager"
|
||||
| "checkly"
|
||||
| "cloudflare-pages"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
| "bitbucket"
|
||||
| "cloud-66"
|
||||
| "terraform-cloud"
|
||||
| "teamcity"
|
||||
| "northflank"
|
||||
| "windmill";
|
||||
teamId: string;
|
||||
accountId: string;
|
||||
url: string;
|
||||
namespace: string;
|
||||
refreshCiphertext?: string;
|
||||
refreshIV?: string;
|
||||
refreshTag?: string;
|
||||
accessIdCiphertext?: string;
|
||||
accessIdIV?: string;
|
||||
accessIdTag?: string;
|
||||
accessCiphertext?: string;
|
||||
accessIV?: string;
|
||||
accessTag?: string;
|
||||
algorithm?: "aes-256-gcm";
|
||||
keyEncoding?: "utf8" | "base64";
|
||||
accessExpiresAt?: Date;
|
||||
}
|
||||
|
||||
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true,
|
||||
},
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
teamId: {
|
||||
// vercel-specific integration param
|
||||
type: String,
|
||||
},
|
||||
url: {
|
||||
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
|
||||
type: String,
|
||||
},
|
||||
namespace: {
|
||||
// hashicorp-vault-specific integration param
|
||||
type: String,
|
||||
},
|
||||
accountId: {
|
||||
// netlify-specific integration param
|
||||
type: String,
|
||||
},
|
||||
refreshCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
refreshIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
refreshTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessExpiresAt: {
|
||||
type: Date,
|
||||
select: false,
|
||||
},
|
||||
algorithm: { // the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true,
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
const IntegrationAuth = model<IIntegrationAuth>(
|
||||
"IntegrationAuth",
|
||||
integrationAuthSchema
|
||||
);
|
||||
|
||||
export default IntegrationAuth;
|
1
backend/src/models/integrationAuth/index.ts
Normal file
1
backend/src/models/integrationAuth/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./integrationAuth";
|
204
backend/src/models/integrationAuth/integrationAuth.ts
Normal file
204
backend/src/models/integrationAuth/integrationAuth.ts
Normal file
@ -0,0 +1,204 @@
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_WINDMILL
|
||||
} from "../../variables";
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
import { IntegrationAuthMetadata } from "./types";
|
||||
|
||||
export interface IIntegrationAuth extends Document {
|
||||
_id: Types.ObjectId;
|
||||
workspace: Types.ObjectId;
|
||||
integration:
|
||||
| "heroku"
|
||||
| "vercel"
|
||||
| "netlify"
|
||||
| "github"
|
||||
| "gitlab"
|
||||
| "render"
|
||||
| "railway"
|
||||
| "flyio"
|
||||
| "azure-key-vault"
|
||||
| "laravel-forge"
|
||||
| "circleci"
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "aws-parameter-store"
|
||||
| "aws-secret-manager"
|
||||
| "checkly"
|
||||
| "cloudflare-pages"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
| "bitbucket"
|
||||
| "cloud-66"
|
||||
| "terraform-cloud"
|
||||
| "teamcity"
|
||||
| "northflank"
|
||||
| "windmill"
|
||||
| "gcp-secret-manager";
|
||||
teamId: string;
|
||||
accountId: string;
|
||||
url: string;
|
||||
namespace: string;
|
||||
refreshCiphertext?: string;
|
||||
refreshIV?: string;
|
||||
refreshTag?: string;
|
||||
accessIdCiphertext?: string;
|
||||
accessIdIV?: string;
|
||||
accessIdTag?: string;
|
||||
accessCiphertext?: string;
|
||||
accessIV?: string;
|
||||
accessTag?: string;
|
||||
algorithm?: "aes-256-gcm";
|
||||
keyEncoding?: "utf8" | "base64";
|
||||
accessExpiresAt?: Date;
|
||||
metadata?: IntegrationAuthMetadata;
|
||||
}
|
||||
|
||||
const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
||||
{
|
||||
workspace: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Workspace",
|
||||
required: true,
|
||||
},
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
INTEGRATION_AZURE_KEY_VAULT,
|
||||
INTEGRATION_AWS_PARAMETER_STORE,
|
||||
INTEGRATION_AWS_SECRET_MANAGER,
|
||||
INTEGRATION_HEROKU,
|
||||
INTEGRATION_VERCEL,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_GITHUB,
|
||||
INTEGRATION_GITLAB,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_FLYIO,
|
||||
INTEGRATION_CIRCLECI,
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
INTEGRATION_CODEFRESH,
|
||||
INTEGRATION_WINDMILL,
|
||||
INTEGRATION_BITBUCKET,
|
||||
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
|
||||
INTEGRATION_CLOUD_66,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_GCP_SECRET_MANAGER
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
teamId: {
|
||||
// vercel-specific integration param
|
||||
type: String,
|
||||
},
|
||||
url: {
|
||||
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
|
||||
type: String,
|
||||
},
|
||||
namespace: {
|
||||
// hashicorp-vault-specific integration param
|
||||
type: String,
|
||||
},
|
||||
accountId: {
|
||||
// netlify-specific integration param
|
||||
type: String,
|
||||
},
|
||||
refreshCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
refreshIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
refreshTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIdTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessCiphertext: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessIV: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessTag: {
|
||||
type: String,
|
||||
select: false,
|
||||
},
|
||||
accessExpiresAt: {
|
||||
type: Date,
|
||||
select: false,
|
||||
},
|
||||
algorithm: { // the encryption algorithm used
|
||||
type: String,
|
||||
enum: [ALGORITHM_AES_256_GCM],
|
||||
required: true,
|
||||
},
|
||||
keyEncoding: {
|
||||
type: String,
|
||||
enum: [
|
||||
ENCODING_SCHEME_UTF8,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
],
|
||||
required: true,
|
||||
},
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
export const IntegrationAuth = model<IIntegrationAuth>(
|
||||
"IntegrationAuth",
|
||||
integrationAuthSchema
|
||||
);
|
5
backend/src/models/integrationAuth/types.ts
Normal file
5
backend/src/models/integrationAuth/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
interface GCPIntegrationAuthMetadata {
|
||||
authMethod: "oauth2" | "serviceAccount"
|
||||
}
|
||||
|
||||
export type IntegrationAuthMetadata = GCPIntegrationAuthMetadata;
|
@ -40,6 +40,4 @@ const keySchema = new Schema<IKey>(
|
||||
}
|
||||
);
|
||||
|
||||
const Key = model<IKey>("Key", keySchema);
|
||||
|
||||
export default Key;
|
||||
export const Key = model<IKey>("Key", keySchema);
|
@ -24,6 +24,4 @@ const loginSRPDetailSchema = new Schema<ILoginSRPDetail>(
|
||||
}
|
||||
);
|
||||
|
||||
const LoginSRPDetail = model("LoginSRPDetail", loginSRPDetailSchema);
|
||||
|
||||
export default LoginSRPDetail;
|
||||
export const LoginSRPDetail = model("LoginSRPDetail", loginSRPDetailSchema);
|
@ -52,6 +52,4 @@ const membershipSchema = new Schema<IMembership>(
|
||||
}
|
||||
);
|
||||
|
||||
const Membership = model<IMembership>("Membership", membershipSchema);
|
||||
|
||||
export default Membership;
|
||||
export const Membership = model<IMembership>("Membership", membershipSchema);
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user