mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 04:27:29 +00:00
feat(infisical-pg): completed secret rotation with queue service
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
import { TQueueServiceFactory } from "@app/queue";
|
||||||
|
|
||||||
|
export const mockQueue = (): TQueueServiceFactory => {
|
||||||
|
const queues: Record<string, unknown> = {};
|
||||||
|
const workers: Record<string, unknown> = {};
|
||||||
|
const job: Record<string, unknown> = {};
|
||||||
|
const events: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
queue: async (name, jobData) => {
|
||||||
|
job[name] = jobData;
|
||||||
|
},
|
||||||
|
shutdown: async () => undefined,
|
||||||
|
stopRepeatableJob: async () => true,
|
||||||
|
start: (name, jobFn) => {
|
||||||
|
queues[name] = jobFn;
|
||||||
|
workers[name] = jobFn;
|
||||||
|
},
|
||||||
|
listen: async (name, event) => {
|
||||||
|
events[name] = event;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ import { initLogger } from "@app/lib/logger";
|
|||||||
|
|
||||||
import "ts-node/register";
|
import "ts-node/register";
|
||||||
import { main } from "@app/server/app";
|
import { main } from "@app/server/app";
|
||||||
|
import { mockQueue } from "./mocks/queue";
|
||||||
|
|
||||||
dotenv.config({ path: path.join(__dirname, "../.env.test") });
|
dotenv.config({ path: path.join(__dirname, "../.env.test") });
|
||||||
export default {
|
export default {
|
||||||
@@ -31,9 +32,10 @@ export default {
|
|||||||
await db.migrate.latest();
|
await db.migrate.latest();
|
||||||
await db.seed.run();
|
await db.seed.run();
|
||||||
const smtp = mockSmtpServer();
|
const smtp = mockSmtpServer();
|
||||||
|
const queue = mockQueue();
|
||||||
const logger = await initLogger();
|
const logger = await initLogger();
|
||||||
initEnvConfig(logger);
|
initEnvConfig(logger);
|
||||||
const server = await main({ db, smtp, logger });
|
const server = await main({ db, smtp, logger, queue });
|
||||||
// @ts-expect-error type
|
// @ts-expect-error type
|
||||||
globalThis.testServer = server;
|
globalThis.testServer = server;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
+78
-46
@@ -3,55 +3,87 @@ import dotenv from "dotenv";
|
|||||||
import { initDbConnection } from "./src/db";
|
import { initDbConnection } from "./src/db";
|
||||||
import { TableName } from "./src/db/schemas";
|
import { TableName } from "./src/db/schemas";
|
||||||
import { selectAllTableCols } from "./src/lib/knex";
|
import { selectAllTableCols } from "./src/lib/knex";
|
||||||
|
import picomatch from "picomatch";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const db = initDbConnection(process.env.DB_CONNECTION_URI);
|
// 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 main = async () => {
|
||||||
const folders = db
|
const isMatch = picomatch("/ui/**/seomthing", { strictSlashes: false });
|
||||||
.withRecursive("parent", (qb) => {
|
console.log(
|
||||||
qb.select({
|
picomatch.isMatch(
|
||||||
depth: 1,
|
"/ui/(base|new)/seomthing",
|
||||||
path: db.raw("'/'")
|
["/ui/base/seomthing", "/ui/(base|new)/seomthing"],
|
||||||
})
|
{
|
||||||
.select(selectAllTableCols(db, TableName.SecretFolder))
|
strictSlashes: false
|
||||||
.from(TableName.SecretFolder)
|
}
|
||||||
.join(
|
)
|
||||||
TableName.Environment,
|
);
|
||||||
`${TableName.SecretFolder}.envId`,
|
// console.log(isMatch("/ui/(base|new)/seomthing"));
|
||||||
`${TableName.Environment}.id`
|
// const folders = db(TableName.SecretVersion)
|
||||||
)
|
// .whereIn(`${TableName.SecretVersion}.secretId`, ["8fe1c5e2-af6a-40ed-874b-6738816236bc"])
|
||||||
.where({
|
// .join(
|
||||||
projectId: "01c10de1-8743-490f-9c8a-7a19c4dc72a9",
|
// db(TableName.SecretVersion)
|
||||||
parentId: null
|
// .groupBy("secretId")
|
||||||
})
|
// .max("version")
|
||||||
.where(`${TableName.Environment}.slug`, "dev")
|
// .select("secretId")
|
||||||
.union((qb) =>
|
// .as("latestVersion"),
|
||||||
qb
|
// (bd) => {
|
||||||
.select({
|
// bd.on(`${TableName.SecretVersion}.secretId`, "latestVersion.secretId").andOn(
|
||||||
depth: db.raw("parent.depth + 1"),
|
// `${TableName.SecretVersion}.version`,
|
||||||
path: db.raw(
|
// "latestVersion.max"
|
||||||
"CONCAT((CASE WHEN parent.path = '/' THEN '' ELSE parent.path END),'/', secret_folders.name)"
|
// );
|
||||||
)
|
// }
|
||||||
})
|
// );
|
||||||
.select(selectAllTableCols(db, TableName.SecretFolder))
|
// console.log(folders.toSQL());
|
||||||
.whereRaw(
|
// console.log(JSON.stringify(await folders, null, 4));
|
||||||
`depth = array_position(ARRAY[${[1, 2]
|
// process.exit(0);
|
||||||
.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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|||||||
Generated
+376
-2
@@ -20,18 +20,23 @@
|
|||||||
"@fastify/swagger-ui": "^1.10.1",
|
"@fastify/swagger-ui": "^1.10.1",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
|
"ajv": "^8.12.0",
|
||||||
"argon2": "^0.31.2",
|
"argon2": "^0.31.2",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
"axios-retry": "^4.0.0",
|
"axios-retry": "^4.0.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
|
"bullmq": "^5.1.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
"fastify": "^4.24.3",
|
"fastify": "^4.24.3",
|
||||||
"fastify-plugin": "^4.5.1",
|
"fastify-plugin": "^4.5.1",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
"ioredis": "^5.3.2",
|
||||||
|
"jmespath": "^0.16.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
"knex": "^3.0.1",
|
"knex": "^3.0.1",
|
||||||
|
"mysql2": "^3.6.5",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
"nodemailer": "^6.9.7",
|
"nodemailer": "^6.9.7",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
@@ -48,6 +53,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
|
"@types/jmespath": "^0.15.2",
|
||||||
"@types/jsonwebtoken": "^9.0.5",
|
"@types/jsonwebtoken": "^9.0.5",
|
||||||
"@types/jsrp": "^0.2.6",
|
"@types/jsrp": "^0.2.6",
|
||||||
"@types/node": "^20.9.5",
|
"@types/node": "^20.9.5",
|
||||||
@@ -753,6 +759,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
|
||||||
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw=="
|
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@ioredis/commands": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@@ -913,6 +924,78 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -1602,6 +1685,12 @@
|
|||||||
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/jmespath": {
|
||||||
|
"version": "0.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.2.tgz",
|
||||||
|
"integrity": "sha512-pegh49FtNsC389Flyo9y8AfkVIZn9MMPE9yJrO9svhq6Fks2MwymULWjZqySuxmctd3ZH4/n7Mr98D+1Qo5vGA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -2803,6 +2892,22 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bullmq": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-j3zbNEQWsyHjpqGWiem2XBfmxAjYcArbwsmGlkM1E9MAVcrqB5hQUsXmyy9gEBAdL+PVotMICr7xTquR4Y2sKQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"cron-parser": "^4.6.0",
|
||||||
|
"glob": "^8.0.3",
|
||||||
|
"ioredis": "^5.3.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"msgpackr": "^1.6.2",
|
||||||
|
"node-abort-controller": "^3.1.1",
|
||||||
|
"semver": "^7.5.4",
|
||||||
|
"tslib": "^2.0.0",
|
||||||
|
"uuid": "^9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bundle-name": {
|
"node_modules/bundle-name": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
|
||||||
@@ -2973,6 +3078,14 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cluster-key-slot": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -3082,6 +3195,17 @@
|
|||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/cron-parser": {
|
||||||
|
"version": "4.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
|
||||||
|
"integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"luxon": "^3.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
@@ -3217,6 +3341,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
|
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/denque": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -4463,6 +4595,14 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/generate-function": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"is-property": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-func-name": {
|
"node_modules/get-func-name": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
|
||||||
@@ -4876,6 +5016,17 @@
|
|||||||
"node": ">=14.18.0"
|
"node": ">=14.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
@@ -4967,6 +5118,50 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ioredis": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ioredis/commands": "^1.1.1",
|
||||||
|
"cluster-key-slot": "^1.1.0",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"denque": "^2.1.0",
|
||||||
|
"lodash.defaults": "^4.2.0",
|
||||||
|
"lodash.isarguments": "^3.1.0",
|
||||||
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0",
|
||||||
|
"standard-as-callback": "^2.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.22.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/ioredis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ioredis/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/ioredis/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/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@@ -5174,6 +5369,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-property": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
|
||||||
|
},
|
||||||
"node_modules/is-regex": {
|
"node_modules/is-regex": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||||
@@ -5342,6 +5542,14 @@
|
|||||||
"@pkgjs/parseargs": "^0.11.0"
|
"@pkgjs/parseargs": "^0.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jmespath": {
|
||||||
|
"version": "0.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
|
||||||
|
"integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/joycon": {
|
"node_modules/joycon": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
||||||
@@ -5656,11 +5864,21 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.defaults": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
|
||||||
|
},
|
||||||
"node_modules/lodash.includes": {
|
"node_modules/lodash.includes": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.isarguments": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="
|
||||||
|
},
|
||||||
"node_modules/lodash.isboolean": {
|
"node_modules/lodash.isboolean": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
@@ -5717,6 +5935,11 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
|
||||||
|
},
|
||||||
"node_modules/loupe": {
|
"node_modules/loupe": {
|
||||||
"version": "2.3.7",
|
"version": "2.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
|
||||||
@@ -5737,6 +5960,14 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/luxon": {
|
||||||
|
"version": "3.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
||||||
|
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.5",
|
"version": "0.30.5",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
|
||||||
@@ -5948,6 +6179,61 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/msgpackr": {
|
||||||
|
"version": "1.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.1.tgz",
|
||||||
|
"integrity": "sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ==",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"msgpackr-extract": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/msgpackr-extract": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"node-gyp-build-optional-packages": "5.0.7"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.2",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.2",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.2",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.2",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.2",
|
||||||
|
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mysql2": {
|
||||||
|
"version": "3.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.5.tgz",
|
||||||
|
"integrity": "sha512-pS/KqIb0xlXmtmqEuTvBXTmLoQ5LmAz5NW/r8UyQ1ldvnprNEj3P9GbmuQQ2J0A4LO+ynotGi6TbscPa8OUb+w==",
|
||||||
|
"dependencies": {
|
||||||
|
"denque": "^2.1.0",
|
||||||
|
"generate-function": "^2.3.1",
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"long": "^5.2.1",
|
||||||
|
"lru-cache": "^8.0.0",
|
||||||
|
"named-placeholders": "^1.1.3",
|
||||||
|
"seq-queue": "^0.0.5",
|
||||||
|
"sqlstring": "^2.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mysql2/node_modules/lru-cache": {
|
||||||
|
"version": "8.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz",
|
||||||
|
"integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mz": {
|
"node_modules/mz": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
@@ -5959,6 +6245,25 @@
|
|||||||
"thenify-all": "^1.0.0"
|
"thenify-all": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/named-placeholders": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^7.14.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/named-placeholders/node_modules/lru-cache": {
|
||||||
|
"version": "7.18.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||||
|
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "5.0.4",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.4.tgz",
|
||||||
@@ -5986,6 +6291,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/node-abort-controller": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="
|
||||||
|
},
|
||||||
"node_modules/node-addon-api": {
|
"node_modules/node-addon-api": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||||
@@ -6010,6 +6320,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-gyp-build-optional-packages": {
|
||||||
|
"version": "5.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz",
|
||||||
|
"integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==",
|
||||||
|
"optional": true,
|
||||||
|
"bin": {
|
||||||
|
"node-gyp-build-optional-packages": "bin.js",
|
||||||
|
"node-gyp-build-optional-packages-optional": "optional.js",
|
||||||
|
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nodemailer": {
|
"node_modules/nodemailer": {
|
||||||
"version": "6.9.7",
|
"version": "6.9.7",
|
||||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz",
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.7.tgz",
|
||||||
@@ -7073,6 +7394,25 @@
|
|||||||
"node": ">= 10.13.0"
|
"node": ">= 10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redis-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||||
|
"dependencies": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz",
|
||||||
@@ -7436,6 +7776,11 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
|
},
|
||||||
"node_modules/secure-json-parse": {
|
"node_modules/secure-json-parse": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
|
||||||
@@ -7455,6 +7800,11 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/seq-queue": {
|
||||||
|
"version": "0.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||||
|
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
||||||
|
},
|
||||||
"node_modules/set-blocking": {
|
"node_modules/set-blocking": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||||
@@ -7605,12 +7955,25 @@
|
|||||||
"node": ">= 10.x"
|
"node": ">= 10.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sqlstring": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/stackback": {
|
"node_modules/stackback": {
|
||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||||
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/standard-as-callback": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
@@ -8178,8 +8541,7 @@
|
|||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/tsup": {
|
"node_modules/tsup": {
|
||||||
"version": "8.0.1",
|
"version": "8.0.1",
|
||||||
@@ -8971,6 +9333,18 @@
|
|||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/v8-compile-cache-lib": {
|
"node_modules/v8-compile-cache-lib": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
|
"@types/jmespath": "^0.15.2",
|
||||||
"@types/jsonwebtoken": "^9.0.5",
|
"@types/jsonwebtoken": "^9.0.5",
|
||||||
"@types/jsrp": "^0.2.6",
|
"@types/jsrp": "^0.2.6",
|
||||||
"@types/node": "^20.9.5",
|
"@types/node": "^20.9.5",
|
||||||
@@ -73,18 +74,23 @@
|
|||||||
"@fastify/swagger-ui": "^1.10.1",
|
"@fastify/swagger-ui": "^1.10.1",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
|
"ajv": "^8.12.0",
|
||||||
"argon2": "^0.31.2",
|
"argon2": "^0.31.2",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
"axios-retry": "^4.0.0",
|
"axios-retry": "^4.0.0",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
|
"bullmq": "^5.1.1",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||||
"fastify": "^4.24.3",
|
"fastify": "^4.24.3",
|
||||||
"fastify-plugin": "^4.5.1",
|
"fastify-plugin": "^4.5.1",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
"ioredis": "^5.3.2",
|
||||||
|
"jmespath": "^0.16.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
"knex": "^3.0.1",
|
"knex": "^3.0.1",
|
||||||
|
"mysql2": "^3.6.5",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
"nodemailer": "^6.9.7",
|
"nodemailer": "^6.9.7",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export const register${pascalCase}Router = async (server: FastifyZodProvider) =>
|
|||||||
response: {
|
response: {
|
||||||
200: z.object({})
|
200: z.object({})
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {}
|
handler: async (req) => {}
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+5
-1
@@ -2,6 +2,9 @@ import "fastify";
|
|||||||
|
|
||||||
import { TUsers } from "@app/db/schemas";
|
import { TUsers } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-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";
|
||||||
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||||
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
||||||
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
|
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
|
||||||
@@ -23,7 +26,6 @@ import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key
|
|||||||
import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
|
import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
|
||||||
import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service";
|
import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service";
|
||||||
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
||||||
import { TSecretApprovalPolicyServiceFactory } from "@app/services/secret-approval-policy/secret-approval-policy-service";
|
|
||||||
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
|
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
|
||||||
import { TSecretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
import { TSecretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
||||||
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
||||||
@@ -92,6 +94,8 @@ declare module "fastify" {
|
|||||||
identityProject: TIdentityProjectServiceFactory;
|
identityProject: TIdentityProjectServiceFactory;
|
||||||
identityUa: TIdentityUaServiceFactory;
|
identityUa: TIdentityUaServiceFactory;
|
||||||
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;
|
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;
|
||||||
|
secretApprovalRequest: TSecretApprovalRequestServiceFactory;
|
||||||
|
secretRotation: TSecretRotationServiceFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
|
|||||||
Vendored
+16
@@ -94,6 +94,12 @@ import {
|
|||||||
TSecretImports,
|
TSecretImports,
|
||||||
TSecretImportsInsert,
|
TSecretImportsInsert,
|
||||||
TSecretImportsUpdate,
|
TSecretImportsUpdate,
|
||||||
|
TSecretRotationOutputs,
|
||||||
|
TSecretRotationOutputsInsert,
|
||||||
|
TSecretRotationOutputsUpdate,
|
||||||
|
TSecretRotations,
|
||||||
|
TSecretRotationsInsert,
|
||||||
|
TSecretRotationsUpdate,
|
||||||
TSecrets,
|
TSecrets,
|
||||||
TSecretsInsert,
|
TSecretsInsert,
|
||||||
TSecretsUpdate,
|
TSecretsUpdate,
|
||||||
@@ -304,6 +310,16 @@ declare module "knex/types/tables" {
|
|||||||
TSaRequestSecretTagsInsert,
|
TSaRequestSecretTagsInsert,
|
||||||
TSaRequestSecretTagsUpdate
|
TSaRequestSecretTagsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SecretRotation]: Knex.CompositeTableType<
|
||||||
|
TSecretRotations,
|
||||||
|
TSecretRotationsInsert,
|
||||||
|
TSecretRotationsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.SecretRotationOutput]: Knex.CompositeTableType<
|
||||||
|
TSecretRotationOutputs,
|
||||||
|
TSecretRotationOutputsInsert,
|
||||||
|
TSecretRotationOutputsUpdate
|
||||||
|
>;
|
||||||
// Junction tables
|
// Junction tables
|
||||||
[TableName.JnSecretTag]: Knex.CompositeTableType<
|
[TableName.JnSecretTag]: Knex.CompositeTableType<
|
||||||
TSecretTagJunction,
|
TSecretTagJunction,
|
||||||
|
|||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import Redis from "ioredis";
|
||||||
|
|
||||||
|
export const initRedisConnection = (redisUrl: string) => {
|
||||||
|
const redis = new Redis(redisUrl);
|
||||||
|
return redis;
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
import { TableName } from "../schemas";
|
||||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
|
import { SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
|
||||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
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.SecretRotation))) {
|
||||||
|
await knex.schema.createTable(TableName.SecretRotation, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("provider").notNullable();
|
||||||
|
t.string("secretPath").notNullable();
|
||||||
|
t.integer("interval").notNullable();
|
||||||
|
t.datetime("lastRotatedAt");
|
||||||
|
t.string("status");
|
||||||
|
t.text("statusMessage");
|
||||||
|
t.text("encryptedData");
|
||||||
|
t.text("encryptedDataIV");
|
||||||
|
t.text("encryptedDataTag");
|
||||||
|
t.string("algorithm");
|
||||||
|
t.string("keyEncoding");
|
||||||
|
t.uuid("envId").notNullable();
|
||||||
|
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SecretRotation);
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SecretRotationOutput))) {
|
||||||
|
await knex.schema.createTable(TableName.SecretRotationOutput, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("key").notNullable();
|
||||||
|
t.uuid("secretId").notNullable();
|
||||||
|
t.foreign("secretId").references("id").inTable(TableName.Secret).onDelete("CASCADE");
|
||||||
|
t.uuid("rotationId").notNullable();
|
||||||
|
t.foreign("rotationId")
|
||||||
|
.references("id")
|
||||||
|
.inTable(TableName.SecretRotation)
|
||||||
|
.onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretRotationOutput);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretRotation);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SecretRotation);
|
||||||
|
}
|
||||||
@@ -30,6 +30,8 @@ export * from "./secret-approval-requests";
|
|||||||
export * from "./secret-blind-indexes";
|
export * from "./secret-blind-indexes";
|
||||||
export * from "./secret-folders";
|
export * from "./secret-folders";
|
||||||
export * from "./secret-imports";
|
export * from "./secret-imports";
|
||||||
|
export * from "./secret-rotation-outputs";
|
||||||
|
export * from "./secret-rotations";
|
||||||
export * from "./secret-tag-junction";
|
export * from "./secret-tag-junction";
|
||||||
export * from "./secret-tags";
|
export * from "./secret-tags";
|
||||||
export * from "./secret-versions";
|
export * from "./secret-versions";
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ export enum TableName {
|
|||||||
SarReviewer = "sar_reviewers",
|
SarReviewer = "sar_reviewers",
|
||||||
SarSecret = "sa_request_secrets",
|
SarSecret = "sa_request_secrets",
|
||||||
SarSecretTag = "sa_request_secret_tags",
|
SarSecretTag = "sa_request_secret_tags",
|
||||||
|
SecretRotation = "secret_rotations",
|
||||||
|
SecretRotationOutput = "secret_rotation_outputs",
|
||||||
// junction tables
|
// junction tables
|
||||||
JnSecretTag = "secret_tag_junction",
|
JnSecretTag = "secret_tag_junction",
|
||||||
JnSecretVersionTag = "secret_version_tag_junction"
|
JnSecretVersionTag = "secret_version_tag_junction"
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ export const SaRequestSecretsSchema = z.object({
|
|||||||
secretReminderNotice: z.string().nullable().optional(),
|
secretReminderNotice: z.string().nullable().optional(),
|
||||||
secretReminderRepeatDays: z.number().nullable().optional(),
|
secretReminderRepeatDays: z.number().nullable().optional(),
|
||||||
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
||||||
algorithm: z.string().default('aes-256-gcm'),
|
algorithm: z.string().default("aes-256-gcm"),
|
||||||
keyEncoding: z.string().default('utf8'),
|
keyEncoding: z.string().default("utf8"),
|
||||||
metadata: z.unknown().nullable().optional(),
|
metadata: z.unknown().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
requestId: z.string().uuid(),
|
requestId: z.string().uuid(),
|
||||||
op: z.string(),
|
op: z.string(),
|
||||||
secretId: z.string().uuid().nullable().optional(),
|
secretId: z.string().uuid().nullable().optional(),
|
||||||
secretVersion: z.string().uuid().nullable().optional(),
|
secretVersion: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSaRequestSecrets = z.infer<typeof SaRequestSecretsSchema>;
|
export type TSaRequestSecrets = z.infer<typeof SaRequestSecretsSchema>;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// 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 SecretRotationOutputsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
key: z.string(),
|
||||||
|
secretId: z.string().uuid(),
|
||||||
|
rotationId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretRotationOutputs = z.infer<typeof SecretRotationOutputsSchema>;
|
||||||
|
export type TSecretRotationOutputsInsert = Omit<TSecretRotationOutputs, TImmutableDBKeys>;
|
||||||
|
export type TSecretRotationOutputsUpdate = Partial<Omit<TSecretRotationOutputs, TImmutableDBKeys>>;
|
||||||
@@ -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 SecretRotationsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
provider: z.string(),
|
||||||
|
secretPath: z.string(),
|
||||||
|
interval: z.number(),
|
||||||
|
lastRotatedAt: z.date().nullable().optional(),
|
||||||
|
status: z.string().nullable().optional(),
|
||||||
|
statusMessage: z.string().nullable().optional(),
|
||||||
|
encryptedData: z.string().nullable().optional(),
|
||||||
|
encryptedDataIV: z.string().nullable().optional(),
|
||||||
|
encryptedDataTag: z.string().nullable().optional(),
|
||||||
|
algorithm: z.string().nullable().optional(),
|
||||||
|
keyEncoding: z.string().nullable().optional(),
|
||||||
|
envId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretRotations = z.infer<typeof SecretRotationsSchema>;
|
||||||
|
export type TSecretRotationsInsert = Omit<TSecretRotations, TImmutableDBKeys>;
|
||||||
|
export type TSecretRotationsUpdate = Partial<Omit<TSecretRotations, TImmutableDBKeys>>;
|
||||||
@@ -1,10 +1,20 @@
|
|||||||
import { registerOrgRoleRouter } from "./org-role-router";
|
import { registerOrgRoleRouter } from "./org-role-router";
|
||||||
import { registerProjectRoleRouter } from "./project-role-router";
|
import { registerProjectRoleRouter } from "./project-role-router";
|
||||||
import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router";
|
import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router";
|
||||||
|
import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router";
|
||||||
|
import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router";
|
||||||
|
import { registerSecretRotationRouter } from "./secret-rotation-router";
|
||||||
|
|
||||||
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||||
// org role starts with organization
|
// org role starts with organization
|
||||||
await server.register(registerOrgRoleRouter, { prefix: "/organization" });
|
await server.register(registerOrgRoleRouter, { prefix: "/organization" });
|
||||||
await server.register(registerProjectRoleRouter, { prefix: "/workspace" });
|
await server.register(registerProjectRoleRouter, { prefix: "/workspace" });
|
||||||
await server.register(registerSecretApprovalPolicyRouter, { prefix: "/secret-approvals" });
|
await server.register(registerSecretApprovalPolicyRouter, { prefix: "/secret-approvals" });
|
||||||
|
await server.register(registerSecretApprovalRequestRouter, {
|
||||||
|
prefix: "/secret-approval-requests"
|
||||||
|
});
|
||||||
|
await server.register(registerSecretRotationProviderRouter, {
|
||||||
|
prefix: "/secret-rotation-providers"
|
||||||
|
});
|
||||||
|
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
|
||||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
SaRequestSecretsSchema,
|
||||||
|
SarReviewersSchema,
|
||||||
|
SecretApprovalRequestsSchema,
|
||||||
|
SecretsSchema,
|
||||||
|
SecretVersionsSchema
|
||||||
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
ApprovalStatus,
|
||||||
|
RequestState
|
||||||
|
} from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerSecretApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
workspaceId: z.string().trim(),
|
||||||
|
environment: z.string().trim().optional(),
|
||||||
|
committer: z.string().trim().optional(),
|
||||||
|
status: z.nativeEnum(RequestState).optional(),
|
||||||
|
limit: z.coerce.number().default(20),
|
||||||
|
offset: z.coerce.number().default(0)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
approvals: SecretApprovalRequestsSchema.merge(
|
||||||
|
z.object({
|
||||||
|
// secretPath: z.string(),
|
||||||
|
policy: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
approvals: z.number(),
|
||||||
|
approvers: z.string().array(),
|
||||||
|
secretPath: z.string().optional().nullable()
|
||||||
|
}),
|
||||||
|
reviewers: z.object({ member: z.string(), status: z.string() }).array(),
|
||||||
|
approvers: z.string().array()
|
||||||
|
})
|
||||||
|
).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const approvals = await server.services.secretApprovalRequest.getSecretApprovals({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
...req.query,
|
||||||
|
projectId: req.query.workspaceId
|
||||||
|
});
|
||||||
|
return { approvals };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/count",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
approvals: z.object({
|
||||||
|
open: z.number().default(0),
|
||||||
|
closed: z.number().default(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const approvals = await server.services.secretApprovalRequest.requestCount({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
projectId: req.query.workspaceId
|
||||||
|
});
|
||||||
|
return { approvals };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:id",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
approval: SecretApprovalRequestsSchema.merge(
|
||||||
|
z.object({
|
||||||
|
// secretPath: z.string(),
|
||||||
|
policy: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
approvals: z.number(),
|
||||||
|
approvers: z.string().array(),
|
||||||
|
secretPath: z.string().optional().nullable()
|
||||||
|
}),
|
||||||
|
reviewers: z.object({ member: z.string(), status: z.string() }).array(),
|
||||||
|
approvers: z.string().array(),
|
||||||
|
commits: SaRequestSecretsSchema.omit({ secretBlindIndex: true })
|
||||||
|
.merge(
|
||||||
|
z.object({
|
||||||
|
secret: SecretsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
secretKeyIV: true,
|
||||||
|
secretKeyTag: true,
|
||||||
|
secretKeyCiphertext: true,
|
||||||
|
secretValueIV: true,
|
||||||
|
secretValueTag: true,
|
||||||
|
secretValueCiphertext: true,
|
||||||
|
secretCommentIV: true,
|
||||||
|
secretCommentTag: true,
|
||||||
|
secretCommentCiphertext: true
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
secretVersion: SecretVersionsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
secretKeyIV: true,
|
||||||
|
secretKeyTag: true,
|
||||||
|
secretKeyCiphertext: true,
|
||||||
|
secretValueIV: true,
|
||||||
|
secretValueTag: true,
|
||||||
|
secretValueCiphertext: true,
|
||||||
|
secretCommentIV: true,
|
||||||
|
secretCommentTag: true,
|
||||||
|
secretCommentCiphertext: true
|
||||||
|
}).optional()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const approval = await server.services.secretApprovalRequest.getSecretApprovalDetails({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:id/merge",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
approval: SecretApprovalRequestsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { approval } = await server.services.secretApprovalRequest.mergeSecretApprovalRequest({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
approvalId: req.params.id
|
||||||
|
});
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:id/review",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
status: z.enum([ApprovalStatus.APPROVED, ApprovalStatus.REJECTED])
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
review: SarReviewersSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const review = await server.services.secretApprovalRequest.reviewApproval({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
approvalId: req.params.id,
|
||||||
|
status: req.body.status
|
||||||
|
});
|
||||||
|
return { review };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:id/status",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
status: z.nativeEnum(RequestState)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
approval: SecretApprovalRequestsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const approval = await server.services.secretApprovalRequest.updateApprovalStatus({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
approvalId: req.params.id,
|
||||||
|
status: req.body.status
|
||||||
|
});
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerSecretRotationProviderRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/:workspaceId",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
providers: z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
image: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
template: z.any()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const providers = await server.services.secretRotation.getProviderTemplates({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
projectId: req.params.workspaceId
|
||||||
|
});
|
||||||
|
return providers;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerSecretRotationRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
workspaceId: z.string().trim(),
|
||||||
|
secretPath: z.string().trim(),
|
||||||
|
environment: z.string().trim(),
|
||||||
|
interval: z.number().min(1),
|
||||||
|
provider: z.string().trim(),
|
||||||
|
customProvider: z.string().trim().optional(),
|
||||||
|
inputs: z.record(z.unknown()),
|
||||||
|
outputs: z.record(z.string())
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
secretRotation: SecretRotationsSchema.merge(
|
||||||
|
z.object({
|
||||||
|
environment: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string()
|
||||||
|
}),
|
||||||
|
outputs: SecretRotationOutputsSchema.array()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const secretRotation = await server.services.secretRotation.createRotation({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
...req.body,
|
||||||
|
projectId: req.body.workspaceId
|
||||||
|
});
|
||||||
|
return { secretRotation };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/restart",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
id: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
secretRotation: SecretRotationsSchema.merge(
|
||||||
|
z.object({
|
||||||
|
environment: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const secretRotation = await server.services.secretRotation.restartById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
rotationId: req.body.id
|
||||||
|
});
|
||||||
|
return { secretRotation };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
secretRotations: SecretRotationsSchema.merge(
|
||||||
|
z.object({
|
||||||
|
environment: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string()
|
||||||
|
}),
|
||||||
|
outputs: z
|
||||||
|
.object({
|
||||||
|
key: z.string(),
|
||||||
|
secret: SecretsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
version: true,
|
||||||
|
secretKeyIV: true,
|
||||||
|
secretKeyTag: true,
|
||||||
|
secretKeyCiphertext: true,
|
||||||
|
secretValueIV: true,
|
||||||
|
secretValueTag: true,
|
||||||
|
secretValueCiphertext: true,
|
||||||
|
secretCommentIV: true,
|
||||||
|
secretCommentTag: true,
|
||||||
|
secretCommentCiphertext: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const secretRotations = await server.services.secretRotation.getByProjectId({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
projectId: req.query.workspaceId
|
||||||
|
});
|
||||||
|
return { secretRotations };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:id",
|
||||||
|
method: "DELETE",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
secretRotation: SecretRotationsSchema.merge(
|
||||||
|
z.object({
|
||||||
|
environment: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const secretRotation = await server.services.secretRotation.deleteById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
rotationId: req.params.id
|
||||||
|
});
|
||||||
|
return { secretRotation };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TSecretApprovalPolicies, TableName } from "@app/db/schemas";
|
import { TableName,TSecretApprovalPolicies } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import {
|
import {
|
||||||
TFindFilter,
|
|
||||||
buildFindFilter,
|
buildFindFilter,
|
||||||
mergeOneToManyRelation,
|
mergeOneToManyRelation,
|
||||||
ormify,
|
ormify,
|
||||||
selectAllTableCols
|
selectAllTableCols,
|
||||||
} from "@app/lib/knex";
|
TFindFilter} from "@app/lib/knex";
|
||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
export type TSecretApprovalPolicyDalFactory = ReturnType<typeof secretApprovalPolicyDalFactory>;
|
export type TSecretApprovalPolicyDalFactory = ReturnType<typeof secretApprovalPolicyDalFactory>;
|
||||||
|
|
||||||
|
|||||||
+13
-11
@@ -1,6 +1,18 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import picomatch from "picomatch";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { TSecretApprovalPolicyDalFactory } from "./secret-approval-policy-dal";
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { containsGlobPatterns } from "@app/lib/picomatch";
|
||||||
|
import { TProjectEnvDalFactory } from "@app/services/project-env/project-env-dal";
|
||||||
|
import { TProjectMembershipDalFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
|
|
||||||
import { TSapApproverDalFactory } from "./sap-approver-dal";
|
import { TSapApproverDalFactory } from "./sap-approver-dal";
|
||||||
|
import { TSecretApprovalPolicyDalFactory } from "./secret-approval-policy-dal";
|
||||||
import {
|
import {
|
||||||
TCreateSapDTO,
|
TCreateSapDTO,
|
||||||
TDeleteSapDTO,
|
TDeleteSapDTO,
|
||||||
@@ -8,16 +20,6 @@ import {
|
|||||||
TListSapDTO,
|
TListSapDTO,
|
||||||
TUpdateSapDTO
|
TUpdateSapDTO
|
||||||
} from "./secret-approval-policy-types";
|
} from "./secret-approval-policy-types";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
|
||||||
import {
|
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "@app/ee/services/permission/project-permission";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
|
||||||
import picomatch from "picomatch";
|
|
||||||
import { containsGlobPatterns } from "@app/lib/picomatch";
|
|
||||||
import { TProjectMembershipDalFactory } from "@app/services/project-membership/project-membership-dal";
|
|
||||||
import { TProjectEnvDalFactory } from "@app/services/project-env/project-env-dal";
|
|
||||||
|
|
||||||
const getPolicyScore = (policy: { secretPath?: string | null }) =>
|
const getPolicyScore = (policy: { secretPath?: string | null }) =>
|
||||||
// if glob pattern score is 1, if not exist score is 0 and if its not both then its exact path meaning score 2
|
// if glob pattern score is 1, if not exist score is 0 and if its not both then its exact path meaning score 2
|
||||||
|
|||||||
@@ -1,25 +1,121 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
export type TSarSecretDalFactory = ReturnType<typeof sarSecretDalFactory>;
|
export type TSarSecretDalFactory = ReturnType<typeof sarSecretDalFactory>;
|
||||||
|
|
||||||
export const sarSecretDalFactory = (db: TDbClient) => {
|
export const sarSecretDalFactory = (db: TDbClient) => {
|
||||||
const sarSecretOrm = ormify(db, TableName.SarSecret);
|
const sarSecretOrm = ormify(db, TableName.SarSecret);
|
||||||
|
|
||||||
const findByRequestId = (requestId: string, tx?: Knex) => {
|
const findByRequestId = async (requestId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const doc = (tx || db)(TableName.SarSecret)
|
const doc = await (tx || db)(TableName.SarSecret)
|
||||||
.where({ requestId })
|
.where({ requestId })
|
||||||
.leftJoin(TableName.Secret, `${TableName.SarSecret}.secretId`, `${TableName.Secret}.id`)
|
.leftJoin(TableName.Secret, `${TableName.SarSecret}.secretId`, `${TableName.Secret}.id`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretVersion,
|
||||||
|
`${TableName.SecretVersion}.id`,
|
||||||
|
`${TableName.SarSecret}.secretVersion`
|
||||||
|
)
|
||||||
.select(selectAllTableCols(TableName.SarSecret))
|
.select(selectAllTableCols(TableName.SarSecret))
|
||||||
.select(
|
.select(
|
||||||
db.ref("secretBlindIndex").withSchema(TableName.Secret).as("latestSecretBlindIndex"),
|
db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindIndex"),
|
||||||
db.ref("version").withSchema(TableName.Secret).as("latestSecretVersion")
|
db.ref("version").withSchema(TableName.Secret).as("orgSecVersion"),
|
||||||
|
db.ref("secretKeyIV").withSchema(TableName.Secret).as("orgSecKeyIV"),
|
||||||
|
db.ref("secretKeyTag").withSchema(TableName.Secret).as("orgSecKeyTag"),
|
||||||
|
db.ref("secretKeyCiphertext").withSchema(TableName.Secret).as("orgSecKeyCiphertext"),
|
||||||
|
db.ref("secretValueIV").withSchema(TableName.Secret).as("orgSecValueIV"),
|
||||||
|
db.ref("secretValueTag").withSchema(TableName.Secret).as("orgSecValueTag"),
|
||||||
|
db.ref("secretValueCiphertext").withSchema(TableName.Secret).as("orgSecValueCiphertext"),
|
||||||
|
db.ref("secretCommentIV").withSchema(TableName.Secret).as("orgSecCommentIV"),
|
||||||
|
db.ref("secretCommentTag").withSchema(TableName.Secret).as("orgSecCommentTag"),
|
||||||
|
db
|
||||||
|
.ref("secretCommentCiphertext")
|
||||||
|
.withSchema(TableName.Secret)
|
||||||
|
.as("orgSecCommentCiphertext")
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
// db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindInex"),
|
||||||
|
db.ref("version").withSchema(TableName.SecretVersion).as("secVerVersion"),
|
||||||
|
db.ref("secretKeyIV").withSchema(TableName.SecretVersion).as("secVerKeyIV"),
|
||||||
|
db.ref("secretKeyTag").withSchema(TableName.SecretVersion).as("secVerKeyTag"),
|
||||||
|
db
|
||||||
|
.ref("secretKeyCiphertext")
|
||||||
|
.withSchema(TableName.SecretVersion)
|
||||||
|
.as("secVerKeyCiphertext"),
|
||||||
|
db.ref("secretValueIV").withSchema(TableName.SecretVersion).as("secVerValueIV"),
|
||||||
|
db.ref("secretValueTag").withSchema(TableName.SecretVersion).as("secVerValueTag"),
|
||||||
|
db
|
||||||
|
.ref("secretValueCiphertext")
|
||||||
|
.withSchema(TableName.SecretVersion)
|
||||||
|
.as("secVerValueCiphertext"),
|
||||||
|
db.ref("secretCommentIV").withSchema(TableName.SecretVersion).as("secVerCommentIV"),
|
||||||
|
db.ref("secretCommentTag").withSchema(TableName.SecretVersion).as("secVerCommentTag"),
|
||||||
|
db
|
||||||
|
.ref("secretCommentCiphertext")
|
||||||
|
.withSchema(TableName.SecretVersion)
|
||||||
|
.as("secVerCommentCiphertext")
|
||||||
);
|
);
|
||||||
return doc;
|
return doc.map(
|
||||||
|
({
|
||||||
|
orgSecKeyIV,
|
||||||
|
orgSecKeyTag,
|
||||||
|
orgSecValueIV,
|
||||||
|
orgSecVersion,
|
||||||
|
orgSecValueTag,
|
||||||
|
orgSecCommentIV,
|
||||||
|
orgSecBlindIndex,
|
||||||
|
orgSecCommentTag,
|
||||||
|
orgSecKeyCiphertext,
|
||||||
|
orgSecValueCiphertext,
|
||||||
|
orgSecCommentCiphertext,
|
||||||
|
secVerCommentIV,
|
||||||
|
secVerCommentCiphertext,
|
||||||
|
secVerCommentTag,
|
||||||
|
secVerValueCiphertext,
|
||||||
|
secVerKeyIV,
|
||||||
|
secVerKeyTag,
|
||||||
|
secVerValueIV,
|
||||||
|
secVerVersion,
|
||||||
|
secVerValueTag,
|
||||||
|
secVerKeyCiphertext,
|
||||||
|
...el
|
||||||
|
}) => ({
|
||||||
|
...el,
|
||||||
|
secret: el.secretId
|
||||||
|
? {
|
||||||
|
id: el.secretId,
|
||||||
|
secretBlindIndex: orgSecBlindIndex,
|
||||||
|
secretKeyIV: orgSecKeyIV,
|
||||||
|
secretKeyTag: orgSecKeyTag,
|
||||||
|
secretKeyCiphertext: orgSecKeyCiphertext,
|
||||||
|
secretValueIV: orgSecValueIV,
|
||||||
|
secretValueTag: orgSecValueTag,
|
||||||
|
secretValueCiphertext: orgSecValueCiphertext,
|
||||||
|
secretCommentIV: orgSecCommentIV,
|
||||||
|
secretCommentTag: orgSecCommentTag,
|
||||||
|
secretCommentCiphertext: orgSecCommentCiphertext
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
secretVersion: el.secretVersion
|
||||||
|
? {
|
||||||
|
id: el.secretVersion,
|
||||||
|
secretKeyIV: secVerKeyIV,
|
||||||
|
secretKeyTag: secVerKeyTag,
|
||||||
|
secretKeyCiphertext: secVerKeyCiphertext,
|
||||||
|
secretValueIV: secVerValueIV,
|
||||||
|
secretValueTag: secVerValueTag,
|
||||||
|
secretValueCiphertext: secVerValueCiphertext,
|
||||||
|
secretCommentIV: secVerCommentIV,
|
||||||
|
secretCommentTag: secVerCommentTag,
|
||||||
|
secretCommentCiphertext: secVerCommentCiphertext
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
})
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindByRequestId" });
|
throw new DatabaseError({ error, name: "FindByRequestId" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import { TDbClient } from "@app/db";
|
|
||||||
import { TSecretApprovalRequests, TableName } from "@app/db/schemas";
|
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
|
||||||
import { TFindFilter, ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { SecretApprovalRequestsSchema, TableName, TSecretApprovalRequests } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import {
|
||||||
|
ormify,
|
||||||
|
selectAllTableCols,
|
||||||
|
sqlNestRelationships,
|
||||||
|
stripUndefinedInWhere,
|
||||||
|
TFindFilter
|
||||||
|
} from "@app/lib/knex";
|
||||||
|
|
||||||
import { RequestState } from "./secret-approval-request-types";
|
import { RequestState } from "./secret-approval-request-types";
|
||||||
|
|
||||||
export type TSecretApprovalRequestDalFactory = ReturnType<typeof secretApprovalRequestDalFactory>;
|
export type TSecretApprovalRequestDalFactory = ReturnType<typeof secretApprovalRequestDalFactory>;
|
||||||
@@ -23,27 +31,33 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
|
|||||||
const findQuery = (filter: TFindFilter<TSecretApprovalRequests>, tx: Knex) =>
|
const findQuery = (filter: TFindFilter<TSecretApprovalRequests>, tx: Knex) =>
|
||||||
tx(TableName.SecretApprovalRequest)
|
tx(TableName.SecretApprovalRequest)
|
||||||
.where(filter)
|
.where(filter)
|
||||||
|
.join(
|
||||||
|
TableName.SecretFolder,
|
||||||
|
`${TableName.SecretApprovalRequest}.folderId`,
|
||||||
|
`${TableName.SecretFolder}.id`
|
||||||
|
)
|
||||||
|
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||||
.join(
|
.join(
|
||||||
TableName.SecretApprovalPolicy,
|
TableName.SecretApprovalPolicy,
|
||||||
`${TableName.SecretApprovalRequest}.policyId`,
|
`${TableName.SecretApprovalRequest}.policyId`,
|
||||||
`${TableName.SecretApprovalPolicy}.id`
|
`${TableName.SecretApprovalPolicy}.id`
|
||||||
)
|
)
|
||||||
.join(
|
|
||||||
TableName.SarReviewer,
|
|
||||||
`${TableName.SecretApprovalRequest}.id`,
|
|
||||||
`${TableName.SarReviewer}.requestId`
|
|
||||||
)
|
|
||||||
.join(
|
.join(
|
||||||
TableName.SapApprover,
|
TableName.SapApprover,
|
||||||
`${TableName.SecretApprovalPolicy}.id`,
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
`${TableName.SapApprover}.policyId`
|
`${TableName.SapApprover}.policyId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SarReviewer,
|
||||||
|
`${TableName.SecretApprovalRequest}.id`,
|
||||||
|
`${TableName.SarReviewer}.requestId`
|
||||||
|
)
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
||||||
.select(tx.ref("id").withSchema(TableName.SarReviewer).as("reviewerMemberId"))
|
.select(tx.ref("member").withSchema(TableName.SarReviewer).as("reviewerMemberId"))
|
||||||
.select(tx.ref("statue").withSchema(TableName.SarReviewer).as("reviewerStatus"))
|
.select(tx.ref("status").withSchema(TableName.SarReviewer).as("reviewerStatus"))
|
||||||
.select(tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"))
|
.select(tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"))
|
||||||
.select(tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"))
|
.select(tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"))
|
||||||
.select(tx.ref("projectId").withSchema(TableName.SecretApprovalPolicy))
|
.select(tx.ref("projectId").withSchema(TableName.Environment))
|
||||||
.select(
|
.select(
|
||||||
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath")
|
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath")
|
||||||
)
|
)
|
||||||
@@ -52,51 +66,27 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const findById = async (id: string, tx?: Knex) => {
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const docs = await findQuery({ id }, tx || db);
|
const sql = findQuery({ [`${TableName.SecretApprovalRequest}.id` as "id"]: id }, tx || db);
|
||||||
|
const docs = await sql;
|
||||||
const formatedDoc = sqlNestRelationships({
|
const formatedDoc = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
parentMapper: ({
|
parentMapper: (el) => ({
|
||||||
id: pk,
|
...SecretApprovalRequestsSchema.parse(el),
|
||||||
projectId,
|
projectId: el.projectId,
|
||||||
hasMerged,
|
|
||||||
status,
|
|
||||||
conflicts,
|
|
||||||
slug,
|
|
||||||
folderId,
|
|
||||||
statusChangeBy,
|
|
||||||
committerId,
|
|
||||||
createdAt,
|
|
||||||
updatedAt,
|
|
||||||
policyId,
|
|
||||||
policyName,
|
|
||||||
policyApprovals,
|
|
||||||
policySecretPath
|
|
||||||
}) => ({
|
|
||||||
id: pk,
|
|
||||||
hasMerged,
|
|
||||||
projectId,
|
|
||||||
status,
|
|
||||||
conflicts,
|
|
||||||
slug,
|
|
||||||
folderId,
|
|
||||||
statusChangeBy,
|
|
||||||
committerId,
|
|
||||||
createdAt,
|
|
||||||
updatedAt,
|
|
||||||
policyId,
|
|
||||||
policy: {
|
policy: {
|
||||||
id: policyId,
|
id: el.policyId,
|
||||||
name: policyName,
|
name: el.policyName,
|
||||||
approvals: policyApprovals,
|
approvals: el.policyApprovals,
|
||||||
secretPath: policySecretPath
|
secretPath: el.policySecretPath
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
key: "reviewerMemberId",
|
key: "reviewerMemberId",
|
||||||
label: "reviewers",
|
label: "reviewers",
|
||||||
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => ({ member, status })
|
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) =>
|
||||||
|
member ? { member, status } : undefined
|
||||||
},
|
},
|
||||||
{ key: "approverId", label: "approvers", mapper: ({ approverId }) => approverId }
|
{ key: "approverId", label: "approvers", mapper: ({ approverId }) => approverId }
|
||||||
] as const
|
] as const
|
||||||
@@ -124,18 +114,27 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretFolder}.envId`,
|
`${TableName.SecretFolder}.envId`,
|
||||||
`${TableName.Environment}.id`
|
`${TableName.Environment}.id`
|
||||||
)
|
)
|
||||||
.where({ projectId })
|
|
||||||
.join(
|
.join(
|
||||||
TableName.SapApprover,
|
TableName.SapApprover,
|
||||||
`${TableName.SecretApprovalRequest}.policyId`,
|
`${TableName.SecretApprovalRequest}.policyId`,
|
||||||
`${TableName.SapApprover}.policyId`
|
`${TableName.SapApprover}.policyId`
|
||||||
)
|
)
|
||||||
|
.where({ projectId })
|
||||||
.where(`${TableName.SapApprover}.approverId`, membershipId)
|
.where(`${TableName.SapApprover}.approverId`, membershipId)
|
||||||
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
|
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
|
||||||
.groupBy("status")
|
.groupBy("status")
|
||||||
.count("status");
|
.count("status")
|
||||||
console.log(JSON.stringify(doc, null, 4));
|
.select("status");
|
||||||
return { open: 0, closed: 0 };
|
return {
|
||||||
|
open: parseInt(
|
||||||
|
(doc.find(({ status }) => status === RequestState.Open)?.count as string) || "0",
|
||||||
|
10
|
||||||
|
),
|
||||||
|
closed: parseInt(
|
||||||
|
(doc.find(({ status }) => status === RequestState.Closed)?.count as string) || "0",
|
||||||
|
10
|
||||||
|
)
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindRequestCount" });
|
throw new DatabaseError({ error, name: "FindRequestCount" });
|
||||||
}
|
}
|
||||||
@@ -157,7 +156,6 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretFolder}.envId`,
|
`${TableName.SecretFolder}.envId`,
|
||||||
`${TableName.Environment}.id`
|
`${TableName.Environment}.id`
|
||||||
)
|
)
|
||||||
.where({ projectId, slug: environment, status, committerId: committer })
|
|
||||||
.join(
|
.join(
|
||||||
TableName.SecretApprovalPolicy,
|
TableName.SecretApprovalPolicy,
|
||||||
`${TableName.SecretApprovalRequest}.policyId`,
|
`${TableName.SecretApprovalRequest}.policyId`,
|
||||||
@@ -168,11 +166,28 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalPolicy}.id`,
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
`${TableName.SapApprover}.policyId`
|
`${TableName.SapApprover}.policyId`
|
||||||
)
|
)
|
||||||
.where(`${TableName.SapApprover}.approverId`, membershipId)
|
.leftJoin(
|
||||||
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
|
TableName.SarReviewer,
|
||||||
|
`${TableName.SecretApprovalRequest}.id`,
|
||||||
|
`${TableName.SarReviewer}.requestId`
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
stripUndefinedInWhere({
|
||||||
|
projectId,
|
||||||
|
slug: environment,
|
||||||
|
[`${TableName.SecretApprovalRequest}.status`]: status,
|
||||||
|
committerId: committer
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.andWhere((bd) =>
|
||||||
|
bd
|
||||||
|
.where(`${TableName.SapApprover}.approverId`, membershipId)
|
||||||
|
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
|
||||||
|
)
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
||||||
|
.select(db.ref("projectId").withSchema(TableName.Environment))
|
||||||
.select(db.ref("id").withSchema(TableName.SarReviewer).as("reviewerMemberId"))
|
.select(db.ref("id").withSchema(TableName.SarReviewer).as("reviewerMemberId"))
|
||||||
.select(db.ref("statue").withSchema(TableName.SarReviewer).as("reviewerStatus"))
|
.select(db.ref("status").withSchema(TableName.SarReviewer).as("reviewerStatus"))
|
||||||
.select(db.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"))
|
.select(db.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"))
|
||||||
.select(db.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"))
|
.select(db.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"))
|
||||||
.select(
|
.select(
|
||||||
@@ -185,46 +200,22 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => {
|
|||||||
const formatedDoc = sqlNestRelationships({
|
const formatedDoc = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
parentMapper: ({
|
parentMapper: (el) => ({
|
||||||
id: pk,
|
...SecretApprovalRequestsSchema.parse(el),
|
||||||
hasMerged,
|
projectId: el.projectId,
|
||||||
status: pStatus,
|
|
||||||
conflicts,
|
|
||||||
slug,
|
|
||||||
folderId,
|
|
||||||
statusChangeBy,
|
|
||||||
committerId,
|
|
||||||
createdAt,
|
|
||||||
updatedAt,
|
|
||||||
policyId,
|
|
||||||
policyName,
|
|
||||||
policyApprovals,
|
|
||||||
policySecretPath
|
|
||||||
}) => ({
|
|
||||||
id: pk,
|
|
||||||
hasMerged,
|
|
||||||
projectId,
|
|
||||||
status: pStatus,
|
|
||||||
conflicts,
|
|
||||||
slug,
|
|
||||||
folderId,
|
|
||||||
statusChangeBy,
|
|
||||||
committerId,
|
|
||||||
createdAt,
|
|
||||||
updatedAt,
|
|
||||||
policyId,
|
|
||||||
policy: {
|
policy: {
|
||||||
id: policyId,
|
id: el.policyId,
|
||||||
name: policyName,
|
name: el.policyName,
|
||||||
approvals: policyApprovals,
|
approvals: el.policyApprovals,
|
||||||
secretPath: policySecretPath
|
secretPath: el.policySecretPath
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
key: "reviewerMemberId",
|
key: "reviewerMemberId",
|
||||||
label: "reviewers",
|
label: "reviewers",
|
||||||
mapper: ({ reviewerMemberId: member, reviewerStatus: s }) => ({ member, status: s })
|
mapper: ({ reviewerMemberId: member, reviewerStatus: s }) =>
|
||||||
|
member ? { member, status: s } : undefined
|
||||||
},
|
},
|
||||||
{ key: "approverId", label: "approvers", mapper: ({ approverId }) => approverId }
|
{ key: "approverId", label: "approvers", mapper: ({ approverId }) => approverId }
|
||||||
] as const
|
] as const
|
||||||
|
|||||||
+168
-134
@@ -1,19 +1,5 @@
|
|||||||
import { TSecretFolderDalFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import { TSecretApprovalRequestDalFactory } from "./secret-approval-request-dal";
|
|
||||||
import {
|
|
||||||
ApprovalStatus,
|
|
||||||
CommitType,
|
|
||||||
TApprovalRequestCountDTO,
|
|
||||||
TGenerateSecretApprovalRequestDTO,
|
|
||||||
TListApprovalsDTO,
|
|
||||||
TMergeSecretApprovalRequestDTO,
|
|
||||||
TReviewRequestDTO,
|
|
||||||
TSecretApprovalDetailsDTO,
|
|
||||||
TStatusChangeDTO
|
|
||||||
} from "./secret-approval-request-types";
|
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
|
||||||
import { TSecretBlindIndexDalFactory } from "@app/services/secret/secret-blind-index-dal";
|
|
||||||
import { generateSecretBlindIndexBySalt } from "@app/services/secret/secret-service";
|
|
||||||
import {
|
import {
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
SecretEncryptionAlgo,
|
SecretEncryptionAlgo,
|
||||||
@@ -22,13 +8,32 @@ import {
|
|||||||
TSaRequestSecretsInsert,
|
TSaRequestSecretsInsert,
|
||||||
TSecrets
|
TSecrets
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TSecretDalFactory } from "@app/services/secret/secret-dal";
|
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { TSecretVersionDalFactory } from "@app/services/secret/secret-version-dal";
|
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TSarSecretDalFactory } from "./sar-secret-dal";
|
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
import { TSecretBlindIndexDalFactory } from "@app/services/secret/secret-blind-index-dal";
|
||||||
|
import { TSecretDalFactory } from "@app/services/secret/secret-dal";
|
||||||
|
import { generateSecretBlindIndexBySalt } from "@app/services/secret/secret-service";
|
||||||
|
import { TSecretVersionDalFactory } from "@app/services/secret/secret-version-dal";
|
||||||
|
import { TSecretFolderDalFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TSarReviewerDalFactory } from "./sar-reviewer-dal";
|
import { TSarReviewerDalFactory } from "./sar-reviewer-dal";
|
||||||
|
import { TSarSecretDalFactory } from "./sar-secret-dal";
|
||||||
|
import { TSecretApprovalRequestDalFactory } from "./secret-approval-request-dal";
|
||||||
|
import {
|
||||||
|
ApprovalStatus,
|
||||||
|
CommitType,
|
||||||
|
RequestState,
|
||||||
|
TApprovalRequestCountDTO,
|
||||||
|
TGenerateSecretApprovalRequestDTO,
|
||||||
|
TListApprovalsDTO,
|
||||||
|
TMergeSecretApprovalRequestDTO,
|
||||||
|
TReviewRequestDTO,
|
||||||
|
TSecretApprovalDetailsDTO,
|
||||||
|
TStatusChangeDTO
|
||||||
|
} from "./secret-approval-request-types";
|
||||||
|
|
||||||
type TSecretApprovalRequestServiceFactoryDep = {
|
type TSecretApprovalRequestServiceFactoryDep = {
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
@@ -167,9 +172,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
if (secretApprovalRequest.hasMerged)
|
if (secretApprovalRequest.hasMerged)
|
||||||
throw new BadRequestError({ message: "Approval request has been merged" });
|
throw new BadRequestError({ message: "Approval request has been merged" });
|
||||||
if (secretApprovalRequest.status === "close" && status === "close")
|
if (secretApprovalRequest.status === RequestState.Closed && status === RequestState.Closed)
|
||||||
throw new BadRequestError({ message: "Approval request is already closed" });
|
throw new BadRequestError({ message: "Approval request is already closed" });
|
||||||
if (secretApprovalRequest.status === "open" && status === "open")
|
if (secretApprovalRequest.status === RequestState.Open && status === RequestState.Open)
|
||||||
throw new BadRequestError({ message: "Approval request is already open" });
|
throw new BadRequestError({ message: "Approval request is already open" });
|
||||||
|
|
||||||
const updatedRequest = await secretApprovalRequestDal.updateById(secretApprovalRequest.id, {
|
const updatedRequest = await secretApprovalRequestDal.updateById(secretApprovalRequest.id, {
|
||||||
@@ -203,7 +208,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
throw new UnauthorizedError({ message: "User has no access" });
|
throw new UnauthorizedError({ message: "User has no access" });
|
||||||
}
|
}
|
||||||
const reviewers = secretApprovalRequest.reviewers.reduce<Record<string, ApprovalStatus>>(
|
const reviewers = secretApprovalRequest.reviewers.reduce<Record<string, ApprovalStatus>>(
|
||||||
(prev, curr) => ({ ...prev, [curr.member.toString()]: curr.status }),
|
(prev, curr) => ({ ...prev, [curr.member.toString()]: curr.status as ApprovalStatus }),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const hasMinApproval =
|
const hasMinApproval =
|
||||||
@@ -245,10 +250,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (secretUpdationCommits.length) {
|
if (secretUpdationCommits.length) {
|
||||||
const conflictedByNewBlindIndex = await secretDal.findByBlindIndexes(
|
const conflictedByNewBlindIndex = await secretDal.findByBlindIndexes(
|
||||||
folderId,
|
folderId,
|
||||||
secretUpdationCommits.map(({ secretBlindIndex }) => ({
|
secretUpdationCommits
|
||||||
type: SecretType.Shared,
|
.filter(
|
||||||
blindIndex: secretBlindIndex
|
({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex
|
||||||
}))
|
)
|
||||||
|
.map(({ secretBlindIndex }) => ({
|
||||||
|
type: SecretType.Shared,
|
||||||
|
blindIndex: secretBlindIndex
|
||||||
|
}))
|
||||||
);
|
);
|
||||||
const conflictGroupByBlindIndex = conflictedByNewBlindIndex.reduce<Record<string, boolean>>(
|
const conflictGroupByBlindIndex = conflictedByNewBlindIndex.reduce<Record<string, boolean>>(
|
||||||
(prev, curr) =>
|
(prev, curr) =>
|
||||||
@@ -276,118 +285,128 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const mergeStatus = await secretDal.transaction(async (tx) => {
|
const mergeStatus = await secretDal.transaction(async (tx) => {
|
||||||
const newSecrets = await secretDal.insertMany(
|
const newSecrets = secretCreationCommits.length
|
||||||
secretCreationCommits.map(
|
? await secretDal.insertMany(
|
||||||
({
|
secretCreationCommits.map(
|
||||||
secretBlindIndex,
|
({
|
||||||
metadata,
|
secretBlindIndex,
|
||||||
secretKeyIV,
|
metadata,
|
||||||
secretKeyTag,
|
secretKeyIV,
|
||||||
secretKeyCiphertext,
|
secretKeyTag,
|
||||||
secretValueIV,
|
secretKeyCiphertext,
|
||||||
secretValueTag,
|
secretValueIV,
|
||||||
secretValueCiphertext,
|
secretValueTag,
|
||||||
secretCommentIV,
|
secretValueCiphertext,
|
||||||
secretCommentTag,
|
secretCommentIV,
|
||||||
secretCommentCiphertext,
|
secretCommentTag,
|
||||||
skipMultilineEncoding,
|
secretCommentCiphertext,
|
||||||
secretReminderNotice,
|
skipMultilineEncoding,
|
||||||
secretReminderRepeatDays
|
secretReminderNotice,
|
||||||
}) => ({
|
secretReminderRepeatDays
|
||||||
secretBlindIndex,
|
}) => ({
|
||||||
metadata,
|
secretBlindIndex,
|
||||||
secretKeyIV,
|
metadata,
|
||||||
secretKeyTag,
|
secretKeyIV,
|
||||||
secretKeyCiphertext,
|
secretKeyTag,
|
||||||
secretValueIV,
|
secretKeyCiphertext,
|
||||||
secretValueTag,
|
secretValueIV,
|
||||||
secretValueCiphertext,
|
secretValueTag,
|
||||||
secretCommentIV,
|
secretValueCiphertext,
|
||||||
secretCommentTag,
|
secretCommentIV,
|
||||||
secretCommentCiphertext,
|
secretCommentTag,
|
||||||
skipMultilineEncoding,
|
secretCommentCiphertext,
|
||||||
secretReminderNotice,
|
skipMultilineEncoding,
|
||||||
secretReminderRepeatDays,
|
secretReminderNotice,
|
||||||
version: 1,
|
secretReminderRepeatDays,
|
||||||
|
version: 1,
|
||||||
|
folderId,
|
||||||
|
type: SecretType.Shared,
|
||||||
|
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||||
|
keyEncoding: SecretKeyEncoding.UTF8
|
||||||
|
})
|
||||||
|
),
|
||||||
|
tx
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
const updatedSecrets = secretUpdationCommits.length
|
||||||
|
? await secretDal.bulkUpdate(
|
||||||
|
secretUpdationCommits.map(
|
||||||
|
({
|
||||||
|
version,
|
||||||
|
secretId,
|
||||||
|
secretBlindIndex,
|
||||||
|
metadata,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
secretReminderNotice,
|
||||||
|
secretReminderRepeatDays
|
||||||
|
}) => ({
|
||||||
|
folderId,
|
||||||
|
version: (version || 0) + 1,
|
||||||
|
id: secretId as string,
|
||||||
|
type: SecretType.Shared,
|
||||||
|
secretBlindIndex,
|
||||||
|
metadata,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
secretReminderNotice,
|
||||||
|
secretReminderRepeatDays
|
||||||
|
})
|
||||||
|
),
|
||||||
|
tx
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
const deletedSecret = secretDeletionCommits.length
|
||||||
|
? await secretDal.deleteMany(
|
||||||
|
secretDeletionCommits.map(({ secretBlindIndex }) => ({
|
||||||
|
blindIndex: secretBlindIndex,
|
||||||
|
type: SecretType.Shared
|
||||||
|
})),
|
||||||
folderId,
|
folderId,
|
||||||
type: SecretType.Shared,
|
actorId,
|
||||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
tx
|
||||||
keyEncoding: SecretKeyEncoding.UTF8
|
)
|
||||||
})
|
: [];
|
||||||
),
|
if (newSecrets.length || updatedSecrets.length) {
|
||||||
tx
|
await secretVersionDal.insertMany(
|
||||||
);
|
newSecrets
|
||||||
const updatedSecrets = await secretDal.bulkUpdate(
|
.map(({ id, updatedAt, createdAt, ...el }) => ({
|
||||||
secretUpdationCommits.map(
|
|
||||||
({
|
|
||||||
secretId,
|
|
||||||
secretBlindIndex,
|
|
||||||
metadata,
|
|
||||||
secretKeyIV,
|
|
||||||
secretKeyTag,
|
|
||||||
secretKeyCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag,
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretCommentIV,
|
|
||||||
secretCommentTag,
|
|
||||||
secretCommentCiphertext,
|
|
||||||
skipMultilineEncoding,
|
|
||||||
secretReminderNotice,
|
|
||||||
secretReminderRepeatDays
|
|
||||||
}) => ({
|
|
||||||
folderId,
|
|
||||||
id: secretId as string,
|
|
||||||
type: SecretType.Shared,
|
|
||||||
secretBlindIndex,
|
|
||||||
metadata,
|
|
||||||
secretKeyIV,
|
|
||||||
secretKeyTag,
|
|
||||||
secretKeyCiphertext,
|
|
||||||
secretValueIV,
|
|
||||||
secretValueTag,
|
|
||||||
secretValueCiphertext,
|
|
||||||
secretCommentIV,
|
|
||||||
secretCommentTag,
|
|
||||||
secretCommentCiphertext,
|
|
||||||
skipMultilineEncoding,
|
|
||||||
secretReminderNotice,
|
|
||||||
secretReminderRepeatDays
|
|
||||||
})
|
|
||||||
),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
const deletedSecret = await secretDal.deleteMany(
|
|
||||||
secretDeletionCommits.map(({ secretBlindIndex }) => ({
|
|
||||||
blindIndex: secretBlindIndex,
|
|
||||||
type: SecretType.Shared
|
|
||||||
})),
|
|
||||||
folderId,
|
|
||||||
actorId,
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
await secretVersionDal.insertMany(
|
|
||||||
newSecrets
|
|
||||||
.map(({ id, updatedAt, createdAt, ...el }) => ({
|
|
||||||
...el,
|
|
||||||
secretId: id
|
|
||||||
}))
|
|
||||||
.concat(
|
|
||||||
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({
|
|
||||||
...el,
|
...el,
|
||||||
secretId: id
|
secretId: id
|
||||||
}))
|
}))
|
||||||
),
|
.concat(
|
||||||
tx
|
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({
|
||||||
);
|
...el,
|
||||||
|
secretId: id
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const updatedSecretApproval = await secretApprovalRequestDal.updateById(
|
const updatedSecretApproval = await secretApprovalRequestDal.updateById(
|
||||||
secretApprovalRequest.id,
|
secretApprovalRequest.id,
|
||||||
{
|
{
|
||||||
conflicts,
|
conflicts: JSON.stringify(conflicts),
|
||||||
hasMerged: true,
|
hasMerged: true,
|
||||||
status: "close",
|
status: RequestState.Closed,
|
||||||
statusChangeBy: actorId
|
statusChangeBy: membership.id
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -403,12 +422,23 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
// this will keep a copy to do merge later when accepting
|
// this will keep a copy to do merge later when accepting
|
||||||
const generateSecretApprovalRequest = async ({
|
const generateSecretApprovalRequest = async ({
|
||||||
data,
|
data,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
policy,
|
policy,
|
||||||
projectId,
|
projectId,
|
||||||
secretPath,
|
secretPath,
|
||||||
environment,
|
environment
|
||||||
commiterMembershipId
|
|
||||||
}: TGenerateSecretApprovalRequestDTO) => {
|
}: TGenerateSecretApprovalRequestDTO) => {
|
||||||
|
const { permission, membership } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
|
);
|
||||||
|
|
||||||
const folder = await folderDal.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDal.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
throw new BadRequestError({ message: "Folder not found", name: "GenSecretApproval" });
|
throw new BadRequestError({ message: "Folder not found", name: "GenSecretApproval" });
|
||||||
@@ -449,7 +479,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
...el,
|
...el,
|
||||||
op: CommitType.Create as const,
|
op: CommitType.Create as const,
|
||||||
version: 0,
|
version: 0,
|
||||||
secretBlindIndex: secretBlindIndexes[el.secretName]
|
secretBlindIndex: secretBlindIndexes[el.secretName],
|
||||||
|
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||||
|
keyEncoding: SecretKeyEncoding.BASE64
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -533,7 +565,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secret: secretId,
|
secret: secretId,
|
||||||
secretVersion: latestSecretVersions[secretId].id,
|
secretVersion: latestSecretVersions[secretId].id,
|
||||||
...el,
|
...el,
|
||||||
secretBlindIndex: newSecretBlindIndexes?.[el.secretName],
|
secretBlindIndex:
|
||||||
|
newSecretBlindIndexes?.[el.secretName] || secretBlindIndexes[el.secretName],
|
||||||
version: secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].version || 1
|
version: secretsGroupedByBlindIndex[secretBlindIndexes[el.secretName]].version || 1
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -596,6 +629,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!commits.length) throw new BadRequestError({ message: "Empty commits" });
|
||||||
const secretApprovalRequest = await secretApprovalRequestDal.transaction(async (tx) => {
|
const secretApprovalRequest = await secretApprovalRequestDal.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalRequestDal.create(
|
const doc = await secretApprovalRequestDal.create(
|
||||||
{
|
{
|
||||||
@@ -604,7 +638,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
policyId: policy.id,
|
policyId: policy.id,
|
||||||
status: "open",
|
status: "open",
|
||||||
hasMerged: false,
|
hasMerged: false,
|
||||||
committerId: commiterMembershipId
|
committerId: membership.id
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
+20
-12
@@ -9,7 +9,7 @@ export enum CommitType {
|
|||||||
|
|
||||||
export enum RequestState {
|
export enum RequestState {
|
||||||
Open = "open",
|
Open = "open",
|
||||||
Closed = "closed"
|
Closed = "close"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ApprovalStatus {
|
export enum ApprovalStatus {
|
||||||
@@ -18,28 +18,36 @@ export enum ApprovalStatus {
|
|||||||
REJECTED = "rejected"
|
REJECTED = "rejected"
|
||||||
}
|
}
|
||||||
|
|
||||||
type TApprovalCreateSecret = Omit<TSaRequestSecrets, TImmutableDBKeys | "version"> & {
|
type TApprovalCreateSecret = Omit<
|
||||||
|
TSaRequestSecrets,
|
||||||
|
| TImmutableDBKeys
|
||||||
|
| "version"
|
||||||
|
| "algorithm"
|
||||||
|
| "keyEncoding"
|
||||||
|
| "requestId"
|
||||||
|
| "op"
|
||||||
|
| "secretVersion"
|
||||||
|
| "secretBlindIndex"
|
||||||
|
> & {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
};
|
};
|
||||||
type TApprovalUpdateSecret = Partial<Omit<TSaRequestSecrets, TImmutableDBKeys | "version">> & {
|
type TApprovalUpdateSecret = Partial<TApprovalCreateSecret> & {
|
||||||
secretName: string;
|
secretName: string;
|
||||||
newSecretName?: string;
|
newSecretName?: string;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGenerateSecretApprovalRequestDTO = {
|
export type TGenerateSecretApprovalRequestDTO = {
|
||||||
projectId: string;
|
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
policy: TSecretApprovalPolicies;
|
policy: TSecretApprovalPolicies;
|
||||||
commiterMembershipId: string;
|
|
||||||
data: {
|
data: {
|
||||||
[CommitType.Create]: TApprovalCreateSecret[];
|
[CommitType.Create]?: TApprovalCreateSecret[];
|
||||||
[CommitType.Update]: TApprovalUpdateSecret[];
|
[CommitType.Update]?: TApprovalUpdateSecret[];
|
||||||
[CommitType.Delete]: { secretName: string }[];
|
[CommitType.Delete]?: { secretName: string }[];
|
||||||
};
|
};
|
||||||
};
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TMergeSecretApprovalRequestDTO = {
|
export type TMergeSecretApprovalRequestDTO = {
|
||||||
approvalId: string;
|
approvalId: string;
|
||||||
@@ -47,7 +55,7 @@ export type TMergeSecretApprovalRequestDTO = {
|
|||||||
|
|
||||||
export type TStatusChangeDTO = {
|
export type TStatusChangeDTO = {
|
||||||
approvalId: string;
|
approvalId: string;
|
||||||
status: "open" | "close";
|
status: RequestState;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TReviewRequestDTO = {
|
export type TReviewRequestDTO = {
|
||||||
@@ -67,5 +75,5 @@ export type TListApprovalsDTO = {
|
|||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TSecretApprovalDetailsDTO = {
|
export type TSecretApprovalDetailsDTO = {
|
||||||
id:string;
|
id: string;
|
||||||
} & Omit<TProjectPermission, 'projectId'>
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { SecretRotationsSchema, TableName, TSecretRotations } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TSecretRotationDalFactory = ReturnType<typeof secretRotationDalFactory>;
|
||||||
|
|
||||||
|
export const secretRotationDalFactory = (db: TDbClient) => {
|
||||||
|
const secretRotationOrm = ormify(db, TableName.SecretRotation);
|
||||||
|
const secretRotationOutputOrm = ormify(db, TableName.SecretRotationOutput);
|
||||||
|
|
||||||
|
const findQuery = (filter: TFindFilter<TSecretRotations & { projectId: string }>, tx: Knex) =>
|
||||||
|
tx(TableName.SecretRotation)
|
||||||
|
.where(filter)
|
||||||
|
.join(
|
||||||
|
TableName.Environment,
|
||||||
|
`${TableName.SecretRotation}.envId`,
|
||||||
|
`${TableName.Environment}.id`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretRotationOutput,
|
||||||
|
`${TableName.SecretRotation}.id`,
|
||||||
|
`${TableName.SecretRotationOutput}.rotationId`
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
TableName.Secret,
|
||||||
|
`${TableName.SecretRotationOutput}.secretId`,
|
||||||
|
`${TableName.Secret}.id`
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.SecretRotation))
|
||||||
|
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
||||||
|
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
||||||
|
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
|
||||||
|
.select(tx.ref("projectId").withSchema(TableName.Environment))
|
||||||
|
.select(tx.ref("key").withSchema(TableName.SecretRotationOutput).as("outputKey"))
|
||||||
|
.select(tx.ref("id").withSchema(TableName.Secret).as("secId"))
|
||||||
|
.select(tx.ref("version").withSchema(TableName.Secret).as("secVersion"))
|
||||||
|
.select(tx.ref("secretKeyIV").withSchema(TableName.Secret))
|
||||||
|
.select(tx.ref("secretKeyTag").withSchema(TableName.Secret))
|
||||||
|
.select(tx.ref("secretKeyCiphertext").withSchema(TableName.Secret))
|
||||||
|
.select(tx.ref("secretValueIV").withSchema(TableName.Secret))
|
||||||
|
.select(tx.ref("secretValueTag").withSchema(TableName.Secret))
|
||||||
|
.select(tx.ref("secretValueCiphertext").withSchema(TableName.Secret))
|
||||||
|
.select(tx.ref("secretCommentIV").withSchema(TableName.Secret))
|
||||||
|
.select(tx.ref("secretCommentTag").withSchema(TableName.Secret))
|
||||||
|
.select(tx.ref("secretCommentCiphertext").withSchema(TableName.Secret));
|
||||||
|
|
||||||
|
const find = async (filter: TFindFilter<TSecretRotations & { projectId: string }>, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const data = await findQuery(filter, tx || db);
|
||||||
|
return sqlNestRelationships({
|
||||||
|
data,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => ({
|
||||||
|
...SecretRotationsSchema.parse(el),
|
||||||
|
projectId: el.projectId,
|
||||||
|
environment: { id: el.envId, name: el.envName, slug: el.envSlug }
|
||||||
|
}),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "secId",
|
||||||
|
label: "outputs",
|
||||||
|
mapper: ({
|
||||||
|
secId,
|
||||||
|
outputKey,
|
||||||
|
secVersion,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentCiphertext
|
||||||
|
}) => ({
|
||||||
|
key: outputKey,
|
||||||
|
secret: {
|
||||||
|
id: secId,
|
||||||
|
version: secVersion,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentCiphertext
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "SecretRotationFind" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const doc = await (tx || db)(TableName.SecretRotation)
|
||||||
|
.join(
|
||||||
|
TableName.Environment,
|
||||||
|
`${TableName.SecretRotation}.envId`,
|
||||||
|
`${TableName.Environment}.id`
|
||||||
|
)
|
||||||
|
.where({ [`${TableName.SecretRotation}.id` as "id"]: id })
|
||||||
|
.select(selectAllTableCols(TableName.SecretRotation))
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.Environment).as("envId"),
|
||||||
|
db.ref("projectId").withSchema(TableName.Environment),
|
||||||
|
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||||
|
db.ref("name").withSchema(TableName.Environment).as("envName")
|
||||||
|
)
|
||||||
|
.first();
|
||||||
|
if (doc) {
|
||||||
|
const { envName, envSlug, envId, ...el } = doc;
|
||||||
|
return { ...el, envId, environment: { id: envId, slug: envSlug, name: envName } };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "SecretRotationFindById" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findRotationOutputsByRotationId = async (rotationId: string) =>
|
||||||
|
secretRotationOutputOrm.find({ rotationId });
|
||||||
|
|
||||||
|
return {
|
||||||
|
...secretRotationOrm,
|
||||||
|
find,
|
||||||
|
findById,
|
||||||
|
secretOutputInsertMany: secretRotationOutputOrm.insertMany,
|
||||||
|
findRotationOutputsByRotationId
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export {
|
||||||
|
DisableRotationErrors,
|
||||||
|
secretRotationQueueFactory,
|
||||||
|
TSecretRotationQueueFactory
|
||||||
|
} from "./secret-rotation-queue";
|
||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
import axios from "axios";
|
||||||
|
import jmespath from "jmespath";
|
||||||
|
import knex from "knex";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import {
|
||||||
|
TAssignOp,
|
||||||
|
TDbProviderClients,
|
||||||
|
TDirectAssignOp,
|
||||||
|
THttpProviderFunction
|
||||||
|
} from "../templates/types";
|
||||||
|
import { TSecretRotationData, TSecretRotationDbFn } from "./secret-rotation-queue-types";
|
||||||
|
|
||||||
|
const REGEX = /\${([^}]+)}/g;
|
||||||
|
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
||||||
|
|
||||||
|
export const interpolate = (data: any, getValue: (key: string) => unknown) => {
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
if (typeof data === "number") return data;
|
||||||
|
|
||||||
|
if (typeof data === "string") {
|
||||||
|
return data.replace(REGEX, (_a, b) => getValue(b) as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data === "object" && Array.isArray(data)) {
|
||||||
|
data.forEach((el, index) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
data[index] = interpolate(el, getValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data === "object") {
|
||||||
|
if ((data as { ref: string })?.ref) return getValue((data as { ref: string }).ref);
|
||||||
|
const temp = data as Record<string, unknown>; // for converting ts object to record type
|
||||||
|
Object.keys(temp).forEach((key) => {
|
||||||
|
temp[key as keyof typeof temp] = interpolate(data[key as keyof typeof temp], getValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInterpolationValue = (variables: TSecretRotationData) => (key: string) => {
|
||||||
|
if (key.includes("|")) {
|
||||||
|
const [keyword, ...arg] = key.split("|").map((el) => el.trim());
|
||||||
|
switch (keyword) {
|
||||||
|
case "random": {
|
||||||
|
return alphaNumericNanoId(parseInt(arg[0], 10));
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw Error(`Interpolation key not found - ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const [type, keyName] = key.split(".").map((el) => el.trim());
|
||||||
|
return variables[type as keyof TSecretRotationData][keyName];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const secretRotationHttpFn = async (
|
||||||
|
func: THttpProviderFunction,
|
||||||
|
variables: TSecretRotationData
|
||||||
|
) => {
|
||||||
|
// string interpolation
|
||||||
|
const headers = interpolate(func.header, getInterpolationValue(variables));
|
||||||
|
const url = interpolate(func.url, getInterpolationValue(variables));
|
||||||
|
const body = interpolate(func.body, getInterpolationValue(variables));
|
||||||
|
// axios will automatically throw error if req status is not between 2xx range
|
||||||
|
return axios({
|
||||||
|
method: func.method,
|
||||||
|
url,
|
||||||
|
headers,
|
||||||
|
data: body,
|
||||||
|
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const secretRotationDbFn = async ({
|
||||||
|
ca,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
query,
|
||||||
|
database,
|
||||||
|
password,
|
||||||
|
username,
|
||||||
|
client,
|
||||||
|
variables
|
||||||
|
}: TSecretRotationDbFn) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
|
||||||
|
if (host === "localhost" || host === "127.0.0.1" || appCfg.DB_CONNECTION_URI.includes(host))
|
||||||
|
throw new Error("Invalid db host");
|
||||||
|
|
||||||
|
const db = knex({
|
||||||
|
client,
|
||||||
|
connection: {
|
||||||
|
db: database,
|
||||||
|
port,
|
||||||
|
host,
|
||||||
|
user: username,
|
||||||
|
password,
|
||||||
|
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
|
||||||
|
ssl,
|
||||||
|
pool: { min: 0, max: 1 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await db.raw(query, variables);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const secretRotationPreSetFn = (
|
||||||
|
op: Record<string, TDirectAssignOp>,
|
||||||
|
variables: TSecretRotationData
|
||||||
|
) => {
|
||||||
|
const getValFn = getInterpolationValue(variables);
|
||||||
|
Object.entries(op || {}).forEach(([key, assignFn]) => {
|
||||||
|
const [type, keyName] = key.split(".") as [keyof TSecretRotationData, string];
|
||||||
|
variables[type][keyName] = interpolate(assignFn.value, getValFn);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const secretRotationHttpSetFn = async (
|
||||||
|
func: THttpProviderFunction,
|
||||||
|
variables: TSecretRotationData
|
||||||
|
) => {
|
||||||
|
const getValFn = getInterpolationValue(variables);
|
||||||
|
// http setter
|
||||||
|
const res = await secretRotationHttpFn(func, variables);
|
||||||
|
Object.entries(func.setter || {}).forEach(([key, assignFn]) => {
|
||||||
|
const [type, keyName] = key.split(".") as [keyof TSecretRotationData, string];
|
||||||
|
if (assignFn.assign === TAssignOp.JmesPath) {
|
||||||
|
variables[type][keyName] = jmespath.search(res.data, assignFn.path);
|
||||||
|
} else if (assignFn.value) {
|
||||||
|
variables[type][keyName] = interpolate(assignFn.value, getValFn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDbSetQuery = (
|
||||||
|
db: TDbProviderClients,
|
||||||
|
variable: { username: string; password: string }
|
||||||
|
) => {
|
||||||
|
if (db === TDbProviderClients.Pg) {
|
||||||
|
return {
|
||||||
|
query: "ALTER USER :username WITH PASSWORD :password",
|
||||||
|
variable
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// add more based on client
|
||||||
|
return {
|
||||||
|
query: "ALTER USER :username IDENTIFIED BY :password",
|
||||||
|
variable
|
||||||
|
};
|
||||||
|
};
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
import { TDbProviderClients } from "../templates/types";
|
||||||
|
|
||||||
|
export type TSecretRotationEncData = {
|
||||||
|
inputs: Record<string, unknown>;
|
||||||
|
creds: Array<{
|
||||||
|
outputs: Record<string, unknown>;
|
||||||
|
internal: Record<string, unknown>;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSecretRotationData = {
|
||||||
|
inputs: Record<string, unknown>;
|
||||||
|
outputs: Record<string, unknown>;
|
||||||
|
internal: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSecretRotationDbFn = {
|
||||||
|
client: TDbProviderClients;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
host: string;
|
||||||
|
database: string;
|
||||||
|
port: number;
|
||||||
|
query: string;
|
||||||
|
variables: Record<string, unknown>;
|
||||||
|
ca?: string;
|
||||||
|
};
|
||||||
+245
@@ -0,0 +1,245 @@
|
|||||||
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
encryptSymmetric128BitHexKeyUTF8,
|
||||||
|
infisicalSymmetricDecrypt,
|
||||||
|
infisicalSymmetricEncypt
|
||||||
|
} from "@app/lib/crypto/encryption";
|
||||||
|
import { daysToMillisecond } from "@app/lib/dates";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
|
import { TSecretDalFactory } from "@app/services/secret/secret-dal";
|
||||||
|
import { TSecretVersionDalFactory } from "@app/services/secret/secret-version-dal";
|
||||||
|
|
||||||
|
import { TSecretRotationDalFactory } from "../secret-rotation-dal";
|
||||||
|
import { rotationTemplates } from "../templates";
|
||||||
|
import { TProviderFunctionTypes, TSecretRotationProviderTemplate } from "../templates/types";
|
||||||
|
import {
|
||||||
|
getDbSetQuery,
|
||||||
|
secretRotationDbFn,
|
||||||
|
secretRotationHttpFn,
|
||||||
|
secretRotationHttpSetFn,
|
||||||
|
secretRotationPreSetFn} from "./secret-rotation-queue-fn";
|
||||||
|
import {
|
||||||
|
TSecretRotationData,
|
||||||
|
TSecretRotationDbFn,
|
||||||
|
TSecretRotationEncData
|
||||||
|
} from "./secret-rotation-queue-types";
|
||||||
|
|
||||||
|
export type TSecretRotationQueueFactory = ReturnType<typeof secretRotationQueueFactory>;
|
||||||
|
|
||||||
|
type TSecretRotationQueueFactoryDep = {
|
||||||
|
queue: TQueueServiceFactory;
|
||||||
|
secretRotationDal: TSecretRotationDalFactory;
|
||||||
|
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||||
|
secretDal: Pick<TSecretDalFactory, "bulkUpdate">;
|
||||||
|
secretVersionDal: Pick<TSecretVersionDalFactory, "insertMany">;
|
||||||
|
};
|
||||||
|
|
||||||
|
// These error should stop the repeatable job and ask user to reconfigure rotation
|
||||||
|
export class DisableRotationErrors extends Error {
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
error: unknown;
|
||||||
|
|
||||||
|
constructor({ name, error, message }: { message: string; name?: string; error?: unknown }) {
|
||||||
|
super(message);
|
||||||
|
this.name = name || "DisableRotationErrors";
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const secretRotationQueueFactory = ({
|
||||||
|
queue,
|
||||||
|
secretRotationDal,
|
||||||
|
projectBotService,
|
||||||
|
secretDal,
|
||||||
|
secretVersionDal
|
||||||
|
}: TSecretRotationQueueFactoryDep) => {
|
||||||
|
const addToQueue = async (rotationId: string, interval: number) =>
|
||||||
|
queue.queue(
|
||||||
|
QueueName.SecretRotation,
|
||||||
|
QueueJobs.SecretRotation,
|
||||||
|
{ rotationId },
|
||||||
|
{ jobId: rotationId, repeat: { every: daysToMillisecond(interval), immediately: true } }
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeFromQueue = async (rotationId: string) =>
|
||||||
|
queue.stopRepeatableJob(QueueName.SecretRotation, rotationId);
|
||||||
|
|
||||||
|
queue.start(QueueName.SecretRotation, async (job) => {
|
||||||
|
const { rotationId } = job.data;
|
||||||
|
logger.info(`secretRotationQueue.process: [rotationDocument=${rotationId}]`);
|
||||||
|
const secretRotation = await secretRotationDal.findById(rotationId);
|
||||||
|
const rotationProvider = rotationTemplates.find(
|
||||||
|
({ name }) => name === secretRotation?.provider
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!rotationProvider || !secretRotation)
|
||||||
|
throw new DisableRotationErrors({ message: "Provider not found" });
|
||||||
|
|
||||||
|
const rotationOutputs = await secretRotationDal.findRotationOutputsByRotationId(rotationId);
|
||||||
|
if (!rotationOutputs.length)
|
||||||
|
throw new DisableRotationErrors({ message: "Secrets not found" });
|
||||||
|
|
||||||
|
// deep copy
|
||||||
|
const provider = JSON.parse(
|
||||||
|
JSON.stringify(rotationProvider)
|
||||||
|
) as TSecretRotationProviderTemplate;
|
||||||
|
|
||||||
|
// now get the encrypted variable values
|
||||||
|
// in includes the inputs, the previous outputs
|
||||||
|
// internal mapping variables etc
|
||||||
|
const { encryptedDataTag, encryptedDataIV, encryptedData, keyEncoding } = secretRotation;
|
||||||
|
if (!encryptedDataTag || !encryptedDataIV || !encryptedData || !keyEncoding) {
|
||||||
|
throw new DisableRotationErrors({ message: "No inputs found" });
|
||||||
|
}
|
||||||
|
const decryptedData = infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: encryptedData,
|
||||||
|
iv: encryptedDataIV,
|
||||||
|
tag: encryptedDataTag
|
||||||
|
});
|
||||||
|
|
||||||
|
const variables = JSON.parse(decryptedData) as TSecretRotationEncData;
|
||||||
|
// rotation set cycle
|
||||||
|
const newCredential: TSecretRotationData = {
|
||||||
|
inputs: variables.inputs,
|
||||||
|
outputs: {},
|
||||||
|
internal: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// when its a database we keep cycling the variables accordingly
|
||||||
|
if (provider.template.type === TProviderFunctionTypes.DB) {
|
||||||
|
const lastCred = variables.creds.at(-1);
|
||||||
|
if (lastCred && variables.creds.length === 1) {
|
||||||
|
newCredential.internal.username =
|
||||||
|
lastCred.internal.username === variables.inputs.username1
|
||||||
|
? variables.inputs.username2
|
||||||
|
: variables.inputs.username1;
|
||||||
|
} else {
|
||||||
|
newCredential.internal.username = lastCred
|
||||||
|
? lastCred.internal.username
|
||||||
|
: variables.inputs.username1;
|
||||||
|
}
|
||||||
|
// set a random value for new password
|
||||||
|
newCredential.internal.rotated_password = alphaNumericNanoId(32);
|
||||||
|
const { username, password, host, database, port, ca } = newCredential.inputs;
|
||||||
|
const dbFunctionArg = {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
host,
|
||||||
|
database,
|
||||||
|
port,
|
||||||
|
ca: ca as string,
|
||||||
|
client: provider.template.client
|
||||||
|
} as TSecretRotationDbFn;
|
||||||
|
// set function
|
||||||
|
await secretRotationDbFn({
|
||||||
|
...dbFunctionArg,
|
||||||
|
...getDbSetQuery(provider.template.client, {
|
||||||
|
password: newCredential.internal.rotated_password as string,
|
||||||
|
username: newCredential.internal.username as string
|
||||||
|
})
|
||||||
|
});
|
||||||
|
// test function
|
||||||
|
await secretRotationDbFn({
|
||||||
|
...dbFunctionArg,
|
||||||
|
query: "SELECT NOW()",
|
||||||
|
variables: {}
|
||||||
|
});
|
||||||
|
// clean up
|
||||||
|
if (variables.creds.length === 2) variables.creds.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.template.type === TProviderFunctionTypes.HTTP) {
|
||||||
|
if (provider.template.functions.set?.pre) {
|
||||||
|
secretRotationPreSetFn(provider.template.functions.set.pre, newCredential);
|
||||||
|
}
|
||||||
|
await secretRotationHttpSetFn(provider.template.functions.set, newCredential);
|
||||||
|
// now test
|
||||||
|
await secretRotationHttpFn(provider.template.functions.test, newCredential);
|
||||||
|
if (variables.creds.length === 2) {
|
||||||
|
const deleteCycleCred = variables.creds.pop();
|
||||||
|
if (deleteCycleCred && provider.template.functions.remove) {
|
||||||
|
const deleteCycleVar = { inputs: variables.inputs, ...deleteCycleCred };
|
||||||
|
if (provider.template.type === TProviderFunctionTypes.HTTP) {
|
||||||
|
await secretRotationHttpFn(provider.template.functions.remove, deleteCycleVar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variables.creds.unshift({
|
||||||
|
outputs: newCredential.outputs,
|
||||||
|
internal: newCredential.internal
|
||||||
|
});
|
||||||
|
const encVarData = infisicalSymmetricEncypt(JSON.stringify(variables));
|
||||||
|
const key = await projectBotService.getBotKey(secretRotation.projectId);
|
||||||
|
const encryptedSecrets = rotationOutputs.map(({ key: outputKey, secretId }) => ({
|
||||||
|
secretId,
|
||||||
|
value: encryptSymmetric128BitHexKeyUTF8(
|
||||||
|
typeof newCredential.outputs[outputKey] === "object"
|
||||||
|
? JSON.stringify(newCredential.outputs[outputKey])
|
||||||
|
: String(newCredential.outputs[outputKey]),
|
||||||
|
key
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
await secretRotationDal.transaction(async (tx) => {
|
||||||
|
await secretRotationDal.updateById(
|
||||||
|
rotationId,
|
||||||
|
{
|
||||||
|
encryptedData: encVarData.ciphertext,
|
||||||
|
encryptedDataIV: encVarData.iv,
|
||||||
|
encryptedDataTag: encVarData.tag,
|
||||||
|
keyEncoding: encVarData.encoding,
|
||||||
|
algorithm: encVarData.algorithm,
|
||||||
|
lastRotatedAt: new Date(),
|
||||||
|
statusMessage: "Rotated successfull",
|
||||||
|
status: "success"
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
const updatedSecrets = await secretDal.bulkUpdate(
|
||||||
|
encryptedSecrets.map(({ secretId, value }) => ({
|
||||||
|
id: secretId,
|
||||||
|
secretValueCiphertext: value.ciphertext,
|
||||||
|
secretValueIV: value.iv,
|
||||||
|
secretValueTag: value.tag
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await secretDal.bulkUpdate(
|
||||||
|
updatedSecrets.map(({ id, version }) => ({ id, version: (version || 0) + 1 })),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await secretVersionDal.insertMany(
|
||||||
|
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({
|
||||||
|
...el,
|
||||||
|
secretId: id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DisableRotationErrors) {
|
||||||
|
if (job.id) {
|
||||||
|
queue.stopRepeatableJob(QueueName.SecretRotation, job.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await secretRotationDal.updateById(rotationId, {
|
||||||
|
status: "failed",
|
||||||
|
statusMessage: (error as Error).message.slice(0, 500),
|
||||||
|
lastRotatedAt: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
addToQueue,
|
||||||
|
removeFromQueue
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import Ajv from "ajv";
|
||||||
|
|
||||||
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
import { TProjectEnvDalFactory } from "@app/services/project-env/project-env-dal";
|
||||||
|
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
|
import { TSecretRotationDalFactory } from "./secret-rotation-dal";
|
||||||
|
import { TSecretRotationQueueFactory } from "./secret-rotation-queue";
|
||||||
|
import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types";
|
||||||
|
import {
|
||||||
|
TCreateSecretRotationDTO,
|
||||||
|
TDeleteDTO,
|
||||||
|
TGetByIdDTO,
|
||||||
|
TListByProjectIdDTO,
|
||||||
|
TRestartDTO
|
||||||
|
} from "./secret-rotation-types";
|
||||||
|
import { rotationTemplates } from "./templates";
|
||||||
|
|
||||||
|
type TSecretRotationServiceFactoryDep = {
|
||||||
|
secretRotationDal: TSecretRotationDalFactory;
|
||||||
|
projectEnvDal: Pick<TProjectEnvDalFactory, "findOne">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
secretRotationQueue: TSecretRotationQueueFactory;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSecretRotationServiceFactory = ReturnType<typeof secretRotationServiceFactory>;
|
||||||
|
|
||||||
|
const ajv = new Ajv({ strict: false });
|
||||||
|
export const secretRotationServiceFactory = ({
|
||||||
|
secretRotationDal,
|
||||||
|
permissionService,
|
||||||
|
projectEnvDal,
|
||||||
|
secretRotationQueue
|
||||||
|
}: TSecretRotationServiceFactoryDep) => {
|
||||||
|
const getProviderTemplates = async ({ actor, actorId, projectId }: TProjectPermission) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionSub.SecretRotation
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
custom: [],
|
||||||
|
providers: rotationTemplates
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createRotation = async ({
|
||||||
|
projectId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
interval,
|
||||||
|
provider,
|
||||||
|
secretPath,
|
||||||
|
environment
|
||||||
|
}: TCreateSecretRotationDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionSub.SecretRotation
|
||||||
|
);
|
||||||
|
const env = await projectEnvDal.findOne({ slug: environment });
|
||||||
|
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
||||||
|
|
||||||
|
const selectedTemplate = rotationTemplates.find(({ name }) => name === provider);
|
||||||
|
if (!selectedTemplate) throw new BadRequestError({ message: "Provider not found" });
|
||||||
|
const formattedInputs: Record<string, unknown> = {};
|
||||||
|
Object.entries(inputs).forEach(([key, value]) => {
|
||||||
|
const { type } = selectedTemplate.template.inputs.properties[key];
|
||||||
|
if (type === "string") {
|
||||||
|
formattedInputs[key] = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type === "integer") {
|
||||||
|
formattedInputs[key] = parseInt(value as string, 10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
formattedInputs[key] = JSON.parse(value as string);
|
||||||
|
});
|
||||||
|
// ensure input one follows the correct schema
|
||||||
|
const valid = ajv.validate(selectedTemplate.template.inputs, formattedInputs);
|
||||||
|
if (!valid) {
|
||||||
|
throw new BadRequestError({ message: ajv.errors?.[0].message });
|
||||||
|
}
|
||||||
|
|
||||||
|
const unencryptedData: Partial<TSecretRotationEncData> = {
|
||||||
|
inputs: formattedInputs,
|
||||||
|
creds: []
|
||||||
|
};
|
||||||
|
const encData = infisicalSymmetricEncypt(JSON.stringify(unencryptedData));
|
||||||
|
const secretRotation = secretRotationDal.transaction(async (tx) => {
|
||||||
|
const doc = await secretRotationDal.create(
|
||||||
|
{
|
||||||
|
provider,
|
||||||
|
secretPath,
|
||||||
|
interval,
|
||||||
|
envId: env.id,
|
||||||
|
encryptedDataTag: encData.tag,
|
||||||
|
encryptedDataIV: encData.iv,
|
||||||
|
encryptedData: encData.ciphertext,
|
||||||
|
algorithm: encData.algorithm,
|
||||||
|
keyEncoding: encData.encoding
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await secretRotationQueue.addToQueue(doc.id, doc.interval);
|
||||||
|
const outputSecretMapping = await secretRotationDal.secretOutputInsertMany(
|
||||||
|
Object.entries(outputs).map(([key, secretId]) => ({ key, secretId, rotationId: doc.id })),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return { ...doc, outputs: outputSecretMapping, environment: env };
|
||||||
|
});
|
||||||
|
return secretRotation;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getById = async ({ rotationId, actor, actorId }: TGetByIdDTO) => {
|
||||||
|
const [doc] = await secretRotationDal.find({ id: rotationId });
|
||||||
|
if (!doc) throw new BadRequestError({ message: "Rotation not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
doc.projectId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionSub.SecretRotation
|
||||||
|
);
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getByProjectId = async ({ actorId, projectId, actor }: TListByProjectIdDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionSub.SecretRotation
|
||||||
|
);
|
||||||
|
const doc = await secretRotationDal.find({ projectId });
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
|
|
||||||
|
const restartById = async ({ actor, actorId, rotationId }: TRestartDTO) => {
|
||||||
|
const doc = await secretRotationDal.findById(rotationId);
|
||||||
|
if (!doc) throw new BadRequestError({ message: "Rotation not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
doc.projectId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionSub.SecretRotation
|
||||||
|
);
|
||||||
|
await secretRotationQueue.removeFromQueue(doc.id);
|
||||||
|
await secretRotationQueue.addToQueue(doc.id, doc.interval);
|
||||||
|
return doc;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteById = async ({ actor, actorId, rotationId }: TDeleteDTO) => {
|
||||||
|
const doc = await secretRotationDal.findById(rotationId);
|
||||||
|
if (!doc) throw new BadRequestError({ message: "Rotation not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
doc.projectId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Delete,
|
||||||
|
ProjectPermissionSub.SecretRotation
|
||||||
|
);
|
||||||
|
const deletedDoc = await secretRotationDal.transaction(async (tx) => {
|
||||||
|
const strat = await secretRotationDal.deleteById(rotationId, tx);
|
||||||
|
await secretRotationQueue.removeFromQueue(strat.id);
|
||||||
|
return strat;
|
||||||
|
});
|
||||||
|
return { ...doc, ...deletedDoc };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getProviderTemplates,
|
||||||
|
getById,
|
||||||
|
getByProjectId,
|
||||||
|
createRotation,
|
||||||
|
restartById,
|
||||||
|
deleteById
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type TCreateSecretRotationDTO = {
|
||||||
|
secretPath: string;
|
||||||
|
environment: string;
|
||||||
|
interval: number;
|
||||||
|
provider: string;
|
||||||
|
inputs: Record<string, unknown>;
|
||||||
|
outputs: Record<string, string>;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TListByProjectIdDTO = TProjectPermission;
|
||||||
|
|
||||||
|
export type TDeleteDTO = {
|
||||||
|
rotationId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TRestartDTO = {
|
||||||
|
rotationId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetByIdDTO = {
|
||||||
|
rotationId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { MYSQL_TEMPLATE } from "./mysql";
|
||||||
|
import { POSTGRES_TEMPLATE } from "./postgres";
|
||||||
|
import { SENDGRID_TEMPLATE } from "./sendgrid";
|
||||||
|
import { TSecretRotationProviderTemplate } from "./types";
|
||||||
|
|
||||||
|
export const rotationTemplates: TSecretRotationProviderTemplate[] = [
|
||||||
|
{
|
||||||
|
name: "sendgrid",
|
||||||
|
title: "Twilio Sendgrid",
|
||||||
|
image: "sendgrid.png",
|
||||||
|
description: "Rotate Twilio Sendgrid API keys",
|
||||||
|
template: SENDGRID_TEMPLATE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "postgres",
|
||||||
|
title: "PostgreSQL",
|
||||||
|
image: "postgres.png",
|
||||||
|
description: "Rotate PostgreSQL/CockroachDB user credentials",
|
||||||
|
template: POSTGRES_TEMPLATE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mysql",
|
||||||
|
title: "MySQL",
|
||||||
|
image: "mysql.png",
|
||||||
|
description: "Rotate MySQL@7/MariaDB user credentials",
|
||||||
|
template: MYSQL_TEMPLATE
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { TDbProviderClients, TProviderFunctionTypes } from "./types";
|
||||||
|
|
||||||
|
export const MYSQL_TEMPLATE = {
|
||||||
|
type: TProviderFunctionTypes.DB as const,
|
||||||
|
client: TDbProviderClients.MySql,
|
||||||
|
inputs: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
admin_username: { type: "string" as const },
|
||||||
|
admin_password: { type: "string" as const },
|
||||||
|
host: { type: "string" as const },
|
||||||
|
database: { type: "string" as const },
|
||||||
|
port: { type: "integer" as const, default: "3306" },
|
||||||
|
username1: {
|
||||||
|
type: "string",
|
||||||
|
default: "infisical-sql-user1",
|
||||||
|
desc: "This user must be created in your database"
|
||||||
|
},
|
||||||
|
username2: {
|
||||||
|
type: "string",
|
||||||
|
default: "infisical-sql-user2",
|
||||||
|
desc: "This user must be created in your database"
|
||||||
|
},
|
||||||
|
ca: { type: "string", desc: "SSL certificate for db auth(string)" }
|
||||||
|
},
|
||||||
|
required: [
|
||||||
|
"admin_username",
|
||||||
|
"admin_password",
|
||||||
|
"host",
|
||||||
|
"database",
|
||||||
|
"username1",
|
||||||
|
"username2",
|
||||||
|
"port"
|
||||||
|
],
|
||||||
|
additionalProperties: false
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
db_username: { type: "string" },
|
||||||
|
db_password: { type: "string" }
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { TDbProviderClients, TProviderFunctionTypes } from "./types";
|
||||||
|
|
||||||
|
export const POSTGRES_TEMPLATE = {
|
||||||
|
type: TProviderFunctionTypes.DB as const,
|
||||||
|
client: TDbProviderClients.Pg as const,
|
||||||
|
inputs: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
admin_username: { type: "string" as const },
|
||||||
|
admin_password: { type: "string" as const },
|
||||||
|
host: { type: "string" as const },
|
||||||
|
database: { type: "string" as const },
|
||||||
|
port: { type: "integer" as const, default: "5432" },
|
||||||
|
username1: {
|
||||||
|
type: "string",
|
||||||
|
default: "infisical-pg-user1",
|
||||||
|
desc: "This user must be created in your database"
|
||||||
|
},
|
||||||
|
username2: {
|
||||||
|
type: "string",
|
||||||
|
default: "infisical-pg-user2",
|
||||||
|
desc: "This user must be created in your database"
|
||||||
|
},
|
||||||
|
ca: { type: "string", desc: "SSL certificate for db auth(string)" }
|
||||||
|
},
|
||||||
|
required: [
|
||||||
|
"admin_username",
|
||||||
|
"admin_password",
|
||||||
|
"host",
|
||||||
|
"database",
|
||||||
|
"username1",
|
||||||
|
"username2",
|
||||||
|
"port"
|
||||||
|
],
|
||||||
|
additionalProperties: false
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
db_username: { type: "string" },
|
||||||
|
db_password: { type: "string" }
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/* eslint-disable no-template-curly-in-string */
|
||||||
|
import { TAssignOp, TProviderFunctionTypes } from "./types";
|
||||||
|
|
||||||
|
export const SENDGRID_TEMPLATE = {
|
||||||
|
type: TProviderFunctionTypes.HTTP as const,
|
||||||
|
inputs: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
admin_api_key: { type: "string" as const, desc: "Sendgrid admin api key to create new keys" },
|
||||||
|
api_key_scopes: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" as const },
|
||||||
|
desc: "Scopes for created tokens by rotation(Array)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
required: ["admin_api_key", "api_key_scopes"],
|
||||||
|
additionalProperties: false
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
api_key: { type: "string" }
|
||||||
|
},
|
||||||
|
internal: {
|
||||||
|
api_key_id: { type: "string" }
|
||||||
|
},
|
||||||
|
functions: {
|
||||||
|
set: {
|
||||||
|
url: "https://api.sendgrid.com/v3/api_keys",
|
||||||
|
method: "POST",
|
||||||
|
header: {
|
||||||
|
Authorization: "Bearer ${inputs.admin_api_key}"
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
name: "infisical-${random | 16}",
|
||||||
|
scopes: { ref: "inputs.api_key_scopes" }
|
||||||
|
},
|
||||||
|
setter: {
|
||||||
|
"outputs.api_key": {
|
||||||
|
assign: TAssignOp.JmesPath as const,
|
||||||
|
path: "api_key"
|
||||||
|
},
|
||||||
|
"internal.api_key_id": {
|
||||||
|
assign: TAssignOp.JmesPath as const,
|
||||||
|
path: "api_key_id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}",
|
||||||
|
header: {
|
||||||
|
Authorization: "Bearer ${inputs.admin_api_key}"
|
||||||
|
},
|
||||||
|
method: "DELETE"
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}",
|
||||||
|
header: {
|
||||||
|
Authorization: "Bearer ${inputs.admin_api_key}"
|
||||||
|
},
|
||||||
|
method: "GET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
export enum TProviderFunctionTypes {
|
||||||
|
HTTP = "http",
|
||||||
|
DB = "database"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TDbProviderClients {
|
||||||
|
// postgres, cockroack db, amazon red shift
|
||||||
|
Pg = "pg",
|
||||||
|
// mysql and maria db
|
||||||
|
MySql = "mysql"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TAssignOp {
|
||||||
|
Direct = "direct",
|
||||||
|
JmesPath = "jmesopath"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TJmesPathAssignOp = {
|
||||||
|
assign: TAssignOp.JmesPath;
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDirectAssignOp = {
|
||||||
|
assign: TAssignOp.Direct;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAssignFunction = TJmesPathAssignOp | TDirectAssignOp;
|
||||||
|
|
||||||
|
export type THttpProviderFunction = {
|
||||||
|
url: string;
|
||||||
|
method: string;
|
||||||
|
header?: Record<string, string>;
|
||||||
|
query?: Record<string, string>;
|
||||||
|
body?: Record<string, unknown>;
|
||||||
|
setter?: Record<string, TAssignFunction>;
|
||||||
|
pre?: Record<string, TDirectAssignOp>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TSecretRotationProviderTemplate = {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
image?: string;
|
||||||
|
description?: string;
|
||||||
|
template: THttpProviderTemplate | TDbProviderTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type THttpProviderTemplate = {
|
||||||
|
type: TProviderFunctionTypes.HTTP;
|
||||||
|
inputs: {
|
||||||
|
type: "object";
|
||||||
|
properties: Record<string, { type: string; [x: string]: unknown; desc?: string }>;
|
||||||
|
required?: string[];
|
||||||
|
};
|
||||||
|
outputs: Record<string, unknown>;
|
||||||
|
functions: {
|
||||||
|
set: THttpProviderFunction;
|
||||||
|
remove?: THttpProviderFunction;
|
||||||
|
test: THttpProviderFunction;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDbProviderTemplate = {
|
||||||
|
type: TProviderFunctionTypes.DB;
|
||||||
|
client: TDbProviderClients;
|
||||||
|
inputs: {
|
||||||
|
type: "object";
|
||||||
|
properties: Record<string, { type: string; [x: string]: unknown; desc?: string }>;
|
||||||
|
required?: string[];
|
||||||
|
};
|
||||||
|
outputs: Record<string, unknown>;
|
||||||
|
};
|
||||||
@@ -13,6 +13,7 @@ const zodStrBool = z
|
|||||||
const envSchema = z
|
const envSchema = z
|
||||||
.object({
|
.object({
|
||||||
PORT: z.coerce.number().default(4000),
|
PORT: z.coerce.number().default(4000),
|
||||||
|
REDIS_URL: zpStr(z.string()),
|
||||||
HOST: zpStr(z.string().default("localhost")),
|
HOST: zpStr(z.string().default("localhost")),
|
||||||
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database conntection string")),
|
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database conntection string")),
|
||||||
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
|
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import naclUtils from "tweetnacl-util";
|
|||||||
|
|
||||||
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
|
||||||
|
import { getConfig } from "../config/env";
|
||||||
|
|
||||||
export type TDecryptSymmetricInput = {
|
export type TDecryptSymmetricInput = {
|
||||||
ciphertext: string;
|
ciphertext: string;
|
||||||
iv: string;
|
iv: string;
|
||||||
@@ -191,3 +193,52 @@ export const createSecretBlindIndex = (rootEncryptionKey?: string, encryptionKey
|
|||||||
}
|
}
|
||||||
throw new Error("Failed to generate blind index due to encryption key missing");
|
throw new Error("Failed to generate blind index due to encryption key missing");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const infisicalSymmetricEncypt = (data: string) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY;
|
||||||
|
const encryptionKey = appCfg.ENCRYPTION_KEY;
|
||||||
|
if (rootEncryptionKey) {
|
||||||
|
const { iv, tag, ciphertext } = encryptSymmetric(data, rootEncryptionKey);
|
||||||
|
return {
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
ciphertext,
|
||||||
|
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||||
|
encoding: SecretKeyEncoding.BASE64
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (encryptionKey) {
|
||||||
|
const { iv, tag, ciphertext } = encryptSymmetric128BitHexKeyUTF8(data, encryptionKey);
|
||||||
|
return {
|
||||||
|
iv,
|
||||||
|
tag,
|
||||||
|
ciphertext,
|
||||||
|
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||||
|
encoding: SecretKeyEncoding.UTF8
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error("Missing both encryption keys");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const infisicalSymmetricDecrypt = <T extends any = string>({
|
||||||
|
keyEncoding,
|
||||||
|
ciphertext,
|
||||||
|
tag,
|
||||||
|
iv
|
||||||
|
}: Omit<TDecryptSymmetricInput, "key"> & {
|
||||||
|
keyEncoding: SecretKeyEncoding;
|
||||||
|
}) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY;
|
||||||
|
const encryptionKey = appCfg.ENCRYPTION_KEY;
|
||||||
|
if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) {
|
||||||
|
const data = decryptSymmetric({ key: rootEncryptionKey, iv, tag, ciphertext });
|
||||||
|
return data as T;
|
||||||
|
}
|
||||||
|
if (encryptionKey && keyEncoding === SecretKeyEncoding.UTF8) {
|
||||||
|
const data = decryptSymmetric128BitHexKeyUTF8({ key: encryptionKey, iv, tag, ciphertext });
|
||||||
|
return data as T;
|
||||||
|
}
|
||||||
|
throw new Error("Missing both encryption keys");
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const daysToMillisecond = (days: number) => days * 24 * 60 * 60 * 1000;
|
||||||
@@ -17,7 +17,7 @@ export class UnauthorizedError extends Error {
|
|||||||
error: unknown;
|
error: unknown;
|
||||||
|
|
||||||
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown } = {}) {
|
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown } = {}) {
|
||||||
super(message ?? "You are not allowed to access this resourve");
|
super(message ?? "You are not allowed to access this resource");
|
||||||
this.name = name || "UnauthorizedError";
|
this.name = name || "UnauthorizedError";
|
||||||
this.error = error;
|
this.error = error;
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ export class ForbiddenRequestError extends Error {
|
|||||||
error: unknown;
|
error: unknown;
|
||||||
|
|
||||||
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) {
|
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) {
|
||||||
super(message ?? "You are not allowed to access this resourve");
|
super(message ?? "You are not allowed to access this resource");
|
||||||
this.name = name || "ForbideenError";
|
this.name = name || "ForbideenError";
|
||||||
this.error = error;
|
this.error = error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export type TChildMapper<T extends {}, U extends string = string, R extends unkn
|
|||||||
};
|
};
|
||||||
|
|
||||||
type MappedRecord<T extends TChildMapper<any>> = {
|
type MappedRecord<T extends TChildMapper<any>> = {
|
||||||
[K in T["label"]]: ReturnType<Extract<T, { label: K }>["mapper"]>[];
|
[K in T["label"]]: Exclude<ReturnType<Extract<T, { label: K }>["mapper"]>, null | undefined>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sqlNestRelationships = <
|
export const sqlNestRelationships = <
|
||||||
|
|||||||
@@ -2,3 +2,13 @@ import { Tables } from "knex/types/tables";
|
|||||||
|
|
||||||
export const selectAllTableCols = <Tname extends keyof Tables>(tableName: Tname) =>
|
export const selectAllTableCols = <Tname extends keyof Tables>(tableName: Tname) =>
|
||||||
`${tableName}.*` as keyof Tables[Tname]["base"];
|
`${tableName}.*` as keyof Tables[Tname]["base"];
|
||||||
|
|
||||||
|
export const stripUndefinedInWhere = <T extends object>(val: T) => {
|
||||||
|
const copy = val;
|
||||||
|
Object.entries(copy).forEach(([key, value]) => {
|
||||||
|
if (typeof value === "undefined") {
|
||||||
|
delete copy[key as keyof T];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return copy;
|
||||||
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import dotenv from "dotenv";
|
|||||||
import { initDbConnection } from "./db";
|
import { initDbConnection } from "./db";
|
||||||
import { formatSmtpConfig, initEnvConfig } from "./lib/config/env";
|
import { formatSmtpConfig, initEnvConfig } from "./lib/config/env";
|
||||||
import { initLogger } from "./lib/logger";
|
import { initLogger } from "./lib/logger";
|
||||||
|
import { queueServiceFactory } from "./queue";
|
||||||
import { main } from "./server/app";
|
import { main } from "./server/app";
|
||||||
import { smtpServiceFactory } from "./services/smtp/smtp-service";
|
import { smtpServiceFactory } from "./services/smtp/smtp-service";
|
||||||
|
|
||||||
@@ -12,8 +13,9 @@ const run = async () => {
|
|||||||
const appCfg = initEnvConfig(logger);
|
const appCfg = initEnvConfig(logger);
|
||||||
const db = initDbConnection(appCfg.DB_CONNECTION_URI);
|
const db = initDbConnection(appCfg.DB_CONNECTION_URI);
|
||||||
const smtp = smtpServiceFactory(formatSmtpConfig());
|
const smtp = smtpServiceFactory(formatSmtpConfig());
|
||||||
|
const queue = queueServiceFactory(appCfg.REDIS_URL);
|
||||||
|
|
||||||
const server = await main({ db, smtp, logger });
|
const server = await main({ db, smtp, logger, queue });
|
||||||
process.on("SIGINT", async () => {
|
process.on("SIGINT", async () => {
|
||||||
await server.close();
|
await server.close();
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export {
|
||||||
|
QueueJobs,
|
||||||
|
QueueName,
|
||||||
|
queueServiceFactory,
|
||||||
|
TQueueJobTypes,
|
||||||
|
TQueueServiceFactory
|
||||||
|
} from "./queue-service";
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { Job, JobsOptions, Queue, Worker, WorkerListener } from "bullmq";
|
||||||
|
import Redis from "ioredis";
|
||||||
|
|
||||||
|
export enum QueueName {
|
||||||
|
SecretRotation = "secret-rotation"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum QueueJobs {
|
||||||
|
SecretRotation = "secret-rotation-job"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TQueueJobTypes = {
|
||||||
|
[QueueName.SecretRotation]: {
|
||||||
|
payload: { rotationId: string };
|
||||||
|
name: QueueJobs.SecretRotation;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
||||||
|
export const queueServiceFactory = (redisUrl: string) => {
|
||||||
|
const connection = new Redis(redisUrl);
|
||||||
|
const queueContainer: Record<
|
||||||
|
QueueName,
|
||||||
|
Queue<TQueueJobTypes[QueueName]["payload"], void, TQueueJobTypes[QueueName]["name"]>
|
||||||
|
> = {} as any;
|
||||||
|
const workerContainer: Record<
|
||||||
|
QueueName,
|
||||||
|
Worker<TQueueJobTypes[QueueName]["payload"], void, TQueueJobTypes[QueueName]["name"]>
|
||||||
|
> = {} as any;
|
||||||
|
|
||||||
|
const start = <T extends QueueName>(
|
||||||
|
name: T,
|
||||||
|
jobFn: (
|
||||||
|
job: Job<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>
|
||||||
|
) => Promise<void>
|
||||||
|
) => {
|
||||||
|
if (queueContainer[name]) {
|
||||||
|
throw new Error(`${name} queue is already initialized`);
|
||||||
|
}
|
||||||
|
|
||||||
|
queueContainer[name] = new Queue<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>(
|
||||||
|
name as string,
|
||||||
|
{ connection }
|
||||||
|
);
|
||||||
|
workerContainer[name] = new Worker<
|
||||||
|
TQueueJobTypes[T]["payload"],
|
||||||
|
void,
|
||||||
|
TQueueJobTypes[T]["name"]
|
||||||
|
>(name, jobFn);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listen = async <
|
||||||
|
T extends QueueName,
|
||||||
|
U extends keyof WorkerListener<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>
|
||||||
|
>(
|
||||||
|
name: T,
|
||||||
|
event: U,
|
||||||
|
listener: WorkerListener<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>[U]
|
||||||
|
) => {
|
||||||
|
const worker = workerContainer[name];
|
||||||
|
worker.on(event, listener);
|
||||||
|
};
|
||||||
|
|
||||||
|
const queue = async <T extends QueueName>(
|
||||||
|
name: T,
|
||||||
|
job: TQueueJobTypes[T]["name"],
|
||||||
|
data: TQueueJobTypes[T]["payload"],
|
||||||
|
opts: JobsOptions & { jobId: string }
|
||||||
|
) => {
|
||||||
|
const q = queueContainer[name];
|
||||||
|
await q.add(job, data, opts);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopRepeatableJob = async <T extends QueueName>(name: T, jobId: string) => {
|
||||||
|
const q = queueContainer[name];
|
||||||
|
const job = await q.getJob(jobId);
|
||||||
|
if (!job) return true;
|
||||||
|
if (!job.repeatJobKey) return true;
|
||||||
|
return q.removeRepeatableByKey(job.repeatJobKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
const shutdown = async () => {
|
||||||
|
await Promise.all(Object.values(workerContainer).map((worker) => worker.close()));
|
||||||
|
};
|
||||||
|
|
||||||
|
return { start, listen, queue, shutdown, stopRepeatableJob };
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ import fasitfy from "fastify";
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import { Logger } from "pino";
|
import { Logger } from "pino";
|
||||||
|
|
||||||
|
import { TQueueServiceFactory } from "@app/queue";
|
||||||
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
|
|
||||||
import { getConfig } from "@lib/config/env";
|
import { getConfig } from "@lib/config/env";
|
||||||
@@ -23,10 +24,11 @@ type TMain = {
|
|||||||
db: Knex;
|
db: Knex;
|
||||||
smtp: TSmtpService;
|
smtp: TSmtpService;
|
||||||
logger?: Logger;
|
logger?: Logger;
|
||||||
|
queue: TQueueServiceFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run the server!
|
// Run the server!
|
||||||
export const main = async ({ db, smtp, logger }: TMain) => {
|
export const main = async ({ db, smtp, logger, queue }: TMain) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const server = fasitfy({
|
const server = fasitfy({
|
||||||
logger,
|
logger,
|
||||||
@@ -54,12 +56,13 @@ export const main = async ({ db, smtp, logger }: TMain) => {
|
|||||||
await server.register<FastifyRateLimitOptions>(ratelimiter, globalRateLimiterCfg);
|
await server.register<FastifyRateLimitOptions>(ratelimiter, globalRateLimiterCfg);
|
||||||
await server.register(helmet, { contentSecurityPolicy: false });
|
await server.register(helmet, { contentSecurityPolicy: false });
|
||||||
|
|
||||||
await server.register(registerRoutes, { prefix: "/api", smtp, db });
|
await server.register(registerRoutes, { prefix: "/api", smtp, queue, db });
|
||||||
await server.ready();
|
await server.ready();
|
||||||
server.swagger();
|
server.swagger();
|
||||||
return server;
|
return server;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
server.log.error(err);
|
server.log.error(err);
|
||||||
|
await queue.shutdown();
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,18 @@ import { z } from "zod";
|
|||||||
import { registerV1EERoutes } from "@app/ee/routes/v1";
|
import { registerV1EERoutes } from "@app/ee/routes/v1";
|
||||||
import { permissionDalFactory } from "@app/ee/services/permission/permission-dal";
|
import { permissionDalFactory } from "@app/ee/services/permission/permission-dal";
|
||||||
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { permissionServiceFactory } from "@app/ee/services/permission/permission-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";
|
||||||
|
import { sarReviewerDalFactory } from "@app/ee/services/secret-approval-request/sar-reviewer-dal";
|
||||||
|
import { sarSecretDalFactory } from "@app/ee/services/secret-approval-request/sar-secret-dal";
|
||||||
|
import { secretApprovalRequestDalFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||||
|
import { secretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service";
|
||||||
|
import { secretRotationDalFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal";
|
||||||
|
import { secretRotationQueueFactory } from "@app/ee/services/secret-rotation/secret-rotation-queue";
|
||||||
|
import { secretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { TQueueServiceFactory } from "@app/queue";
|
||||||
import { apiKeyDalFactory } from "@app/services/api-key/api-key-dal";
|
import { apiKeyDalFactory } from "@app/services/api-key/api-key-dal";
|
||||||
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||||
import { authDalFactory } from "@app/services/auth/auth-dal";
|
import { authDalFactory } from "@app/services/auth/auth-dal";
|
||||||
@@ -69,13 +80,14 @@ import { injectPermission } from "../plugins/auth/inject-permission";
|
|||||||
import { registerV1Routes } from "./v1";
|
import { registerV1Routes } from "./v1";
|
||||||
import { registerV2Routes } from "./v2";
|
import { registerV2Routes } from "./v2";
|
||||||
import { registerV3Routes } from "./v3";
|
import { registerV3Routes } from "./v3";
|
||||||
import { secretApprovalPolicyDalFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
|
|
||||||
import { sapApproverDalFactory } from "@app/ee/services/secret-approval-policy/sap-approver-dal";
|
|
||||||
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
|
||||||
|
|
||||||
export const registerRoutes = async (
|
export const registerRoutes = async (
|
||||||
server: FastifyZodProvider,
|
server: FastifyZodProvider,
|
||||||
{ db, smtp: smtpService }: { db: Knex; smtp: TSmtpService }
|
{
|
||||||
|
db,
|
||||||
|
smtp: smtpService,
|
||||||
|
queue: queueService
|
||||||
|
}: { db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory }
|
||||||
) => {
|
) => {
|
||||||
// db layers
|
// db layers
|
||||||
const userDal = userDalFactory(db);
|
const userDal = userDalFactory(db);
|
||||||
@@ -118,8 +130,12 @@ export const registerRoutes = async (
|
|||||||
const permissionDal = permissionDalFactory(db);
|
const permissionDal = permissionDalFactory(db);
|
||||||
const sapApproverDal = sapApproverDalFactory(db);
|
const sapApproverDal = sapApproverDalFactory(db);
|
||||||
const secretApprovalPolicyDal = secretApprovalPolicyDalFactory(db);
|
const secretApprovalPolicyDal = secretApprovalPolicyDalFactory(db);
|
||||||
|
const secretApprovalRequestDal = secretApprovalRequestDalFactory(db);
|
||||||
|
const sarReviewerDal = sarReviewerDalFactory(db);
|
||||||
|
const sarSecretDal = sarSecretDalFactory(db);
|
||||||
|
|
||||||
|
const secretRotationDal = secretRotationDalFactory(db);
|
||||||
|
|
||||||
// ee services
|
|
||||||
const permissionService = permissionServiceFactory({ permissionDal, orgRoleDal, projectRoleDal });
|
const permissionService = permissionServiceFactory({ permissionDal, orgRoleDal, projectRoleDal });
|
||||||
const sapService = secretApprovalPolicyServiceFactory({
|
const sapService = secretApprovalPolicyServiceFactory({
|
||||||
projectMembershipDal,
|
projectMembershipDal,
|
||||||
@@ -128,8 +144,17 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyDal
|
secretApprovalPolicyDal
|
||||||
});
|
});
|
||||||
|
const sarService = secretApprovalRequestServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
folderDal,
|
||||||
|
secretDal,
|
||||||
|
sarSecretDal,
|
||||||
|
sarReviewerDal,
|
||||||
|
secretVersionDal,
|
||||||
|
secretBlindIndexDal,
|
||||||
|
secretApprovalRequestDal
|
||||||
|
});
|
||||||
|
|
||||||
// service layers
|
|
||||||
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal });
|
const tokenService = tokenServiceFactory({ tokenDal: authTokenDal });
|
||||||
const userService = userServiceFactory({ userDal });
|
const userService = userServiceFactory({ userDal });
|
||||||
const loginService = authLoginServiceFactory({ userDal, smtpService, tokenService });
|
const loginService = authLoginServiceFactory({ userDal, smtpService, tokenService });
|
||||||
@@ -211,6 +236,21 @@ export const registerRoutes = async (
|
|||||||
secretImportDal
|
secretImportDal
|
||||||
});
|
});
|
||||||
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDal });
|
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDal });
|
||||||
|
|
||||||
|
const secretRotationQueue = secretRotationQueueFactory({
|
||||||
|
secretRotationDal,
|
||||||
|
queue: queueService,
|
||||||
|
secretDal,
|
||||||
|
secretVersionDal,
|
||||||
|
projectBotService
|
||||||
|
});
|
||||||
|
const secretRotationService = secretRotationServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
projectEnvDal,
|
||||||
|
secretRotationDal,
|
||||||
|
secretRotationQueue
|
||||||
|
});
|
||||||
|
|
||||||
const integrationService = integrationServiceFactory({
|
const integrationService = integrationServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
folderDal,
|
folderDal,
|
||||||
@@ -287,7 +327,9 @@ export const registerRoutes = async (
|
|||||||
identityAccessToken: identityAccessTokenService,
|
identityAccessToken: identityAccessTokenService,
|
||||||
identityProject: identityProjectService,
|
identityProject: identityProjectService,
|
||||||
identityUa: identityUaService,
|
identityUa: identityUaService,
|
||||||
secretApprovalPolicy: sapService
|
secretApprovalPolicy: sapService,
|
||||||
|
secretApprovalRequest: sarService,
|
||||||
|
secretRotation: secretRotationService
|
||||||
});
|
});
|
||||||
|
|
||||||
server.decorate<FastifyZodProvider["store"]>("store", {
|
server.decorate<FastifyZodProvider["store"]>("store", {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretsSchema,SecretType } from "@app/db/schemas";
|
import { SecretApprovalRequestsSchema, SecretsSchema, SecretType } from "@app/db/schemas";
|
||||||
|
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -102,32 +103,92 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretName: z.string().trim()
|
secretName: z.string().trim()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.union([
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
z.object({
|
||||||
})
|
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
||||||
|
}),
|
||||||
|
z
|
||||||
|
.object({ approval: SecretApprovalRequestsSchema })
|
||||||
|
.describe("When secret protection policy is enabled")
|
||||||
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
workspaceId: projectId,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
metadata,
|
||||||
|
type,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
skipMultilineEncoding
|
||||||
|
} = req.body;
|
||||||
|
if (req.body.type !== SecretType.Personal && req.permission.type === ActorType.USER) {
|
||||||
|
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (policy) {
|
||||||
|
const approval =
|
||||||
|
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
policy,
|
||||||
|
data: {
|
||||||
|
[CommitType.Create]: [
|
||||||
|
{
|
||||||
|
secretName: req.params.secretName,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
}
|
||||||
const secret = await server.services.secret.createSecret({
|
const secret = await server.services.secret.createSecret({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
path: req.body.secretPath,
|
path: secretPath,
|
||||||
type: req.body.type,
|
type,
|
||||||
environment: req.body.environment,
|
environment: req.body.environment,
|
||||||
secretName: req.params.secretName,
|
secretName: req.params.secretName,
|
||||||
projectId: req.body.workspaceId,
|
projectId,
|
||||||
secretKeyIV: req.body.secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag: req.body.secretKeyTag,
|
secretKeyTag,
|
||||||
secretKeyCiphertext: req.body.secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretValueIV: req.body.secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag: req.body.secretValueTag,
|
secretValueTag,
|
||||||
secretValueCiphertext: req.body.secretValueCiphertext,
|
secretValueCiphertext,
|
||||||
secretCommentIV: req.body.secretCommentIV,
|
secretCommentIV,
|
||||||
secretCommentTag: req.body.secretCommentTag,
|
secretCommentTag,
|
||||||
secretCommentCiphertext: req.body.secretCommentCiphertext,
|
secretCommentCiphertext,
|
||||||
skipMultilineEncoding: req.body.skipMultilineEncoding,
|
skipMultilineEncoding,
|
||||||
metadata: req.body.metadata
|
metadata
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secret };
|
return { secret };
|
||||||
@@ -165,35 +226,103 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
metadata: z.record(z.string()).optional()
|
metadata: z.record(z.string()).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.union([
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
z.object({
|
||||||
})
|
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
||||||
|
}),
|
||||||
|
z
|
||||||
|
.object({ approval: SecretApprovalRequestsSchema })
|
||||||
|
.describe("When secret protection policy is enabled")
|
||||||
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueTag,
|
||||||
|
secretValueIV,
|
||||||
|
type,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
workspaceId: projectId,
|
||||||
|
tags,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
secretName: newSecretName,
|
||||||
|
secretKeyIV,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
secretReminderRepeatDays,
|
||||||
|
secretReminderNote,
|
||||||
|
metadata
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
if (req.body.type !== SecretType.Personal && req.permission.type === ActorType.USER) {
|
||||||
|
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (policy) {
|
||||||
|
const approval =
|
||||||
|
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
policy,
|
||||||
|
data: {
|
||||||
|
[CommitType.Update]: [
|
||||||
|
{
|
||||||
|
secretName: req.params.secretName,
|
||||||
|
newSecretName,
|
||||||
|
secretValueCiphertext,
|
||||||
|
secretValueIV,
|
||||||
|
secretValueTag,
|
||||||
|
secretCommentIV,
|
||||||
|
secretCommentTag,
|
||||||
|
secretCommentCiphertext,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
secretKeyTag,
|
||||||
|
secretKeyCiphertext,
|
||||||
|
secretKeyIV
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const secret = await server.services.secret.updateSecret({
|
const secret = await server.services.secret.updateSecret({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
path: req.body.secretPath,
|
path: secretPath,
|
||||||
type: req.body.type,
|
type,
|
||||||
environment: req.body.environment,
|
environment,
|
||||||
secretName: req.params.secretName,
|
secretName: req.params.secretName,
|
||||||
projectId: req.body.workspaceId,
|
projectId,
|
||||||
secretKeyIV: req.body.secretKeyIV,
|
secretKeyIV,
|
||||||
secretKeyTag: req.body.secretKeyTag,
|
secretKeyTag,
|
||||||
secretKeyCiphertext: req.body.secretKeyCiphertext,
|
secretKeyCiphertext,
|
||||||
secretValueIV: req.body.secretValueIV,
|
secretValueIV,
|
||||||
secretValueTag: req.body.secretValueTag,
|
tags,
|
||||||
secretValueCiphertext: req.body.secretValueCiphertext,
|
secretValueTag,
|
||||||
secretCommentIV: req.body.secretCommentIV,
|
secretValueCiphertext,
|
||||||
secretCommentTag: req.body.secretCommentTag,
|
secretCommentIV,
|
||||||
secretCommentCiphertext: req.body.secretCommentCiphertext,
|
secretCommentTag,
|
||||||
skipMultilineEncoding: req.body.skipMultilineEncoding,
|
secretCommentCiphertext,
|
||||||
metadata: req.body.metadata,
|
skipMultilineEncoding,
|
||||||
secretReminderRepeatDays: req.body.secretReminderRepeatDays,
|
metadata,
|
||||||
secretReminderNote: req.body.secretReminderNote,
|
secretReminderRepeatDays,
|
||||||
newSecretName: req.body.secretName
|
secretReminderNote,
|
||||||
|
newSecretName
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secret };
|
return { secret };
|
||||||
@@ -215,22 +344,57 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
environment: z.string().trim()
|
environment: z.string().trim()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.union([
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
z.object({
|
||||||
})
|
secret: SecretsSchema.omit({ secretBlindIndex: true })
|
||||||
|
}),
|
||||||
|
z
|
||||||
|
.object({ approval: SecretApprovalRequestsSchema })
|
||||||
|
.describe("When secret protection policy is enabled")
|
||||||
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const { secretPath, type, workspaceId: projectId, secretId, environment } = req.body;
|
||||||
|
if (req.body.type !== SecretType.Personal && req.permission.type === ActorType.USER) {
|
||||||
|
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (policy) {
|
||||||
|
const approval =
|
||||||
|
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
policy,
|
||||||
|
data: {
|
||||||
|
[CommitType.Delete]: [
|
||||||
|
{
|
||||||
|
secretName: req.params.secretName
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const secret = await server.services.secret.deleteSecret({
|
const secret = await server.services.secret.deleteSecret({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
path: req.body.secretPath,
|
path: secretPath,
|
||||||
type: req.body.type,
|
type,
|
||||||
environment: req.body.environment,
|
environment,
|
||||||
secretName: req.params.secretName,
|
secretName: req.params.secretName,
|
||||||
projectId: req.body.workspaceId,
|
projectId,
|
||||||
secretId: req.body.secretId
|
secretId
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secret };
|
return { secret };
|
||||||
@@ -265,20 +429,51 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
.min(1)
|
.min(1)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.union([
|
||||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
z.object({
|
||||||
})
|
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||||
|
}),
|
||||||
|
z
|
||||||
|
.object({ approval: SecretApprovalRequestsSchema })
|
||||||
|
.describe("When secret protection policy is enabled")
|
||||||
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body;
|
||||||
|
if (req.permission.type === ActorType.USER) {
|
||||||
|
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (policy) {
|
||||||
|
const approval =
|
||||||
|
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
policy,
|
||||||
|
data: {
|
||||||
|
[CommitType.Create]: inputSecrets.filter(({ type }) => type === "shared")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const secrets = await server.services.secret.createManySecret({
|
const secrets = await server.services.secret.createManySecret({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
path: req.body.secretPath,
|
path: secretPath,
|
||||||
environment: req.body.environment,
|
environment,
|
||||||
projectId: req.body.workspaceId,
|
projectId,
|
||||||
secrets: req.body.secrets
|
secrets: inputSecrets
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secrets };
|
return { secrets };
|
||||||
@@ -313,20 +508,50 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
.min(1)
|
.min(1)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.union([
|
||||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
z.object({
|
||||||
})
|
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||||
|
}),
|
||||||
|
z
|
||||||
|
.object({ approval: SecretApprovalRequestsSchema })
|
||||||
|
.describe("When secret protection policy is enabled")
|
||||||
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body;
|
||||||
|
if (req.permission.type === ActorType.USER) {
|
||||||
|
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (policy) {
|
||||||
|
const approval =
|
||||||
|
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
policy,
|
||||||
|
data: {
|
||||||
|
[CommitType.Update]: inputSecrets.filter(({ type }) => type === "shared")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
}
|
||||||
const secrets = await server.services.secret.updateManySecret({
|
const secrets = await server.services.secret.updateManySecret({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
path: req.body.secretPath,
|
path: secretPath,
|
||||||
environment: req.body.environment,
|
environment,
|
||||||
projectId: req.body.workspaceId,
|
projectId,
|
||||||
secrets: req.body.secrets
|
secrets: inputSecrets
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secrets };
|
return { secrets };
|
||||||
@@ -350,20 +575,50 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
.min(1)
|
.min(1)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.union([
|
||||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
z.object({
|
||||||
})
|
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||||
|
}),
|
||||||
|
z
|
||||||
|
.object({ approval: SecretApprovalRequestsSchema })
|
||||||
|
.describe("When secret protection policy is enabled")
|
||||||
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const { environment, workspaceId: projectId, secretPath, secrets: inputSecrets } = req.body;
|
||||||
|
if (req.permission.type === ActorType.USER) {
|
||||||
|
const policy = await server.services.secretApprovalPolicy.getSapOfFolder({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (policy) {
|
||||||
|
const approval =
|
||||||
|
await server.services.secretApprovalRequest.generateSecretApprovalRequest({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
policy,
|
||||||
|
data: {
|
||||||
|
[CommitType.Delete]: inputSecrets.filter(({ type }) => type === "shared")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
}
|
||||||
const secrets = await server.services.secret.deleteManySecret({
|
const secrets = await server.services.secret.deleteManySecret({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
path: req.body.secretPath,
|
path: req.body.secretPath,
|
||||||
environment: req.body.environment,
|
environment,
|
||||||
projectId: req.body.workspaceId,
|
projectId,
|
||||||
secrets: req.body.secrets
|
secrets: inputSecrets
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secrets };
|
return { secrets };
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ export const secretDalFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
// the idea is to use postgres specific function
|
// the idea is to use postgres specific function
|
||||||
// insert with id this will cause a conflict then merge the data
|
// insert with id this will cause a conflict then merge the data
|
||||||
const bulkUpdate = async (data: Array<TSecretsUpdate & { id: string }>, tx?: Knex) => {
|
const bulkUpdate = async (
|
||||||
|
data: Array<TSecretsUpdate & { id: string }>,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const secs = await (tx || db)(TableName.Secret)
|
const secs = await (tx || db)(TableName.Secret)
|
||||||
.insert(data as TSecretsInsert[])
|
.insert(data as TSecretsInsert[])
|
||||||
.increment("version", 1)
|
|
||||||
.onConflict("id")
|
.onConflict("id")
|
||||||
.merge()
|
.merge()
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TSecretVersions, TableName } from "@app/db/schemas";
|
import { TableName,TSecretVersions } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify } from "@app/lib/knex";
|
||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
export type TSecretVersionDalFactory = ReturnType<typeof secretVersionDalFactory>;
|
export type TSecretVersionDalFactory = ReturnType<typeof secretVersionDalFactory>;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const useUpdateSecretApprovalReviewStatus = () => {
|
|||||||
|
|
||||||
return useMutation<{}, {}, TUpdateSecretApprovalReviewStatusDTO>({
|
return useMutation<{}, {}, TUpdateSecretApprovalReviewStatusDTO>({
|
||||||
mutationFn: async ({ id, status }) => {
|
mutationFn: async ({ id, status }) => {
|
||||||
const { data } = await apiRequest.post(`/api/v1/secret-approval-requests/${id}/review`, {
|
const { data } = await apiRequest.post(`/api/ee/v1/secret-approval-requests/${id}/review`, {
|
||||||
status
|
status
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -30,7 +30,7 @@ export const useUpdateSecretApprovalRequestStatus = () => {
|
|||||||
|
|
||||||
return useMutation<{}, {}, TUpdateSecretApprovalRequestStatusDTO>({
|
return useMutation<{}, {}, TUpdateSecretApprovalRequestStatusDTO>({
|
||||||
mutationFn: async ({ id, status }) => {
|
mutationFn: async ({ id, status }) => {
|
||||||
const { data } = await apiRequest.post(`/api/v1/secret-approval-requests/${id}/status`, {
|
const { data } = await apiRequest.post(`/api/ee/v1/secret-approval-requests/${id}/status`, {
|
||||||
status
|
status
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -47,7 +47,7 @@ export const usePerformSecretApprovalRequestMerge = () => {
|
|||||||
|
|
||||||
return useMutation<{}, {}, TPerformSecretApprovalRequestMerge>({
|
return useMutation<{}, {}, TPerformSecretApprovalRequestMerge>({
|
||||||
mutationFn: async ({ id }) => {
|
mutationFn: async ({ id }) => {
|
||||||
const { data } = await apiRequest.post(`/api/v1/secret-approval-requests/${id}/merge`);
|
const { data } = await apiRequest.post(`/api/ee/v1/secret-approval-requests/${id}/merge`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { id, workspaceId }) => {
|
onSuccess: (_, { id, workspaceId }) => {
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import { UserWsKeyPair } from "../keys/types";
|
|||||||
import { decryptSecrets } from "../secrets/queries";
|
import { decryptSecrets } from "../secrets/queries";
|
||||||
import { DecryptedSecret } from "../secrets/types";
|
import { DecryptedSecret } from "../secrets/types";
|
||||||
import {
|
import {
|
||||||
|
CommitType,
|
||||||
TGetSecretApprovalRequestCount,
|
TGetSecretApprovalRequestCount,
|
||||||
TGetSecretApprovalRequestDetails,
|
TGetSecretApprovalRequestDetails,
|
||||||
TGetSecretApprovalRequestList,
|
TGetSecretApprovalRequestList,
|
||||||
TSecretApprovalRequest,
|
TSecretApprovalRequest,
|
||||||
TSecretApprovalRequestCount,
|
TSecretApprovalRequestCount,
|
||||||
TSecretApprovalSecChange,
|
|
||||||
TSecretApprovalSecChangeData
|
TSecretApprovalSecChangeData
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ const fetchSecretApprovalRequestList = async ({
|
|||||||
offset
|
offset
|
||||||
}: TGetSecretApprovalRequestList) => {
|
}: TGetSecretApprovalRequestList) => {
|
||||||
const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequest[] }>(
|
const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequest[] }>(
|
||||||
"/api/v1/secret-approval-requests",
|
"/api/ee/v1/secret-approval-requests",
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -158,7 +158,7 @@ const fetchSecretApprovalRequestDetails = async ({
|
|||||||
id
|
id
|
||||||
}: Omit<TGetSecretApprovalRequestDetails, "decryptKey">) => {
|
}: Omit<TGetSecretApprovalRequestDetails, "decryptKey">) => {
|
||||||
const { data } = await apiRequest.get<{ approval: TSecretApprovalRequest }>(
|
const { data } = await apiRequest.get<{ approval: TSecretApprovalRequest }>(
|
||||||
`/api/v1/secret-approval-requests/${id}`
|
`/api/ee/v1/secret-approval-requests/${id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data.approval;
|
return data.approval;
|
||||||
@@ -173,7 +173,7 @@ export const useGetSecretApprovalRequestDetails = ({
|
|||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
TSecretApprovalRequest,
|
TSecretApprovalRequest,
|
||||||
unknown,
|
unknown,
|
||||||
TSecretApprovalRequest<TSecretApprovalSecChange, DecryptedSecret>,
|
TSecretApprovalRequest<DecryptedSecret>,
|
||||||
ReturnType<typeof secretApprovalRequestKeys.detail>
|
ReturnType<typeof secretApprovalRequestKeys.detail>
|
||||||
>,
|
>,
|
||||||
"queryKey" | "queryFn"
|
"queryKey" | "queryFn"
|
||||||
@@ -184,11 +184,12 @@ export const useGetSecretApprovalRequestDetails = ({
|
|||||||
queryFn: () => fetchSecretApprovalRequestDetails({ id }),
|
queryFn: () => fetchSecretApprovalRequestDetails({ id }),
|
||||||
select: (data) => ({
|
select: (data) => ({
|
||||||
...data,
|
...data,
|
||||||
commits: data.commits.map(({ secretVersion, op, newVersion, secret }) => ({
|
commits: data.commits.map(({ secretVersion, op, secret, ...newVersion }) => ({
|
||||||
op,
|
op,
|
||||||
secret,
|
secret,
|
||||||
secretVersion: secretVersion ? decryptSecrets([secretVersion], decryptKey)[0] : undefined,
|
secretVersion: secretVersion ? decryptSecrets([secretVersion], decryptKey)[0] : undefined,
|
||||||
newVersion: newVersion ? decryptSecretApprovalSecret(newVersion, decryptKey) : undefined
|
newVersion:
|
||||||
|
op !== CommitType.DELETE ? decryptSecretApprovalSecret(newVersion, decryptKey) : undefined
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
enabled: Boolean(id && decryptKey) && (options?.enabled ?? true)
|
enabled: Boolean(id && decryptKey) && (options?.enabled ?? true)
|
||||||
@@ -196,7 +197,7 @@ export const useGetSecretApprovalRequestDetails = ({
|
|||||||
|
|
||||||
const fetchSecretApprovalRequestCount = async ({ workspaceId }: TGetSecretApprovalRequestCount) => {
|
const fetchSecretApprovalRequestCount = async ({ workspaceId }: TGetSecretApprovalRequestCount) => {
|
||||||
const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequestCount }>(
|
const { data } = await apiRequest.get<{ approvals: TSecretApprovalRequestCount }>(
|
||||||
"/api/v1/secret-approval-requests/count",
|
"/api/ee/v1/secret-approval-requests/count",
|
||||||
{ params: { workspaceId } }
|
{ params: { workspaceId } }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -42,10 +42,7 @@ export type TSecretApprovalSecChange = {
|
|||||||
tags?: string[];
|
tags?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalRequest<
|
export type TSecretApprovalRequest<J extends unknown = EncryptedSecret> = {
|
||||||
T extends unknown = TSecretApprovalSecChangeData,
|
|
||||||
J extends unknown = EncryptedSecret
|
|
||||||
> = {
|
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
@@ -63,14 +60,13 @@ export type TSecretApprovalRequest<
|
|||||||
policy: TSecretApprovalPolicy;
|
policy: TSecretApprovalPolicy;
|
||||||
statusChangeBy: string;
|
statusChangeBy: string;
|
||||||
conflicts: Array<{ secretId: string; op: CommitType.UPDATE }>;
|
conflicts: Array<{ secretId: string; op: CommitType.UPDATE }>;
|
||||||
commits: {
|
commits: ({
|
||||||
// if there is no secret means it was creation
|
// if there is no secret means it was creation
|
||||||
secret?: { version: number };
|
secret?: { version: number };
|
||||||
secretVersion: J;
|
secretVersion: J;
|
||||||
// if there is no new version its for Delete
|
// if there is no new version its for Delete
|
||||||
newVersion?: T;
|
|
||||||
op: CommitType;
|
op: CommitType;
|
||||||
}[];
|
} & TSecretApprovalSecChangeData)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalRequestCount = {
|
export type TSecretApprovalRequestCount = {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const useCreateSecretRotation = () => {
|
|||||||
|
|
||||||
return useMutation<{}, {}, TCreateSecretRotationDTO>({
|
return useMutation<{}, {}, TCreateSecretRotationDTO>({
|
||||||
mutationFn: async (dto) => {
|
mutationFn: async (dto) => {
|
||||||
const { data } = await apiRequest.post("/api/v1/secret-rotations", dto);
|
const { data } = await apiRequest.post("/api/ee/v1/secret-rotations", dto);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { workspaceId }) => {
|
onSuccess: (_, { workspaceId }) => {
|
||||||
@@ -28,7 +28,7 @@ export const useDeleteSecretRotation = () => {
|
|||||||
|
|
||||||
return useMutation<{}, {}, TDeleteSecretRotationDTO>({
|
return useMutation<{}, {}, TDeleteSecretRotationDTO>({
|
||||||
mutationFn: async (dto) => {
|
mutationFn: async (dto) => {
|
||||||
const { data } = await apiRequest.delete(`/api/v1/secret-rotations/${dto.id}`);
|
const { data } = await apiRequest.delete(`/api/ee/v1/secret-rotations/${dto.id}`);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { workspaceId }) => {
|
onSuccess: (_, { workspaceId }) => {
|
||||||
@@ -42,7 +42,7 @@ export const useRestartSecretRotation = () => {
|
|||||||
|
|
||||||
return useMutation<{}, {}, TRestartSecretRotationDTO>({
|
return useMutation<{}, {}, TRestartSecretRotationDTO>({
|
||||||
mutationFn: async (dto) => {
|
mutationFn: async (dto) => {
|
||||||
const { data } = await apiRequest.post("/api/v1/secret-rotations/restart", { id: dto.id });
|
const { data } = await apiRequest.post("/api/ee/v1/secret-rotations/restart", { id: dto.id });
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
onSuccess: (_, { workspaceId }) => {
|
onSuccess: (_, { workspaceId }) => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const secretRotationKeys = {
|
|||||||
|
|
||||||
const fetchSecretRotationProviders = async ({ workspaceId }: TGetSecretRotationProviders) => {
|
const fetchSecretRotationProviders = async ({ workspaceId }: TGetSecretRotationProviders) => {
|
||||||
const { data } = await apiRequest.get<TSecretRotationProviderList>(
|
const { data } = await apiRequest.get<TSecretRotationProviderList>(
|
||||||
`/api/v1/secret-rotation-providers/${workspaceId}`
|
`/api/ee/v1/secret-rotation-providers/${workspaceId}`
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
@@ -55,7 +55,7 @@ const fetchSecretRotations = async ({
|
|||||||
workspaceId
|
workspaceId
|
||||||
}: Omit<TGetSecretRotationList, "decryptFileKey">) => {
|
}: Omit<TGetSecretRotationList, "decryptFileKey">) => {
|
||||||
const { data } = await apiRequest.get<{ secretRotations: TSecretRotation[] }>(
|
const { data } = await apiRequest.get<{ secretRotations: TSecretRotation[] }>(
|
||||||
"/api/v1/secret-rotations",
|
"/api/ee/v1/secret-rotations",
|
||||||
{ params: { workspaceId } }
|
{ params: { workspaceId } }
|
||||||
);
|
);
|
||||||
return data.secretRotations;
|
return data.secretRotations;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { UserWsKeyPair } from "../keys/types";
|
import { UserWsKeyPair } from "../keys/types";
|
||||||
import { EncryptedSecret } from "../secrets/types";
|
import { EncryptedSecret } from "../secrets/types";
|
||||||
|
import { WorkspaceEnv } from "../workspace/types";
|
||||||
|
|
||||||
export enum TProviderFunctionTypes {
|
export enum TProviderFunctionTypes {
|
||||||
HTTP = "http",
|
HTTP = "http",
|
||||||
@@ -31,7 +32,6 @@ export type TDirectAssignOp = {
|
|||||||
export type TAssignFunction = TJmesPathAssignOp | TDirectAssignOp;
|
export type TAssignFunction = TJmesPathAssignOp | TDirectAssignOp;
|
||||||
|
|
||||||
export type THttpProviderFunction = {
|
export type THttpProviderFunction = {
|
||||||
type: TProviderFunctionTypes.HTTP;
|
|
||||||
url: string;
|
url: string;
|
||||||
method: string;
|
method: string;
|
||||||
header?: Record<string, string>;
|
header?: Record<string, string>;
|
||||||
@@ -41,42 +41,47 @@ export type THttpProviderFunction = {
|
|||||||
pre?: Record<string, TDirectAssignOp>;
|
pre?: Record<string, TDirectAssignOp>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TDbProviderFunction = {
|
export type TSecretRotationProviderTemplate = {
|
||||||
type: TProviderFunctionTypes.DB;
|
name: string;
|
||||||
client: TDbProviderClients;
|
title: string;
|
||||||
username: string;
|
image?: string;
|
||||||
password: string;
|
description?: string;
|
||||||
host: string;
|
template: THttpProviderTemplate | TDbProviderTemplate;
|
||||||
database: string;
|
|
||||||
port: string;
|
|
||||||
query: string;
|
|
||||||
setter?: Record<string, TAssignFunction>;
|
|
||||||
pre?: Record<string, TDirectAssignOp>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TProviderFunction = THttpProviderFunction | TDbProviderFunction;
|
export type THttpProviderTemplate = {
|
||||||
|
type: TProviderFunctionTypes.HTTP;
|
||||||
export type TProviderTemplate = {
|
|
||||||
inputs: {
|
inputs: {
|
||||||
properties: Record<string, { type: string; helperText?: string; defaultValue?: string }>;
|
|
||||||
type: "object";
|
type: "object";
|
||||||
|
properties: Record<string, { type: string; [x: string]: unknown; desc?: string }>;
|
||||||
required: string[];
|
required: string[];
|
||||||
};
|
};
|
||||||
outputs: Record<string, unknown>;
|
outputs: Record<string, unknown>;
|
||||||
functions: {
|
functions: {
|
||||||
set: TProviderFunction;
|
set: THttpProviderFunction;
|
||||||
remove?: TProviderFunction;
|
remove?: THttpProviderFunction;
|
||||||
test: TProviderFunction;
|
test: THttpProviderFunction;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TDbProviderTemplate = {
|
||||||
|
type: TProviderFunctionTypes.DB;
|
||||||
|
inputs: {
|
||||||
|
type: "object";
|
||||||
|
properties: Record<string, { type: string; [x: string]: unknown; desc?: string }>;
|
||||||
|
required: string[];
|
||||||
|
};
|
||||||
|
outputs: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
export type TSecretRotation<T extends unknown = EncryptedSecret> = {
|
export type TSecretRotation<T extends unknown = EncryptedSecret> = {
|
||||||
id: string;
|
id: string;
|
||||||
interval: number;
|
interval: number;
|
||||||
provider: string;
|
provider: string;
|
||||||
customProvider: string;
|
customProvider: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
environment: string;
|
envId: string;
|
||||||
|
environment: WorkspaceEnv;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
outputs: Array<{
|
outputs: Array<{
|
||||||
key: string;
|
key: string;
|
||||||
@@ -89,17 +94,9 @@ export type TSecretRotation<T extends unknown = EncryptedSecret> = {
|
|||||||
keyEncoding: string;
|
keyEncoding: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretRotationProvider = {
|
|
||||||
name: string;
|
|
||||||
image: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
template: TProviderTemplate;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TSecretRotationProviderList = {
|
export type TSecretRotationProviderList = {
|
||||||
custom: TSecretRotationProvider[];
|
custom: TSecretRotationProviderTemplate[];
|
||||||
providers: TSecretRotationProvider[];
|
providers: TSecretRotationProviderTemplate[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetSecretRotationProviders = {
|
export type TGetSecretRotationProviders = {
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ export type { TSecretFolder } from "./secretFolders/types";
|
|||||||
export type { TImportedSecrets, TSecretImport } from "./secretImports/types";
|
export type { TImportedSecrets, TSecretImport } from "./secretImports/types";
|
||||||
export type {
|
export type {
|
||||||
TGetSecretRotationProviders,
|
TGetSecretRotationProviders,
|
||||||
TProviderTemplate,
|
TSecretRotationProviderList,
|
||||||
TSecretRotationProvider,
|
TSecretRotationProviderTemplate
|
||||||
TSecretRotationProviderList
|
|
||||||
} from "./secretRotation/types";
|
} from "./secretRotation/types";
|
||||||
export * from "./secrets/types";
|
export * from "./secrets/types";
|
||||||
export type { CreateServiceTokenDTO, ServiceToken } from "./serviceTokens/types";
|
export type { CreateServiceTokenDTO, ServiceToken } from "./serviceTokens/types";
|
||||||
|
|||||||
+1
@@ -93,6 +93,7 @@ export const SecretApprovalRequestChanges = ({
|
|||||||
id: approvalRequestId,
|
id: approvalRequestId,
|
||||||
decryptKey: decryptFileKey!
|
decryptKey: decryptFileKey!
|
||||||
});
|
});
|
||||||
|
console.log(secretApprovalRequestDetails);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
mutateAsync: updateSecretApprovalRequestStatus,
|
mutateAsync: updateSecretApprovalRequestStatus,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ import {
|
|||||||
useRestartSecretRotation,
|
useRestartSecretRotation,
|
||||||
useUpdateBotActiveStatus
|
useUpdateBotActiveStatus
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { TSecretRotationProvider } from "@app/hooks/api/types";
|
import { TSecretRotationProviderTemplate } from "@app/hooks/api/types";
|
||||||
|
|
||||||
import { CreateRotationForm } from "./components/CreateRotationForm";
|
import { CreateRotationForm } from "./components/CreateRotationForm";
|
||||||
import { generateBotKey } from "./SecretRotationPage.utils";
|
import { generateBotKey } from "./SecretRotationPage.utils";
|
||||||
@@ -143,7 +143,7 @@ export const SecretRotationPage = withProjectPermission(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUserAcceptBotCondition = async () => {
|
const handleUserAcceptBotCondition = async () => {
|
||||||
const provider = popUp.activeBot?.data as TSecretRotationProvider;
|
const provider = popUp.activeBot?.data as TSecretRotationProviderTemplate;
|
||||||
try {
|
try {
|
||||||
if (bot?.id) {
|
if (bot?.id) {
|
||||||
const botKey = generateBotKey(bot.publicKey, userWsKey!);
|
const botKey = generateBotKey(bot.publicKey, userWsKey!);
|
||||||
@@ -165,7 +165,7 @@ export const SecretRotationPage = withProjectPermission(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateRotation = async (provider: TSecretRotationProvider) => {
|
const handleCreateRotation = async (provider: TSecretRotationProviderTemplate) => {
|
||||||
if (subscription && !subscription?.secretRotation) {
|
if (subscription && !subscription?.secretRotation) {
|
||||||
handlePopUpOpen("upgradePlan");
|
handlePopUpOpen("upgradePlan");
|
||||||
return;
|
return;
|
||||||
@@ -256,7 +256,7 @@ export const SecretRotationPage = withProjectPermission(
|
|||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<div className="flex w-min items-center rounded border border-bunker-400 p-1 px-2">
|
<div className="flex w-min items-center rounded border border-bunker-400 p-1 px-2">
|
||||||
<div>{environment}</div>
|
<div>{environment.slug}</div>
|
||||||
<div className="ml-1 flex items-center border-l border-bunker-400 pl-1 text-xs">
|
<div className="ml-1 flex items-center border-l border-bunker-400 pl-1 text-xs">
|
||||||
<FontAwesomeIcon icon={faFolder} className="mr-1" />
|
<FontAwesomeIcon icon={faFolder} className="mr-1" />
|
||||||
{secretPath}
|
{secretPath}
|
||||||
@@ -389,7 +389,7 @@ export const SecretRotationPage = withProjectPermission(
|
|||||||
isOpen={popUp.createRotation.isOpen}
|
isOpen={popUp.createRotation.isOpen}
|
||||||
workspaceId={workspaceId}
|
workspaceId={workspaceId}
|
||||||
onToggle={(isOpen) => handlePopUpToggle("createRotation", isOpen)}
|
onToggle={(isOpen) => handlePopUpToggle("createRotation", isOpen)}
|
||||||
provider={(popUp.createRotation.data as TSecretRotationProvider) || {}}
|
provider={(popUp.createRotation.data as TSecretRotationProviderTemplate) || {}}
|
||||||
/>
|
/>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={popUp.activeBot?.isOpen}
|
isOpen={popUp.activeBot?.isOpen}
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ import { AnimatePresence, motion } from "framer-motion";
|
|||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { Modal, ModalContent, Step, Stepper } from "@app/components/v2";
|
import { Modal, ModalContent, Step, Stepper } from "@app/components/v2";
|
||||||
import { useCreateSecretRotation } from "@app/hooks/api";
|
import { useCreateSecretRotation } from "@app/hooks/api";
|
||||||
import { TSecretRotationProvider } from "@app/hooks/api/types";
|
import { TSecretRotationProviderTemplate } from "@app/hooks/api/types";
|
||||||
|
|
||||||
import { RotationInputForm } from "./steps/RotationInputForm";
|
import { RotationInputForm } from "./steps/RotationInputForm";
|
||||||
import {
|
import {
|
||||||
@@ -28,7 +28,7 @@ type Props = {
|
|||||||
onToggle: (isOpen: boolean) => void;
|
onToggle: (isOpen: boolean) => void;
|
||||||
customProvider?: string;
|
customProvider?: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
provider: TSecretRotationProvider;
|
provider: TSecretRotationProviderTemplate;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateRotationForm = ({
|
export const CreateRotationForm = ({
|
||||||
|
|||||||
Reference in New Issue
Block a user