feat(infisical-pg): completed audit log moved ee routes to normal routing pattern

This commit is contained in:
Akhil Mohan
2024-01-09 16:18:25 +05:30
parent 6e6a1c87f2
commit 16f0ac6d43
78 changed files with 3178 additions and 445 deletions

View File

@@ -1,89 +0,0 @@
import dotenv from "dotenv";
import { initDbConnection } from "./src/db";
import { TableName } from "./src/db/schemas";
import { selectAllTableCols } from "./src/lib/knex";
import picomatch from "picomatch";
dotenv.config();
// const db = initDbConnection(process.env.DB_CONNECTION_URI);
// const main = async () => {
// const folders = db
// .withRecursive("parent", (qb) => {
// qb.select({
// depth: 1,
// path: db.raw("'/'")
// })
// .select(selectAllTableCols(db, TableName.SecretFolder))
// .from(TableName.SecretFolder)
// .join(
// TableName.Environment,
// `${TableName.SecretFolder}.envId`,
// `${TableName.Environment}.id`
// )
// .where({
// projectId: "01c10de1-8743-490f-9c8a-7a19c4dc72a9",
// parentId: null
// })
// .where(`${TableName.Environment}.slug`, "dev")
// .union((qb) =>
// qb
// .select({
// depth: db.raw("parent.depth + 1"),
// path: db.raw(
// "CONCAT((CASE WHEN parent.path = '/' THEN '' ELSE parent.path END),'/', secret_folders.name)"
// )
// })
// .select(selectAllTableCols(db, TableName.SecretFolder))
// .whereRaw(
// `depth = array_position(ARRAY[${[1, 2]
// .map((_) => "?")
// .join(",")}]::varchar[], secret_folders.name,depth)`,
// [...["ui", "design"]]
// )
// .from(TableName.SecretFolder)
// .join("parent", "parent.id", `${TableName.SecretFolder}.parentId`)
// );
// })
// .select("*")
// .from("parent")
// .orderBy("depth", "desc")
// .first();
// console.log(folders.toSQL());
// console.log(JSON.stringify(await folders, null, 4));
// process.exit(0);
// };
//
const main = async () => {
const isMatch = picomatch("/ui/**/seomthing", { strictSlashes: false });
console.log(
picomatch.isMatch(
"/ui/(base|new)/seomthing",
["/ui/base/seomthing", "/ui/(base|new)/seomthing"],
{
strictSlashes: false
}
)
);
// console.log(isMatch("/ui/(base|new)/seomthing"));
// const folders = db(TableName.SecretVersion)
// .whereIn(`${TableName.SecretVersion}.secretId`, ["8fe1c5e2-af6a-40ed-874b-6738816236bc"])
// .join(
// db(TableName.SecretVersion)
// .groupBy("secretId")
// .max("version")
// .select("secretId")
// .as("latestVersion"),
// (bd) => {
// bd.on(`${TableName.SecretVersion}.secretId`, "latestVersion.secretId").andOn(
// `${TableName.SecretVersion}.version`,
// "latestVersion.max"
// );
// }
// );
// console.log(folders.toSQL());
// console.log(JSON.stringify(await folders, null, 4));
// process.exit(0);
};
main();

View File

@@ -18,6 +18,7 @@
"@fastify/session": "^10.7.0",
"@fastify/swagger": "^8.12.0",
"@fastify/swagger-ui": "^1.10.1",
"@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^20.0.2",
"@ucast/mongo2js": "^1.3.4",
"ajv": "^8.12.0",
@@ -996,6 +997,64 @@
"win32"
]
},
"node_modules/@node-saml/node-saml": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-4.0.5.tgz",
"integrity": "sha512-J5DglElbY1tjOuaR1NPtjOXkXY5bpUhDoKVoeucYN98A3w4fwgjIOPqIGcb6cQsqFq2zZ6vTCeKn5C/hvefSaw==",
"dependencies": {
"@types/debug": "^4.1.7",
"@types/passport": "^1.0.11",
"@types/xml-crypto": "^1.4.2",
"@types/xml-encryption": "^1.2.1",
"@types/xml2js": "^0.4.11",
"@xmldom/xmldom": "^0.8.6",
"debug": "^4.3.4",
"xml-crypto": "^3.0.1",
"xml-encryption": "^3.0.2",
"xml2js": "^0.5.0",
"xmlbuilder": "^15.1.1"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@node-saml/node-saml/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/@node-saml/node-saml/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/@node-saml/passport-saml": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@node-saml/passport-saml/-/passport-saml-4.0.4.tgz",
"integrity": "sha512-xFw3gw0yo+K1mzlkW15NeBF7cVpRHN/4vpjmBKzov5YFImCWh/G0LcTZ8krH3yk2/eRPc3Or8LRPudVJBjmYaw==",
"dependencies": {
"@node-saml/node-saml": "^4.0.4",
"@types/express": "^4.17.14",
"@types/passport": "^1.0.11",
"@types/passport-strategy": "^0.2.35",
"passport": "^0.6.0",
"passport-strategy": "^1.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1640,7 +1699,6 @@
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
"dev": true,
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
@@ -1650,16 +1708,22 @@
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
"dependencies": {
"@types/ms": "*"
}
},
"node_modules/@types/express": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
"dev": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
@@ -1671,7 +1735,6 @@
"version": "4.17.41",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz",
"integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==",
"dev": true,
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
@@ -1682,8 +1745,7 @@
"node_modules/@types/http-errors": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
"dev": true
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="
},
"node_modules/@types/jmespath": {
"version": "0.15.2",
@@ -1719,14 +1781,17 @@
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
},
"node_modules/@types/ms": {
"version": "0.7.34",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
},
"node_modules/@types/node": {
"version": "20.9.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.5.tgz",
"integrity": "sha512-Uq2xbNq0chGg+/WQEU0LJTSs/1nKxz6u1iemLcGomkSnKokbW1fbLqc3HOqCf2JP7KjlL4QkS7oZZTrOQHQYgQ==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
@@ -1753,7 +1818,6 @@
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.16.tgz",
"integrity": "sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
@@ -1791,6 +1855,15 @@
"@types/passport": "*"
}
},
"node_modules/@types/passport-strategy": {
"version": "0.2.38",
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz",
"integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==",
"dependencies": {
"@types/express": "*",
"@types/passport": "*"
}
},
"node_modules/@types/pg": {
"version": "8.10.9",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.10.9.tgz",
@@ -1874,14 +1947,12 @@
"node_modules/@types/qs": {
"version": "6.9.10",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz",
"integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==",
"dev": true
"integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw=="
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"dev": true
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
},
"node_modules/@types/semver": {
"version": "7.5.6",
@@ -1892,7 +1963,6 @@
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"dev": true,
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
@@ -1902,13 +1972,37 @@
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz",
"integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==",
"dev": true,
"dependencies": {
"@types/http-errors": "*",
"@types/mime": "*",
"@types/node": "*"
}
},
"node_modules/@types/xml-crypto": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.6.tgz",
"integrity": "sha512-A6jEW2FxLZo1CXsRWnZHUX2wzR3uDju2Bozt6rDbSmU/W8gkilaVbwFEVN0/NhnUdMVzwYobWtM6bU1QJJFb7Q==",
"dependencies": {
"@types/node": "*",
"xpath": "0.0.27"
}
},
"node_modules/@types/xml-encryption": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@types/xml-encryption/-/xml-encryption-1.2.4.tgz",
"integrity": "sha512-I69K/WW1Dv7j6O3jh13z0X8sLWJRXbu5xnHDl9yHzUNDUBtUoBY058eb5s+x/WG6yZC1h8aKdI2EoyEPjyEh+Q==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/xml2js": {
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.13.2.tgz",
@@ -2305,6 +2399,14 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -6699,6 +6801,23 @@
"node": ">=6"
}
},
"node_modules/passport": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz",
"integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==",
"dependencies": {
"passport-strategy": "1.x.x",
"pause": "0.0.1",
"utils-merge": "^1.0.1"
},
"engines": {
"node": ">= 0.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jaredhanson"
}
},
"node_modules/passport-github": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/passport-github/-/passport-github-1.1.0.tgz",
@@ -6836,6 +6955,11 @@
"node": "*"
}
},
"node_modules/pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"node_modules/pg": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
@@ -7781,6 +7905,11 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sax": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
},
"node_modules/secure-json-parse": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
@@ -9295,8 +9424,7 @@
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/universal-user-agent": {
"version": "6.0.1",
@@ -10297,6 +10425,83 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/xml-crypto": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.0.tgz",
"integrity": "sha512-qVurBUOQrmvlgmZqIVBqmb06TD2a/PpEUfFPgD7BuBfjmoH4zgkqaWSIJrnymlCvM2GGt9x+XtJFA+ttoAufqg==",
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"xpath": "0.0.32"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xml-crypto/node_modules/xpath": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==",
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/xml-encryption": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-3.0.2.tgz",
"integrity": "sha512-VxYXPvsWB01/aqVLd6ZMPWZ+qaj0aIdF+cStrVJMcFj3iymwZeI0ABzB3VqMYv48DkSpRhnrXqTUkR34j+UDyg==",
"dependencies": {
"@xmldom/xmldom": "^0.8.5",
"escape-html": "^1.0.3",
"xpath": "0.0.32"
},
"engines": {
"node": ">=12"
}
},
"node_modules/xml-encryption/node_modules/xpath": {
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==",
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/xml2js": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xml2js/node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"engines": {
"node": ">=4.0"
}
},
"node_modules/xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
"engines": {
"node": ">=8.0"
}
},
"node_modules/xpath": {
"version": "0.0.27",
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz",
"integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==",
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@@ -72,6 +72,7 @@
"@fastify/session": "^10.7.0",
"@fastify/swagger": "^8.12.0",
"@fastify/swagger-ui": "^1.10.1",
"@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^20.0.2",
"@ucast/mongo2js": "^1.3.4",
"ajv": "^8.12.0",

View File

@@ -1,7 +1,10 @@
import "fastify";
import { TUsers } from "@app/db/schemas";
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service";
import { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
@@ -62,6 +65,8 @@ declare module "fastify" {
isCompleted: string;
providerAuthToken: string;
};
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
}
interface FastifyInstance {
@@ -98,6 +103,8 @@ declare module "fastify" {
secretApprovalRequest: TSecretApprovalRequestServiceFactory;
secretRotation: TSecretRotationServiceFactory;
snapshot: TSecretSnapshotServiceFactory;
saml: TSamlConfigServiceFactory;
auditLog: TAuditLogServiceFactory;
};
// this is exclusive use for middlewares in which we need to inject data

View File

@@ -5,6 +5,9 @@ import {
TApiKeys,
TApiKeysInsert,
TApiKeysUpdate,
TAuditLogs,
TAuditLogsInsert,
TAuditLogsUpdate,
TAuthTokens,
TAuthTokenSessions,
TAuthTokenSessionsInsert,
@@ -43,6 +46,9 @@ import {
TOrganizations,
TOrganizationsInsert,
TOrganizationsUpdate,
TOrgBots,
TOrgBotsInsert,
TOrgBotsUpdate,
TOrgMemberships,
TOrgMembershipsInsert,
TOrgMembershipsUpdate,
@@ -67,6 +73,9 @@ import {
TProjects,
TProjectsInsert,
TProjectsUpdate,
TSamlConfigs,
TSamlConfigsInsert,
TSamlConfigsUpdate,
TSapApprovers,
TSapApproversInsert,
TSapApproversUpdate,
@@ -357,6 +366,13 @@ declare module "knex/types/tables" {
TSecretSnapshotFoldersInsert,
TSecretSnapshotFoldersUpdate
>;
[TableName.SamlConfig]: Knex.CompositeTableType<
TSamlConfigs,
TSamlConfigsInsert,
TSamlConfigsUpdate
>;
[TableName.OrgBot]: Knex.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>;
[TableName.AuditLog]: Knex.CompositeTableType<TAuditLogs, TAuditLogsInsert, TAuditLogsUpdate>;
// Junction tables
[TableName.JnSecretTag]: Knex.CompositeTableType<
TSecretTagJunction,

View File

@@ -22,7 +22,7 @@ export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Secret))) {
await knex.schema.createTable(TableName.Secret, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("version").defaultTo(1);
t.integer("version").defaultTo(1).notNullable();
t.string("type").notNullable().defaultTo(SecretType.Shared);
// t.text("secretKeyHash").notNullable();
// t.text("secretValueHash");

View File

@@ -7,7 +7,7 @@ export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretVersion))) {
await knex.schema.createTable(TableName.SecretVersion, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("version").defaultTo(1);
t.integer("version").defaultTo(1).notNullable();
t.string("type").notNullable().defaultTo(SecretType.Shared);
t.text("secretBlindIndex").notNullable();
t.text("secretKeyCiphertext").notNullable();

View File

@@ -0,0 +1,33 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SamlConfig))) {
await knex.schema.createTable(TableName.SamlConfig, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("authProvider").notNullable();
t.boolean("isActive").notNullable();
t.string("encryptedEntryPoint");
t.string("entryPointIV");
t.string("entryPointTag");
t.string("encryptedIssuer");
t.string("issuerTag");
t.string("issuerIV");
t.string("encryptedCert");
t.string("certIV");
t.string("certTag");
t.timestamps(true, true, true);
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization);
});
}
await createOnUpdateTrigger(knex, TableName.SamlConfig);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SamlConfig);
await dropOnUpdateTrigger(knex, TableName.SamlConfig);
}

View File

@@ -0,0 +1,35 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.OrgBot))) {
await knex.schema.createTable(TableName.OrgBot, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.text("publicKey").notNullable();
t.text("encryptedSymmetricKey").notNullable();
t.text("symmetricKeyIV").notNullable();
t.text("symmetricKeyTag").notNullable();
t.string("symmetricKeyAlgorithm").notNullable();
t.string("symmetricKeyKeyEncoding").notNullable();
t.text("encryptedPrivateKey").notNullable();
t.text("privateKeyIV").notNullable();
t.text("privateKeyTag").notNullable();
t.string("privateKeyAlgorithm").notNullable();
t.string("privateKeyKeyEncoding").notNullable();
// one to one relationship
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.OrgBot);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.OrgBot);
await dropOnUpdateTrigger(knex, TableName.OrgBot);
}

View File

@@ -0,0 +1,29 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.AuditLog))) {
await knex.schema.createTable(TableName.AuditLog, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("actor").notNullable();
t.jsonb("actorMetadata").notNullable();
t.string("ipAddress");
t.string("eventType").notNullable();
t.jsonb("eventMetadata");
t.string("userAgent");
t.string("userAgentType");
t.datetime("expiresAt");
t.timestamps(true, true, true);
// no trigger needed as this collection is append only
t.uuid("orgId");
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.uuid("projectId");
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.AuditLog);
}

View File

@@ -0,0 +1,28 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const AuditLogsSchema = z.object({
id: z.string().uuid(),
actor: z.string(),
actorMetadata: z.unknown(),
ipAddress: z.string().nullable().optional(),
eventType: z.string(),
eventMetadata: z.unknown().nullable().optional(),
userAgent: z.string().nullable().optional(),
userAgentType: z.string().nullable().optional(),
expiresAt: z.date().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
orgId: z.string().uuid().nullable().optional(),
projectId: z.string().uuid().nullable().optional(),
});
export type TAuditLogs = z.infer<typeof AuditLogsSchema>;
export type TAuditLogsInsert = Omit<TAuditLogs, TImmutableDBKeys>;
export type TAuditLogsUpdate = Partial<Omit<TAuditLogs, TImmutableDBKeys>>;

View File

@@ -1,4 +1,5 @@
export * from "./api-keys";
export * from "./audit-logs";
export * from "./auth-token-sessions";
export * from "./auth-tokens";
export * from "./backup-private-key";
@@ -12,6 +13,7 @@ export * from "./incident-contacts";
export * from "./integration-auths";
export * from "./integrations";
export * from "./models";
export * from "./org-bots";
export * from "./org-memberships";
export * from "./org-roles";
export * from "./organizations";
@@ -23,6 +25,7 @@ export * from "./project-roles";
export * from "./projects";
export * from "./sa-request-secret-tags";
export * from "./sa-request-secrets";
export * from "./saml-configs";
export * from "./sap-approvers";
export * from "./sar-reviewers";
export * from "./secret-approval-policies";

View File

@@ -9,6 +9,7 @@ export enum TableName {
Organization = "organizations",
OrgMembership = "org_memberships",
OrgRoles = "org_roles",
OrgBot = "org_bots",
IncidentContact = "incident_contacts",
UserAction = "user_actions",
SuperAdmin = "super_admin",
@@ -47,6 +48,8 @@ export enum TableName {
SarSecretTag = "sa_request_secret_tags",
SecretRotation = "secret_rotations",
SecretRotationOutput = "secret_rotation_outputs",
SamlConfig = "saml_configs",
AuditLog = "audit_logs",
// junction tables
JnSecretTag = "secret_tag_junction",
JnSecretVersionTag = "secret_version_tag_junction"

View File

@@ -0,0 +1,31 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const OrgBotsSchema = z.object({
id: z.string().uuid(),
name: z.string(),
publicKey: z.string(),
encryptedSymmetricKey: z.string(),
symmetricKeyIV: z.string(),
symmetricKeyTag: z.string(),
symmetricKeyAlgorithm: z.string(),
symmetricKeyKeyEncoding: z.string(),
encryptedPrivateKey: z.string(),
privateKeyIV: z.string(),
privateKeyTag: z.string(),
privateKeyAlgorithm: z.string(),
privateKeyKeyEncoding: z.string(),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
});
export type TOrgBots = z.infer<typeof OrgBotsSchema>;
export type TOrgBotsInsert = Omit<TOrgBots, TImmutableDBKeys>;
export type TOrgBotsUpdate = Partial<Omit<TOrgBots, TImmutableDBKeys>>;

View File

@@ -0,0 +1,30 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const SamlConfigsSchema = z.object({
id: z.string().uuid(),
authProvider: z.string(),
isActive: z.boolean(),
encryptedEntryPoint: z.string().nullable().optional(),
entryPointIV: z.string().nullable().optional(),
entryPointTag: z.string().nullable().optional(),
encryptedIssuer: z.string().nullable().optional(),
issuerTag: z.string().nullable().optional(),
issuerIV: z.string().nullable().optional(),
encryptedCert: z.string().nullable().optional(),
certIV: z.string().nullable().optional(),
certTag: z.string().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
orgId: z.string().uuid(),
});
export type TSamlConfigs = z.infer<typeof SamlConfigsSchema>;
export type TSamlConfigsInsert = Omit<TSamlConfigs, TImmutableDBKeys>;
export type TSamlConfigsUpdate = Partial<Omit<TSamlConfigs, TImmutableDBKeys>>;

View File

@@ -9,7 +9,7 @@ import { TImmutableDBKeys } from "./models";
export const SecretVersionsSchema = z.object({
id: z.string().uuid(),
version: z.number().default(1).nullable().optional(),
version: z.number().default(1),
type: z.string().default("shared"),
secretBlindIndex: z.string(),
secretKeyCiphertext: z.string(),

View File

@@ -9,7 +9,7 @@ import { TImmutableDBKeys } from "./models";
export const SecretsSchema = z.object({
id: z.string().uuid(),
version: z.number().default(1).nullable().optional(),
version: z.number().default(1),
type: z.string().default("shared"),
secretBlindIndex: z.string(),
secretKeyCiphertext: z.string(),

View File

@@ -1,6 +1,7 @@
import { registerOrgRoleRouter } from "./org-role-router";
import { registerProjectRoleRouter } from "./project-role-router";
import { registerProjectRouter } from "./project-router";
import { registerSamlRouter } from "./saml-router";
import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router";
import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router";
import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router";
@@ -25,5 +26,6 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerSecretRotationProviderRouter, {
prefix: "/secret-rotation-providers"
});
await server.register(registerSamlRouter, { prefix: "/sso" });
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
};

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretSnapshotsSchema } from "@app/db/schemas";
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -65,4 +66,74 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
return { count };
}
});
server.route({
method: "GET",
url: "/:workspaceId/audit-logs",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
querystring: z.object({
eventType: z.nativeEnum(EventType).optional(),
userAgentType: z.nativeEnum(UserAgentType).optional(),
startDate: z.string().datetime().optional(),
endDate: z.string().datetime().optional(),
offset: z.coerce.number().default(0),
limit: z.coerce.number().default(20),
actor: z.string().optional()
}),
response: {
200: z.object({
auditLogs: AuditLogsSchema.omit({
eventMetadata: true,
eventType: true,
actor: true,
actorMetadata: true
})
.merge(
z.object({
event: z.object({
type: z.string(),
metadata: z.any()
}),
actor: z.object({
type: z.string(),
metadata: z.any()
})
})
)
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const auditLogs = await server.services.auditLog.listProjectAuditLogs({
actorId: req.permission.id,
projectId: req.params.workspaceId,
...req.query,
auditLogActor: req.query.actor,
actor: req.permission.type
});
return { auditLogs };
}
});
server.route({
method: "GET",
url: "/:workspaceId/audit-logs/filters/actors",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
actors: z.string().array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async () => ({ actors: [] })
});
};

View File

@@ -0,0 +1,237 @@
import { Authenticator } from "@fastify/passport";
import fastifySession from "@fastify/session";
import { MultiSamlStrategy } from "@node-saml/passport-saml";
import { z } from "zod";
import { SamlConfigsSchema } from "@app/db/schemas";
import { SamlProviders } from "@app/ee/services/saml-config/saml-config-types";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
type TSAMLConfig = {
callbackUrl: string;
entryPoint: string;
issuer: string;
cert: string;
audience: string;
wantAuthnResponseSigned?: boolean;
};
export const registerSamlRouter = async (server: FastifyZodProvider) => {
const appCfg = getConfig();
const passport = new Authenticator({ key: "saml", userProperty: "passportUser" });
await server.register(fastifySession, { secret: appCfg.COOKIE_SECRET_SIGN_KEY });
await server.register(passport.initialize());
await server.register(passport.secureSession());
server.decorateRequest("ssoConfig", null);
passport.use(
new MultiSamlStrategy(
{
passReqToCallback: true,
getSamlOptions: async (req, done) => {
const { ssoIdentifier } = req.params;
if (!ssoIdentifier) throw new BadRequestError({ message: "Missing sso identitier" });
const ssoConfig = await server.services.saml.getSaml({
type: "ssoId",
id: ssoIdentifier
});
if (!ssoConfig) throw new BadRequestError({ message: "SSO config not found" });
const samlConfig: TSAMLConfig = {
callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${ssoIdentifier}`,
entryPoint: ssoConfig.entryPoint,
issuer: ssoConfig.issuer,
cert: ssoConfig.cert,
audience: appCfg.SITE_URL || ""
};
if (ssoConfig.authProvider === SamlProviders.JUMPCLOUD_SAML) {
samlConfig.wantAuthnResponseSigned = false;
}
if (ssoConfig.authProvider === SamlProviders.AZURE_SAML) {
if (req.body.RelayState && JSON.parse(req.body.RelayState).spIntiaited) {
samlConfig.audience = `spn:${ssoConfig.issuer}`;
}
}
req.ssoConfig = ssoConfig;
done(null, samlConfig);
}
},
async (req, profile, cb) => {
console.log(req.ssoConfig);
console.log(profile);
try {
const serverCfg = server.services.superAdmin.getServerCfg();
if (!profile) throw new BadRequestError({ message: "Missing profile" });
const { email, firstName } = profile;
if (!email || !firstName)
throw new BadRequestError({ message: "Invalid request. Missing email or first name" });
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
email,
firstName: profile.firstName as string,
lastName: profile.lastName as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp),
relayState: (req.body as { RelayState?: string }).RelayState,
authProvider: req.ssoConfig.authProvider,
orgId: req.ssoConfig.orgId
});
cb(null, { isUserCompleted, providerAuthToken });
} catch (error) {
cb(null, {});
}
},
() => {}
)
);
server.route({
url: "/redirect/saml2/:ssoIdentifier",
method: "GET",
schema: {
params: z.object({
ssoIdentifier: z.string().trim()
}),
querystring: z.object({
callback_port: z.string().optional()
})
},
preValidation: (req, res) =>
(
passport.authenticate("saml", {
failureRedirect: "/",
additionalParams: {
RelayState: JSON.stringify({
spInitiated: true,
callbackPort: req.query.callback_port ?? ""
})
}
} as any) as any
)(req, res),
handler: () => {}
});
server.route({
url: "/saml2/:ssoIdentifier",
method: "GET",
schema: {
params: z.object({
ssoIdentifier: z.string().trim()
})
},
preValidation: passport.authenticate("saml", {
session: false,
failureFlash: true,
failureRedirect: "/login/provider/error"
// this is due to zod type difference
}) as any,
handler: (req, res) => {
if (req.passportUser.isCompleted) {
return res.redirect(
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
return res.redirect(
`${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(
req.passportUser.providerAuthToken
)}`
);
}
});
server.route({
url: "/config",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({
organizationId: z.string().trim()
}),
response: {
200: z
.object({
id: z.string(),
organization: z.string(),
orgId: z.string(),
authProvider: z.string(),
isActive: z.boolean(),
entryPoint: z.string(),
issuer: z.string(),
cert: z.string()
})
.optional()
}
},
handler: async (req) => {
const saml = await server.services.saml.getSaml({
actor: req.permission.type,
actorId: req.permission.id,
orgId: req.query.organizationId,
type: "org"
});
return saml;
}
});
server.route({
url: "/config",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
organizationId: z.string(),
authProvider: z.nativeEnum(SamlProviders),
isActive: z.boolean(),
entryPoint: z.string(),
issuer: z.string(),
cert: z.string()
}),
response: {
200: SamlConfigsSchema
}
},
handler: async (req) => {
const saml = await server.services.saml.createSamlCfg({
actor: req.permission.type,
actorId: req.permission.id,
orgId: req.body.organizationId,
...req.body
});
return saml;
}
});
server.route({
url: "/config",
method: "PATCH",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z
.object({
authProvider: z.nativeEnum(SamlProviders),
isActive: z.boolean(),
entryPoint: z.string(),
issuer: z.string(),
cert: z.string()
})
.partial()
.merge(z.object({ organizationId: z.string() })),
response: {
200: SamlConfigsSchema
}
},
handler: async (req) => {
const saml = await server.services.saml.updateSamlCfg({
actor: req.permission.type,
actorId: req.permission.id,
orgId: req.body.organizationId,
...req.body
});
return saml;
}
});
};

View File

@@ -7,6 +7,7 @@ import {
SecretsSchema,
SecretVersionsSchema
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import {
ApprovalStatus,
RequestState
@@ -230,6 +231,23 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
approvalId: req.params.id,
status: req.body.status
});
const isClosing = approval.status === RequestState.Closed;
await server.services.auditLog.createAuditLog({
projectId: approval.projectId,
...req.auditLogInfo,
event: {
type: isClosing ? EventType.SECRET_APPROVAL_CLOSED : EventType.SECRET_APPROVAL_REOPENED,
metadata: {
[isClosing ? ("closedBy" as const) : ("reopenedBy" as const)]:
approval.statusChangeBy as string,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
} as any
// akhilmhdh: had to apply any to avoid ts issue with this
}
});
return { approval };
}
});

View File

@@ -0,0 +1,61 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify, stripUndefinedInWhere } from "@app/lib/knex";
export type TAuditLogDalFactory = ReturnType<typeof auditLogDalFactory>;
type TFindQuery = {
actor?: string;
projectId?: string;
orgId?: string;
eventType?: string;
startDate?: string;
endDate?: string;
userAgentType?: string;
limit?: number;
offset?: number;
};
export const auditLogDalFactory = (db: TDbClient) => {
const auditLogOrm = ormify(db, TableName.AuditLog);
const find = async (
{
orgId,
projectId,
userAgentType,
startDate,
endDate,
limit = 20,
offset = 0,
actor,
eventType
}: TFindQuery,
tx?: Knex
) => {
const sqlQuery = (tx || db)(TableName.AuditLog)
.where(
stripUndefinedInWhere({
projectId,
orgId,
eventType,
actor,
userAgentType
})
)
.limit(limit)
.offset(offset);
if (startDate) {
sqlQuery.where("createdAt", ">=", startDate);
}
if (endDate) {
sqlQuery.where("createdAt", "<=", endDate);
}
const docs = await sqlQuery;
return docs;
};
return { ...auditLogOrm, find };
};

View File

@@ -0,0 +1,46 @@
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TAuditLogDalFactory } from "./audit-log-dal";
import { TCreateAuditLogDTO } from "./audit-log-types";
type TAuditLogQueueServiceFactoryDep = {
auditLogDal: TAuditLogDalFactory;
queueService: TQueueServiceFactory;
};
export type TAuditLogQueueServiceFactory = ReturnType<typeof auditLogQueueServiceFactory>;
export const auditLogQueueServiceFactory = ({
auditLogDal,
queueService
}: TAuditLogQueueServiceFactoryDep) => {
const pushToLog = async (data: TCreateAuditLogDTO) => {
await queueService.queue(QueueName.AuditLog, QueueJobs.AuditLog, data, {
removeOnFail: {
count: 5
},
removeOnComplete: true
});
};
queueService.start(QueueName.AuditLog, async (job) => {
const { actor, orgId, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
const MS_IN_DAY = 24 * 60 * 60 * 1000;
await auditLogDal.create({
actor: actor.type,
actorMetadata: actor.metadata,
userAgent,
projectId,
ipAddress,
orgId,
eventType: event.type,
expiresAt: new Date(Date.now() + 30 * MS_IN_DAY),
eventMetadata: event.metadata,
userAgentType
});
});
return {
pushToLog
};
};

View File

@@ -0,0 +1,73 @@
import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "@app/lib/errors";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
import { TAuditLogDalFactory } from "./audit-log-dal";
import { TAuditLogQueueServiceFactory } from "./audit-log-queue";
import { EventType, TCreateAuditLogDTO, TListProjectAuditLogDTO } from "./audit-log-types";
type TAuditLogServiceFactoryDep = {
auditLogDal: TAuditLogDalFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
auditLogQueue: TAuditLogQueueServiceFactory;
};
export type TAuditLogServiceFactory = ReturnType<typeof auditLogServiceFactory>;
export const auditLogServiceFactory = ({
auditLogDal,
auditLogQueue,
permissionService
}: TAuditLogServiceFactoryDep) => {
const listProjectAuditLogs = async ({
userAgentType,
eventType,
offset,
limit,
endDate,
startDate,
actor,
actorId,
projectId,
auditLogActor
}: TListProjectAuditLogDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs
);
const auditLogs = await auditLogDal.find({
startDate,
endDate,
limit,
offset,
eventType,
userAgentType,
actor: auditLogActor,
projectId
});
return auditLogs.map(
({ eventType: logEventType, actor: eActor, actorMetadata, eventMetadata, ...el }) => ({
...el,
event: { type: logEventType, metadata: eventMetadata },
actor: { type: eActor, metadata: actorMetadata }
})
);
};
const createAuditLog = async (data: TCreateAuditLogDTO) => {
// add all cases in which project id or org id cannot be added
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
if (!data.projectId && !data.orgId)
throw new BadRequestError({ message: "Must either project id or org id" });
}
return auditLogQueue.pushToLog(data);
};
return {
createAuditLog,
listProjectAuditLogs
};
};

View File

@@ -0,0 +1,674 @@
import { TProjectPermission } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
export type TListProjectAuditLogDTO = {
auditLogActor?: string;
projectId: string;
eventType?: string;
startDate?: string;
endDate?: string;
userAgentType?: string;
limit?: number;
offset?: number;
} & TProjectPermission;
export type TCreateAuditLogDTO = {
event: Event;
actor: UserActor | IdentityActor | ServiceActor;
orgId?: string;
projectId?: string;
} & BaseAuthData;
interface BaseAuthData {
ipAddress?: string;
userAgent?: string;
userAgentType?: UserAgentType;
}
export enum UserAgentType {
WEB = "web",
CLI = "cli",
K8_OPERATOR = "k8-operator",
TERRAFORM = "terraform",
OTHER = "other",
PYTHON_SDK = "InfisicalPythonSDK",
NODE_SDK = "InfisicalNodeSDK"
}
export enum EventType {
GET_SECRETS = "get-secrets",
GET_SECRET = "get-secret",
REVEAL_SECRET = "reveal-secret",
CREATE_SECRET = "create-secret",
CREATE_SECRETS = "create-secrets",
UPDATE_SECRET = "update-secret",
UPDATE_SECRETS = "update-secrets",
DELETE_SECRET = "delete-secret",
DELETE_SECRETS = "delete-secrets",
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", // v2
DELETE_SERVICE_TOKEN = "delete-service-token", // v2
CREATE_IDENTITY = "create-identity",
UPDATE_IDENTITY = "update-identity",
DELETE_IDENTITY = "delete-identity",
LOGIN_IDENTITY_UNIVERSAL_AUTH = "login-identity-universal-auth",
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth",
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",
ADD_WORKSPACE_MEMBER = "add-workspace-member",
ADD_BATCH_WORKSPACE_MEMBER = "add-workspace-members",
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",
SECRET_APPROVAL_MERGED = "secret-approval-merged",
SECRET_APPROVAL_REQUEST = "secret-approval-request",
SECRET_APPROVAL_CLOSED = "secret-approval-closed",
SECRET_APPROVAL_REOPENED = "secret-approval-reopened"
}
interface UserActorMetadata {
userId: string;
email: string;
}
interface ServiceActorMetadata {
serviceId: string;
name: string;
}
interface IdentityActorMetadata {
identityId: string;
name: string;
}
export interface UserActor {
type: ActorType.USER;
metadata: UserActorMetadata;
}
export interface ServiceActor {
type: ActorType.SERVICE;
metadata: ServiceActorMetadata;
}
export interface IdentityActor {
type: ActorType.IDENTITY;
metadata: IdentityActorMetadata;
}
export type Actor = UserActor | ServiceActor | IdentityActor;
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 CreateSecretBatchEvent {
type: EventType.CREATE_SECRETS;
metadata: {
environment: string;
secretPath: string;
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
};
}
interface UpdateSecretEvent {
type: EventType.UPDATE_SECRET;
metadata: {
environment: string;
secretPath: string;
secretId: string;
secretKey: string;
secretVersion: number;
};
}
interface UpdateSecretBatchEvent {
type: EventType.UPDATE_SECRETS;
metadata: {
environment: string;
secretPath: string;
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
};
}
interface DeleteSecretEvent {
type: EventType.DELETE_SECRET;
metadata: {
environment: string;
secretPath: string;
secretId: string;
secretKey: string;
secretVersion: number;
};
}
interface DeleteSecretBatchEvent {
type: EventType.DELETE_SECRETS;
metadata: {
environment: string;
secretPath: string;
secrets: Array<{ 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 CreateIdentityEvent {
// note: currently not logging org-role
type: EventType.CREATE_IDENTITY;
metadata: {
identityId: string;
name: string;
};
}
interface UpdateIdentityEvent {
type: EventType.UPDATE_IDENTITY;
metadata: {
identityId: string;
name?: string;
};
}
interface DeleteIdentityEvent {
type: EventType.DELETE_IDENTITY;
metadata: {
identityId: string;
};
}
interface LoginIdentityUniversalAuthEvent {
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH;
metadata: {
identityId: string;
identityUniversalAuthId: string;
clientSecretId: string;
identityAccessTokenId: string;
};
}
interface AddIdentityUniversalAuthEvent {
type: EventType.ADD_IDENTITY_UNIVERSAL_AUTH;
metadata: {
identityId: string;
clientSecretTrustedIps: Array<TIdentityTrustedIp>;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
};
}
interface UpdateIdentityUniversalAuthEvent {
type: EventType.UPDATE_IDENTITY_UNIVERSAL_AUTH;
metadata: {
identityId: string;
clientSecretTrustedIps?: Array<TIdentityTrustedIp>;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
};
}
interface GetIdentityUniversalAuthEvent {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH;
metadata: {
identityId: string;
};
}
interface CreateIdentityUniversalAuthClientSecretEvent {
type: EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET;
metadata: {
identityId: string;
clientSecretId: string;
};
}
interface GetIdentityUniversalAuthClientSecretsEvent {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS;
metadata: {
identityId: string;
};
}
interface RevokeIdentityUniversalAuthClientSecretEvent {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET;
metadata: {
identityId: string;
clientSecretId: string;
};
}
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 AddBatchWorkspaceMemberEvent {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER;
metadata: Array<{
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;
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;
position: number;
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;
}[];
};
}
interface SecretApprovalMerge {
type: EventType.SECRET_APPROVAL_MERGED;
metadata: {
mergedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
interface SecretApprovalClosed {
type: EventType.SECRET_APPROVAL_CLOSED;
metadata: {
closedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
interface SecretApprovalReopened {
type: EventType.SECRET_APPROVAL_REOPENED;
metadata: {
reopenedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
interface SecretApprovalRequest {
type: EventType.SECRET_APPROVAL_REQUEST;
metadata: {
committedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
export type Event =
| GetSecretsEvent
| GetSecretEvent
| CreateSecretEvent
| CreateSecretBatchEvent
| UpdateSecretEvent
| UpdateSecretBatchEvent
| DeleteSecretEvent
| DeleteSecretBatchEvent
| GetWorkspaceKeyEvent
| AuthorizeIntegrationEvent
| UnauthorizeIntegrationEvent
| CreateIntegrationEvent
| DeleteIntegrationEvent
| AddTrustedIPEvent
| UpdateTrustedIPEvent
| DeleteTrustedIPEvent
| CreateServiceTokenEvent
| DeleteServiceTokenEvent
| CreateIdentityEvent
| UpdateIdentityEvent
| DeleteIdentityEvent
| LoginIdentityUniversalAuthEvent
| AddIdentityUniversalAuthEvent
| UpdateIdentityUniversalAuthEvent
| GetIdentityUniversalAuthEvent
| CreateIdentityUniversalAuthClientSecretEvent
| GetIdentityUniversalAuthClientSecretsEvent
| RevokeIdentityUniversalAuthClientSecretEvent
| CreateEnvironmentEvent
| UpdateEnvironmentEvent
| DeleteEnvironmentEvent
| AddWorkspaceMemberEvent
| AddBatchWorkspaceMemberEvent
| RemoveWorkspaceMemberEvent
| CreateFolderEvent
| UpdateFolderEvent
| DeleteFolderEvent
| CreateWebhookEvent
| UpdateWebhookStatusEvent
| DeleteWebhookEvent
| GetSecretImportsEvent
| CreateSecretImportEvent
| UpdateSecretImportEvent
| DeleteSecretImportEvent
| UpdateUserRole
| UpdateUserDeniedPermissions
| SecretApprovalMerge
| SecretApprovalClosed
| SecretApprovalRequest
| SecretApprovalReopened;

View File

@@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TSamlConfigDalFactory = ReturnType<typeof samlConfigDalFactory>;
export const samlConfigDalFactory = (db: TDbClient) => {
const samlCfgOrm = ormify(db, TableName.SamlConfig);
return samlCfgOrm;
};

View File

@@ -0,0 +1,348 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import {
OrgMembershipRole,
OrgMembershipStatus,
SecretKeyEncoding,
TSamlConfigs,
TSamlConfigsUpdate
} from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import {
decryptSymmetric,
encryptSymmetric,
infisicalSymmetricDecrypt
} from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { AuthTokenType } from "@app/services/auth/auth-type";
import { TOrgBotDalFactory } from "@app/services/org/org-bot-dal";
import { TOrgDalFactory } from "@app/services/org/org-dal";
import { TUserDalFactory } from "@app/services/user/user-dal";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TSamlConfigDalFactory } from "./saml-config-dal";
import {
SamlProviders,
TCreateSamlCfgDTO,
TGetSamlCfgDTO,
TSamlLoginDTO,
TUpdateSamlCfgDTO
} from "./saml-config-types";
type TSamlConfigServiceFactoryDep = {
samlConfigDal: TSamlConfigDalFactory;
userDal: Pick<TUserDalFactory, "create" | "findUserByEmail" | "transaction" | "updateById">;
orgDal: Pick<
TOrgDalFactory,
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById"
>;
orgBotDal: Pick<TOrgBotDalFactory, "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
};
export type TSamlConfigServiceFactory = ReturnType<typeof samlConfigServiceFactory>;
export const samlConfigServiceFactory = ({
samlConfigDal,
orgBotDal,
orgDal,
userDal,
permissionService
}: TSamlConfigServiceFactoryDep) => {
const createSamlCfg = async ({
cert,
actor,
orgId,
issuer,
actorId,
isActive,
entryPoint,
authProvider
}: TCreateSamlCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Create,
OrgPermissionSubjects.Sso
);
// TODO(akhilmhdh-pg): licence check
const orgBot = await orgBotDal.findOne({ orgId });
if (!orgBot)
throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
tag: orgBot.symmetricKeyTag,
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
});
const {
ciphertext: encryptedEntryPoint,
iv: entryPointIV,
tag: entryPointTag
} = encryptSymmetric(entryPoint, key);
const {
ciphertext: encryptedIssuer,
iv: issuerIV,
tag: issuerTag
} = encryptSymmetric(issuer, key);
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
const samlConfig = await samlConfigDal.create({
orgId,
authProvider,
isActive,
encryptedEntryPoint,
entryPointIV,
entryPointTag,
encryptedIssuer,
issuerIV,
issuerTag,
encryptedCert,
certIV,
certTag
});
return samlConfig;
};
const updateSamlCfg = async ({
orgId,
actor,
cert,
actorId,
issuer,
isActive,
entryPoint,
authProvider
}: TUpdateSamlCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit,
OrgPermissionSubjects.Sso
);
const updateQuery: TSamlConfigsUpdate = { authProvider, isActive };
const orgBot = await orgBotDal.findOne({ orgId });
if (!orgBot)
throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
tag: orgBot.symmetricKeyTag,
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
});
if (entryPoint) {
const {
ciphertext: encryptedEntryPoint,
iv: entryPointIV,
tag: entryPointTag
} = encryptSymmetric(entryPoint, key);
updateQuery.encryptedEntryPoint = encryptedEntryPoint;
updateQuery.entryPointIV = entryPointIV;
updateQuery.entryPointTag = entryPointTag;
}
if (issuer) {
const {
ciphertext: encryptedIssuer,
iv: issuerIV,
tag: issuerTag
} = encryptSymmetric(issuer, key);
updateQuery.encryptedIssuer = encryptedIssuer;
updateQuery.issuerIV = issuerIV;
updateQuery.issuerTag = issuerTag;
}
if (cert) {
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
updateQuery.encryptedCert = encryptedCert;
updateQuery.certIV = certIV;
updateQuery.certTag = certTag;
}
const [ssoConfig] = await samlConfigDal.update({ orgId }, updateQuery);
return ssoConfig;
};
const getSaml = async (dto: TGetSamlCfgDTO) => {
let ssoConfig: TSamlConfigs | undefined;
if (dto.type === "org") {
ssoConfig = await samlConfigDal.findOne({ orgId: dto.orgId });
if (!ssoConfig) return;
} else if (dto.type === "ssoId") {
ssoConfig = await samlConfigDal.findById(dto.id);
}
if (!ssoConfig) throw new BadRequestError({ message: "Failed to find organization SSO data" });
// when dto is type id means it's internally used
if (dto.type === "org") {
const { permission } = await permissionService.getOrgPermission(
dto.actor,
dto.actorId,
ssoConfig!.orgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Read,
OrgPermissionSubjects.Sso
);
}
const {
entryPointTag,
entryPointIV,
encryptedEntryPoint,
certTag,
certIV,
encryptedCert,
issuerTag,
issuerIV,
encryptedIssuer
} = ssoConfig;
const orgBot = await orgBotDal.findOne({ orgId: ssoConfig.orgId });
if (!orgBot)
throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
tag: orgBot.symmetricKeyTag,
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
});
let entryPoint = "";
if (encryptedEntryPoint && entryPointIV && entryPointTag) {
entryPoint = decryptSymmetric({
ciphertext: encryptedEntryPoint,
key,
tag: entryPointTag,
iv: entryPointIV
});
}
let issuer = "";
if (encryptedIssuer && issuerTag && issuerIV) {
issuer = decryptSymmetric({
key,
tag: issuerTag,
iv: issuerIV,
ciphertext: encryptedIssuer
});
}
let cert = "";
if (encryptedCert && certTag && certIV) {
cert = decryptSymmetric({ key, tag: certTag, iv: certIV, ciphertext: encryptedCert });
}
return {
id: ssoConfig.id,
organization: ssoConfig.orgId,
orgId: ssoConfig.orgId,
authProvider: ssoConfig.authProvider,
isActive: ssoConfig.isActive,
entryPoint,
issuer,
cert
};
};
const samlLogin = async ({
firstName,
email,
lastName,
authProvider,
orgId,
relayState,
isSignupAllowed
}: TSamlLoginDTO) => {
const appCfg = getConfig();
let user = await userDal.findUserByEmail(email);
const isSamlSignUpDisabled = !isSignupAllowed && !user;
if (isSamlSignUpDisabled)
throw new BadRequestError({ message: "User signup disabled", name: "Saml SSO login" });
const organization = await orgDal.findOrgById(orgId);
if (!organization) throw new BadRequestError({ message: "Org not found" });
if (user) {
const hasSamlEnabled = (user.authMethods || []).some((method) =>
Object.values(SamlProviders).includes(method as SamlProviders)
);
await userDal.transaction(async (tx) => {
if (!hasSamlEnabled) {
await userDal.updateById(user.id, { authMethods: [authProvider] }, tx);
}
const [orgMembership] = await orgDal.findMembership({ userId: user.id, orgId }, { tx });
if (!orgMembership) {
await orgDal.createMembership(
{
userId: user.id,
orgId,
inviteEmail: email,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Accepted
},
tx
);
} else if (orgMembership.status === OrgMembershipStatus.Invited) {
await orgDal.updateMembershipById(
orgMembership.id,
{
status: OrgMembershipStatus.Accepted
},
tx
);
}
});
} else {
user = await userDal.transaction(async (tx) => {
const newUser = await userDal.create(
{
email,
firstName,
lastName,
authMethods: [authProvider]
},
tx
);
await orgDal.createMembership({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
});
return newUser;
});
}
const isUserCompleted = Boolean(user.isAccepted);
const providerAuthToken = jwt.sign(
{
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id,
email: user.email,
firstName,
lastName,
organizationName: organization.name,
organizationId: organization.id,
authMethod: authProvider,
isUserCompleted,
...(relayState
? {
callbackPort: JSON.parse(relayState).callbackPort as string
}
: {})
},
appCfg.JWT_AUTH_SECRET,
{
expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME
}
);
return { isUserCompleted, providerAuthToken };
};
return {
createSamlCfg,
updateSamlCfg,
getSaml,
samlLogin
};
};

View File

@@ -0,0 +1,43 @@
import { TOrgPermission } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
export enum SamlProviders {
OKTA_SAML = "okta-saml",
AZURE_SAML = "azure-saml",
JUMPCLOUD_SAML = "jumpcloud-saml"
}
export type TCreateSamlCfgDTO = {
authProvider: SamlProviders;
isActive: boolean;
entryPoint: string;
issuer: string;
cert: string;
} & TOrgPermission;
export type TUpdateSamlCfgDTO = Partial<{
authProvider: SamlProviders;
isActive: boolean;
entryPoint: string;
issuer: string;
cert: string;
}> &
TOrgPermission;
export type TGetSamlCfgDTO =
| { type: "org"; orgId: string; actor: ActorType; actorId: string }
| {
type: "ssoId";
id: string;
};
export type TSamlLoginDTO = {
email: string;
firstName: string;
lastName?: string;
authProvider: string;
orgId: string;
isSignupAllowed: boolean;
// saml thingy
relayState?: string;
};

View File

@@ -84,12 +84,12 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
childrenMapper: [
{
key: "reviewerMemberId",
label: "reviewers",
label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) =>
member ? { member, status } : undefined
},
{ key: "approverId", label: "approvers", mapper: ({ approverId }) => approverId }
] as const
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
]
});
if (!formatedDoc?.[0]) return;
return {
@@ -213,12 +213,12 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
childrenMapper: [
{
key: "reviewerMemberId",
label: "reviewers",
label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: s }) =>
member ? { member, status: s } : undefined
},
{ key: "approverId", label: "approvers", mapper: ({ approverId }) => approverId }
] as const
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
]
});
return formatedDoc.map((el) => ({
...el,

View File

@@ -181,7 +181,7 @@ export const secretApprovalRequestServiceFactory = ({
status,
statusChangeBy: membership.id
});
return updatedRequest;
return { ...secretApprovalRequest, ...updatedRequest };
};
const mergeSecretApprovalRequest = async ({

View File

@@ -61,7 +61,7 @@ export const secretRotationDalFactory = (db: TDbClient) => {
childrenMapper: [
{
key: "secId",
label: "outputs",
label: "outputs" as const,
mapper: ({
secId,
outputKey,
@@ -92,7 +92,7 @@ export const secretRotationDalFactory = (db: TDbClient) => {
}
})
}
] as const
]
});
} catch (error) {
throw new DatabaseError({ error, name: "SecretRotationFind" });

View File

@@ -7,7 +7,8 @@ import {
TSecretFolderVersions,
TSecretSnapshotFolders,
TSecretSnapshots,
TSecretVersions} from "@app/db/schemas";
TSecretVersions
} from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
@@ -109,15 +110,15 @@ export const snapshotDalFactory = (db: TDbClient) => {
childrenMapper: [
{
key: "id",
label: "secretVersions",
label: "secretVersions" as const,
mapper: (el) => SecretVersionsSchema.parse(el)
},
{
key: "folderVerId",
label: "folderVersion",
label: "folderVersion" as const,
mapper: ({ folderVerId: id, folderVerName: name }) => ({ id, name })
}
] as const
]
})?.[0];
} catch (error) {
throw new DatabaseError({ error, name: "FindSecretSnapshotDataById" });
@@ -237,7 +238,7 @@ export const snapshotDalFactory = (db: TDbClient) => {
childrenMapper: [
{
key: "id",
label: "secretVersions",
label: "secretVersions" as const,
mapper: (el) => ({
...SecretVersionsSchema.parse(el),
latestSecretVersion: el.latestSecretVersion
@@ -245,14 +246,14 @@ export const snapshotDalFactory = (db: TDbClient) => {
},
{
key: "folderVerId",
label: "folderVersion",
label: "folderVersion" as const,
mapper: ({ folderVerId: id, folderVerName: name, latestFolderVersion }) => ({
id,
name,
latestFolderVersion
})
}
] as const
]
});
return formated;
} catch (error) {

View File

@@ -1,190 +0,0 @@
import dotenv from "dotenv";
import { initDbConnection } from "./db";
import {
SecretVersionsSchema,
TableName,
TSecretFolderVersions,
TSecrets,
TSecretSnapshotFolders,
TSecretSnapshots,
TSecretVersions} from "./db/schemas";
import { selectAllTableCols, sqlNestRelationships } from "./lib/knex";
dotenv.config();
// const db = initDbConnection(process.env.DB_CONNECTION_URI);
// const main = async () => {
// const folders = db
// .withRecursive("parent", (qb) => {
// qb.select({
// depth: 1,
// path: db.raw("'/'")
// })
// .select(selectAllTableCols(db, TableName.SecretFolder))
// .from(TableName.SecretFolder)
// .join(
// TableName.Environment,
// `${TableName.SecretFolder}.envId`,
// `${TableName.Environment}.id`
// )
// .where({
// projectId: "01c10de1-8743-490f-9c8a-7a19c4dc72a9",
// parentId: null
// })
// .where(`${TableName.Environment}.slug`, "dev")
// .union((qb) =>
// qb
// .select({
// depth: db.raw("parent.depth + 1"),
// path: db.raw(
// "CONCAT((CASE WHEN parent.path = '/' THEN '' ELSE parent.path END),'/', secret_folders.name)"
// )
// })
// .select(selectAllTableCols(db, TableName.SecretFolder))
// .whereRaw(
// `depth = array_position(ARRAY[${[1, 2]
// .map((_) => "?")
// .join(",")}]::varchar[], secret_folders.name,depth)`,
// [...["ui", "design"]]
// )
// .from(TableName.SecretFolder)
// .join("parent", "parent.id", `${TableName.SecretFolder}.parentId`)
// );
// })
// .select("*")
// .from("parent")
// .orderBy("depth", "desc")
// .first();
// console.log(folders.toSQL());
// console.log(JSON.stringify(await folders, null, 4));
// process.exit(0);
// };
//
const main = async () => {
const db = initDbConnection(process.env.DB_CONNECTION_URI);
const folders = db
.withRecursive("parent", (qb) => {
qb.from(TableName.Snapshot)
.leftJoin<TSecretSnapshotFolders>(
TableName.SnapshotFolder,
`${TableName.SnapshotFolder}.snapshotId`,
`${TableName.Snapshot}.id`
)
.leftJoin<TSecretFolderVersions>(
TableName.SecretFolderVersion,
`${TableName.SnapshotFolder}.folderVersionId`,
`${TableName.SecretFolderVersion}.id`
)
.select(selectAllTableCols(TableName.Snapshot))
.select({ depth: 1 })
.select(
db.ref("name").withSchema(TableName.SecretFolderVersion).as("folderVerName"),
db.ref("folderId").withSchema(TableName.SecretFolderVersion).as("folderVerId")
)
.where(`${TableName.Snapshot}.id`, "abe694d3-957f-40f5-a907-952d3e5ccaf1")
.union((cb) =>
cb
.select(selectAllTableCols(TableName.Snapshot))
.select({ depth: db.raw("parent.depth + 1") })
.select(
db.ref("name").withSchema(TableName.SecretFolderVersion).as("folderVerName"),
db.ref("folderId").withSchema(TableName.SecretFolderVersion).as("folderVerId")
)
.from(TableName.Snapshot)
.join<TSecretSnapshots, TSecretSnapshots & { secretId: string; max: number }>(
db(TableName.Snapshot)
.groupBy("folderId")
.max("createdAt")
.select("folderId")
.as("latestVersion"),
`${TableName.Snapshot}.createdAt`,
"latestVersion.max"
)
.leftJoin<TSecretSnapshotFolders>(
TableName.SnapshotFolder,
`${TableName.SnapshotFolder}.snapshotId`,
`${TableName.Snapshot}.id`
)
.leftJoin<TSecretFolderVersions>(
TableName.SecretFolderVersion,
`${TableName.SnapshotFolder}.folderVersionId`,
`${TableName.SecretFolderVersion}.id`
)
.join("parent", "parent.folderVerId", `${TableName.Snapshot}.folderId`)
);
})
.orderBy("depth", "asc")
.from<TSecretSnapshots & { folderVerId: string; folderVerName: string }>("parent")
.leftJoin<TSecretSnapshots>(
TableName.SnapshotSecret,
`parent.id`,
`${TableName.SnapshotSecret}.snapshotId`
)
.leftJoin<TSecretVersions>(
TableName.SecretVersion,
`${TableName.SnapshotSecret}.secretVersionId`,
`${TableName.SecretVersion}.id`
)
.leftJoin<{ latestSecretVersion: number }>(
db(TableName.SecretVersion)
.groupBy("secretId")
.select("secretId")
.max("version")
.as("secGroupByMaxVersion"),
`${TableName.SecretVersion}.secretId`,
"secGroupByMaxVersion.secretId"
)
.leftJoin<{ latestFolderVersion: number }>(
db(TableName.SecretFolderVersion)
.groupBy("folderId")
.select("folderId")
.max("version")
.as("folderGroupByMaxVersion"),
`parent.folderId`,
"folderGroupByMaxVersion.folderId"
)
.select(selectAllTableCols(TableName.SecretVersion))
.select(
db.ref("id").withSchema("parent").as("snapshotId"),
db.ref("folderId").withSchema("parent").as("snapshotFolderId"),
db.ref("parentFolderId").withSchema("parent").as("snapshotParentFolderId"),
db.ref("folderVerName").withSchema("parent"),
db.ref("folderVerId").withSchema("parent"),
db.ref("max").withSchema("secGroupByMaxVersion").as("latestSecretVersion"),
db.ref("max").withSchema("folderGroupByMaxVersion").as("latestFolderVersion")
);
console.log(folders.toSQL());
const data = await folders;
// console.log(data);
const formated = sqlNestRelationships({
data,
key: "snapshotId",
parentMapper: ({ snapshotId: id }) => ({
id
}),
childrenMapper: [
{
key: "id",
label: "secretVersions",
mapper: (el) => ({
...SecretVersionsSchema.parse(el),
latestSecretVersion: el.latestSecretVersion
})
},
{
key: "folderVerId",
label: "folderVersion",
mapper: ({ folderVerId: id, folderVerName: name, latestFolderVersion }) => ({
id,
name,
version: latestFolderVersion
})
}
] as const
});
console.log(formated);
process.exit(0);
};
main();

View File

@@ -122,6 +122,8 @@ export const decryptAsymmetric = ({
return naclUtils.encodeUTF8(plaintext);
};
export const generateSymmetricKey = (size = 32) => crypto.randomBytes(size).toString("base64");
export const generateAsymmetricKeyPair = () => {
const pair = nacl.box.keyPair();

View File

@@ -15,3 +15,23 @@ export const pick = <T extends object, TKeys extends keyof T>(
{} as Pick<T, TKeys>
);
};
/**
* Removes (shakes out) undefined entries from an
* object. Optional second argument shakes out values
* by custom evaluation.
*/
export const shake = <RemovedKeys extends string, T = {}>(
obj: T,
filter: (value: any) => boolean = (x) => x === undefined || x === null
): Omit<T, RemovedKeys> => {
if (!obj) return {} as T;
const keys = Object.keys(obj) as (keyof T)[];
return keys.reduce((acc, key) => {
if (filter(obj[key])) {
return acc;
}
acc[key] = obj[key];
return acc;
}, {} as T);
};

View File

@@ -3,12 +3,12 @@ import { Tables } from "knex/types/tables";
export const selectAllTableCols = <Tname extends keyof Tables>(tableName: Tname) =>
`${tableName}.*` as keyof Tables[Tname]["base"];
export const stripUndefinedInWhere = <T extends object>(val: T) => {
export const stripUndefinedInWhere = <T extends object>(val: T): Exclude<T, undefined> => {
const copy = val;
Object.entries(copy).forEach(([key, value]) => {
if (typeof value === "undefined") {
delete copy[key as keyof T];
}
});
return copy;
return copy as Exclude<T, undefined>;
};

View File

@@ -1,12 +1,16 @@
import { Job, JobsOptions, Queue, Worker, WorkerListener } from "bullmq";
import Redis from "ioredis";
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
export enum QueueName {
SecretRotation = "secret-rotation"
SecretRotation = "secret-rotation",
AuditLog = "audit-log"
}
export enum QueueJobs {
SecretRotation = "secret-rotation-job"
SecretRotation = "secret-rotation-job",
AuditLog = "audit-log-job"
}
export type TQueueJobTypes = {
@@ -14,6 +18,10 @@ export type TQueueJobTypes = {
payload: { rotationId: string };
name: QueueJobs.SecretRotation;
};
[QueueName.AuditLog]: {
name: QueueJobs.AuditLog;
payload: TCreateAuditLogDTO;
};
};
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
@@ -65,7 +73,7 @@ export const queueServiceFactory = (redisUrl: string) => {
name: T,
job: TQueueJobTypes[T]["name"],
data: TQueueJobTypes[T]["payload"],
opts: JobsOptions & { jobId: string }
opts: JobsOptions & { jobId?: string }
) => {
const q = queueContainer[name];
await q.add(job, data, opts);

View File

@@ -0,0 +1,55 @@
import fp from "fastify-plugin";
import { UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
export const getUserAgentType = (userAgent: string | undefined) => {
if (userAgent === undefined) {
return UserAgentType.OTHER;
}
if (userAgent === UserAgentType.CLI) {
return UserAgentType.CLI;
}
if (userAgent === UserAgentType.K8_OPERATOR) {
return UserAgentType.K8_OPERATOR;
}
if (userAgent === UserAgentType.TERRAFORM) {
return UserAgentType.TERRAFORM;
}
if (userAgent.toLowerCase().includes("mozilla")) {
return UserAgentType.WEB;
}
if (userAgent.includes(UserAgentType.NODE_SDK)) {
return UserAgentType.NODE_SDK;
}
if (userAgent.includes(UserAgentType.PYTHON_SDK)) {
return UserAgentType.PYTHON_SDK;
}
return UserAgentType.OTHER;
};
export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
server.decorateRequest("auditLogInfo", null);
server.addHook("onRequest", async (req) => {
if (!req.auth) return;
const userAgent = req.headers["user-agent"] ?? "";
const payload = {
ipAddress: req.realIp,
userAgent,
userAgentType: getUserAgentType(userAgent)
} as typeof req.auditLogInfo;
if (req.auth.actor === ActorType.USER) {
payload.actor = {
type: ActorType.USER,
metadata: {
email: req.auth.user.email,
userId: req.auth.userId
}
};
} else {
throw new BadRequestError({ message: "Missing logic for other actor" });
}
req.auditLogInfo = payload;
});
});

View File

@@ -2,8 +2,13 @@ import { Knex } from "knex";
import { z } from "zod";
import { registerV1EERoutes } from "@app/ee/routes/v1";
import { auditLogDalFactory } from "@app/ee/services/audit-log/audit-log-dal";
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { permissionDalFactory } from "@app/ee/services/permission/permission-dal";
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { samlConfigDalFactory } from "@app/ee/services/saml-config/saml-config-dal";
import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
import { sapApproverDalFactory } from "@app/ee/services/secret-approval-policy/sap-approver-dal";
import { secretApprovalPolicyDalFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
@@ -43,6 +48,7 @@ import { integrationServiceFactory } from "@app/services/integration/integration
import { integrationAuthDalFactory } from "@app/services/integration-auth/integration-auth-dal";
import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
import { incidentContactDalFactory } from "@app/services/org/incident-contacts-dal";
import { orgBotDalFactory } from "@app/services/org/org-bot-dal";
import { orgDalFactory } from "@app/services/org/org-dal";
import { orgRoleDalFactory } from "@app/services/org/org-role-dal";
import { orgRoleServiceFactory } from "@app/services/org/org-role-service";
@@ -80,6 +86,7 @@ import { userServiceFactory } from "@app/services/user/user-service";
import { webhookDalFactory } from "@app/services/webhook/webhook-dal";
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
import { injectAuditLogInfo } from "../plugins/audit-log";
import { injectIdentity } from "../plugins/auth/inject-identity";
import { injectPermission } from "../plugins/auth/inject-permission";
import { registerV1Routes } from "./v1";
@@ -99,6 +106,7 @@ export const registerRoutes = async (
const authDal = authDalFactory(db);
const authTokenDal = tokenDalFactory(db);
const orgDal = orgDalFactory(db);
const orgBotDal = orgBotDalFactory(db);
const incidentContactDal = incidentContactDalFactory(db);
const orgRoleDal = orgRoleDalFactory(db);
const superAdminDal = superAdminDalFactory(db);
@@ -132,8 +140,11 @@ export const registerRoutes = async (
const identityUaDal = identityUaDalFactory(db);
const identityUaClientSecretDal = identityUaClientSecretDalFactory(db);
const auditLogDal = auditLogDalFactory(db);
// ee db layer ops
const permissionDal = permissionDalFactory(db);
const samlConfigDal = samlConfigDalFactory(db);
const sapApproverDal = sapApproverDalFactory(db);
const secretApprovalPolicyDal = secretApprovalPolicyDalFactory(db);
const secretApprovalRequestDal = secretApprovalRequestDalFactory(db);
@@ -146,6 +157,8 @@ export const registerRoutes = async (
const snapshotFolderDal = snapshotFolderDalFactory(db);
const permissionService = permissionServiceFactory({ permissionDal, orgRoleDal, projectRoleDal });
const auditLogQueue = auditLogQueueServiceFactory({ auditLogDal, queueService });
const auditLogService = auditLogServiceFactory({ auditLogDal, permissionService, auditLogQueue });
const sapService = secretApprovalPolicyServiceFactory({
projectMembershipDal,
projectEnvDal,
@@ -153,6 +166,13 @@ export const registerRoutes = async (
permissionService,
secretApprovalPolicyDal
});
const samlService = samlConfigServiceFactory({
permissionService,
orgBotDal,
orgDal,
userDal,
samlConfigDal
});
const sarService = secretApprovalRequestServiceFactory({
permissionService,
folderDal,
@@ -180,7 +200,8 @@ export const registerRoutes = async (
incidentContactDal,
tokenService,
smtpService,
userDal
userDal,
orgBotDal
});
const signupService = authSignupServiceFactory({
tokenService,
@@ -354,7 +375,9 @@ export const registerRoutes = async (
secretApprovalPolicy: sapService,
secretApprovalRequest: sarService,
secretRotation: secretRotationService,
snapshot: snapshotService
snapshot: snapshotService,
saml: samlService,
auditLog: auditLogService
});
server.decorate<FastifyZodProvider["store"]>("store", {
@@ -363,6 +386,7 @@ export const registerRoutes = async (
await server.register(injectIdentity);
await server.register(injectPermission);
await server.register(injectAuditLogInfo);
server.route({
url: "/status",
@@ -394,9 +418,13 @@ export const registerRoutes = async (
});
// register routes for v1
await server.register(registerV1Routes, { prefix: "/v1" });
await server.register(
async (v1Server) => {
await v1Server.register(registerV1EERoutes);
await v1Server.register(registerV1Routes);
},
{ prefix: "/v1" }
);
await server.register(registerV2Routes, { prefix: "/v2" });
await server.register(registerV3Routes, { prefix: "/v3" });
await server.register(registerV1EERoutes, { prefix: "/ee/v1" });
};

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -28,6 +29,19 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
...req.body,
orgId: req.body.organizationId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.body.organizationId,
event: {
type: EventType.CREATE_IDENTITY,
metadata: {
name: identity.name,
identityId: identity.id
}
}
});
return { identity };
}
});
@@ -57,6 +71,19 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
id: req.params.identityId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identity.orgId,
event: {
type: EventType.UPDATE_IDENTITY,
metadata: {
name: identity.name,
identityId: identity.id
}
}
});
return { identity };
}
});
@@ -81,6 +108,17 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
id: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identity.orgId,
event: {
type: EventType.DELETE_IDENTITY,
metadata: {
identityId: identity.id
}
}
});
return { identity };
}
});

View File

@@ -1,8 +1,10 @@
import { z } from "zod";
import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
export const sanitizedClientSecretSchema = IdentityUaClientSecretsSchema.pick({
id: true,
@@ -36,10 +38,21 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { identityUa, accessToken } = await server.services.identityUa.login(
req.body.clientId,
req.body.clientSecret
);
const { identityUa, accessToken, identityAccessToken, validClientSecretInfo } =
await server.services.identityUa.login(req.body.clientId, req.body.clientSecret);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
event: {
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH,
metadata: {
clientSecretId: validClientSecretInfo.id,
identityId: identityUa.identityId,
identityAccessTokenId: identityAccessToken.id,
identityUniversalAuthId: identityUa.id
}
}
});
return {
accessToken,
tokenType: "Bearer" as const,
@@ -102,6 +115,24 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityUniversalAuth.orgId,
event: {
type: EventType.ADD_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId,
accessTokenTTL: identityUniversalAuth.accessTokenTTL,
accessTokenMaxTTL: identityUniversalAuth.accessTokenMaxTTL,
accessTokenTrustedIps:
identityUniversalAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
clientSecretTrustedIps:
identityUniversalAuth.clientSecretTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit
}
}
});
return { identityUniversalAuth };
}
});
@@ -152,6 +183,25 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityUniversalAuth.orgId,
event: {
type: EventType.UPDATE_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId,
accessTokenTTL: identityUniversalAuth.accessTokenTTL,
accessTokenMaxTTL: identityUniversalAuth.accessTokenMaxTTL,
accessTokenTrustedIps:
identityUniversalAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
clientSecretTrustedIps:
identityUniversalAuth.clientSecretTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityUniversalAuth.accessTokenNumUsesLimit
}
}
});
return { identityUniversalAuth };
}
});
@@ -176,6 +226,18 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityUniversalAuth.orgId,
event: {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH,
metadata: {
identityId: identityUniversalAuth.identityId
}
}
});
return { identityUniversalAuth };
}
});
@@ -201,13 +263,26 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { clientSecret, clientSecretData } =
const { clientSecret, clientSecretData, orgId } =
await server.services.identityUa.createUaClientSecret({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId,
event: {
type: EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
metadata: {
identityId: req.params.identityId,
clientSecretId: clientSecretData.id
}
}
});
return { clientSecret, clientSecretData };
}
});
@@ -227,10 +302,22 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const clientSecretData = await server.services.identityUa.getUaClientSecrets({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId
const { clientSecrets: clientSecretData, orgId } =
await server.services.identityUa.getUaClientSecrets({
actor: req.permission.type,
actorId: req.permission.id,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId,
event: {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS,
metadata: {
identityId: req.params.identityId
}
}
});
return { clientSecretData };
}
@@ -258,6 +345,19 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
identityId: req.params.identityId,
clientSecretId: req.params.clientSecretId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: clientSecretData.orgId,
event: {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET,
metadata: {
identityId: clientSecretData.identityId,
clientSecretId: clientSecretData.id
}
}
});
return { clientSecretData };
}
});

View File

@@ -1,5 +1,6 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -77,6 +78,18 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
actor: req.permission.type,
id: req.params.integrationAuthId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: integrationAuth.projectId,
event: {
type: EventType.UNAUTHORIZE_INTEGRATION,
metadata: {
integration: integrationAuth.integration
}
}
});
return { integrationAuth };
}
});
@@ -85,6 +98,45 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
url: "/oauth-token",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
workspaceId: z.string().trim(),
code: z.string().trim(),
integration: z.string().trim(),
url: z.string().trim().url().optional()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.oauthExchange({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.AUTHORIZE_INTEGRATION,
metadata: {
integration: integrationAuth.integration
}
}
});
return { integrationAuth };
}
});
server.route({
url: "/access-token",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
workspaceId: z.string().trim(),
@@ -108,6 +160,17 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.AUTHORIZE_INTEGRATION,
metadata: {
integration: integrationAuth.integration
}
}
});
return { integrationAuth };
}
});

View File

@@ -1,6 +1,8 @@
import { z } from "zod";
import { IntegrationsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { shake } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -45,11 +47,33 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const integration = await server.services.integration.createIntegration({
const { integration, integrationAuth } = await server.services.integration.createIntegration({
actorId: req.permission.id,
actor: req.permission.type,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: integrationAuth.projectId,
event: {
type: EventType.CREATE_INTEGRATION,
metadata: shake({
integrationId: integration.id.toString(),
integration: integration.integration,
environment: req.body.sourceEnvironment,
secretPath: req.body.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
}) as any
}
});
return { integration };
}
});
@@ -108,6 +132,29 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
id: req.params.integrationId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: integration.projectId,
event: {
type: EventType.DELETE_INTEGRATION,
metadata: shake({
integrationId: integration.id,
integration: integration.integration,
environment: integration.environment.slug,
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
}) as any
}
});
return { integration };
}
});

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -32,6 +33,18 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.CREATE_ENVIRONMENT,
metadata: {
name: environment.name,
slug: environment.slug
}
}
});
return {
message: "Successfully created new environment",
workspace: req.params.workspaceId,
@@ -62,13 +75,28 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
const environment = await server.services.projectEnv.updateEnvironment({
const { environment, old } = await server.services.projectEnv.updateEnvironment({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
id: req.params.id,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.UPDATE_ENVIRONMENT,
metadata: {
oldName: old.name,
oldSlug: old.slug,
newName: old.name,
newSlug: old.slug
}
}
});
return {
message: "Successfully updated environment",
workspace: req.params.workspaceId,
@@ -101,6 +129,19 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId,
id: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: environment.projectId,
event: {
type: EventType.DELETE_ENVIRONMENT,
metadata: {
slug: environment.slug,
name: environment.name
}
}
});
return {
message: "Successfully deleted environment",
workspace: req.params.workspaceId,

View File

@@ -1,6 +1,12 @@
import { z } from "zod";
import { ProjectMembershipsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import {
OrgMembershipsSchema,
ProjectMembershipsSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -42,6 +48,55 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
}
});
server.route({
url: "/:workspaceId/memberships",
method: "POST",
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
body: z.object({
members: z
.object({
orgMembershipId: z.string().trim(),
workspaceEncryptedKey: z.string().trim(),
workspaceEncryptedNonce: z.string().trim()
})
.array()
.min(1)
}),
response: {
200: z.object({
success: z.boolean(),
data: OrgMembershipsSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const data = await server.services.projectMembership.addUsersToProject({
actorId: req.permission.id,
actor: req.permission.type,
projectId: req.params.workspaceId,
members: req.body.members
});
await server.services.auditLog.createAuditLog({
projectId: req.params.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
metadata: data.map(({ userId }) => ({
userId: userId || "",
email: ""
}))
}
});
return { data, success: true };
}
});
server.route({
url: "/:workspaceId/memberships/:membershipId",
method: "PATCH",
@@ -68,6 +123,20 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
membershipId: req.params.membershipId,
role: req.body.role
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.UPDATE_USER_WORKSPACE_ROLE,
metadata: {
userId: membership.userId,
newRole: req.body.role,
oldRole: membership.role,
email: ""
}
}
});
return { membership };
}
});
@@ -94,6 +163,18 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
projectId: req.params.workspaceId,
membershipId: req.params.membershipId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.REMOVE_WORKSPACE_MEMBER,
metadata: {
userId: membership.userId,
email: ""
}
}
});
return { membership };
}
});

View File

@@ -8,6 +8,7 @@ import {
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -265,6 +266,18 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId,
email: req.body.email
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.ADD_WORKSPACE_MEMBER,
metadata: {
userId: invitee.id,
email: invitee.email
}
}
});
return { invitee, latestKey };
}
});

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretFoldersSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -28,6 +29,19 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
actor: req.permission.type,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.projectId,
event: {
type: EventType.CREATE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderName: folder.name,
folderPath: req.body.path
}
}
});
return { folder };
}
});
@@ -53,12 +67,26 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
handler: async (req) => {
const folder = await server.services.folder.updateFolder({
const { folder, old } = await server.services.folder.updateFolder({
actorId: req.permission.id,
actor: req.permission.type,
...req.body,
id: req.params.folderId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.projectId,
event: {
type: EventType.UPDATE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderPath: req.body.path,
newFolderName: folder.name,
oldFolderName: old.name
}
}
});
return { folder };
}
});
@@ -89,6 +117,19 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
...req.body,
id: req.params.folderId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.projectId,
event: {
type: EventType.DELETE_FOLDER,
metadata: {
environment: req.body.environment,
folderId: folder.id,
folderPath: req.body.path,
folderName: folder.name
}
}
});
return { folder };
}
});

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -37,6 +38,22 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
...req.body,
data: req.body.import
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.projectId,
event: {
type: EventType.CREATE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
importFromSecretPath: secretImport.importPath,
importFromEnvironment: secretImport.importEnv.slug,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully created secret import", secretImport };
}
});
@@ -78,6 +95,22 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
...req.body,
data: req.body.import
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.projectId,
event: {
type: EventType.UPDATE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
position: secretImport.position,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully updated secret import", secretImport };
}
});
@@ -113,6 +146,22 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
id: req.params.secretImportId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.projectId,
event: {
type: EventType.DELETE_SECRET_IMPORT,
metadata: {
secretImportId: secretImport.id,
folderId: secretImport.folderId,
importFromEnvironment: secretImport.importEnv.slug,
importFromSecretPath: secretImport.importPath,
importToEnvironment: req.body.environment,
importToSecretPath: req.body.path
}
}
});
return { message: "Successfully deleted secret import", secretImport };
}
});
@@ -146,6 +195,19 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
actor: req.permission.type,
...req.query
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.query.projectId,
event: {
type: EventType.GET_SECRET_IMPORTS,
metadata: {
environment: req.query.environment,
folderId: secretImports?.[0]?.folderId,
numberOfImports: secretImports.length
}
}
});
return { message: "Successfully fetched secret imports", secretImports };
}
});

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { WebhooksSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -9,10 +10,10 @@ export const sanitizedWebhookSchema = WebhooksSchema.omit({
iv: true,
tag: true,
algorithm: true,
keyEncoding: true,
keyEncoding: true
}).merge(
z.object({
projectId:z.string(),
projectId: z.string(),
environment: z.object({
id: z.string(),
name: z.string(),
@@ -48,6 +49,22 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
projectId: req.body.workspaceId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.workspaceId,
event: {
type: EventType.CREATE_WEBHOOK,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath,
webhookUrl: webhook.url
}
}
});
return { message: "Successfully created webhook", webhook };
}
});
@@ -77,6 +94,22 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
id: req.params.webhookId,
isDisabled: req.body.isDisabled
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: webhook.projectId,
event: {
type: EventType.UPDATE_WEBHOOK_STATUS,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath,
webhookUrl: webhook.url
}
}
});
return { message: "Successfully updated webhook", webhook };
}
});
@@ -96,6 +129,22 @@ export const registerWebhookRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
id: req.params.webhookId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: webhook.projectId,
event: {
type: EventType.DELETE_WEBHOOK,
metadata: {
environment: webhook.environment.slug,
webhookId: webhook.id,
isDisabled: webhook.isDisabled,
secretPath: webhook.secretPath,
webhookUrl: webhook.url
}
}
});
return { message: "Successfully deleted webhook", webhook };
}
});

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { ProjectKeysSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -30,6 +31,17 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.params.workspaceId,
event: {
type: EventType.GET_WORKSPACE_KEY,
metadata: {
keyId: key.id
}
}
});
return key;
}
});

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { ServiceTokensSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -68,6 +69,18 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) =>
...req.body,
projectId: req.body.workspaceId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: serviceToken.projectId,
event: {
type: EventType.CREATE_SERVICE_TOKEN,
metadata: {
name: serviceToken.name,
scopes: req.body.scopes
}
}
});
return { serviceToken: token, serviceTokenData: serviceToken };
}
});
@@ -92,6 +105,19 @@ export const registerServiceTokenRouter = async (server: FastifyZodProvider) =>
actor: req.permission.type,
id: req.params.serviceTokenId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: serviceTokenData.projectId,
event: {
type: EventType.DELETE_SERVICE_TOKEN,
metadata: {
name: serviceTokenData.name,
scopes: serviceTokenData.scopes as Array<{ environment: string; secretPath: string }>
}
}
});
return { serviceTokenData };
}
});

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretApprovalRequestsSchema, SecretsSchema, SecretType } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
@@ -35,6 +36,19 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
path: req.query.secretPath
});
await server.services.auditLog.createAuditLog({
projectId: req.query.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.GET_SECRETS,
metadata: {
environment: req.query.environment,
secretPath: req.query.secretPath,
numberOfSecrets: secrets.length
}
}
});
return { secrets };
}
});
@@ -74,6 +88,20 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
type: req.query.type
});
await server.services.auditLog.createAuditLog({
projectId: req.query.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.GET_SECRET,
metadata: {
environment: req.query.environment,
secretPath: req.query.secretPath,
secretId: secret.id,
secretKey: req.params.secretName,
secretVersion: secret.version
}
}
});
return { secret };
}
});
@@ -167,6 +195,19 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
]
}
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
}
});
return { approval };
}
}
@@ -191,6 +232,20 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
metadata
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.CREATE_SECRET,
metadata: {
environment: req.body.environment,
secretPath: req.body.secretPath,
secretId: secret.id,
secretKey: req.params.secretName,
secretVersion: secret.version
}
}
});
return { secret };
}
});
@@ -296,6 +351,19 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
]
}
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
}
});
return { approval };
}
}
@@ -325,6 +393,20 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
newSecretName
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.UPDATE_SECRET,
metadata: {
environment: req.body.environment,
secretPath: req.body.secretPath,
secretId: secret.id,
secretKey: req.params.secretName,
secretVersion: secret.version
}
}
});
return { secret };
}
});
@@ -382,6 +464,19 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
]
}
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
}
});
return { approval };
}
}
@@ -397,6 +492,20 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretId
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.DELETE_SECRET,
metadata: {
environment: req.body.environment,
secretPath: req.body.secretPath,
secretId: secret.id,
secretKey: req.params.secretName,
secretVersion: secret.version
}
}
});
return { secret };
}
});
@@ -463,6 +572,19 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
[CommitType.Create]: inputSecrets.filter(({ type }) => type === "shared")
}
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
}
});
return { approval };
}
}
@@ -476,6 +598,22 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secrets: inputSecrets
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.CREATE_SECRETS,
metadata: {
environment: req.body.environment,
secretPath: req.body.secretPath,
secrets: secrets.map((secret, i) => ({
secretId: secret.id,
secretKey: inputSecrets[i].secretName,
secretVersion: secret.version
}))
}
}
});
return { secrets };
}
});
@@ -542,6 +680,19 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
[CommitType.Update]: inputSecrets.filter(({ type }) => type === "shared")
}
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
}
});
return { approval };
}
}
@@ -554,6 +705,22 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secrets: inputSecrets
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.UPDATE_SECRETS,
metadata: {
environment: req.body.environment,
secretPath: req.body.secretPath,
secrets: secrets.map((secret, i) => ({
secretId: secret.id,
secretKey: inputSecrets[i].secretName,
secretVersion: secret.version
}))
}
}
});
return { secrets };
}
});
@@ -609,6 +776,18 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
[CommitType.Delete]: inputSecrets.filter(({ type }) => type === "shared")
}
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
}
});
return { approval };
}
}
@@ -621,6 +800,22 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secrets: inputSecrets
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.DELETE_SECRETS,
metadata: {
environment: req.body.environment,
secretPath: req.body.secretPath,
secrets: secrets.map((secret, i) => ({
secretId: secret.id,
secretKey: inputSecrets[i].secretName,
secretVersion: secret.version
}))
}
}
});
return { secrets };
}
});

View File

@@ -32,7 +32,8 @@ export enum AuthMode {
export enum ActorType { // would extend to AWS, Azure, ...
USER = "user", // userIdentity
SERVICE = "service",
IDENTITY = "identity"
IDENTITY = "identity",
Machine = "machine"
}
export type AuthModeJwtTokenPayload = {

View File

@@ -132,7 +132,7 @@ export const identityUaServiceFactory = ({
: identityAccessToken.accessTokenMaxTTL
}
);
return { accessToken, identityUa };
return { accessToken, identityUa, validClientSecretInfo, identityAccessToken };
};
const attachUa = async ({
@@ -216,7 +216,7 @@ export const identityUaServiceFactory = ({
);
return doc;
});
return identityUa;
return { ...identityUa, orgId: identityMembershipOrg.orgId };
};
const updateUa = async ({
@@ -295,7 +295,7 @@ export const identityUaServiceFactory = ({
? JSON.stringify(reformattedAccessTokenTrustedIps)
: undefined
});
return updatedUaAuth;
return { ...updatedUaAuth, orgId: identityMembershipOrg.orgId };
};
const getIdentityUa = async ({ identityId, actorId, actor }: TGetUaDTO) => {
@@ -317,7 +317,7 @@ export const identityUaServiceFactory = ({
OrgPermissionActions.Read,
OrgPermissionSubjects.Identity
);
return uaIdentityAuth;
return { ...uaIdentityAuth, orgId: identityMembershipOrg.orgId };
};
const createUaClientSecret = async ({
@@ -375,7 +375,8 @@ export const identityUaServiceFactory = ({
return {
clientSecret,
clientSecretData: identityUaClientSecret,
uaAuth: identityUniversalAuth
uaAuth: identityUniversalAuth,
orgId: identityMembershipOrg.orgId
};
};
@@ -415,7 +416,7 @@ export const identityUaServiceFactory = ({
identityUAId: identityUniversalAuth.id,
isClientSecretRevoked: false
});
return clientSecrets;
return { clientSecrets, orgId: identityMembershipOrg.orgId };
};
const revokeUaClientSecret = async ({
@@ -454,7 +455,7 @@ export const identityUaServiceFactory = ({
const clientSecret = await identityUaClientSecretDal.updateById(clientSecretId, {
isClientSecretRevoked: true
});
return clientSecret;
return { ...clientSecret, identityId, orgId: identityMembershipOrg.orgId };
};
return {

View File

@@ -111,8 +111,7 @@ export const identityServiceFactory = ({
return newIdentity;
});
// TODO(akhilmhdh-pg): add audit log here
return identity;
return { ...identity, orgId: identityOrgMembership.orgId };
};
const deleteIdentity = async ({ actorId, actor, id }: TDeleteIdentityDTO) => {
@@ -139,7 +138,7 @@ export const identityServiceFactory = ({
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
const deletedIdentity = await identityDal.deleteById(id);
return deletedIdentity;
return { ...deletedIdentity, orgId: identityOrgMembership.orgId };
};
const listOrgIdentities = async ({ orgId, actor, actorId }: TOrgPermission) => {

View File

@@ -1,3 +1,4 @@
import { IPType } from "@app/lib/ip";
import { TOrgPermission } from "@app/lib/types";
export type TCreateIdentityDTO = {
@@ -14,3 +15,9 @@ export type TUpdateIdentityDTO = {
export type TDeleteIdentityDTO = {
id: string;
} & Omit<TOrgPermission, "orgId">;
export interface TIdentityTrustedIp {
ipAddress: string;
type: IPType;
prefix: number;
}

View File

@@ -998,7 +998,7 @@ export const integrationAuthServiceFactory = ({
};
return {
listIntegrationAuthByProjectId,
listIntegrationAuthByProjectId,
getIntegrationOptions,
getIntegrationAuth,
oauthExchange,

View File

@@ -7,7 +7,7 @@ export type TGetIntegrationAuthDTO = {
export type TOauthExchangeDTO = {
integration: string;
code: string;
url: string;
url?: string;
} & TProjectPermission;
export type TSaveIntegrationAccessTokenDTO = {

View File

@@ -91,7 +91,7 @@ export const integrationServiceFactory = ({
});
// TODO(akhilmhdh-pg): audit log
return integration;
return { integration, integrationAuth };
};
const updateIntegration = async ({
@@ -150,7 +150,7 @@ export const integrationServiceFactory = ({
);
const deletedIntegration = await integrationDal.deleteById(id);
return deletedIntegration;
return { ...integration, ...deletedIntegration };
};
const listIntegrationByProject = async ({ actor, actorId, projectId }: TProjectPermission) => {

View File

@@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TOrgBotDalFactory = ReturnType<typeof orgBotDalFactory>;
export const orgBotDalFactory = (db: TDbClient) => {
const orgBotOrm = ormify(db, TableName.OrgBot);
return orgBotOrm;
};

View File

@@ -9,7 +9,7 @@ import {
TOrgMembershipsUpdate
} from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { withTransaction } from "@app/lib/knex";
import { ormify, withTransaction } from "@app/lib/knex";
export type TOrgDalFactory = ReturnType<typeof orgDalFactory>;
@@ -110,14 +110,7 @@ export const orgDalFactory = (db: TDbClient) => {
// MEMBERSHIP OPERATIONS
// --------------------
const findMembership = async (filter: Partial<TOrgMemberships>, tx?: Knex) => {
try {
const membership = await (tx || db)(TableName.OrgMembership).where(filter);
return membership;
} catch (error) {
throw new DatabaseError({ error, name: "Find org membership" });
}
};
const orgMembershipOrm = ormify(db, TableName.OrgMembership);
const createMembership = async (data: TOrgMembershipsInsert, tx?: Knex) => {
try {
@@ -175,7 +168,7 @@ export const orgDalFactory = (db: TDbClient) => {
create,
updateById,
deleteById,
findMembership,
findMembership: orgMembershipOrm.find,
createMembership,
updateMembershipById,
deleteMembershipById,

View File

@@ -8,6 +8,8 @@ import {
} from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { getConfig } from "@app/lib/config/env";
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
import { generateSymmetricKey, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { isDisposableEmail } from "@app/lib/validator";
@@ -17,6 +19,7 @@ import { TokenType } from "../auth-token/auth-token-types";
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { TUserDalFactory } from "../user/user-dal";
import { TIncidentContactsDalFactory } from "./incident-contacts-dal";
import { TOrgBotDalFactory } from "./org-bot-dal";
import { TOrgDalFactory } from "./org-dal";
import { TOrgRoleDalFactory } from "./org-role-dal";
import {
@@ -28,6 +31,7 @@ import {
type TOrgServiceFactoryDep = {
orgDal: TOrgDalFactory;
orgBotDal: TOrgBotDalFactory;
orgRoleDal: TOrgRoleDalFactory;
userDal: TUserDalFactory;
incidentContactDal: TIncidentContactsDalFactory;
@@ -45,7 +49,8 @@ export const orgServiceFactory = ({
incidentContactDal,
permissionService,
smtpService,
tokenService
tokenService,
orgBotDal
}: TOrgServiceFactoryDep) => {
/*
* Get organization details by the organization id
@@ -95,6 +100,23 @@ export const orgServiceFactory = ({
* Create organization
* */
const createOrganization = async (userId: string, orgName: string) => {
const { privateKey, publicKey } = generateAsymmetricKeyPair();
const key = generateSymmetricKey();
const {
ciphertext: encryptedPrivateKey,
iv: privateKeyIV,
tag: privateKeyTag,
encoding: privateKeyKeyEncoding,
algorithm: privateKeyAlgorithm
} = infisicalSymmetricEncypt(privateKey);
const {
ciphertext: encryptedSymmetricKey,
iv: symmetricKeyIV,
tag: symmetricKeyTag,
encoding: symmetricKeyKeyEncoding,
algorithm: symmetricKeyAlgorithm
} = infisicalSymmetricEncypt(key);
const organization = await orgDal.transaction(async (tx) => {
const org = await orgDal.create({ name: orgName }, tx);
await orgDal.createMembership(
@@ -106,6 +128,24 @@ export const orgServiceFactory = ({
},
tx
);
await orgBotDal.create(
{
name: org.name,
publicKey,
privateKeyIV,
encryptedPrivateKey,
symmetricKeyIV,
symmetricKeyTag,
encryptedSymmetricKey,
symmetricKeyAlgorithm,
orgId: org.id,
privateKeyTag,
privateKeyAlgorithm,
privateKeyKeyEncoding,
symmetricKeyKeyEncoding
},
tx
);
return org;
});
@@ -178,7 +218,7 @@ export const orgServiceFactory = ({
// Thus the signup flow is not needed anymore
const [inviteeMembership] = await orgDal.findMembership(
{ orgId, userId: inviteeUser.id },
tx
{ tx }
);
if (inviteeMembership && inviteeMembership.status === OrgMembershipStatus.Accepted) {
throw new BadRequestError({

View File

@@ -58,6 +58,9 @@ export const projectEnvServiceFactory = ({
ProjectPermissionSub.Environments
);
const oldEnv = await projectEnvDal.findOne({ id, projectId });
if (!oldEnv) throw new BadRequestError({ message: "Environment not found" });
if (slug) {
const existingEnv = await projectEnvDal.findOne({ slug });
if (existingEnv && existingEnv.id !== id) {
@@ -68,8 +71,8 @@ export const projectEnvServiceFactory = ({
}
}
const [env] = await projectEnvDal.update({ id, projectId }, { name, slug });
return env;
const env = await projectEnvDal.updateById(oldEnv.id, { name, slug });
return { environment: env, old: oldEnv };
};
const deleteEnvironment = async ({ projectId, actor, actorId, id }: TDeleteEnvDTO) => {

View File

@@ -8,6 +8,7 @@ import {
} from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { TOrgDalFactory } from "../org/org-dal";
import { TProjectDalFactory } from "../project/project-dal";
@@ -17,6 +18,7 @@ import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
import { TUserDalFactory } from "../user/user-dal";
import { TProjectMembershipDalFactory } from "./project-membership-dal";
import {
TAddUsersToWorkspaceDTO,
TDeleteProjectMembershipDTO,
TGetProjectMembershipDTO,
TInviteUserToProjectDTO,
@@ -31,7 +33,7 @@ type TProjectMembershipServiceFactoryDep = {
projectRoleDal: Pick<TProjectRoleDalFactory, "findOne">;
orgDal: Pick<TOrgDalFactory, "findMembership">;
projectDal: Pick<TProjectDalFactory, "findById">;
projectKeyDal: Pick<TProjectKeyDalFactory, "findLatestProjectKey" | "delete">;
projectKeyDal: Pick<TProjectKeyDalFactory, "findLatestProjectKey" | "delete" | "insertMany">;
};
export type TProjectMembershipServiceFactory = ReturnType<typeof projectMembershipServiceFactory>;
@@ -122,6 +124,71 @@ export const projectMembershipServiceFactory = ({
return { invitee, latestKey };
};
const addUsersToProject = async ({
projectId,
actorId,
actor,
members
}: TAddUsersToWorkspaceDTO) => {
const project = await projectDal.findById(projectId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
);
const orgMembers = await orgDal.findMembership({
orgId: project.orgId,
$in: { id: members.map(({ orgMembershipId }) => orgMembershipId) }
});
if (orgMembers.length !== members.length)
throw new BadRequestError({ message: "Some users are not part of org" });
const existingMembers = await projectMembershipDal.find({
projectId,
$in: { userId: orgMembers.map(({ userId }) => userId).filter(Boolean) as string[] }
});
if (existingMembers.length)
throw new BadRequestError({ message: "Some users are already part of project" });
await projectMembershipDal.transaction(async (tx) => {
await projectMembershipDal.insertMany(
orgMembers.map(({ userId }) => ({
projectId,
userId: userId as string,
role: ProjectMembershipRole.Member
})),
tx
);
const encKeyGroupByOrgMembId = groupBy(members, (i) => i.orgMembershipId);
await projectKeyDal.insertMany(
orgMembers.map(({ userId, id }) => ({
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
senderId: actorId,
receiverId: userId as string,
projectId
})),
tx
);
});
const sender = await userDal.findById(actorId);
const appCfg = getConfig();
await smtpService.sendMail({
template: SmtpTemplates.WorkspaceInvite,
subjectLine: "Infisical workspace invitation",
recipients: orgMembers.map(({ userId }) => userId).filter(Boolean) as string[],
substitutions: {
inviterFirstName: sender.firstName,
inviterEmail: sender.email,
workspaceName: project.name,
callback_url: `${appCfg.SITE_URL}/login`
}
});
return orgMembers;
};
const updateProjectMembership = async ({
actorId,
actor,
@@ -186,6 +253,7 @@ export const projectMembershipServiceFactory = ({
getProjectMemberships,
inviteUserToProject,
updateProjectMembership,
deleteProjectMembership
deleteProjectMembership,
addUsersToProject
};
};

View File

@@ -14,3 +14,11 @@ export type TUpdateProjectMembershipDTO = {
export type TDeleteProjectMembershipDTO = {
membershipId: string;
} & TProjectPermission;
export type TAddUsersToWorkspaceDTO = {
members: {
orgMembershipId: string;
workspaceEncryptedKey: string;
workspaceEncryptedNonce: string;
}[];
} & TProjectPermission;

View File

@@ -91,14 +91,16 @@ export const secretFolderServiceFactory = ({
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
);
const parentFolder = await folderDal.findBySecretPath(projectId, environment, path);
if (!parentFolder) throw new BadRequestError({ message: "Secret path not found" });
const env = await projectEnvDal.findOne({ projectId, slug: environment });
if (!env)
throw new BadRequestError({ message: "Environment not found", name: "Create folder" });
const folder = await folderDal.transaction(async (tx) => {
const parentFolder = await folderDal.findBySecretPath(projectId, environment, path, tx);
if (!parentFolder) throw new BadRequestError({ message: "Secret path not found" });
throw new BadRequestError({ message: "Environment not found", name: "Update folder" });
const folder = await folderDal.findOne({ envId: env.id, id, parentId: parentFolder.id });
if (!folder) throw new BadRequestError({ message: "Folder not found" });
const newFolder = await folderDal.transaction(async (tx) => {
const [doc] = await folderDal.update(
{ envId: env.id, id, parentId: parentFolder.id },
{ name },
@@ -117,8 +119,8 @@ export const secretFolderServiceFactory = ({
return doc;
});
await snapshotService.performSnapshot(folder.parentId as string);
return folder;
await snapshotService.performSnapshot(newFolder.parentId as string);
return { folder: newFolder, old: folder };
};
const deleteFolder = async ({

View File

@@ -6,8 +6,10 @@ import {
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { TProjectEnvDalFactory } from "../project-env/project-env-dal";
import { TSecretDalFactory } from "../secret/secret-dal";
import { TSecretFolderDalFactory } from "../secret-folder/secret-folder-dal";
import { TSecretImportDalFactory } from "./secret-import-dal";
import {
@@ -17,8 +19,6 @@ import {
TGetSecretsFromImportDTO,
TUpdateSecretImportDTO
} from "./secret-import-types";
import { TSecretDalFactory } from "../secret/secret-dal";
import { groupBy } from "@app/lib/fn";
type TSecretImportServiceFactoryDep = {
secretImportDal: TSecretImportDalFactory;

View File

@@ -256,9 +256,10 @@ export const secretServiceFactory = ({
}
const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({
inputSecrets: [{ secretName: inputSecret.secretName }],
inputSecrets: [{ secretName: inputSecret.secretName, type: inputSecret.type as SecretType }],
folderId,
isNew: true,
userId: actorId,
blindIndexCfg
});
@@ -539,6 +540,7 @@ export const secretServiceFactory = ({
fnSecretBulkInsert({
inputSecrets: inputSecrets.map(({ secretName, ...el }) => ({
...el,
version:0,
secretBlindIndex: keyName2BlindIndex[secretName],
type: SecretType.Shared,
algorithm: SecretEncryptionAlgo.AES_256_GCM,

View File

@@ -18,7 +18,7 @@ export const useCreateProjectRole = () => {
return useMutation({
mutationFn: ({ projectId, permissions, ...dto }: TCreateProjectRoleDTO) =>
apiRequest.post(`/api/ee/v1/workspace/${projectId}/roles`, {
apiRequest.post(`/api/v1/workspace/${projectId}/roles`, {
...dto,
permissions: permissions.length ? packRules(permissions) : []
}),
@@ -33,7 +33,7 @@ export const useUpdateProjectRole = () => {
return useMutation({
mutationFn: ({ id, projectId, permissions, ...dto }: TUpdateProjectRoleDTO) =>
apiRequest.patch(`/api/ee/v1/workspace/${projectId}/roles/${id}`, {
apiRequest.patch(`/api/v1/workspace/${projectId}/roles/${id}`, {
...dto,
permissions: permissions?.length ? packRules(permissions) : []
}),
@@ -48,7 +48,7 @@ export const useDeleteProjectRole = () => {
return useMutation({
mutationFn: ({ projectId, id }: TDeleteProjectRoleDTO) =>
apiRequest.delete(`/api/ee/v1/workspace/${projectId}/roles/${id}`, {
apiRequest.delete(`/api/v1/workspace/${projectId}/roles/${id}`, {
data: { projectId }
}),
onSuccess: (_, { projectId }) => {
@@ -62,7 +62,7 @@ export const useCreateOrgRole = () => {
return useMutation({
mutationFn: ({ orgId, permissions, ...dto }: TCreateOrgRoleDTO) =>
apiRequest.post(`/api/ee/v1/organization/${orgId}/roles`, {
apiRequest.post(`/api/v1/organization/${orgId}/roles`, {
...dto,
permissions: permissions.length ? packRules(permissions) : []
}),
@@ -77,7 +77,7 @@ export const useUpdateOrgRole = () => {
return useMutation({
mutationFn: ({ id, orgId, permissions, ...dto }: TUpdateOrgRoleDTO) =>
apiRequest.patch(`/api/ee/v1/organization/${orgId}/roles/${id}`, {
apiRequest.patch(`/api/v1/organization/${orgId}/roles/${id}`, {
...dto,
permissions: permissions?.length ? packRules(permissions) : []
}),
@@ -92,7 +92,7 @@ export const useDeleteOrgRole = () => {
return useMutation({
mutationFn: ({ orgId, id }: TDeleteOrgRoleDTO) =>
apiRequest.delete(`/api/ee/v1/organization/${orgId}/roles/${id}`, {
apiRequest.delete(`/api/v1/organization/${orgId}/roles/${id}`, {
data: { orgId }
}),
onSuccess: (_, { orgId }) => {

View File

@@ -48,7 +48,7 @@ export const roleQueryKeys = {
const getProjectRoles = async (projectId: string) => {
const { data } = await apiRequest.get<{
data: { roles: Array<Omit<TProjectRole, "permissions"> & { permissions: unknown }> };
}>(`/api/ee/v1/workspace/${projectId}/roles`);
}>(`/api/v1/workspace/${projectId}/roles`);
return data.data.roles.map(({ permissions, ...el }) => ({
...el,
permissions: unpackRules(permissions as PackRule<TProjectPermission>[])
@@ -65,7 +65,7 @@ export const useGetProjectRoles = (projectId: string) =>
const getOrgRoles = async (orgId: string) => {
const { data } = await apiRequest.get<{
data: { roles: Array<Omit<TOrgRole, "permissions"> & { permissions: unknown }> };
}>(`/api/ee/v1/organization/${orgId}/roles`);
}>(`/api/v1/organization/${orgId}/roles`);
return data.data.roles.map(({ permissions, ...el }) => ({
...el,
permissions: unpackRules(permissions as PackRule<TPermission>[])
@@ -85,7 +85,7 @@ const getUserOrgPermissions = async ({ orgId }: TGetUserOrgPermissionsDTO) => {
const { data } = await apiRequest.get<{
permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[];
membership: OrgUser;
}>(`/api/ee/v1/organization/${orgId}/permissions`);
}>(`/api/v1/organization/${orgId}/permissions`);
return data;
};
@@ -105,7 +105,7 @@ export const useGetUserOrgPermissions = ({ orgId }: TGetUserOrgPermissionsDTO) =
const getUserProjectPermissions = async ({ workspaceId }: TGetUserProjectPermissionDTO) => {
const { data } = await apiRequest.get<{
data: { permissions: PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[] };
}>(`/api/ee/v1/workspace/${workspaceId}/permissions`, {});
}>(`/api/v1/workspace/${workspaceId}/permissions`, {});
return data.data.permissions;
};

View File

@@ -10,7 +10,7 @@ export const useCreateSecretApprovalPolicy = () => {
return useMutation<{}, {}, TCreateSecretPolicyDTO>({
mutationFn: async ({ environment, workspaceId, approvals, approvers, secretPath, name }) => {
const { data } = await apiRequest.post("/api/ee/v1/secret-approvals", {
const { data } = await apiRequest.post("/api/v1/secret-approvals", {
environment,
workspaceId,
approvals,
@@ -31,7 +31,7 @@ export const useUpdateSecretApprovalPolicy = () => {
return useMutation<{}, {}, TUpdateSecretPolicyDTO>({
mutationFn: async ({ id, approvers, approvals, secretPath, name }) => {
const { data } = await apiRequest.patch(`/api/ee/v1/secret-approvals/${id}`, {
const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, {
approvals,
approvers,
secretPath,
@@ -50,7 +50,7 @@ export const useDeleteSecretApprovalPolicy = () => {
return useMutation<{}, {}, TDeleteSecretPolicyDTO>({
mutationFn: async ({ id }) => {
const { data } = await apiRequest.delete(`/api/ee/v1/secret-approvals/${id}`);
const { data } = await apiRequest.delete(`/api/v1/secret-approvals/${id}`);
return data;
},
onSuccess: (_, { workspaceId }) => {

View File

@@ -19,7 +19,7 @@ export const secretApprovalKeys = {
const fetchApprovalPolicies = async (workspaceId: string) => {
const { data } = await apiRequest.get<{ approvals: TSecretApprovalPolicy[] }>(
"/api/ee/v1/secret-approvals",
"/api/v1/secret-approvals",
{ params: { workspaceId } }
);
return data.approvals;
@@ -49,7 +49,7 @@ const fetchApprovalPolicyOfABoard = async (
secretPath: string
) => {
const { data } = await apiRequest.get<{ policy: TSecretApprovalPolicy }>(
"/api/ee/v1/secret-approvals/board",
"/api/v1/secret-approvals/board",
{ params: { workspaceId, environment, secretPath } }
);
return data.policy || "";

View File

@@ -14,7 +14,7 @@ export const useUpdateSecretApprovalReviewStatus = () => {
return useMutation<{}, {}, TUpdateSecretApprovalReviewStatusDTO>({
mutationFn: async ({ id, status }) => {
const { data } = await apiRequest.post(`/api/ee/v1/secret-approval-requests/${id}/review`, {
const { data } = await apiRequest.post(`/api/v1/secret-approval-requests/${id}/review`, {
status
});
return data;
@@ -30,7 +30,7 @@ export const useUpdateSecretApprovalRequestStatus = () => {
return useMutation<{}, {}, TUpdateSecretApprovalRequestStatusDTO>({
mutationFn: async ({ id, status }) => {
const { data } = await apiRequest.post(`/api/ee/v1/secret-approval-requests/${id}/status`, {
const { data } = await apiRequest.post(`/api/v1/secret-approval-requests/${id}/status`, {
status
});
return data;
@@ -47,7 +47,7 @@ export const usePerformSecretApprovalRequestMerge = () => {
return useMutation<{}, {}, TPerformSecretApprovalRequestMerge>({
mutationFn: async ({ id }) => {
const { data } = await apiRequest.post(`/api/ee/v1/secret-approval-requests/${id}/merge`);
const { data } = await apiRequest.post(`/api/v1/secret-approval-requests/${id}/merge`);
return data;
},
onSuccess: (_, { id, workspaceId }) => {

View File

@@ -96,7 +96,7 @@ const fetchSecretApprovalRequestList = async ({
offset
}: TGetSecretApprovalRequestList) => {
const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequest[] }>(
"/api/ee/v1/secret-approval-requests",
"/api/v1/secret-approval-requests",
{
params: {
workspaceId,
@@ -158,7 +158,7 @@ const fetchSecretApprovalRequestDetails = async ({
id
}: Omit<TGetSecretApprovalRequestDetails, "decryptKey">) => {
const { data } = await apiRequest.get<{ approval: TSecretApprovalRequest }>(
`/api/ee/v1/secret-approval-requests/${id}`
`/api/v1/secret-approval-requests/${id}`
);
return data.approval;
@@ -197,7 +197,7 @@ export const useGetSecretApprovalRequestDetails = ({
const fetchSecretApprovalRequestCount = async ({ workspaceId }: TGetSecretApprovalRequestCount) => {
const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequestCount }>(
"/api/ee/v1/secret-approval-requests/count",
"/api/v1/secret-approval-requests/count",
{ params: { workspaceId } }
);

View File

@@ -14,7 +14,7 @@ export const useCreateSecretRotation = () => {
return useMutation<{}, {}, TCreateSecretRotationDTO>({
mutationFn: async (dto) => {
const { data } = await apiRequest.post("/api/ee/v1/secret-rotations", dto);
const { data } = await apiRequest.post("/api/v1/secret-rotations", dto);
return data;
},
onSuccess: (_, { workspaceId }) => {
@@ -28,7 +28,7 @@ export const useDeleteSecretRotation = () => {
return useMutation<{}, {}, TDeleteSecretRotationDTO>({
mutationFn: async (dto) => {
const { data } = await apiRequest.delete(`/api/ee/v1/secret-rotations/${dto.id}`);
const { data } = await apiRequest.delete(`/api/v1/secret-rotations/${dto.id}`);
return data;
},
onSuccess: (_, { workspaceId }) => {
@@ -42,7 +42,7 @@ export const useRestartSecretRotation = () => {
return useMutation<{}, {}, TRestartSecretRotationDTO>({
mutationFn: async (dto) => {
const { data } = await apiRequest.post("/api/ee/v1/secret-rotations/restart", { id: dto.id });
const { data } = await apiRequest.post("/api/v1/secret-rotations/restart", { id: dto.id });
return data;
},
onSuccess: (_, { workspaceId }) => {

View File

@@ -25,7 +25,7 @@ export const secretRotationKeys = {
const fetchSecretRotationProviders = async ({ workspaceId }: TGetSecretRotationProviders) => {
const { data } = await apiRequest.get<TSecretRotationProviderList>(
`/api/ee/v1/secret-rotation-providers/${workspaceId}`
`/api/v1/secret-rotation-providers/${workspaceId}`
);
return data;
};
@@ -55,7 +55,7 @@ const fetchSecretRotations = async ({
workspaceId
}: Omit<TGetSecretRotationList, "decryptFileKey">) => {
const { data } = await apiRequest.get<{ secretRotations: TSecretRotation[] }>(
"/api/ee/v1/secret-rotations",
"/api/v1/secret-rotations",
{ params: { workspaceId } }
);
return data.secretRotations;

View File

@@ -35,7 +35,7 @@ const fetchWorkspaceSnaphots = async ({
offset = 0
}: TGetSecretSnapshotsDTO & { offset: number }) => {
const res = await apiRequest.get<{ secretSnapshots: TSecretSnapshot[] }>(
`/api/ee/v1/workspace/${workspaceId}/secret-snapshots`,
`/api/v1/workspace/${workspaceId}/secret-snapshots`,
{
params: {
limit,
@@ -60,7 +60,7 @@ export const useGetWorkspaceSnapshotList = (dto: TGetSecretSnapshotsDTO & { isPa
const fetchSnapshotEncSecrets = async (snapshotId: string) => {
const res = await apiRequest.get<{ secretSnapshot: TSnapshotData }>(
`/api/ee/v1/secret-snapshot/${snapshotId}`
`/api/v1/secret-snapshot/${snapshotId}`
);
return res.data.secretSnapshot;
};
@@ -142,7 +142,7 @@ const fetchWorkspaceSecretSnaphotCount = async (
directory = "/"
) => {
const res = await apiRequest.get<{ count: number }>(
`/api/ee/v1/workspace/${workspaceId}/secret-snapshots/count`,
`/api/v1/workspace/${workspaceId}/secret-snapshots/count`,
{
params: {
environment,
@@ -170,7 +170,7 @@ export const usePerformSecretRollback = () => {
return useMutation<{}, {}, TSecretRollbackDTO>({
mutationFn: async ({ snapshotId }) => {
const { data } = await apiRequest.post(`/api/ee/v1/secret-snapshot/${snapshotId}/rollback`);
const { data } = await apiRequest.post(`/api/v1/secret-snapshot/${snapshotId}/rollback`);
return data;
},
onSuccess: (_, { workspaceId, environment, directory }) => {

View File

@@ -3,22 +3,20 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
const ssoConfigKeys = {
getSSOConfig: (orgId: string) => [{ orgId }, "organization-saml-sso"] as const,
}
getSSOConfig: (orgId: string) => [{ orgId }, "organization-saml-sso"] as const
};
export const useGetSSOConfig = (organizationId: string) => {
return useQuery({
queryKey: ssoConfigKeys.getSSOConfig(organizationId),
queryFn: async () => {
const { data } = await apiRequest.get(
`/api/v1/sso/config?organizationId=${organizationId}`
);
const { data } = await apiRequest.get(`/api/v1/sso/config?organizationId=${organizationId}`);
return data;
},
enabled: true
});
}
};
export const useCreateSSOConfig = () => {
const queryClient = useQueryClient();
@@ -38,18 +36,15 @@ export const useCreateSSOConfig = () => {
issuer: string;
cert: string;
}) => {
const { data } = await apiRequest.post(
"/api/v1/sso/config",
{
organizationId,
authProvider,
isActive,
entryPoint,
issuer,
cert
}
);
const { data } = await apiRequest.post("/api/v1/sso/config", {
organizationId,
authProvider,
isActive,
entryPoint,
issuer,
cert
});
return data;
},
onSuccess(_, dto) {
@@ -76,22 +71,19 @@ export const useUpdateSSOConfig = () => {
issuer?: string;
cert?: string;
}) => {
const { data } = await apiRequest.patch(
"/api/v1/sso/config",
{
organizationId,
...(authProvider !== undefined ? { authProvider } : {}),
...(isActive !== undefined ? { isActive } : {}),
...(entryPoint !== undefined ? { entryPoint } : {}),
...(issuer !== undefined ? { issuer } : {}),
...(cert !== undefined ? { cert } : {})
}
);
const { data } = await apiRequest.patch("/api/v1/sso/config", {
organizationId,
...(authProvider !== undefined ? { authProvider } : {}),
...(isActive !== undefined ? { isActive } : {}),
...(entryPoint !== undefined ? { entryPoint } : {}),
...(issuer !== undefined ? { issuer } : {}),
...(cert !== undefined ? { cert } : {})
});
return data;
},
onSuccess(_, dto) {
queryClient.invalidateQueries(ssoConfigKeys.getSSOConfig(dto.organizationId));
}
});
};
};