Merge remote-tracking branch 'origin' into ssh-certs
@@ -14,15 +14,6 @@
|
|||||||
<a href="https://infisical.com/careers">Hiring (Remote/SF)</a>
|
<a href="https://infisical.com/careers">Hiring (Remote/SF)</a>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://infisical.com/docs/self-hosting/deployment-options/aws-ec2">
|
|
||||||
<img src=".github/images/deploy-to-aws.png" width="137" />
|
|
||||||
</a>
|
|
||||||
<a href="https://infisical.com/docs/self-hosting/deployment-options/digital-ocean-marketplace" alt="Deploy to DigitalOcean">
|
|
||||||
<img width="200" alt="Deploy to DO" src="https://www.deploytodo.com/do-btn-blue.svg"/>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h4 align="center">
|
<h4 align="center">
|
||||||
<a href="https://github.com/Infisical/infisical/blob/main/LICENSE">
|
<a href="https://github.com/Infisical/infisical/blob/main/LICENSE">
|
||||||
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Infisical is released under the MIT license." />
|
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Infisical is released under the MIT license." />
|
||||||
|
|||||||
@@ -10,12 +10,15 @@ export const mockQueue = (): TQueueServiceFactory => {
|
|||||||
queue: async (name, jobData) => {
|
queue: async (name, jobData) => {
|
||||||
job[name] = jobData;
|
job[name] = jobData;
|
||||||
},
|
},
|
||||||
|
queuePg: async () => {},
|
||||||
|
initialize: async () => {},
|
||||||
shutdown: async () => undefined,
|
shutdown: async () => undefined,
|
||||||
stopRepeatableJob: async () => true,
|
stopRepeatableJob: async () => true,
|
||||||
start: (name, jobFn) => {
|
start: (name, jobFn) => {
|
||||||
queues[name] = jobFn;
|
queues[name] = jobFn;
|
||||||
workers[name] = jobFn;
|
workers[name] = jobFn;
|
||||||
},
|
},
|
||||||
|
startPg: async () => {},
|
||||||
listen: (name, event) => {
|
listen: (name, event) => {
|
||||||
events[name] = event;
|
events[name] = event;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ export default {
|
|||||||
extension: "ts"
|
extension: "ts"
|
||||||
});
|
});
|
||||||
const smtp = mockSmtpServer();
|
const smtp = mockSmtpServer();
|
||||||
const queue = queueServiceFactory(cfg.REDIS_URL);
|
const queue = queueServiceFactory(cfg.REDIS_URL, { dbConnectionUrl: cfg.DB_CONNECTION_URI });
|
||||||
const keyStore = keyStoreFactory(cfg.REDIS_URL);
|
const keyStore = keyStoreFactory(cfg.REDIS_URL);
|
||||||
|
|
||||||
const hsmModule = initializeHsmModule();
|
const hsmModule = initializeHsmModule();
|
||||||
hsmModule.initialize();
|
hsmModule.initialize();
|
||||||
|
|
||||||
const server = await main({ db, smtp, logger, queue, keyStore, hsmModule: hsmModule.getModule() });
|
const server = await main({ db, smtp, logger, queue, keyStore, hsmModule: hsmModule.getModule(), redis });
|
||||||
|
|
||||||
// @ts-expect-error type
|
// @ts-expect-error type
|
||||||
globalThis.testServer = server;
|
globalThis.testServer = server;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"@fastify/session": "^10.7.0",
|
"@fastify/session": "^10.7.0",
|
||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
|
"@google-cloud/kms": "^4.5.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@octokit/auth-app": "^7.1.1",
|
"@octokit/auth-app": "^7.1.1",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
@@ -92,6 +93,7 @@
|
|||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-ldapauth": "^3.0.1",
|
"passport-ldapauth": "^3.0.1",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
|
"pg-boss": "^10.1.5",
|
||||||
"pg-query-stream": "^4.5.3",
|
"pg-query-stream": "^4.5.3",
|
||||||
"picomatch": "^3.0.1",
|
"picomatch": "^3.0.1",
|
||||||
"pino": "^8.16.2",
|
"pino": "^8.16.2",
|
||||||
@@ -5598,6 +5600,18 @@
|
|||||||
"yaml": "^2.2.2"
|
"yaml": "^2.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@google-cloud/kms": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@google-cloud/kms/-/kms-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-i2vC0DI7bdfEhQszqASTw0KVvbB7HsO2CwTBod423NawAu7FWi+gVVa7NLfXVNGJaZZayFfci2Hu+om/HmyEjQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"google-gax": "^4.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@google-cloud/paginator": {
|
"node_modules/@google-cloud/paginator": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz",
|
||||||
@@ -12259,14 +12273,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
|
||||||
},
|
},
|
||||||
"node_modules/buffer-writer": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/bullmq": {
|
"node_modules/bullmq": {
|
||||||
"version": "5.4.2",
|
"version": "5.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.4.2.tgz",
|
||||||
@@ -15086,6 +15092,44 @@
|
|||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/google-gax": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@grpc/grpc-js": "^1.10.9",
|
||||||
|
"@grpc/proto-loader": "^0.7.13",
|
||||||
|
"@types/long": "^4.0.0",
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"duplexify": "^4.0.0",
|
||||||
|
"google-auth-library": "^9.3.0",
|
||||||
|
"node-fetch": "^2.7.0",
|
||||||
|
"object-hash": "^3.0.0",
|
||||||
|
"proto3-json-serializer": "^2.0.2",
|
||||||
|
"protobufjs": "^7.3.2",
|
||||||
|
"retry-request": "^7.0.0",
|
||||||
|
"uuid": "^9.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/google-gax/node_modules/@types/long": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/google-gax/node_modules/object-hash": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/googleapis": {
|
"node_modules/googleapis": {
|
||||||
"version": "137.1.0",
|
"version": "137.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/googleapis/-/googleapis-137.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/googleapis/-/googleapis-137.1.0.tgz",
|
||||||
@@ -18185,11 +18229,6 @@
|
|||||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||||
"license": "BlueOak-1.0.0"
|
"license": "BlueOak-1.0.0"
|
||||||
},
|
},
|
||||||
"node_modules/packet-reader": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
|
||||||
},
|
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||||
@@ -18408,15 +18447,13 @@
|
|||||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
||||||
},
|
},
|
||||||
"node_modules/pg": {
|
"node_modules/pg": {
|
||||||
"version": "8.11.3",
|
"version": "8.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz",
|
||||||
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
|
"integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-writer": "2.0.0",
|
"pg-connection-string": "^2.7.0",
|
||||||
"packet-reader": "1.0.0",
|
"pg-pool": "^3.7.0",
|
||||||
"pg-connection-string": "^2.6.2",
|
"pg-protocol": "^1.7.0",
|
||||||
"pg-pool": "^3.6.1",
|
|
||||||
"pg-protocol": "^1.6.0",
|
|
||||||
"pg-types": "^2.1.0",
|
"pg-types": "^2.1.0",
|
||||||
"pgpass": "1.x"
|
"pgpass": "1.x"
|
||||||
},
|
},
|
||||||
@@ -18435,6 +18472,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pg-boss": {
|
||||||
|
"version": "10.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-boss/-/pg-boss-10.1.5.tgz",
|
||||||
|
"integrity": "sha512-H87NL6c7N6nTCSCePh16EaSQVSFevNXWdJuzY6PZz4rw+W/nuMKPfI/vYyXS0AdT1g1Q3S3EgeOYOHcB7ZVToQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"cron-parser": "^4.9.0",
|
||||||
|
"pg": "^8.13.0",
|
||||||
|
"serialize-error": "^8.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pg-cloudflare": {
|
"node_modules/pg-cloudflare": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
|
||||||
@@ -18471,17 +18521,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pg-pool": {
|
"node_modules/pg-pool": {
|
||||||
"version": "3.6.1",
|
"version": "3.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz",
|
||||||
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
|
"integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"pg": ">=8.0"
|
"pg": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pg-protocol": {
|
"node_modules/pg-protocol": {
|
||||||
"version": "1.6.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz",
|
||||||
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
|
"integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ=="
|
||||||
},
|
},
|
||||||
"node_modules/pg-query-stream": {
|
"node_modules/pg-query-stream": {
|
||||||
"version": "4.5.3",
|
"version": "4.5.3",
|
||||||
@@ -18510,9 +18560,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pg/node_modules/pg-connection-string": {
|
"node_modules/pg/node_modules/pg-connection-string": {
|
||||||
"version": "2.6.2",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
|
||||||
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA=="
|
"integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA=="
|
||||||
},
|
},
|
||||||
"node_modules/pgpass": {
|
"node_modules/pgpass": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
@@ -19223,6 +19273,18 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proto3-json-serializer": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"protobufjs": "^7.2.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/protobufjs": {
|
"node_modules/protobufjs": {
|
||||||
"version": "7.4.0",
|
"version": "7.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
|
||||||
@@ -20111,6 +20173,20 @@
|
|||||||
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||||
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/serialize-error": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"type-fest": "^0.20.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/serve-static": {
|
"node_modules/serve-static": {
|
||||||
"version": "1.16.2",
|
"version": "1.16.2",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||||
@@ -22130,7 +22206,6 @@
|
|||||||
"version": "0.20.2",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
||||||
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
|
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -136,6 +136,7 @@
|
|||||||
"@fastify/session": "^10.7.0",
|
"@fastify/session": "^10.7.0",
|
||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
|
"@google-cloud/kms": "^4.5.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@octokit/auth-app": "^7.1.1",
|
"@octokit/auth-app": "^7.1.1",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
@@ -200,6 +201,7 @@
|
|||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-ldapauth": "^3.0.1",
|
"passport-ldapauth": "^3.0.1",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
|
"pg-boss": "^10.1.5",
|
||||||
"pg-query-stream": "^4.5.3",
|
"pg-query-stream": "^4.5.3",
|
||||||
"picomatch": "^3.0.1",
|
"picomatch": "^3.0.1",
|
||||||
"pino": "^8.16.2",
|
"pino": "^8.16.2",
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import "@fastify/request-context";
|
|||||||
|
|
||||||
declare module "@fastify/request-context" {
|
declare module "@fastify/request-context" {
|
||||||
interface RequestContextData {
|
interface RequestContextData {
|
||||||
requestId: string;
|
reqId: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import "fastify";
|
import "fastify";
|
||||||
|
|
||||||
|
import { Redis } from "ioredis";
|
||||||
|
|
||||||
import { TUsers } from "@app/db/schemas";
|
import { TUsers } from "@app/db/schemas";
|
||||||
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
||||||
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
||||||
@@ -89,6 +91,10 @@ import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
|||||||
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
|
|
||||||
declare module "fastify" {
|
declare module "fastify" {
|
||||||
|
interface Session {
|
||||||
|
callbackPort: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
realIp: string;
|
realIp: string;
|
||||||
// used for mfa session authentication
|
// used for mfa session authentication
|
||||||
@@ -117,6 +123,7 @@ declare module "fastify" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
|
redis: Redis;
|
||||||
services: {
|
services: {
|
||||||
login: TAuthLoginFactory;
|
login: TAuthLoginFactory;
|
||||||
password: TAuthPasswordFactory;
|
password: TAuthPasswordFactory;
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasAccessApprovalPolicyDeletedAtColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.AccessApprovalPolicy,
|
||||||
|
"deletedAt"
|
||||||
|
);
|
||||||
|
const hasSecretApprovalPolicyDeletedAtColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.SecretApprovalPolicy,
|
||||||
|
"deletedAt"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasAccessApprovalPolicyDeletedAtColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
|
||||||
|
t.timestamp("deletedAt");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!hasSecretApprovalPolicyDeletedAtColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
|
||||||
|
t.timestamp("deletedAt");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
|
||||||
|
t.dropForeign(["privilegeId"]);
|
||||||
|
|
||||||
|
// Add the new foreign key constraint with ON DELETE SET NULL
|
||||||
|
t.foreign("privilegeId").references("id").inTable(TableName.ProjectUserAdditionalPrivilege).onDelete("SET NULL");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasAccessApprovalPolicyDeletedAtColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.AccessApprovalPolicy,
|
||||||
|
"deletedAt"
|
||||||
|
);
|
||||||
|
const hasSecretApprovalPolicyDeletedAtColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.SecretApprovalPolicy,
|
||||||
|
"deletedAt"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasAccessApprovalPolicyDeletedAtColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
|
||||||
|
t.dropColumn("deletedAt");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hasSecretApprovalPolicyDeletedAtColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
|
||||||
|
t.dropColumn("deletedAt");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
|
||||||
|
t.dropForeign(["privilegeId"]);
|
||||||
|
t.foreign("privilegeId").references("id").inTable(TableName.ProjectUserAdditionalPrivilege).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -15,7 +15,8 @@ export const AccessApprovalPoliciesSchema = z.object({
|
|||||||
envId: z.string().uuid(),
|
envId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
enforcementLevel: z.string().default("hard")
|
enforcementLevel: z.string().default("hard"),
|
||||||
|
deletedAt: z.date().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
|
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ export const SecretApprovalPoliciesSchema = z.object({
|
|||||||
envId: z.string().uuid(),
|
envId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
enforcementLevel: z.string().default("hard")
|
enforcementLevel: z.string().default("hard"),
|
||||||
|
deletedAt: z.date().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretApprovalPolicies = z.infer<typeof SecretApprovalPoliciesSchema>;
|
export type TSecretApprovalPolicies = z.infer<typeof SecretApprovalPoliciesSchema>;
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
approvers: z.string().array(),
|
approvers: z.string().array(),
|
||||||
secretPath: z.string().nullish(),
|
secretPath: z.string().nullish(),
|
||||||
envId: z.string(),
|
envId: z.string(),
|
||||||
enforcementLevel: z.string()
|
enforcementLevel: z.string(),
|
||||||
|
deletedAt: z.date().nullish()
|
||||||
}),
|
}),
|
||||||
reviewers: z
|
reviewers: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -8,6 +7,7 @@ import { DYNAMIC_SECRETS } from "@app/lib/api-docs";
|
|||||||
import { daysToMillisecond } from "@app/lib/dates";
|
import { daysToMillisecond } from "@app/lib/dates";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
|
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -48,15 +48,7 @@ export const registerDynamicSecretRouter = async (server: FastifyZodProvider) =>
|
|||||||
.nullable(),
|
.nullable(),
|
||||||
path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash),
|
path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash),
|
||||||
environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1),
|
environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1),
|
||||||
name: z
|
name: slugSchema({ min: 1, max: 64, field: "Name" }).describe(DYNAMIC_SECRETS.CREATE.name)
|
||||||
.string()
|
|
||||||
.describe(DYNAMIC_SECRETS.CREATE.name)
|
|
||||||
.min(1)
|
|
||||||
.toLowerCase()
|
|
||||||
.max(64)
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid"
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -4,9 +4,15 @@ import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
|
|||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import {
|
import {
|
||||||
ExternalKmsAwsSchema,
|
ExternalKmsAwsSchema,
|
||||||
|
ExternalKmsGcpCredentialSchema,
|
||||||
|
ExternalKmsGcpSchema,
|
||||||
ExternalKmsInputSchema,
|
ExternalKmsInputSchema,
|
||||||
ExternalKmsInputUpdateSchema
|
ExternalKmsInputUpdateSchema,
|
||||||
|
KmsGcpKeyFetchAuthType,
|
||||||
|
KmsProviders,
|
||||||
|
TExternalKmsGcpCredentialSchema
|
||||||
} from "@app/ee/services/external-kms/providers/model";
|
} from "@app/ee/services/external-kms/providers/model";
|
||||||
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -44,7 +50,8 @@ const sanitizedExternalSchemaForGetById = KmsKeysSchema.extend({
|
|||||||
statusDetails: true,
|
statusDetails: true,
|
||||||
provider: true
|
provider: true
|
||||||
}).extend({
|
}).extend({
|
||||||
providerInput: ExternalKmsAwsSchema
|
// for GCP, we don't return the credential object as it is sensitive data that should not be exposed
|
||||||
|
providerInput: z.union([ExternalKmsAwsSchema, ExternalKmsGcpSchema.pick({ gcpRegion: true, keyName: true })])
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -286,4 +293,67 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { externalKms };
|
return { externalKms };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/gcp/keys",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.discriminatedUnion("authMethod", [
|
||||||
|
z.object({
|
||||||
|
authMethod: z.literal(KmsGcpKeyFetchAuthType.Credential),
|
||||||
|
region: z.string().trim().min(1),
|
||||||
|
credential: ExternalKmsGcpCredentialSchema
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
authMethod: z.literal(KmsGcpKeyFetchAuthType.Kms),
|
||||||
|
region: z.string().trim().min(1),
|
||||||
|
kmsId: z.string().trim().min(1)
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
keys: z.string().array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { region, authMethod } = req.body;
|
||||||
|
let credentialJson: TExternalKmsGcpCredentialSchema | undefined;
|
||||||
|
|
||||||
|
if (authMethod === KmsGcpKeyFetchAuthType.Credential) {
|
||||||
|
credentialJson = req.body.credential;
|
||||||
|
} else if (authMethod === KmsGcpKeyFetchAuthType.Kms) {
|
||||||
|
const externalKms = await server.services.externalKms.findById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.body.kmsId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!externalKms || externalKms.external.provider !== KmsProviders.Gcp) {
|
||||||
|
throw new NotFoundError({ message: "KMS not found or not of type GCP" });
|
||||||
|
}
|
||||||
|
|
||||||
|
credentialJson = externalKms.external.providerInput.credential as TExternalKmsGcpCredentialSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!credentialJson) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Something went wrong while fetching the GCP credential, please check inputs and try again"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await server.services.externalKms.fetchGcpKeys({
|
||||||
|
credential: credentialJson,
|
||||||
|
gcpRegion: region
|
||||||
|
});
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { GroupsSchema, OrgMembershipRole, UsersSchema } from "@app/db/schemas";
|
import { GroupsSchema, OrgMembershipRole, UsersSchema } from "@app/db/schemas";
|
||||||
|
import { EFilterReturnedUsers } from "@app/ee/services/group/group-types";
|
||||||
import { GROUPS } from "@app/lib/api-docs";
|
import { GROUPS } from "@app/lib/api-docs";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -14,15 +15,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().min(1).max(50).describe(GROUPS.CREATE.name),
|
name: z.string().trim().min(1).max(50).describe(GROUPS.CREATE.name),
|
||||||
slug: z
|
slug: slugSchema({ min: 5, max: 36 }).optional().describe(GROUPS.CREATE.slug),
|
||||||
.string()
|
|
||||||
.min(5)
|
|
||||||
.max(36)
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe(GROUPS.CREATE.slug),
|
|
||||||
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(GROUPS.CREATE.role)
|
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(GROUPS.CREATE.role)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -100,14 +93,7 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().trim().min(1).describe(GROUPS.UPDATE.name),
|
name: z.string().trim().min(1).describe(GROUPS.UPDATE.name),
|
||||||
slug: z
|
slug: slugSchema({ min: 5, max: 36 }).describe(GROUPS.UPDATE.slug),
|
||||||
.string()
|
|
||||||
.min(5)
|
|
||||||
.max(36)
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.describe(GROUPS.UPDATE.slug),
|
|
||||||
role: z.string().trim().min(1).describe(GROUPS.UPDATE.role)
|
role: z.string().trim().min(1).describe(GROUPS.UPDATE.role)
|
||||||
})
|
})
|
||||||
.partial(),
|
.partial(),
|
||||||
@@ -166,7 +152,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
|
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
|
||||||
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
|
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
|
||||||
username: z.string().trim().optional().describe(GROUPS.LIST_USERS.username),
|
username: z.string().trim().optional().describe(GROUPS.LIST_USERS.username),
|
||||||
search: z.string().trim().optional().describe(GROUPS.LIST_USERS.search)
|
search: z.string().trim().optional().describe(GROUPS.LIST_USERS.search),
|
||||||
|
filter: z.nativeEnum(EFilterReturnedUsers).optional().describe(GROUPS.LIST_USERS.filterUsers)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -179,7 +166,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.merge(
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
isPartOfGroup: z.boolean()
|
isPartOfGroup: z.boolean(),
|
||||||
|
joinedGroupAt: z.date().nullable()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.array(),
|
.array(),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
|||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { UnauthorizedError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionSchema,
|
ProjectPermissionSchema,
|
||||||
@@ -33,17 +34,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId),
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId),
|
||||||
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug),
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug),
|
||||||
slug: z
|
slug: slugSchema({ min: 1, max: 60 }).optional().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
.string()
|
|
||||||
.min(1)
|
|
||||||
.max(60)
|
|
||||||
.trim()
|
|
||||||
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
|
||||||
permissions: ProjectPermissionSchema.array()
|
permissions: ProjectPermissionSchema.array()
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -77,7 +68,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
...req.body,
|
...req.body,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
slug: req.body.slug ?? slugify(alphaNumericNanoId(12)),
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore-error this is valid ts
|
// @ts-ignore-error this is valid ts
|
||||||
@@ -103,17 +94,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId),
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId),
|
||||||
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug),
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug),
|
||||||
slug: z
|
slug: slugSchema({ min: 1, max: 60 }).optional().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
.string()
|
|
||||||
.min(1)
|
|
||||||
.max(60)
|
|
||||||
.trim()
|
|
||||||
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
|
||||||
permissions: ProjectPermissionSchema.array()
|
permissions: ProjectPermissionSchema.array()
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -159,7 +140,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
...req.body,
|
...req.body,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
slug: req.body.slug ?? slugify(alphaNumericNanoId(12)),
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore-error this is valid ts
|
// @ts-ignore-error this is valid ts
|
||||||
@@ -189,16 +170,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.projectSlug),
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.projectSlug),
|
||||||
privilegeDetails: z
|
privilegeDetails: z
|
||||||
.object({
|
.object({
|
||||||
slug: z
|
slug: slugSchema({ min: 1, max: 60 }).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.newSlug),
|
||||||
.string()
|
|
||||||
.min(1)
|
|
||||||
.max(60)
|
|
||||||
.trim()
|
|
||||||
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.newSlug),
|
|
||||||
permissions: ProjectPermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
permissions: ProjectPermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||||
privilegePermission: ProjectSpecificPrivilegePermissionSchema.describe(
|
privilegePermission: ProjectSpecificPrivilegePermissionSchema.describe(
|
||||||
IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.privilegePermission
|
IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.privilegePermission
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
import { Authenticator, Strategy } from "@fastify/passport";
|
import { Authenticator, Strategy } from "@fastify/passport";
|
||||||
import fastifySession from "@fastify/session";
|
import fastifySession from "@fastify/session";
|
||||||
import RedisStore from "connect-redis";
|
import RedisStore from "connect-redis";
|
||||||
import { Redis } from "ioredis";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OidcConfigsSchema } from "@app/db/schemas/oidc-configs";
|
import { OidcConfigsSchema } from "@app/db/schemas/oidc-configs";
|
||||||
@@ -21,7 +20,6 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
|
|
||||||
export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const redis = new Redis(appCfg.REDIS_URL);
|
|
||||||
const passport = new Authenticator({ key: "oidc", userProperty: "passportUser" });
|
const passport = new Authenticator({ key: "oidc", userProperty: "passportUser" });
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -30,7 +28,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
- Fastify session <> Redis structure is based on the ff: https://github.com/fastify/session/blob/master/examples/redis.js
|
- Fastify session <> Redis structure is based on the ff: https://github.com/fastify/session/blob/master/examples/redis.js
|
||||||
*/
|
*/
|
||||||
const redisStore = new RedisStore({
|
const redisStore = new RedisStore({
|
||||||
client: redis,
|
client: server.redis,
|
||||||
prefix: "oidc-session:",
|
prefix: "oidc-session:",
|
||||||
ttl: 600 // 10 minutes
|
ttl: 600 // 10 minutes
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -18,17 +18,10 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
slug: slugSchema({ min: 1, max: 64 }).refine(
|
||||||
.string()
|
(val) => !Object.values(OrgMembershipRole).includes(val as OrgMembershipRole),
|
||||||
.min(1)
|
"Please choose a different slug, the slug you have entered is reserved"
|
||||||
.trim()
|
),
|
||||||
.refine(
|
|
||||||
(val) => !Object.values(OrgMembershipRole).includes(val as OrgMembershipRole),
|
|
||||||
"Please choose a different slug, the slug you have entered is reserved"
|
|
||||||
)
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid"
|
|
||||||
}),
|
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array()
|
permissions: z.any().array()
|
||||||
@@ -94,17 +87,13 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
roleId: z.string().trim()
|
roleId: z.string().trim()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
// TODO: Switch to slugSchema after verifying correct methods with Akhil - Omar 11/24
|
||||||
.string()
|
slug: slugSchema({ min: 1, max: 64 })
|
||||||
.trim()
|
|
||||||
.optional()
|
|
||||||
.refine(
|
.refine(
|
||||||
(val) => typeof val !== "undefined" && !Object.keys(OrgMembershipRole).includes(val),
|
(val) => !Object.keys(OrgMembershipRole).includes(val),
|
||||||
"Please choose a different slug, the slug you have entered is reserved."
|
"Please choose a different slug, the slug you have entered is reserved."
|
||||||
)
|
)
|
||||||
.refine((val) => typeof val === "undefined" || slugify(val) === val, {
|
.optional(),
|
||||||
message: "Slug must be a valid"
|
|
||||||
}),
|
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().optional(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array().optional()
|
permissions: z.any().array().optional()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
import { packRules } from "@casl/ability/extra";
|
||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
@@ -9,6 +8,7 @@ import {
|
|||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedRoleSchemaV1 } from "@app/server/routes/sanitizedSchemas";
|
import { SanitizedRoleSchemaV1 } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -32,18 +32,11 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectSlug: z.string().trim().describe(PROJECT_ROLE.CREATE.projectSlug)
|
projectSlug: z.string().trim().describe(PROJECT_ROLE.CREATE.projectSlug)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
slug: slugSchema({ max: 64 })
|
||||||
.string()
|
|
||||||
.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
.min(1)
|
|
||||||
.refine(
|
.refine(
|
||||||
(val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole),
|
(val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole),
|
||||||
"Please choose a different slug, the slug you have entered is reserved"
|
"Please choose a different slug, the slug you have entered is reserved"
|
||||||
)
|
)
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid"
|
|
||||||
})
|
|
||||||
.describe(PROJECT_ROLE.CREATE.slug),
|
.describe(PROJECT_ROLE.CREATE.slug),
|
||||||
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
||||||
description: z.string().trim().optional().describe(PROJECT_ROLE.CREATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.CREATE.description),
|
||||||
@@ -94,21 +87,13 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
roleId: z.string().trim().describe(PROJECT_ROLE.UPDATE.roleId)
|
roleId: z.string().trim().describe(PROJECT_ROLE.UPDATE.roleId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
slug: slugSchema({ max: 64 })
|
||||||
.string()
|
|
||||||
.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
.optional()
|
|
||||||
.describe(PROJECT_ROLE.UPDATE.slug)
|
|
||||||
.refine(
|
.refine(
|
||||||
(val) =>
|
(val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole),
|
||||||
typeof val === "undefined" ||
|
|
||||||
!Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole),
|
|
||||||
"Please choose a different slug, the slug you have entered is reserved"
|
"Please choose a different slug, the slug you have entered is reserved"
|
||||||
)
|
)
|
||||||
.refine((val) => typeof val === "undefined" || slugify(val) === val, {
|
.describe(PROJECT_ROLE.UPDATE.slug)
|
||||||
message: "Slug must be a valid"
|
.optional(),
|
||||||
}),
|
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
||||||
permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectTemplatesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectTemplatesSchema } from "@app/db/schemas";
|
||||||
@@ -8,22 +7,13 @@ import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-tem
|
|||||||
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
|
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
|
||||||
import { ProjectTemplates } from "@app/lib/api-docs";
|
import { ProjectTemplates } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
const MAX_JSON_SIZE_LIMIT_IN_BYTES = 32_768;
|
const MAX_JSON_SIZE_LIMIT_IN_BYTES = 32_768;
|
||||||
|
|
||||||
const SlugSchema = z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.min(1)
|
|
||||||
.max(32)
|
|
||||||
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Must be valid slug format"
|
|
||||||
});
|
|
||||||
|
|
||||||
const isReservedRoleSlug = (slug: string) =>
|
const isReservedRoleSlug = (slug: string) =>
|
||||||
Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole);
|
Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole);
|
||||||
|
|
||||||
@@ -34,14 +24,14 @@ const SanitizedProjectTemplateSchema = ProjectTemplatesSchema.extend({
|
|||||||
roles: z
|
roles: z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().trim().min(1),
|
name: z.string().trim().min(1),
|
||||||
slug: SlugSchema,
|
slug: slugSchema(),
|
||||||
permissions: UnpackedPermissionSchema.array()
|
permissions: UnpackedPermissionSchema.array()
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
environments: z
|
environments: z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().trim().min(1),
|
name: z.string().trim().min(1),
|
||||||
slug: SlugSchema,
|
slug: slugSchema(),
|
||||||
position: z.number().min(1)
|
position: z.number().min(1)
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
@@ -50,7 +40,7 @@ const SanitizedProjectTemplateSchema = ProjectTemplatesSchema.extend({
|
|||||||
const ProjectTemplateRolesSchema = z
|
const ProjectTemplateRolesSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().trim().min(1),
|
name: z.string().trim().min(1),
|
||||||
slug: SlugSchema,
|
slug: slugSchema(),
|
||||||
permissions: ProjectPermissionV2Schema.array()
|
permissions: ProjectPermissionV2Schema.array()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
@@ -78,7 +68,7 @@ const ProjectTemplateRolesSchema = z
|
|||||||
const ProjectTemplateEnvironmentsSchema = z
|
const ProjectTemplateEnvironmentsSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().trim().min(1),
|
name: z.string().trim().min(1),
|
||||||
slug: SlugSchema,
|
slug: slugSchema(),
|
||||||
position: z.number().min(1)
|
position: z.number().min(1)
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
@@ -188,9 +178,11 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Create a project template.",
|
description: "Create a project template.",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: SlugSchema.refine((val) => !isInfisicalProjectTemplate(val), {
|
name: slugSchema({ field: "name" })
|
||||||
message: `The requested project template name is reserved.`
|
.refine((val) => !isInfisicalProjectTemplate(val), {
|
||||||
}).describe(ProjectTemplates.CREATE.name),
|
message: `The requested project template name is reserved.`
|
||||||
|
})
|
||||||
|
.describe(ProjectTemplates.CREATE.name),
|
||||||
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
|
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
|
||||||
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
|
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
|
||||||
environments: ProjectTemplateEnvironmentsSchema.default(ProjectTemplateDefaultEnvironments).describe(
|
environments: ProjectTemplateEnvironmentsSchema.default(ProjectTemplateDefaultEnvironments).describe(
|
||||||
@@ -230,9 +222,10 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
|||||||
description: "Update a project template.",
|
description: "Update a project template.",
|
||||||
params: z.object({ templateId: z.string().uuid().describe(ProjectTemplates.UPDATE.templateId) }),
|
params: z.object({ templateId: z.string().uuid().describe(ProjectTemplates.UPDATE.templateId) }),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: SlugSchema.refine((val) => !isInfisicalProjectTemplate(val), {
|
name: slugSchema({ field: "name" })
|
||||||
message: `The requested project template name is reserved.`
|
.refine((val) => !isInfisicalProjectTemplate(val), {
|
||||||
})
|
message: `The requested project template name is reserved.`
|
||||||
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.describe(ProjectTemplates.UPDATE.name),
|
.describe(ProjectTemplates.UPDATE.name),
|
||||||
description: z.string().max(256).trim().optional().describe(ProjectTemplates.UPDATE.description),
|
description: z.string().max(256).trim().optional().describe(ProjectTemplates.UPDATE.description),
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
secretPath: z.string().optional().nullable(),
|
secretPath: z.string().optional().nullable(),
|
||||||
enforcementLevel: z.string()
|
enforcementLevel: z.string(),
|
||||||
|
deletedAt: z.date().nullish()
|
||||||
}),
|
}),
|
||||||
committerUser: approvalRequestUser,
|
committerUser: approvalRequestUser,
|
||||||
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
||||||
@@ -260,7 +261,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
approvals: z.number(),
|
approvals: z.number(),
|
||||||
approvers: approvalRequestUser.array(),
|
approvers: approvalRequestUser.array(),
|
||||||
secretPath: z.string().optional().nullable(),
|
secretPath: z.string().optional().nullable(),
|
||||||
enforcementLevel: z.string()
|
enforcementLevel: z.string(),
|
||||||
|
deletedAt: z.date().nullish()
|
||||||
}),
|
}),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
statusChangedByUser: approvalRequestUser.optional(),
|
statusChangedByUser: approvalRequestUser.optional(),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/pr
|
|||||||
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/santizedSchemas/user-additional-privilege";
|
import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/santizedSchemas/user-additional-privilege";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -21,17 +22,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
||||||
slug: z
|
slug: slugSchema({ min: 1, max: 60 }).optional().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
.string()
|
|
||||||
.min(1)
|
|
||||||
.max(60)
|
|
||||||
.trim()
|
|
||||||
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
|
||||||
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
||||||
type: z.discriminatedUnion("isTemporary", [
|
type: z.discriminatedUnion("isTemporary", [
|
||||||
z.object({
|
z.object({
|
||||||
@@ -87,15 +78,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
}),
|
}),
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
slug: z
|
slug: slugSchema({ min: 1, max: 60 }).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
||||||
.string()
|
|
||||||
.max(60)
|
|
||||||
.trim()
|
|
||||||
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
|
||||||
permissions: ProjectPermissionV2Schema.array()
|
permissions: ProjectPermissionV2Schema.array()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-p
|
|||||||
import { IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
|
import { IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/santizedSchemas/identitiy-additional-privilege";
|
import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/santizedSchemas/identitiy-additional-privilege";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -28,17 +29,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.identityId),
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.identityId),
|
||||||
projectId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.projectId),
|
projectId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.projectId),
|
||||||
slug: z
|
slug: slugSchema({ min: 1, max: 60 }).optional().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.slug),
|
||||||
.string()
|
|
||||||
.min(1)
|
|
||||||
.max(60)
|
|
||||||
.trim()
|
|
||||||
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.slug),
|
|
||||||
permissions: ProjectPermissionV2Schema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.permission),
|
permissions: ProjectPermissionV2Schema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.permission),
|
||||||
type: z.discriminatedUnion("isTemporary", [
|
type: z.discriminatedUnion("isTemporary", [
|
||||||
z.object({
|
z.object({
|
||||||
@@ -100,16 +91,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
id: z.string().trim().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.id)
|
id: z.string().trim().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.id)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
slug: slugSchema({ min: 1, max: 60 }).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.slug),
|
||||||
.string()
|
|
||||||
.min(1)
|
|
||||||
.max(60)
|
|
||||||
.trim()
|
|
||||||
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.slug),
|
|
||||||
permissions: ProjectPermissionV2Schema.array()
|
permissions: ProjectPermissionV2Schema.array()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.privilegePermission),
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.privilegePermission),
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
import { packRules } from "@casl/ability/extra";
|
||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas";
|
import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -29,18 +29,11 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId: z.string().trim().describe(PROJECT_ROLE.CREATE.projectId)
|
projectId: z.string().trim().describe(PROJECT_ROLE.CREATE.projectId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
slug: slugSchema({ min: 1, max: 64 })
|
||||||
.string()
|
|
||||||
.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
.min(1)
|
|
||||||
.refine(
|
.refine(
|
||||||
(val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole),
|
(val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole),
|
||||||
"Please choose a different slug, the slug you have entered is reserved"
|
"Please choose a different slug, the slug you have entered is reserved"
|
||||||
)
|
)
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid"
|
|
||||||
})
|
|
||||||
.describe(PROJECT_ROLE.CREATE.slug),
|
.describe(PROJECT_ROLE.CREATE.slug),
|
||||||
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
||||||
description: z.string().trim().optional().describe(PROJECT_ROLE.CREATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.CREATE.description),
|
||||||
@@ -90,21 +83,13 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
roleId: z.string().trim().describe(PROJECT_ROLE.UPDATE.roleId)
|
roleId: z.string().trim().describe(PROJECT_ROLE.UPDATE.roleId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
slug: slugSchema({ min: 1, max: 64 })
|
||||||
.string()
|
|
||||||
.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
.optional()
|
|
||||||
.describe(PROJECT_ROLE.UPDATE.slug)
|
|
||||||
.refine(
|
.refine(
|
||||||
(val) =>
|
(val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole),
|
||||||
typeof val === "undefined" ||
|
|
||||||
!Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole),
|
|
||||||
"Please choose a different slug, the slug you have entered is reserved"
|
"Please choose a different slug, the slug you have entered is reserved"
|
||||||
)
|
)
|
||||||
.refine((val) => typeof val === "undefined" || slugify(val) === val, {
|
.optional()
|
||||||
message: "Slug must be a valid"
|
.describe(PROJECT_ROLE.UPDATE.slug),
|
||||||
}),
|
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
||||||
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||||
|
|||||||
@@ -139,5 +139,10 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...accessApprovalPolicyOrm, find, findById };
|
const softDeleteById = async (policyId: string, tx?: Knex) => {
|
||||||
|
const softDeletedPolicy = await accessApprovalPolicyOrm.updateById(policyId, { deletedAt: new Date() }, tx);
|
||||||
|
return softDeletedPolicy;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...accessApprovalPolicyOrm, find, findById, softDeleteById };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal
|
|||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
|
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
|
||||||
|
import { TAccessApprovalRequestReviewerDALFactory } from "../access-approval-request/access-approval-request-reviewer-dal";
|
||||||
|
import { ApprovalStatus } from "../access-approval-request/access-approval-request-types";
|
||||||
import { TGroupDALFactory } from "../group/group-dal";
|
import { TGroupDALFactory } from "../group/group-dal";
|
||||||
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal";
|
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal";
|
||||||
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
||||||
import {
|
import {
|
||||||
@@ -21,7 +25,7 @@ import {
|
|||||||
TUpdateAccessApprovalPolicy
|
TUpdateAccessApprovalPolicy
|
||||||
} from "./access-approval-policy-types";
|
} from "./access-approval-policy-types";
|
||||||
|
|
||||||
type TSecretApprovalPolicyServiceFactoryDep = {
|
type TAccessApprovalPolicyServiceFactoryDep = {
|
||||||
projectDAL: TProjectDALFactory;
|
projectDAL: TProjectDALFactory;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
accessApprovalPolicyDAL: TAccessApprovalPolicyDALFactory;
|
accessApprovalPolicyDAL: TAccessApprovalPolicyDALFactory;
|
||||||
@@ -30,6 +34,9 @@ type TSecretApprovalPolicyServiceFactoryDep = {
|
|||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
|
||||||
groupDAL: TGroupDALFactory;
|
groupDAL: TGroupDALFactory;
|
||||||
userDAL: Pick<TUserDALFactory, "find">;
|
userDAL: Pick<TUserDALFactory, "find">;
|
||||||
|
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update" | "find">;
|
||||||
|
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
|
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
|
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
|
||||||
@@ -41,8 +48,11 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
userDAL
|
userDAL,
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
accessApprovalRequestDAL,
|
||||||
|
additionalPrivilegeDAL,
|
||||||
|
accessApprovalRequestReviewerDAL
|
||||||
|
}: TAccessApprovalPolicyServiceFactoryDep) => {
|
||||||
const createAccessApprovalPolicy = async ({
|
const createAccessApprovalPolicy = async ({
|
||||||
name,
|
name,
|
||||||
actor,
|
actor,
|
||||||
@@ -189,7 +199,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
// ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
// ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id });
|
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
||||||
return accessApprovalPolicies;
|
return accessApprovalPolicies;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -326,7 +336,29 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
await accessApprovalPolicyDAL.deleteById(policyId);
|
await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
|
await accessApprovalPolicyDAL.softDeleteById(policyId, tx);
|
||||||
|
const allAccessApprovalRequests = await accessApprovalRequestDAL.find({ policyId });
|
||||||
|
|
||||||
|
if (allAccessApprovalRequests.length) {
|
||||||
|
const accessApprovalRequestsIds = allAccessApprovalRequests.map((request) => request.id);
|
||||||
|
|
||||||
|
const privilegeIdsArray = allAccessApprovalRequests
|
||||||
|
.map((request) => request.privilegeId)
|
||||||
|
.filter((id): id is string => id != null);
|
||||||
|
|
||||||
|
if (privilegeIdsArray.length) {
|
||||||
|
await additionalPrivilegeDAL.delete({ $in: { id: privilegeIdsArray } }, tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
await accessApprovalRequestReviewerDAL.update(
|
||||||
|
{ $in: { id: accessApprovalRequestsIds }, status: ApprovalStatus.PENDING },
|
||||||
|
{ status: ApprovalStatus.REJECTED },
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return policy;
|
return policy;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -356,7 +388,11 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
||||||
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
|
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
|
||||||
|
|
||||||
const policies = await accessApprovalPolicyDAL.find({ envId: environment.id, projectId: project.id });
|
const policies = await accessApprovalPolicyDAL.find({
|
||||||
|
envId: environment.id,
|
||||||
|
projectId: project.id,
|
||||||
|
deletedAt: null
|
||||||
|
});
|
||||||
if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` });
|
if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` });
|
||||||
|
|
||||||
return { count: policies.length };
|
return { count: policies.length };
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||||
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||||
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId")
|
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId"),
|
||||||
|
db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
|
||||||
)
|
)
|
||||||
|
|
||||||
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||||
@@ -118,7 +119,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
approvals: doc.policyApprovals,
|
approvals: doc.policyApprovals,
|
||||||
secretPath: doc.policySecretPath,
|
secretPath: doc.policySecretPath,
|
||||||
enforcementLevel: doc.policyEnforcementLevel,
|
enforcementLevel: doc.policyEnforcementLevel,
|
||||||
envId: doc.policyEnvId
|
envId: doc.policyEnvId,
|
||||||
|
deletedAt: doc.policyDeletedAt
|
||||||
},
|
},
|
||||||
requestedByUser: {
|
requestedByUser: {
|
||||||
userId: doc.requestedByUserId,
|
userId: doc.requestedByUserId,
|
||||||
@@ -141,7 +143,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
isApproved: !!doc.privilegeId
|
isApproved: !!doc.policyDeletedAt || !!doc.privilegeId
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
@@ -252,7 +254,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||||
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals")
|
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||||
|
tx.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
|
||||||
);
|
);
|
||||||
|
|
||||||
const findById = async (id: string, tx?: Knex) => {
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
@@ -271,7 +274,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
name: el.policyName,
|
name: el.policyName,
|
||||||
approvals: el.policyApprovals,
|
approvals: el.policyApprovals,
|
||||||
secretPath: el.policySecretPath,
|
secretPath: el.policySecretPath,
|
||||||
enforcementLevel: el.policyEnforcementLevel
|
enforcementLevel: el.policyEnforcementLevel,
|
||||||
|
deletedAt: el.policyDeletedAt
|
||||||
},
|
},
|
||||||
requestedByUser: {
|
requestedByUser: {
|
||||||
userId: el.requestedByUserId,
|
userId: el.requestedByUserId,
|
||||||
@@ -363,6 +367,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
.where(`${TableName.Environment}.projectId`, projectId)
|
.where(`${TableName.Environment}.projectId`, projectId)
|
||||||
|
.where(`${TableName.AccessApprovalPolicy}.deletedAt`, null)
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||||
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
|
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
|
||||||
.select(db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"));
|
.select(db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"));
|
||||||
|
|||||||
@@ -130,6 +130,9 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
message: `No policy in environment with slug '${environment.slug}' and with secret path '${secretPath}' was found.`
|
message: `No policy in environment with slug '${environment.slug}' and with secret path '${secretPath}' was found.`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (policy.deletedAt) {
|
||||||
|
throw new BadRequestError({ message: "The policy linked to this request has been deleted" });
|
||||||
|
}
|
||||||
|
|
||||||
const approverIds: string[] = [];
|
const approverIds: string[] = [];
|
||||||
const approverGroupIds: string[] = [];
|
const approverGroupIds: string[] = [];
|
||||||
@@ -309,6 +312,12 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { policy } = accessApprovalRequest;
|
const { policy } = accessApprovalRequest;
|
||||||
|
if (policy.deletedAt) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The policy associated with this access request has been deleted."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { RawAxiosRequestHeaders } from "axios";
|
import { RawAxiosRequestHeaders } from "axios";
|
||||||
|
|
||||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
@@ -20,27 +21,130 @@ type TAuditLogQueueServiceFactoryDep = {
|
|||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAuditLogQueueServiceFactory = ReturnType<typeof auditLogQueueServiceFactory>;
|
export type TAuditLogQueueServiceFactory = Awaited<ReturnType<typeof auditLogQueueServiceFactory>>;
|
||||||
|
|
||||||
// keep this timeout 5s it must be fast because else the queue will take time to finish
|
// keep this timeout 5s it must be fast because else the queue will take time to finish
|
||||||
// audit log is a crowded queue thus needs to be fast
|
// audit log is a crowded queue thus needs to be fast
|
||||||
export const AUDIT_LOG_STREAM_TIMEOUT = 5 * 1000;
|
export const AUDIT_LOG_STREAM_TIMEOUT = 5 * 1000;
|
||||||
export const auditLogQueueServiceFactory = ({
|
|
||||||
|
export const auditLogQueueServiceFactory = async ({
|
||||||
auditLogDAL,
|
auditLogDAL,
|
||||||
queueService,
|
queueService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
auditLogStreamDAL
|
auditLogStreamDAL
|
||||||
}: TAuditLogQueueServiceFactoryDep) => {
|
}: TAuditLogQueueServiceFactoryDep) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
||||||
await queueService.queue(QueueName.AuditLog, QueueJobs.AuditLog, data, {
|
if (appCfg.USE_PG_QUEUE && appCfg.SHOULD_INIT_PG_QUEUE) {
|
||||||
removeOnFail: {
|
await queueService.queuePg<QueueName.AuditLog>(QueueJobs.AuditLog, data, {
|
||||||
count: 3
|
retryLimit: 10,
|
||||||
},
|
retryBackoff: true
|
||||||
removeOnComplete: true
|
});
|
||||||
});
|
} else {
|
||||||
|
await queueService.queue<QueueName.AuditLog>(QueueName.AuditLog, QueueJobs.AuditLog, data, {
|
||||||
|
removeOnFail: {
|
||||||
|
count: 3
|
||||||
|
},
|
||||||
|
removeOnComplete: true
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (appCfg.SHOULD_INIT_PG_QUEUE) {
|
||||||
|
await queueService.startPg<QueueName.AuditLog>(
|
||||||
|
QueueJobs.AuditLog,
|
||||||
|
async ([job]) => {
|
||||||
|
const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
|
||||||
|
let { orgId } = job.data;
|
||||||
|
const MS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
|
let project;
|
||||||
|
|
||||||
|
if (!orgId) {
|
||||||
|
// it will never be undefined for both org and project id
|
||||||
|
// TODO(akhilmhdh): use caching here in dal to avoid db calls
|
||||||
|
project = await projectDAL.findById(projectId as string);
|
||||||
|
orgId = project.orgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (plan.auditLogsRetentionDays === 0) {
|
||||||
|
// skip inserting if audit log retention is 0 meaning its not supported
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For project actions, set TTL to project-level audit log retention config
|
||||||
|
// This condition ensures that the plan's audit log retention days cannot be bypassed
|
||||||
|
const ttlInDays =
|
||||||
|
project?.auditLogsRetentionDays && project.auditLogsRetentionDays < plan.auditLogsRetentionDays
|
||||||
|
? project.auditLogsRetentionDays
|
||||||
|
: plan.auditLogsRetentionDays;
|
||||||
|
|
||||||
|
const ttl = ttlInDays * MS_IN_DAY;
|
||||||
|
|
||||||
|
const auditLog = await auditLogDAL.create({
|
||||||
|
actor: actor.type,
|
||||||
|
actorMetadata: actor.metadata,
|
||||||
|
userAgent,
|
||||||
|
projectId,
|
||||||
|
projectName: project?.name,
|
||||||
|
ipAddress,
|
||||||
|
orgId,
|
||||||
|
eventType: event.type,
|
||||||
|
expiresAt: new Date(Date.now() + ttl),
|
||||||
|
eventMetadata: event.metadata,
|
||||||
|
userAgentType
|
||||||
|
});
|
||||||
|
|
||||||
|
const logStreams = orgId ? await auditLogStreamDAL.find({ orgId }) : [];
|
||||||
|
await Promise.allSettled(
|
||||||
|
logStreams.map(
|
||||||
|
async ({
|
||||||
|
url,
|
||||||
|
encryptedHeadersTag,
|
||||||
|
encryptedHeadersIV,
|
||||||
|
encryptedHeadersKeyEncoding,
|
||||||
|
encryptedHeadersCiphertext
|
||||||
|
}) => {
|
||||||
|
const streamHeaders =
|
||||||
|
encryptedHeadersIV && encryptedHeadersCiphertext && encryptedHeadersTag
|
||||||
|
? (JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding,
|
||||||
|
iv: encryptedHeadersIV,
|
||||||
|
tag: encryptedHeadersTag,
|
||||||
|
ciphertext: encryptedHeadersCiphertext
|
||||||
|
})
|
||||||
|
) as LogStreamHeaders[])
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const headers: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
||||||
|
|
||||||
|
if (streamHeaders.length)
|
||||||
|
streamHeaders.forEach(({ key, value }) => {
|
||||||
|
headers[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return request.post(url, auditLog, {
|
||||||
|
headers,
|
||||||
|
// request timeout
|
||||||
|
timeout: AUDIT_LOG_STREAM_TIMEOUT,
|
||||||
|
// connection timeout
|
||||||
|
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
batchSize: 1,
|
||||||
|
workerCount: 30,
|
||||||
|
pollingIntervalSeconds: 0.5
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
queueService.start(QueueName.AuditLog, async (job) => {
|
queueService.start(QueueName.AuditLog, async (job) => {
|
||||||
const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
|
const { actor, event, ipAddress, projectId, userAgent, userAgentType } = job.data;
|
||||||
let { orgId } = job.data;
|
let { orgId } = job.data;
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export enum EventType {
|
|||||||
DELETE_SECRETS = "delete-secrets",
|
DELETE_SECRETS = "delete-secrets",
|
||||||
GET_WORKSPACE_KEY = "get-workspace-key",
|
GET_WORKSPACE_KEY = "get-workspace-key",
|
||||||
AUTHORIZE_INTEGRATION = "authorize-integration",
|
AUTHORIZE_INTEGRATION = "authorize-integration",
|
||||||
|
UPDATE_INTEGRATION_AUTH = "update-integration-auth",
|
||||||
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
|
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
|
||||||
CREATE_INTEGRATION = "create-integration",
|
CREATE_INTEGRATION = "create-integration",
|
||||||
DELETE_INTEGRATION = "delete-integration",
|
DELETE_INTEGRATION = "delete-integration",
|
||||||
@@ -371,6 +372,13 @@ interface AuthorizeIntegrationEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UpdateIntegrationAuthEvent {
|
||||||
|
type: EventType.UPDATE_INTEGRATION_AUTH;
|
||||||
|
metadata: {
|
||||||
|
integration: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface UnauthorizeIntegrationEvent {
|
interface UnauthorizeIntegrationEvent {
|
||||||
type: EventType.UNAUTHORIZE_INTEGRATION;
|
type: EventType.UNAUTHORIZE_INTEGRATION;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1805,6 +1813,7 @@ export type Event =
|
|||||||
| DeleteSecretBatchEvent
|
| DeleteSecretBatchEvent
|
||||||
| GetWorkspaceKeyEvent
|
| GetWorkspaceKeyEvent
|
||||||
| AuthorizeIntegrationEvent
|
| AuthorizeIntegrationEvent
|
||||||
|
| UpdateIntegrationAuthEvent
|
||||||
| UnauthorizeIntegrationEvent
|
| UnauthorizeIntegrationEvent
|
||||||
| CreateIntegrationEvent
|
| CreateIntegrationEvent
|
||||||
| DeleteIntegrationEvent
|
| DeleteIntegrationEvent
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ const ElastiCacheUserManager = (credentials: TBasicAWSCredentials, region: strin
|
|||||||
};
|
};
|
||||||
|
|
||||||
const generatePassword = () => {
|
const generatePassword = () => {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
return customAlphabet(charset, 64)();
|
return customAlphabet(charset, 64)();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ export const AwsElastiCacheDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
return { entityId };
|
return { entityId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const renew = async (inputs: unknown, entityId: string) => {
|
const renew = async (_inputs: unknown, entityId: string) => {
|
||||||
// No renewal necessary
|
// No renewal necessary
|
||||||
return { entityId };
|
return { entityId };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const MSFT_GRAPH_API_URL = "https://graph.microsoft.com/v1.0/";
|
|||||||
const MSFT_LOGIN_URL = "https://login.microsoftonline.com";
|
const MSFT_LOGIN_URL = "https://login.microsoftonline.com";
|
||||||
|
|
||||||
const generatePassword = () => {
|
const generatePassword = () => {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
return customAlphabet(charset, 64)();
|
return customAlphabet(charset, 64)();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ export const AzureEntraIDProvider = (): TDynamicProviderFns & {
|
|||||||
return users;
|
return users;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renew = async (inputs: unknown, entityId: string) => {
|
const renew = async (_inputs: unknown, entityId: string) => {
|
||||||
// No renewal necessary
|
// No renewal necessary
|
||||||
return { entityId };
|
return { entityId };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
|
|||||||
import { DynamicSecretCassandraSchema, TDynamicProviderFns } from "./models";
|
import { DynamicSecretCassandraSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = (size = 48) => {
|
const generatePassword = (size = 48) => {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
|||||||
import { DynamicSecretElasticSearchSchema, ElasticSearchAuthTypes, TDynamicProviderFns } from "./models";
|
import { DynamicSecretElasticSearchSchema, ElasticSearchAuthTypes, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = () => {
|
const generatePassword = () => {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
return customAlphabet(charset, 64)();
|
return customAlphabet(charset, 64)();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ export const ElasticSearchProvider = (): TDynamicProviderFns => {
|
|||||||
return { entityId };
|
return { entityId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const renew = async (inputs: unknown, entityId: string) => {
|
const renew = async (_inputs: unknown, entityId: string) => {
|
||||||
// No renewal necessary
|
// No renewal necessary
|
||||||
return { entityId };
|
return { entityId };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
|
|||||||
import { DynamicSecretMongoAtlasSchema, TDynamicProviderFns } from "./models";
|
import { DynamicSecretMongoAtlasSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = (size = 48) => {
|
const generatePassword = (size = 48) => {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
|||||||
import { DynamicSecretMongoDBSchema, TDynamicProviderFns } from "./models";
|
import { DynamicSecretMongoDBSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = (size = 48) => {
|
const generatePassword = (size = 48) => {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
|||||||
import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models";
|
import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = () => {
|
const generatePassword = () => {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
return customAlphabet(charset, 64)();
|
return customAlphabet(charset, 64)();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ export const RabbitMqProvider = (): TDynamicProviderFns => {
|
|||||||
return { entityId };
|
return { entityId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const renew = async (inputs: unknown, entityId: string) => {
|
const renew = async (_inputs: unknown, entityId: string) => {
|
||||||
// No renewal necessary
|
// No renewal necessary
|
||||||
return { entityId };
|
return { entityId };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
|||||||
import { DynamicSecretRedisDBSchema, TDynamicProviderFns } from "./models";
|
import { DynamicSecretRedisDBSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = () => {
|
const generatePassword = () => {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
return customAlphabet(charset, 64)();
|
return customAlphabet(charset, 64)();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { DynamicSecretSnowflakeSchema, TDynamicProviderFns } from "./models";
|
|||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
|
||||||
const generatePassword = (size = 48) => {
|
const generatePassword = (size = 48) => {
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const generatePassword = (provider: SqlProviders) => {
|
|||||||
// oracle has limit of 48 password length
|
// oracle has limit of 48 password length
|
||||||
const size = provider === SqlProviders.Oracle ? 30 : 48;
|
const size = provider === SqlProviders.Oracle ? 30 : 48;
|
||||||
|
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
return customAlphabet(charset, 48)(size);
|
return customAlphabet(charset, 48)(size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ import {
|
|||||||
TUpdateExternalKmsDTO
|
TUpdateExternalKmsDTO
|
||||||
} from "./external-kms-types";
|
} from "./external-kms-types";
|
||||||
import { AwsKmsProviderFactory } from "./providers/aws-kms";
|
import { AwsKmsProviderFactory } from "./providers/aws-kms";
|
||||||
import { ExternalKmsAwsSchema, KmsProviders } from "./providers/model";
|
import { GcpKmsProviderFactory } from "./providers/gcp-kms";
|
||||||
|
import { ExternalKmsAwsSchema, ExternalKmsGcpSchema, KmsProviders, TExternalKmsGcpSchema } from "./providers/model";
|
||||||
|
|
||||||
type TExternalKmsServiceFactoryDep = {
|
type TExternalKmsServiceFactoryDep = {
|
||||||
externalKmsDAL: TExternalKmsDALFactory;
|
externalKmsDAL: TExternalKmsDALFactory;
|
||||||
@@ -78,6 +79,13 @@ export const externalKmsServiceFactory = ({
|
|||||||
await externalKms.validateConnection();
|
await externalKms.validateConnection();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case KmsProviders.Gcp:
|
||||||
|
{
|
||||||
|
const externalKms = await GcpKmsProviderFactory({ inputs: provider.inputs });
|
||||||
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInput = JSON.stringify(provider.inputs);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({ message: "external kms provided is invalid" });
|
throw new BadRequestError({ message: "external kms provided is invalid" });
|
||||||
}
|
}
|
||||||
@@ -88,7 +96,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { cipherTextBlob: encryptedProviderInputs } = orgDataKeyEncryptor({
|
const { cipherTextBlob: encryptedProviderInputs } = orgDataKeyEncryptor({
|
||||||
plainText: Buffer.from(sanitizedProviderInput, "utf8")
|
plainText: Buffer.from(sanitizedProviderInput)
|
||||||
});
|
});
|
||||||
|
|
||||||
const externalKms = await externalKmsDAL.transaction(async (tx) => {
|
const externalKms = await externalKmsDAL.transaction(async (tx) => {
|
||||||
@@ -162,7 +170,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
case KmsProviders.Aws:
|
case KmsProviders.Aws:
|
||||||
{
|
{
|
||||||
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
|
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
|
||||||
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
|
JSON.parse(decryptedProviderInputBlob.toString())
|
||||||
);
|
);
|
||||||
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
||||||
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
|
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
|
||||||
@@ -170,6 +178,17 @@ export const externalKmsServiceFactory = ({
|
|||||||
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case KmsProviders.Gcp:
|
||||||
|
{
|
||||||
|
const decryptedProviderInput = await ExternalKmsGcpSchema.parseAsync(
|
||||||
|
JSON.parse(decryptedProviderInputBlob.toString())
|
||||||
|
);
|
||||||
|
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
||||||
|
const externalKms = await GcpKmsProviderFactory({ inputs: updatedProviderInput });
|
||||||
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({ message: "external kms provided is invalid" });
|
throw new BadRequestError({ message: "external kms provided is invalid" });
|
||||||
}
|
}
|
||||||
@@ -178,7 +197,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
let encryptedProviderInputs: Buffer | undefined;
|
let encryptedProviderInputs: Buffer | undefined;
|
||||||
if (sanitizedProviderInput) {
|
if (sanitizedProviderInput) {
|
||||||
const { cipherTextBlob } = orgDataKeyEncryptor({
|
const { cipherTextBlob } = orgDataKeyEncryptor({
|
||||||
plainText: Buffer.from(sanitizedProviderInput, "utf8")
|
plainText: Buffer.from(sanitizedProviderInput)
|
||||||
});
|
});
|
||||||
encryptedProviderInputs = cipherTextBlob;
|
encryptedProviderInputs = cipherTextBlob;
|
||||||
}
|
}
|
||||||
@@ -271,10 +290,17 @@ export const externalKmsServiceFactory = ({
|
|||||||
switch (externalKmsDoc.provider) {
|
switch (externalKmsDoc.provider) {
|
||||||
case KmsProviders.Aws: {
|
case KmsProviders.Aws: {
|
||||||
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
|
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
|
||||||
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
|
JSON.parse(decryptedProviderInputBlob.toString())
|
||||||
);
|
);
|
||||||
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
|
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
|
||||||
}
|
}
|
||||||
|
case KmsProviders.Gcp: {
|
||||||
|
const decryptedProviderInput = await ExternalKmsGcpSchema.parseAsync(
|
||||||
|
JSON.parse(decryptedProviderInputBlob.toString())
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({ message: "external kms provided is invalid" });
|
throw new BadRequestError({ message: "external kms provided is invalid" });
|
||||||
}
|
}
|
||||||
@@ -312,21 +338,34 @@ export const externalKmsServiceFactory = ({
|
|||||||
switch (externalKmsDoc.provider) {
|
switch (externalKmsDoc.provider) {
|
||||||
case KmsProviders.Aws: {
|
case KmsProviders.Aws: {
|
||||||
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
|
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
|
||||||
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
|
JSON.parse(decryptedProviderInputBlob.toString())
|
||||||
);
|
);
|
||||||
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
|
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
|
||||||
}
|
}
|
||||||
|
case KmsProviders.Gcp: {
|
||||||
|
const decryptedProviderInput = await ExternalKmsGcpSchema.parseAsync(
|
||||||
|
JSON.parse(decryptedProviderInputBlob.toString())
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({ message: "external kms provided is invalid" });
|
throw new BadRequestError({ message: "external kms provided is invalid" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchGcpKeys = async ({ credential, gcpRegion }: Pick<TExternalKmsGcpSchema, "credential" | "gcpRegion">) => {
|
||||||
|
const externalKms = await GcpKmsProviderFactory({ inputs: { credential, gcpRegion, keyName: "" } });
|
||||||
|
return externalKms.getKeysList();
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
create,
|
create,
|
||||||
updateById,
|
updateById,
|
||||||
deleteById,
|
deleteById,
|
||||||
list,
|
list,
|
||||||
findById,
|
findById,
|
||||||
findByName
|
findByName,
|
||||||
|
fetchGcpKeys
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { KeyManagementServiceClient } from "@google-cloud/kms";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
|
import { ExternalKmsGcpSchema, TExternalKmsGcpClientSchema, TExternalKmsProviderFns } from "./model";
|
||||||
|
|
||||||
|
const getGcpKmsClient = async ({ credential, gcpRegion }: TExternalKmsGcpClientSchema) => {
|
||||||
|
const gcpKmsClient = new KeyManagementServiceClient({
|
||||||
|
credentials: credential
|
||||||
|
});
|
||||||
|
const projectId = credential.project_id;
|
||||||
|
const locationName = gcpKmsClient.locationPath(projectId, gcpRegion);
|
||||||
|
|
||||||
|
return {
|
||||||
|
gcpKmsClient,
|
||||||
|
locationName
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type GcpKmsProviderArgs = {
|
||||||
|
inputs: unknown;
|
||||||
|
};
|
||||||
|
type TGcpKmsProviderFactoryReturn = TExternalKmsProviderFns & {
|
||||||
|
getKeysList: () => Promise<{ keys: string[] }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GcpKmsProviderFactory = async ({ inputs }: GcpKmsProviderArgs): Promise<TGcpKmsProviderFactoryReturn> => {
|
||||||
|
const { credential, gcpRegion, keyName } = await ExternalKmsGcpSchema.parseAsync(inputs);
|
||||||
|
const { gcpKmsClient, locationName } = await getGcpKmsClient({
|
||||||
|
credential,
|
||||||
|
gcpRegion
|
||||||
|
});
|
||||||
|
|
||||||
|
const validateConnection = async () => {
|
||||||
|
try {
|
||||||
|
await gcpKmsClient.listKeyRings({
|
||||||
|
parent: locationName
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Cannot connect to GCP KMS"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used when adding the KMS to fetch the list of keys in specified region
|
||||||
|
const getKeysList = async () => {
|
||||||
|
try {
|
||||||
|
const [keyRings] = await gcpKmsClient.listKeyRings({
|
||||||
|
parent: locationName
|
||||||
|
});
|
||||||
|
|
||||||
|
const validKeyRings = keyRings
|
||||||
|
.filter(
|
||||||
|
(keyRing): keyRing is { name: string } =>
|
||||||
|
keyRing !== null && typeof keyRing === "object" && "name" in keyRing && typeof keyRing.name === "string"
|
||||||
|
)
|
||||||
|
.map((keyRing) => keyRing.name);
|
||||||
|
const keyList: string[] = [];
|
||||||
|
const keyListPromises = validKeyRings.map((keyRingName) =>
|
||||||
|
gcpKmsClient
|
||||||
|
.listCryptoKeys({
|
||||||
|
parent: keyRingName
|
||||||
|
})
|
||||||
|
.then(([cryptoKeys]) =>
|
||||||
|
cryptoKeys
|
||||||
|
.filter(
|
||||||
|
(key): key is { name: string } =>
|
||||||
|
key !== null && typeof key === "object" && "name" in key && typeof key.name === "string"
|
||||||
|
)
|
||||||
|
.map((key) => key.name)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const cryptoKeyLists = await Promise.all(keyListPromises);
|
||||||
|
keyList.push(...cryptoKeyLists.flat());
|
||||||
|
return { keys: keyList };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Could not validate GCP KMS connection and credentials");
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Could not validate GCP KMS connection and credentials",
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const encrypt = async (data: Buffer) => {
|
||||||
|
const encryptedText = await gcpKmsClient.encrypt({
|
||||||
|
name: keyName,
|
||||||
|
plaintext: data
|
||||||
|
});
|
||||||
|
if (!encryptedText[0].ciphertext) throw new Error("encryption failed");
|
||||||
|
return { encryptedBlob: Buffer.from(encryptedText[0].ciphertext) };
|
||||||
|
};
|
||||||
|
|
||||||
|
const decrypt = async (encryptedBlob: Buffer) => {
|
||||||
|
const decryptedText = await gcpKmsClient.decrypt({
|
||||||
|
name: keyName,
|
||||||
|
ciphertext: encryptedBlob
|
||||||
|
});
|
||||||
|
if (!decryptedText[0].plaintext) throw new Error("decryption failed");
|
||||||
|
return { data: Buffer.from(decryptedText[0].plaintext) };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
validateConnection,
|
||||||
|
getKeysList,
|
||||||
|
encrypt,
|
||||||
|
decrypt
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,13 +1,23 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export enum KmsProviders {
|
export enum KmsProviders {
|
||||||
Aws = "aws"
|
Aws = "aws",
|
||||||
|
Gcp = "gcp"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum KmsAwsCredentialType {
|
export enum KmsAwsCredentialType {
|
||||||
AssumeRole = "assume-role",
|
AssumeRole = "assume-role",
|
||||||
AccessKey = "access-key"
|
AccessKey = "access-key"
|
||||||
}
|
}
|
||||||
|
// Google uses snake_case for their enum values and we need to match that
|
||||||
|
export enum KmsGcpCredentialType {
|
||||||
|
ServiceAccount = "service_account"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum KmsGcpKeyFetchAuthType {
|
||||||
|
Credential = "credential",
|
||||||
|
Kms = "kmsId"
|
||||||
|
}
|
||||||
|
|
||||||
export const ExternalKmsAwsSchema = z.object({
|
export const ExternalKmsAwsSchema = z.object({
|
||||||
credential: z
|
credential: z
|
||||||
@@ -42,14 +52,44 @@ export const ExternalKmsAwsSchema = z.object({
|
|||||||
});
|
});
|
||||||
export type TExternalKmsAwsSchema = z.infer<typeof ExternalKmsAwsSchema>;
|
export type TExternalKmsAwsSchema = z.infer<typeof ExternalKmsAwsSchema>;
|
||||||
|
|
||||||
|
export const ExternalKmsGcpCredentialSchema = z.object({
|
||||||
|
type: z.literal(KmsGcpCredentialType.ServiceAccount),
|
||||||
|
project_id: z.string().min(1),
|
||||||
|
private_key_id: z.string().min(1),
|
||||||
|
private_key: z.string().min(1),
|
||||||
|
client_email: z.string().min(1),
|
||||||
|
client_id: z.string().min(1),
|
||||||
|
auth_uri: z.string().min(1),
|
||||||
|
token_uri: z.string().min(1),
|
||||||
|
auth_provider_x509_cert_url: z.string().min(1),
|
||||||
|
client_x509_cert_url: z.string().min(1),
|
||||||
|
universe_domain: z.string().min(1)
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TExternalKmsGcpCredentialSchema = z.infer<typeof ExternalKmsGcpCredentialSchema>;
|
||||||
|
|
||||||
|
export const ExternalKmsGcpSchema = z.object({
|
||||||
|
credential: ExternalKmsGcpCredentialSchema.describe("GCP Service Account JSON credential to connect"),
|
||||||
|
gcpRegion: z.string().trim().describe("GCP region where the KMS key is located"),
|
||||||
|
keyName: z.string().trim().describe("GCP key name")
|
||||||
|
});
|
||||||
|
export type TExternalKmsGcpSchema = z.infer<typeof ExternalKmsGcpSchema>;
|
||||||
|
|
||||||
|
const ExternalKmsGcpClientSchema = ExternalKmsGcpSchema.pick({ gcpRegion: true }).extend({
|
||||||
|
credential: ExternalKmsGcpCredentialSchema
|
||||||
|
});
|
||||||
|
export type TExternalKmsGcpClientSchema = z.infer<typeof ExternalKmsGcpClientSchema>;
|
||||||
|
|
||||||
// The root schema of the JSON
|
// The root schema of the JSON
|
||||||
export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
|
export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema })
|
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema }),
|
||||||
|
z.object({ type: z.literal(KmsProviders.Gcp), inputs: ExternalKmsGcpSchema })
|
||||||
]);
|
]);
|
||||||
export type TExternalKmsInputSchema = z.infer<typeof ExternalKmsInputSchema>;
|
export type TExternalKmsInputSchema = z.infer<typeof ExternalKmsInputSchema>;
|
||||||
|
|
||||||
export const ExternalKmsInputUpdateSchema = z.discriminatedUnion("type", [
|
export const ExternalKmsInputUpdateSchema = z.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema.partial() })
|
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema.partial() }),
|
||||||
|
z.object({ type: z.literal(KmsProviders.Gcp), inputs: ExternalKmsGcpSchema.partial() })
|
||||||
]);
|
]);
|
||||||
export type TExternalKmsInputUpdateSchema = z.infer<typeof ExternalKmsInputUpdateSchema>;
|
export type TExternalKmsInputUpdateSchema = z.infer<typeof ExternalKmsInputUpdateSchema>;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { TableName, TGroups } from "@app/db/schemas";
|
|||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
|
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
|
||||||
|
|
||||||
|
import { EFilterReturnedUsers } from "./group-types";
|
||||||
|
|
||||||
export type TGroupDALFactory = ReturnType<typeof groupDALFactory>;
|
export type TGroupDALFactory = ReturnType<typeof groupDALFactory>;
|
||||||
|
|
||||||
export const groupDALFactory = (db: TDbClient) => {
|
export const groupDALFactory = (db: TDbClient) => {
|
||||||
@@ -66,7 +68,8 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
offset = 0,
|
offset = 0,
|
||||||
limit,
|
limit,
|
||||||
username, // depreciated in favor of search
|
username, // depreciated in favor of search
|
||||||
search
|
search,
|
||||||
|
filter
|
||||||
}: {
|
}: {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
@@ -74,6 +77,7 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
username?: string;
|
username?: string;
|
||||||
search?: string;
|
search?: string;
|
||||||
|
filter?: EFilterReturnedUsers;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const query = db
|
const query = db
|
||||||
@@ -90,6 +94,7 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.OrgMembership),
|
db.ref("id").withSchema(TableName.OrgMembership),
|
||||||
db.ref("groupId").withSchema(TableName.UserGroupMembership),
|
db.ref("groupId").withSchema(TableName.UserGroupMembership),
|
||||||
|
db.ref("createdAt").withSchema(TableName.UserGroupMembership).as("joinedGroupAt"),
|
||||||
db.ref("email").withSchema(TableName.Users),
|
db.ref("email").withSchema(TableName.Users),
|
||||||
db.ref("username").withSchema(TableName.Users),
|
db.ref("username").withSchema(TableName.Users),
|
||||||
db.ref("firstName").withSchema(TableName.Users),
|
db.ref("firstName").withSchema(TableName.Users),
|
||||||
@@ -111,17 +116,37 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
void query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`);
|
void query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (filter) {
|
||||||
|
case EFilterReturnedUsers.EXISTING_MEMBERS:
|
||||||
|
void query.andWhere(`${TableName.UserGroupMembership}.createdAt`, "is not", null);
|
||||||
|
break;
|
||||||
|
case EFilterReturnedUsers.NON_MEMBERS:
|
||||||
|
void query.andWhere(`${TableName.UserGroupMembership}.createdAt`, "is", null);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
const members = await query;
|
const members = await query;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
members: members.map(
|
members: members.map(
|
||||||
({ email, username: memberUsername, firstName, lastName, userId, groupId: memberGroupId }) => ({
|
({
|
||||||
|
email,
|
||||||
|
username: memberUsername,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
userId,
|
||||||
|
groupId: memberGroupId,
|
||||||
|
joinedGroupAt
|
||||||
|
}) => ({
|
||||||
id: userId,
|
id: userId,
|
||||||
email,
|
email,
|
||||||
username: memberUsername,
|
username: memberUsername,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
isPartOfGroup: !!memberGroupId
|
isPartOfGroup: !!memberGroupId,
|
||||||
|
joinedGroupAt
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
// @ts-expect-error col select is raw and not strongly typed
|
// @ts-expect-error col select is raw and not strongly typed
|
||||||
|
|||||||
@@ -222,7 +222,8 @@ export const groupServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
search
|
search,
|
||||||
|
filter
|
||||||
}: TListGroupUsersDTO) => {
|
}: TListGroupUsersDTO) => {
|
||||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
@@ -251,7 +252,8 @@ export const groupServiceFactory = ({
|
|||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
username,
|
username,
|
||||||
search
|
search,
|
||||||
|
filter
|
||||||
});
|
});
|
||||||
|
|
||||||
return { users: members, totalCount };
|
return { users: members, totalCount };
|
||||||
@@ -283,8 +285,8 @@ export const groupServiceFactory = ({
|
|||||||
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
||||||
|
|
||||||
// check if user has broader or equal to privileges than group
|
// check if user has broader or equal to privileges than group
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, groupRolePermission);
|
const hasRequiredPrivileges = isAtLeastAsPrivileged(permission, groupRolePermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPrivileges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to add user to more privileged group" });
|
throw new ForbiddenRequestError({ message: "Failed to add user to more privileged group" });
|
||||||
|
|
||||||
const user = await userDAL.findOne({ username });
|
const user = await userDAL.findOne({ username });
|
||||||
@@ -338,8 +340,8 @@ export const groupServiceFactory = ({
|
|||||||
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
||||||
|
|
||||||
// check if user has broader or equal to privileges than group
|
// check if user has broader or equal to privileges than group
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, groupRolePermission);
|
const hasRequiredPrivileges = isAtLeastAsPrivileged(permission, groupRolePermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPrivileges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to delete user from more privileged group" });
|
throw new ForbiddenRequestError({ message: "Failed to delete user from more privileged group" });
|
||||||
|
|
||||||
const user = await userDAL.findOne({ username });
|
const user = await userDAL.findOne({ username });
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export type TListGroupUsersDTO = {
|
|||||||
limit: number;
|
limit: number;
|
||||||
username?: string;
|
username?: string;
|
||||||
search?: string;
|
search?: string;
|
||||||
|
filter?: EFilterReturnedUsers;
|
||||||
} & TGenericPermission;
|
} & TGenericPermission;
|
||||||
|
|
||||||
export type TAddUserToGroupDTO = {
|
export type TAddUserToGroupDTO = {
|
||||||
@@ -101,3 +102,8 @@ export type TConvertPendingGroupAdditionsToGroupMemberships = {
|
|||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum EFilterReturnedUsers {
|
||||||
|
EXISTING_MEMBERS = "existingMembers",
|
||||||
|
NON_MEMBERS = "nonMembers"
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import { packRules } from "@casl/ability/extra";
|
import { packRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
@@ -62,7 +62,10 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
|
);
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityId,
|
identityId,
|
||||||
@@ -139,7 +142,10 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
|
);
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
identityProjectMembership.identityId,
|
||||||
@@ -216,7 +222,10 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
|
);
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
identityProjectMembership.identityId,
|
||||||
@@ -258,7 +267,10 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...identityPrivilege,
|
...identityPrivilege,
|
||||||
@@ -289,7 +301,10 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
|
);
|
||||||
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
slug,
|
slug,
|
||||||
@@ -321,7 +336,10 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
|
);
|
||||||
|
|
||||||
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find(
|
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
|
||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
@@ -69,7 +69,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
|
);
|
||||||
|
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityId,
|
identityId,
|
||||||
@@ -146,7 +150,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
|
);
|
||||||
|
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
@@ -241,7 +249,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
|
);
|
||||||
|
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
identityProjectMembership.identityId,
|
||||||
@@ -294,7 +306,10 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
|
);
|
||||||
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
slug,
|
slug,
|
||||||
@@ -333,7 +348,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
|
);
|
||||||
|
|
||||||
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({
|
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({
|
||||||
projectMembershipId: identityProjectMembership.id
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ export type SecretImportSubjectFields = {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type IdentityManagementSubjectFields = {
|
||||||
|
identityId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ProjectPermissionSet =
|
export type ProjectPermissionSet =
|
||||||
| [
|
| [
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -124,7 +128,10 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretRotation]
|
| [ProjectPermissionActions, ProjectPermissionSub.SecretRotation]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Identity]
|
| [
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSub.Identity | (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
|
||||||
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
||||||
@@ -219,6 +226,21 @@ const SecretConditionV2Schema = z
|
|||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
|
const IdentityManagementConditionSchema = z
|
||||||
|
.object({
|
||||||
|
identityId: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
const GeneralPermissionSchema = [
|
const GeneralPermissionSchema = [
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
||||||
@@ -268,12 +290,6 @@ const GeneralPermissionSchema = [
|
|||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
z.object({
|
|
||||||
subject: z.literal(ProjectPermissionSub.Identity).describe("The entity this permission pertains to."),
|
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
|
||||||
"Describe what action an entity can take."
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.ServiceTokens).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.ServiceTokens).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
@@ -401,6 +417,12 @@ export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
|
|||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Identity).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
...GeneralPermissionSchema
|
...GeneralPermissionSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -445,6 +467,16 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
|||||||
"When specified, only matching conditions will be allowed to access given resource."
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
).optional()
|
).optional()
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Identity).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
conditions: IdentityManagementConditionSchema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
...GeneralPermissionSchema
|
...GeneralPermissionSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -736,26 +768,26 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
[ProjectPermissionSub.Secrets, ProjectPermissionSub.SecretImports, ProjectPermissionSub.SecretFolders].forEach(
|
[ProjectPermissionSub.Secrets, ProjectPermissionSub.SecretImports, ProjectPermissionSub.SecretFolders].forEach(
|
||||||
(subject) => {
|
(subject) => {
|
||||||
if (canWrite) {
|
if (canWrite) {
|
||||||
// TODO: @Akhi
|
|
||||||
// @ts-expect-error type
|
|
||||||
can(ProjectPermissionActions.Edit, subject, {
|
can(ProjectPermissionActions.Edit, subject, {
|
||||||
|
// TODO: @Akhi
|
||||||
|
// @ts-expect-error type
|
||||||
secretPath: { $glob: secretPath },
|
secretPath: { $glob: secretPath },
|
||||||
environment
|
environment
|
||||||
});
|
});
|
||||||
// @ts-expect-error type
|
|
||||||
can(ProjectPermissionActions.Create, subject, {
|
can(ProjectPermissionActions.Create, subject, {
|
||||||
|
// @ts-expect-error type
|
||||||
secretPath: { $glob: secretPath },
|
secretPath: { $glob: secretPath },
|
||||||
environment
|
environment
|
||||||
});
|
});
|
||||||
// @ts-expect-error type
|
|
||||||
can(ProjectPermissionActions.Delete, subject, {
|
can(ProjectPermissionActions.Delete, subject, {
|
||||||
|
// @ts-expect-error type
|
||||||
secretPath: { $glob: secretPath },
|
secretPath: { $glob: secretPath },
|
||||||
environment
|
environment
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (canRead) {
|
if (canRead) {
|
||||||
// @ts-expect-error type
|
|
||||||
can(ProjectPermissionActions.Read, subject, {
|
can(ProjectPermissionActions.Read, subject, {
|
||||||
|
// @ts-expect-error type
|
||||||
secretPath: { $glob: secretPath },
|
secretPath: { $glob: secretPath },
|
||||||
environment
|
environment
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -177,5 +177,10 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...secretApprovalPolicyOrm, findById, find };
|
const softDeleteById = async (policyId: string, tx?: Knex) => {
|
||||||
|
const softDeletedPolicy = await secretApprovalPolicyOrm.updateById(policyId, { deletedAt: new Date() }, tx);
|
||||||
|
return softDeletedPolicy;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...secretApprovalPolicyOrm, findById, find, softDeleteById };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
|
|
||||||
import { ApproverType } from "../access-approval-policy/access-approval-policy-types";
|
import { ApproverType } from "../access-approval-policy/access-approval-policy-types";
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
|
import { TSecretApprovalRequestDALFactory } from "../secret-approval-request/secret-approval-request-dal";
|
||||||
|
import { RequestState } from "../secret-approval-request/secret-approval-request-types";
|
||||||
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
||||||
import {
|
import {
|
||||||
@@ -34,6 +36,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
|
|||||||
userDAL: Pick<TUserDALFactory, "find">;
|
userDAL: Pick<TUserDALFactory, "find">;
|
||||||
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
||||||
@@ -44,7 +47,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
secretApprovalPolicyApproverDAL,
|
secretApprovalPolicyApproverDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
licenseService
|
licenseService,
|
||||||
|
secretApprovalRequestDAL
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
const createSecretApprovalPolicy = async ({
|
const createSecretApprovalPolicy = async ({
|
||||||
name,
|
name,
|
||||||
@@ -301,8 +305,16 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await secretApprovalPolicyDAL.deleteById(secretPolicyId);
|
const deletedPolicy = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
return sapPolicy;
|
await secretApprovalRequestDAL.update(
|
||||||
|
{ policyId: secretPolicyId, status: RequestState.Open },
|
||||||
|
{ status: RequestState.Closed },
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
const updatedPolicy = await secretApprovalPolicyDAL.softDeleteById(secretPolicyId, tx);
|
||||||
|
return updatedPolicy;
|
||||||
|
});
|
||||||
|
return { ...deletedPolicy, projectId: sapPolicy.projectId, environment: sapPolicy.environment };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecretApprovalPolicyByProjectId = async ({
|
const getSecretApprovalPolicyByProjectId = async ({
|
||||||
@@ -321,7 +333,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId });
|
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
|
||||||
return sapPolicies;
|
return sapPolicies;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -334,7 +346,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const policies = await secretApprovalPolicyDAL.find({ envId: env.id });
|
const policies = await secretApprovalPolicyDAL.find({ envId: env.id, deletedAt: null });
|
||||||
if (!policies.length) return;
|
if (!policies.length) return;
|
||||||
// this will filter policies either without scoped to secret path or the one that matches with secret path
|
// this will filter policies either without scoped to secret path or the one that matches with secret path
|
||||||
const policiesFilteredByPath = policies.filter(
|
const policiesFilteredByPath = policies.filter(
|
||||||
|
|||||||
@@ -111,7 +111,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||||
tx.ref("envId").withSchema(TableName.SecretApprovalPolicy).as("policyEnvId"),
|
tx.ref("envId").withSchema(TableName.SecretApprovalPolicy).as("policyEnvId"),
|
||||||
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals")
|
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||||
|
tx.ref("deletedAt").withSchema(TableName.SecretApprovalPolicy).as("policyDeletedAt")
|
||||||
);
|
);
|
||||||
|
|
||||||
const findById = async (id: string, tx?: Knex) => {
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
@@ -147,7 +148,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
approvals: el.policyApprovals,
|
approvals: el.policyApprovals,
|
||||||
secretPath: el.policySecretPath,
|
secretPath: el.policySecretPath,
|
||||||
enforcementLevel: el.policyEnforcementLevel,
|
enforcementLevel: el.policyEnforcementLevel,
|
||||||
envId: el.policyEnvId
|
envId: el.policyEnvId,
|
||||||
|
deletedAt: el.policyDeletedAt
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
@@ -222,6 +224,11 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalRequest}.policyId`,
|
`${TableName.SecretApprovalRequest}.policyId`,
|
||||||
`${TableName.SecretApprovalPolicyApprover}.policyId`
|
`${TableName.SecretApprovalPolicyApprover}.policyId`
|
||||||
)
|
)
|
||||||
|
.join(
|
||||||
|
TableName.SecretApprovalPolicy,
|
||||||
|
`${TableName.SecretApprovalRequest}.policyId`,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`
|
||||||
|
)
|
||||||
.where({ projectId })
|
.where({ projectId })
|
||||||
.andWhere(
|
.andWhere(
|
||||||
(bd) =>
|
(bd) =>
|
||||||
@@ -229,6 +236,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
.where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId)
|
.where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId)
|
||||||
.orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId)
|
.orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId)
|
||||||
)
|
)
|
||||||
|
.andWhere((bd) => void bd.where(`${TableName.SecretApprovalPolicy}.deletedAt`, null))
|
||||||
.select("status", `${TableName.SecretApprovalRequest}.id`)
|
.select("status", `${TableName.SecretApprovalRequest}.id`)
|
||||||
.groupBy(`${TableName.SecretApprovalRequest}.id`, "status")
|
.groupBy(`${TableName.SecretApprovalRequest}.id`, "status")
|
||||||
.count("status")
|
.count("status")
|
||||||
|
|||||||
@@ -232,10 +232,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
const encrypedSecrets = await secretApprovalRequestSecretDAL.findByRequestIdBridgeSecretV2(
|
const encryptedSecrets = await secretApprovalRequestSecretDAL.findByRequestIdBridgeSecretV2(
|
||||||
secretApprovalRequest.id
|
secretApprovalRequest.id
|
||||||
);
|
);
|
||||||
secrets = encrypedSecrets.map((el) => ({
|
secrets = encryptedSecrets.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
secretKey: el.key,
|
secretKey: el.key,
|
||||||
id: el.id,
|
id: el.id,
|
||||||
@@ -274,8 +274,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
if (!botKey) throw new NotFoundError({ message: `Project bot key not found`, name: "BotKeyNotFound" }); // CLI depends on this error message. TODO(daniel): Make API check for name BotKeyNotFound instead of message
|
if (!botKey) throw new NotFoundError({ message: `Project bot key not found`, name: "BotKeyNotFound" }); // CLI depends on this error message. TODO(daniel): Make API check for name BotKeyNotFound instead of message
|
||||||
const encrypedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
const encryptedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
||||||
secrets = encrypedSecrets.map((el) => ({
|
secrets = encryptedSecrets.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
...decryptSecretWithBot(el, botKey),
|
...decryptSecretWithBot(el, botKey),
|
||||||
secret: el.secret
|
secret: el.secret
|
||||||
@@ -323,6 +323,12 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
|
if (policy.deletedAt) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The policy associated with this secret approval request has been deleted."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -383,6 +389,12 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
|
if (policy.deletedAt) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The policy associated with this secret approval request has been deleted."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -433,6 +445,12 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { policy, folderId, projectId } = secretApprovalRequest;
|
const { policy, folderId, projectId } = secretApprovalRequest;
|
||||||
|
if (policy.deletedAt) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "The policy associated with this secret approval request has been deleted."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ export const GROUPS = {
|
|||||||
offset: "The offset to start from. If you enter 10, it will start from the 10th user.",
|
offset: "The offset to start from. If you enter 10, it will start from the 10th user.",
|
||||||
limit: "The number of users to return.",
|
limit: "The number of users to return.",
|
||||||
username: "The username to search for.",
|
username: "The username to search for.",
|
||||||
search: "The text string that user email or name will be filtered by."
|
search: "The text string that user email or name will be filtered by.",
|
||||||
|
filterUsers:
|
||||||
|
"Whether to filter the list of returned users. 'existingMembers' will only return existing users in the group, 'nonMembers' will only return users not in the group, undefined will return all users in the organization."
|
||||||
},
|
},
|
||||||
ADD_USER: {
|
ADD_USER: {
|
||||||
id: "The ID of the group to add the user to.",
|
id: "The ID of the group to add the user to.",
|
||||||
@@ -1043,6 +1045,9 @@ export const INTEGRATION_AUTH = {
|
|||||||
DELETE_BY_ID: {
|
DELETE_BY_ID: {
|
||||||
integrationAuthId: "The ID of integration authentication object to delete."
|
integrationAuthId: "The ID of integration authentication object to delete."
|
||||||
},
|
},
|
||||||
|
UPDATE_BY_ID: {
|
||||||
|
integrationAuthId: "The ID of integration authentication object to update."
|
||||||
|
},
|
||||||
CREATE_ACCESS_TOKEN: {
|
CREATE_ACCESS_TOKEN: {
|
||||||
workspaceId: "The ID of the project to create the integration auth for.",
|
workspaceId: "The ID of the project to create the integration auth for.",
|
||||||
integration: "The slug of integration for the auth object.",
|
integration: "The slug of integration for the auth object.",
|
||||||
@@ -1099,11 +1104,13 @@ export const INTEGRATION = {
|
|||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
integrationId: "The ID of the integration object.",
|
integrationId: "The ID of the integration object.",
|
||||||
|
region: "AWS region to sync secrets to.",
|
||||||
app: "The name of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
app: "The name of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
||||||
appId:
|
appId:
|
||||||
"The ID of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
"The ID of the external integration providers app entity that you want to sync secrets with. Used in Netlify, GitHub, Vercel integrations.",
|
||||||
isActive: "Whether the integration should be active or disabled.",
|
isActive: "Whether the integration should be active or disabled.",
|
||||||
secretPath: "The path of the secrets to sync secrets from.",
|
secretPath: "The path of the secrets to sync secrets from.",
|
||||||
|
path: "Path to save the synced secrets. Used by Gitlab, AWS Parameter Store, Vault.",
|
||||||
owner: "External integration providers service entity owner. Used in Github.",
|
owner: "External integration providers service entity owner. Used in Github.",
|
||||||
targetEnvironment:
|
targetEnvironment:
|
||||||
"The target environment of the integration provider. Used in cloudflare pages, TeamCity, Gitlab integrations.",
|
"The target environment of the integration provider. Used in cloudflare pages, TeamCity, Gitlab integrations.",
|
||||||
|
|||||||
@@ -178,7 +178,10 @@ const envSchema = z
|
|||||||
HSM_LIB_PATH: zpStr(z.string().optional()),
|
HSM_LIB_PATH: zpStr(z.string().optional()),
|
||||||
HSM_PIN: zpStr(z.string().optional()),
|
HSM_PIN: zpStr(z.string().optional()),
|
||||||
HSM_KEY_LABEL: zpStr(z.string().optional()),
|
HSM_KEY_LABEL: zpStr(z.string().optional()),
|
||||||
HSM_SLOT: z.coerce.number().optional().default(0)
|
HSM_SLOT: z.coerce.number().optional().default(0),
|
||||||
|
|
||||||
|
USE_PG_QUEUE: zodStrBool.default("false"),
|
||||||
|
SHOULD_INIT_PG_QUEUE: zodStrBool.default("false")
|
||||||
})
|
})
|
||||||
// To ensure that basic encryption is always possible.
|
// To ensure that basic encryption is always possible.
|
||||||
.refine(
|
.refine(
|
||||||
|
|||||||
@@ -89,9 +89,9 @@ const redactedKeys = [
|
|||||||
|
|
||||||
const UNKNOWN_REQUEST_ID = "UNKNOWN_REQUEST_ID";
|
const UNKNOWN_REQUEST_ID = "UNKNOWN_REQUEST_ID";
|
||||||
|
|
||||||
const extractRequestId = () => {
|
const extractReqId = () => {
|
||||||
try {
|
try {
|
||||||
return requestContext.get("requestId") || UNKNOWN_REQUEST_ID;
|
return requestContext.get("reqId") || UNKNOWN_REQUEST_ID;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("failed to get request context", err);
|
console.log("failed to get request context", err);
|
||||||
return UNKNOWN_REQUEST_ID;
|
return UNKNOWN_REQUEST_ID;
|
||||||
@@ -133,22 +133,22 @@ export const initLogger = async () => {
|
|||||||
const wrapLogger = (originalLogger: Logger): CustomLogger => {
|
const wrapLogger = (originalLogger: Logger): CustomLogger => {
|
||||||
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
|
||||||
originalLogger.info = (obj: unknown, msg?: string, ...args: any[]) => {
|
originalLogger.info = (obj: unknown, msg?: string, ...args: any[]) => {
|
||||||
return originalLogger.child({ requestId: extractRequestId() }).info(obj, msg, ...args);
|
return originalLogger.child({ reqId: extractReqId() }).info(obj, msg, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
|
||||||
originalLogger.error = (obj: unknown, msg?: string, ...args: any[]) => {
|
originalLogger.error = (obj: unknown, msg?: string, ...args: any[]) => {
|
||||||
return originalLogger.child({ requestId: extractRequestId() }).error(obj, msg, ...args);
|
return originalLogger.child({ reqId: extractReqId() }).error(obj, msg, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
|
||||||
originalLogger.warn = (obj: unknown, msg?: string, ...args: any[]) => {
|
originalLogger.warn = (obj: unknown, msg?: string, ...args: any[]) => {
|
||||||
return originalLogger.child({ requestId: extractRequestId() }).warn(obj, msg, ...args);
|
return originalLogger.child({ reqId: extractReqId() }).warn(obj, msg, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line no-param-reassign, @typescript-eslint/no-explicit-any
|
||||||
originalLogger.debug = (obj: unknown, msg?: string, ...args: any[]) => {
|
originalLogger.debug = (obj: unknown, msg?: string, ...args: any[]) => {
|
||||||
return originalLogger.child({ requestId: extractRequestId() }).debug(obj, msg, ...args);
|
return originalLogger.child({ reqId: extractReqId() }).debug(obj, msg, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
return originalLogger;
|
return originalLogger;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import "./lib/telemetry/instrumentation";
|
import "./lib/telemetry/instrumentation";
|
||||||
|
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
import { Redis } from "ioredis";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns";
|
import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns";
|
||||||
@@ -55,13 +56,21 @@ const run = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const smtp = smtpServiceFactory(formatSmtpConfig());
|
const smtp = smtpServiceFactory(formatSmtpConfig());
|
||||||
const queue = queueServiceFactory(appCfg.REDIS_URL);
|
|
||||||
|
const queue = queueServiceFactory(appCfg.REDIS_URL, {
|
||||||
|
dbConnectionUrl: appCfg.DB_CONNECTION_URI,
|
||||||
|
dbRootCert: appCfg.DB_ROOT_CERT
|
||||||
|
});
|
||||||
|
|
||||||
|
await queue.initialize();
|
||||||
|
|
||||||
const keyStore = keyStoreFactory(appCfg.REDIS_URL);
|
const keyStore = keyStoreFactory(appCfg.REDIS_URL);
|
||||||
|
const redis = new Redis(appCfg.REDIS_URL);
|
||||||
|
|
||||||
const hsmModule = initializeHsmModule();
|
const hsmModule = initializeHsmModule();
|
||||||
hsmModule.initialize();
|
hsmModule.initialize();
|
||||||
|
|
||||||
const server = await main({ db, auditLogDb, hsmModule: hsmModule.getModule(), smtp, logger, queue, keyStore });
|
const server = await main({ db, auditLogDb, hsmModule: hsmModule.getModule(), smtp, logger, queue, keyStore, redis });
|
||||||
const bootstrap = await bootstrapCheck({ db });
|
const bootstrap = await bootstrapCheck({ db });
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq";
|
import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq";
|
||||||
import Redis from "ioredis";
|
import Redis from "ioredis";
|
||||||
|
import PgBoss, { WorkOptions } from "pg-boss";
|
||||||
|
|
||||||
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
@@ -7,6 +8,8 @@ import {
|
|||||||
TScanFullRepoEventPayload,
|
TScanFullRepoEventPayload,
|
||||||
TScanPushEventPayload
|
TScanPushEventPayload
|
||||||
} from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
} from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
import {
|
import {
|
||||||
TFailedIntegrationSyncEmailsPayload,
|
TFailedIntegrationSyncEmailsPayload,
|
||||||
TIntegrationSyncPayload,
|
TIntegrationSyncPayload,
|
||||||
@@ -184,17 +187,48 @@ export type TQueueJobTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
||||||
export const queueServiceFactory = (redisUrl: string) => {
|
export const queueServiceFactory = (
|
||||||
|
redisUrl: string,
|
||||||
|
{ dbConnectionUrl, dbRootCert }: { dbConnectionUrl: string; dbRootCert?: string }
|
||||||
|
) => {
|
||||||
const connection = new Redis(redisUrl, { maxRetriesPerRequest: null });
|
const connection = new Redis(redisUrl, { maxRetriesPerRequest: null });
|
||||||
const queueContainer = {} as Record<
|
const queueContainer = {} as Record<
|
||||||
QueueName,
|
QueueName,
|
||||||
Queue<TQueueJobTypes[QueueName]["payload"], void, TQueueJobTypes[QueueName]["name"]>
|
Queue<TQueueJobTypes[QueueName]["payload"], void, TQueueJobTypes[QueueName]["name"]>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
const pgBoss = new PgBoss({
|
||||||
|
connectionString: dbConnectionUrl,
|
||||||
|
archiveCompletedAfterSeconds: 60,
|
||||||
|
archiveFailedAfterSeconds: 1000, // we want to keep failed jobs for a longer time so that it can be retried
|
||||||
|
deleteAfterSeconds: 30,
|
||||||
|
ssl: dbRootCert
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: Buffer.from(dbRootCert, "base64").toString("ascii")
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const queueContainerPg = {} as Record<QueueJobs, boolean>;
|
||||||
|
|
||||||
const workerContainer = {} as Record<
|
const workerContainer = {} as Record<
|
||||||
QueueName,
|
QueueName,
|
||||||
Worker<TQueueJobTypes[QueueName]["payload"], void, TQueueJobTypes[QueueName]["name"]>
|
Worker<TQueueJobTypes[QueueName]["payload"], void, TQueueJobTypes[QueueName]["name"]>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
const initialize = async () => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (appCfg.SHOULD_INIT_PG_QUEUE) {
|
||||||
|
logger.info("Initializing pg-queue...");
|
||||||
|
await pgBoss.start();
|
||||||
|
|
||||||
|
pgBoss.on("error", (error) => {
|
||||||
|
logger.error(error, "pg-queue error");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const start = <T extends QueueName>(
|
const start = <T extends QueueName>(
|
||||||
name: T,
|
name: T,
|
||||||
jobFn: (job: Job<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>, token?: string) => Promise<void>,
|
jobFn: (job: Job<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>, token?: string) => Promise<void>,
|
||||||
@@ -215,6 +249,27 @@ export const queueServiceFactory = (redisUrl: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const startPg = async <T extends QueueName>(
|
||||||
|
jobName: QueueJobs,
|
||||||
|
jobsFn: (jobs: PgBoss.Job<TQueueJobTypes[T]["payload"]>[]) => Promise<void>,
|
||||||
|
options: WorkOptions & {
|
||||||
|
workerCount: number;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
if (queueContainerPg[jobName]) {
|
||||||
|
throw new Error(`${jobName} queue is already initialized`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await pgBoss.createQueue(jobName);
|
||||||
|
queueContainerPg[jobName] = true;
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Array.from({ length: options.workerCount }).map(() =>
|
||||||
|
pgBoss.work<TQueueJobTypes[T]["payload"]>(jobName, options, jobsFn)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const listen = <
|
const listen = <
|
||||||
T extends QueueName,
|
T extends QueueName,
|
||||||
U extends keyof WorkerListener<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>
|
U extends keyof WorkerListener<TQueueJobTypes[T]["payload"], void, TQueueJobTypes[T]["name"]>
|
||||||
@@ -238,6 +293,18 @@ export const queueServiceFactory = (redisUrl: string) => {
|
|||||||
await q.add(job, data, opts);
|
await q.add(job, data, opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const queuePg = async <T extends QueueName>(
|
||||||
|
job: TQueueJobTypes[T]["name"],
|
||||||
|
data: TQueueJobTypes[T]["payload"],
|
||||||
|
opts?: PgBoss.SendOptions & { jobId?: string }
|
||||||
|
) => {
|
||||||
|
await pgBoss.send({
|
||||||
|
name: job,
|
||||||
|
data,
|
||||||
|
options: opts
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const stopRepeatableJob = async <T extends QueueName>(
|
const stopRepeatableJob = async <T extends QueueName>(
|
||||||
name: T,
|
name: T,
|
||||||
job: TQueueJobTypes[T]["name"],
|
job: TQueueJobTypes[T]["name"],
|
||||||
@@ -274,5 +341,17 @@ export const queueServiceFactory = (redisUrl: string) => {
|
|||||||
await Promise.all(Object.values(workerContainer).map((worker) => worker.close()));
|
await Promise.all(Object.values(workerContainer).map((worker) => worker.close()));
|
||||||
};
|
};
|
||||||
|
|
||||||
return { start, listen, queue, shutdown, stopRepeatableJob, stopRepeatableJobByJobId, clearQueue, stopJobById };
|
return {
|
||||||
|
initialize,
|
||||||
|
start,
|
||||||
|
listen,
|
||||||
|
queue,
|
||||||
|
shutdown,
|
||||||
|
stopRepeatableJob,
|
||||||
|
stopRepeatableJobByJobId,
|
||||||
|
clearQueue,
|
||||||
|
stopJobById,
|
||||||
|
startPg,
|
||||||
|
queuePg
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type { FastifyRateLimitOptions } from "@fastify/rate-limit";
|
|||||||
import ratelimiter from "@fastify/rate-limit";
|
import ratelimiter from "@fastify/rate-limit";
|
||||||
import { fastifyRequestContext } from "@fastify/request-context";
|
import { fastifyRequestContext } from "@fastify/request-context";
|
||||||
import fastify from "fastify";
|
import fastify from "fastify";
|
||||||
|
import { Redis } from "ioredis";
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { HsmModule } from "@app/ee/services/hsm/hsm-types";
|
import { HsmModule } from "@app/ee/services/hsm/hsm-types";
|
||||||
@@ -41,10 +42,11 @@ type TMain = {
|
|||||||
queue: TQueueServiceFactory;
|
queue: TQueueServiceFactory;
|
||||||
keyStore: TKeyStoreFactory;
|
keyStore: TKeyStoreFactory;
|
||||||
hsmModule: HsmModule;
|
hsmModule: HsmModule;
|
||||||
|
redis: Redis;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run the server!
|
// Run the server!
|
||||||
export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore }: TMain) => {
|
export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore, redis }: TMain) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const server = fastify({
|
const server = fastify({
|
||||||
@@ -60,6 +62,7 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
|
|||||||
server.setValidatorCompiler(validatorCompiler);
|
server.setValidatorCompiler(validatorCompiler);
|
||||||
server.setSerializerCompiler(serializerCompiler);
|
server.setSerializerCompiler(serializerCompiler);
|
||||||
|
|
||||||
|
server.decorate("redis", redis);
|
||||||
server.addContentTypeParser("application/scim+json", { parseAs: "string" }, (_, body, done) => {
|
server.addContentTypeParser("application/scim+json", { parseAs: "string" }, (_, body, done) => {
|
||||||
try {
|
try {
|
||||||
const strBody = body instanceof Buffer ? body.toString() : body;
|
const strBody = body instanceof Buffer ? body.toString() : body;
|
||||||
@@ -109,9 +112,9 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
|
|||||||
await server.register(maintenanceMode);
|
await server.register(maintenanceMode);
|
||||||
|
|
||||||
await server.register(fastifyRequestContext, {
|
await server.register(fastifyRequestContext, {
|
||||||
defaultStoreValues: (request) => ({
|
defaultStoreValues: (req) => ({
|
||||||
requestId: request.id,
|
reqId: req.id,
|
||||||
log: request.log.child({ requestId: request.id })
|
log: req.log.child({ reqId: req.id })
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
interface SlugSchemaInputs {
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
field?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const slugSchema = ({ min = 1, max = 32, field = "Slug" }: SlugSchemaInputs = {}) => {
|
||||||
|
return z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(min, {
|
||||||
|
message: `${field} field must be at least ${min} lowercase character${min === 1 ? "" : "s"}`
|
||||||
|
})
|
||||||
|
.max(max, {
|
||||||
|
message: `${field} field must be at most ${max} lowercase character${max === 1 ? "" : "s"}`
|
||||||
|
})
|
||||||
|
.refine((v) => slugify(v, { lowercase: true }) === v, {
|
||||||
|
message: `${field} field can only contain lowercase letters, numbers, and hyphens`
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -27,6 +27,7 @@ enum HttpStatusCodes {
|
|||||||
NotFound = 404,
|
NotFound = 404,
|
||||||
Unauthorized = 401,
|
Unauthorized = 401,
|
||||||
Forbidden = 403,
|
Forbidden = 403,
|
||||||
|
UnprocessableContent = 422,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
InternalServerError = 500,
|
InternalServerError = 500,
|
||||||
GatewayTimeout = 504,
|
GatewayTimeout = 504,
|
||||||
@@ -39,42 +40,42 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
if (error instanceof BadRequestError) {
|
if (error instanceof BadRequestError) {
|
||||||
void res
|
void res
|
||||||
.status(HttpStatusCodes.BadRequest)
|
.status(HttpStatusCodes.BadRequest)
|
||||||
.send({ requestId: req.id, statusCode: HttpStatusCodes.BadRequest, message: error.message, error: error.name });
|
.send({ reqId: req.id, statusCode: HttpStatusCodes.BadRequest, message: error.message, error: error.name });
|
||||||
} else if (error instanceof NotFoundError) {
|
} else if (error instanceof NotFoundError) {
|
||||||
void res
|
void res
|
||||||
.status(HttpStatusCodes.NotFound)
|
.status(HttpStatusCodes.NotFound)
|
||||||
.send({ requestId: req.id, statusCode: HttpStatusCodes.NotFound, message: error.message, error: error.name });
|
.send({ reqId: req.id, statusCode: HttpStatusCodes.NotFound, message: error.message, error: error.name });
|
||||||
} else if (error instanceof UnauthorizedError) {
|
} else if (error instanceof UnauthorizedError) {
|
||||||
void res.status(HttpStatusCodes.Unauthorized).send({
|
void res.status(HttpStatusCodes.Unauthorized).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.Unauthorized,
|
statusCode: HttpStatusCodes.Unauthorized,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
error: error.name
|
error: error.name
|
||||||
});
|
});
|
||||||
} else if (error instanceof DatabaseError || error instanceof InternalServerError) {
|
} else if (error instanceof DatabaseError || error instanceof InternalServerError) {
|
||||||
void res.status(HttpStatusCodes.InternalServerError).send({
|
void res.status(HttpStatusCodes.InternalServerError).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.InternalServerError,
|
statusCode: HttpStatusCodes.InternalServerError,
|
||||||
message: "Something went wrong",
|
message: "Something went wrong",
|
||||||
error: error.name
|
error: error.name
|
||||||
});
|
});
|
||||||
} else if (error instanceof GatewayTimeoutError) {
|
} else if (error instanceof GatewayTimeoutError) {
|
||||||
void res.status(HttpStatusCodes.GatewayTimeout).send({
|
void res.status(HttpStatusCodes.GatewayTimeout).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.GatewayTimeout,
|
statusCode: HttpStatusCodes.GatewayTimeout,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
error: error.name
|
error: error.name
|
||||||
});
|
});
|
||||||
} else if (error instanceof ZodError) {
|
} else if (error instanceof ZodError) {
|
||||||
void res.status(HttpStatusCodes.Unauthorized).send({
|
void res.status(HttpStatusCodes.UnprocessableContent).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.Unauthorized,
|
statusCode: HttpStatusCodes.UnprocessableContent,
|
||||||
error: "ValidationFailure",
|
error: "ValidationFailure",
|
||||||
message: error.issues
|
message: error.issues
|
||||||
});
|
});
|
||||||
} else if (error instanceof ForbiddenError) {
|
} else if (error instanceof ForbiddenError) {
|
||||||
void res.status(HttpStatusCodes.Forbidden).send({
|
void res.status(HttpStatusCodes.Forbidden).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.Forbidden,
|
statusCode: HttpStatusCodes.Forbidden,
|
||||||
error: "PermissionDenied",
|
error: "PermissionDenied",
|
||||||
message: `You are not allowed to ${error.action} on ${error.subjectType}`,
|
message: `You are not allowed to ${error.action} on ${error.subjectType}`,
|
||||||
@@ -87,28 +88,28 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
});
|
});
|
||||||
} else if (error instanceof ForbiddenRequestError) {
|
} else if (error instanceof ForbiddenRequestError) {
|
||||||
void res.status(HttpStatusCodes.Forbidden).send({
|
void res.status(HttpStatusCodes.Forbidden).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.Forbidden,
|
statusCode: HttpStatusCodes.Forbidden,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
error: error.name
|
error: error.name
|
||||||
});
|
});
|
||||||
} else if (error instanceof RateLimitError) {
|
} else if (error instanceof RateLimitError) {
|
||||||
void res.status(HttpStatusCodes.TooManyRequests).send({
|
void res.status(HttpStatusCodes.TooManyRequests).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.TooManyRequests,
|
statusCode: HttpStatusCodes.TooManyRequests,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
error: error.name
|
error: error.name
|
||||||
});
|
});
|
||||||
} else if (error instanceof ScimRequestError) {
|
} else if (error instanceof ScimRequestError) {
|
||||||
void res.status(error.status).send({
|
void res.status(error.status).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
schemas: error.schemas,
|
schemas: error.schemas,
|
||||||
status: error.status,
|
status: error.status,
|
||||||
detail: error.detail
|
detail: error.detail
|
||||||
});
|
});
|
||||||
} else if (error instanceof OidcAuthError) {
|
} else if (error instanceof OidcAuthError) {
|
||||||
void res.status(HttpStatusCodes.InternalServerError).send({
|
void res.status(HttpStatusCodes.InternalServerError).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.InternalServerError,
|
statusCode: HttpStatusCodes.InternalServerError,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
error: error.name
|
error: error.name
|
||||||
@@ -127,14 +128,14 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
void res.status(HttpStatusCodes.Forbidden).send({
|
void res.status(HttpStatusCodes.Forbidden).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.Forbidden,
|
statusCode: HttpStatusCodes.Forbidden,
|
||||||
error: "TokenError",
|
error: "TokenError",
|
||||||
message: errorMessage
|
message: errorMessage
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
void res.status(HttpStatusCodes.InternalServerError).send({
|
void res.status(HttpStatusCodes.InternalServerError).send({
|
||||||
requestId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.InternalServerError,
|
statusCode: HttpStatusCodes.InternalServerError,
|
||||||
error: "InternalServerError",
|
error: "InternalServerError",
|
||||||
message: "Something went wrong"
|
message: "Something went wrong"
|
||||||
|
|||||||
@@ -407,13 +407,14 @@ export const registerRoutes = async (
|
|||||||
permissionService
|
permissionService
|
||||||
});
|
});
|
||||||
|
|
||||||
const auditLogQueue = auditLogQueueServiceFactory({
|
const auditLogQueue = await auditLogQueueServiceFactory({
|
||||||
auditLogDAL,
|
auditLogDAL,
|
||||||
queueService,
|
queueService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
auditLogStreamDAL
|
auditLogStreamDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const auditLogService = auditLogServiceFactory({ auditLogDAL, permissionService, auditLogQueue });
|
const auditLogService = auditLogServiceFactory({ auditLogDAL, permissionService, auditLogQueue });
|
||||||
const auditLogStreamService = auditLogStreamServiceFactory({
|
const auditLogStreamService = auditLogStreamServiceFactory({
|
||||||
licenseService,
|
licenseService,
|
||||||
@@ -426,7 +427,8 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
userDAL
|
userDAL,
|
||||||
|
secretApprovalRequestDAL
|
||||||
});
|
});
|
||||||
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
|
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
|
||||||
|
|
||||||
@@ -1025,7 +1027,10 @@ export const registerRoutes = async (
|
|||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
userDAL
|
userDAL,
|
||||||
|
accessApprovalRequestDAL,
|
||||||
|
additionalPrivilegeDAL: projectUserAdditionalPrivilegeDAL,
|
||||||
|
accessApprovalRequestReviewerDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
|
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
|
||||||
|
|||||||
@@ -30,32 +30,39 @@ export const integrationAuthPubSchema = IntegrationAuthsSchema.pick({
|
|||||||
|
|
||||||
export const DefaultResponseErrorsSchema = {
|
export const DefaultResponseErrorsSchema = {
|
||||||
400: z.object({
|
400: z.object({
|
||||||
requestId: z.string(),
|
reqId: z.string(),
|
||||||
statusCode: z.literal(400),
|
statusCode: z.literal(400),
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
error: z.string()
|
error: z.string()
|
||||||
}),
|
}),
|
||||||
404: z.object({
|
404: z.object({
|
||||||
requestId: z.string(),
|
reqId: z.string(),
|
||||||
statusCode: z.literal(404),
|
statusCode: z.literal(404),
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
error: z.string()
|
error: z.string()
|
||||||
}),
|
}),
|
||||||
401: z.object({
|
401: z.object({
|
||||||
requestId: z.string(),
|
reqId: z.string(),
|
||||||
statusCode: z.literal(401),
|
statusCode: z.literal(401),
|
||||||
message: z.any(),
|
message: z.string(),
|
||||||
error: z.string()
|
error: z.string()
|
||||||
}),
|
}),
|
||||||
403: z.object({
|
403: z.object({
|
||||||
requestId: z.string(),
|
reqId: z.string(),
|
||||||
statusCode: z.literal(403),
|
statusCode: z.literal(403),
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
details: z.any().optional(),
|
details: z.any().optional(),
|
||||||
error: z.string()
|
error: z.string()
|
||||||
}),
|
}),
|
||||||
|
// Zod errors return a message of varying shapes and sizes, so z.any() is used here
|
||||||
|
422: z.object({
|
||||||
|
reqId: z.string(),
|
||||||
|
statusCode: z.literal(422),
|
||||||
|
message: z.any(),
|
||||||
|
error: z.string()
|
||||||
|
}),
|
||||||
500: z.object({
|
500: z.object({
|
||||||
requestId: z.string(),
|
reqId: z.string(),
|
||||||
statusCode: z.literal(500),
|
statusCode: z.literal(500),
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
error: z.string()
|
error: z.string()
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { InternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
|
import { InternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
|
||||||
@@ -8,19 +7,12 @@ import { getBase64SizeInBytes, isBase64 } from "@app/lib/base64";
|
|||||||
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
|
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { CmekOrderBy } from "@app/services/cmek/cmek-types";
|
import { CmekOrderBy } from "@app/services/cmek/cmek-types";
|
||||||
|
|
||||||
const keyNameSchema = z
|
const keyNameSchema = slugSchema({ min: 1, max: 32, field: "Name" });
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.min(1)
|
|
||||||
.max(32)
|
|
||||||
.toLowerCase()
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Name must be slug friendly"
|
|
||||||
});
|
|
||||||
const keyDescriptionSchema = z.string().trim().max(500).optional();
|
const keyDescriptionSchema = z.string().trim().max(500).optional();
|
||||||
|
|
||||||
const base64Schema = z.string().superRefine((val, ctx) => {
|
const base64Schema = z.string().superRefine((val, ctx) => {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ExternalGroupOrgRoleMappingsSchema } from "@app/db/schemas/external-group-org-role-mappings";
|
import { ExternalGroupOrgRoleMappingsSchema } from "@app/db/schemas/external-group-org-role-mappings";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -48,13 +48,7 @@ export const registerExternalGroupOrgRoleMappingRouter = async (server: FastifyZ
|
|||||||
mappings: z
|
mappings: z
|
||||||
.object({
|
.object({
|
||||||
groupName: z.string().trim().min(1),
|
groupName: z.string().trim().min(1),
|
||||||
roleSlug: z
|
roleSlug: slugSchema({ max: 64 })
|
||||||
.string()
|
|
||||||
.min(1)
|
|
||||||
.toLowerCase()
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Role must be a valid slug"
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { OctopusDeployScope } from "@app/services/integration-auth/integration-auth-types";
|
import { OctopusDeployScope } from "@app/services/integration-auth/integration-auth-types";
|
||||||
|
import { Integrations } from "@app/services/integration-auth/integration-list";
|
||||||
|
|
||||||
import { integrationAuthPubSchema } from "../sanitizedSchemas";
|
import { integrationAuthPubSchema } from "../sanitizedSchemas";
|
||||||
|
|
||||||
@@ -82,6 +83,67 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:integrationAuthId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Update the integration authentication object required for syncing secrets.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
querystring: z.object({
|
||||||
|
integrationAuthId: z.string().trim().describe(INTEGRATION_AUTH.UPDATE_BY_ID.integrationAuthId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
integration: z.nativeEnum(Integrations).optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.integration),
|
||||||
|
accessId: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.accessId),
|
||||||
|
accessToken: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.accessToken),
|
||||||
|
awsAssumeIamRoleArn: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.trim()
|
||||||
|
.optional()
|
||||||
|
.describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.awsAssumeIamRoleArn),
|
||||||
|
url: z.string().url().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.url),
|
||||||
|
namespace: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.namespace),
|
||||||
|
refreshToken: z.string().trim().optional().describe(INTEGRATION_AUTH.CREATE_ACCESS_TOKEN.refreshToken)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
integrationAuth: integrationAuthPubSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const integrationAuth = await server.services.integrationAuth.updateIntegrationAuth({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
integrationAuthId: req.query.integrationAuthId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: integrationAuth.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_INTEGRATION_AUTH,
|
||||||
|
metadata: {
|
||||||
|
integration: integrationAuth.integration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { integrationAuth };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/",
|
url: "/",
|
||||||
|
|||||||
@@ -141,7 +141,9 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
targetEnvironment: z.string().trim().optional().describe(INTEGRATION.UPDATE.targetEnvironment),
|
targetEnvironment: z.string().trim().optional().describe(INTEGRATION.UPDATE.targetEnvironment),
|
||||||
owner: z.string().trim().optional().describe(INTEGRATION.UPDATE.owner),
|
owner: z.string().trim().optional().describe(INTEGRATION.UPDATE.owner),
|
||||||
environment: z.string().trim().optional().describe(INTEGRATION.UPDATE.environment),
|
environment: z.string().trim().optional().describe(INTEGRATION.UPDATE.environment),
|
||||||
metadata: IntegrationMetadataSchema.optional()
|
path: z.string().trim().optional().describe(INTEGRATION.UPDATE.path),
|
||||||
|
metadata: IntegrationMetadataSchema.optional(),
|
||||||
|
region: z.string().trim().optional().describe(INTEGRATION.UPDATE.region)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -14,6 +13,7 @@ import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-t
|
|||||||
import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
import { getLastMidnightDateISO } from "@app/lib/fn";
|
import { getLastMidnightDateISO } from "@app/lib/fn";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ActorType, AuthMode, MfaMethod } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode, MfaMethod } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -243,22 +243,10 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().max(64, { message: "Name must be 64 or fewer characters" }).optional(),
|
name: z.string().trim().max(64, { message: "Name must be 64 or fewer characters" }).optional(),
|
||||||
slug: z
|
slug: slugSchema({ max: 64 }).optional(),
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.max(64, { message: "Slug must be 64 or fewer characters" })
|
|
||||||
.regex(/^[a-zA-Z0-9-]+$/, "Slug must only contain alphanumeric characters or hyphens")
|
|
||||||
.optional(),
|
|
||||||
authEnforced: z.boolean().optional(),
|
authEnforced: z.boolean().optional(),
|
||||||
scimEnabled: z.boolean().optional(),
|
scimEnabled: z.boolean().optional(),
|
||||||
defaultMembershipRoleSlug: z
|
defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(),
|
||||||
.string()
|
|
||||||
.min(1)
|
|
||||||
.trim()
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Membership role must be a valid slug"
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
enforceMfa: z.boolean().optional(),
|
enforceMfa: z.boolean().optional(),
|
||||||
selectedMfaMethod: z.nativeEnum(MfaMethod).optional()
|
selectedMfaMethod: z.nativeEnum(MfaMethod).optional()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
|
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ENVIRONMENTS } from "@app/lib/api-docs";
|
import { ENVIRONMENTS } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -124,13 +124,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().describe(ENVIRONMENTS.CREATE.name),
|
name: z.string().trim().describe(ENVIRONMENTS.CREATE.name),
|
||||||
position: z.number().min(1).optional().describe(ENVIRONMENTS.CREATE.position),
|
position: z.number().min(1).optional().describe(ENVIRONMENTS.CREATE.position),
|
||||||
slug: z
|
slug: slugSchema({ max: 64 }).describe(ENVIRONMENTS.CREATE.slug)
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.describe(ENVIRONMENTS.CREATE.slug)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -188,14 +182,7 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
|||||||
id: z.string().trim().describe(ENVIRONMENTS.UPDATE.id)
|
id: z.string().trim().describe(ENVIRONMENTS.UPDATE.id)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
slug: slugSchema({ max: 64 }).optional().describe(ENVIRONMENTS.UPDATE.slug),
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.optional()
|
|
||||||
.refine((v) => !v || slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.describe(ENVIRONMENTS.UPDATE.slug),
|
|
||||||
name: z.string().trim().optional().describe(ENVIRONMENTS.UPDATE.name),
|
name: z.string().trim().optional().describe(ENVIRONMENTS.UPDATE.name),
|
||||||
position: z.number().optional().describe(ENVIRONMENTS.UPDATE.position)
|
position: z.number().optional().describe(ENVIRONMENTS.UPDATE.position)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretTagsSchema } from "@app/db/schemas";
|
import { SecretTagsSchema } from "@app/db/schemas";
|
||||||
import { SECRET_TAGS } from "@app/lib/api-docs";
|
import { SECRET_TAGS } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -111,14 +111,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId)
|
projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
slug: slugSchema({ max: 64 }).describe(SECRET_TAGS.CREATE.slug),
|
||||||
.string()
|
|
||||||
.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
.describe(SECRET_TAGS.CREATE.slug)
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens."
|
|
||||||
}),
|
|
||||||
color: z.string().trim().describe(SECRET_TAGS.CREATE.color)
|
color: z.string().trim().describe(SECRET_TAGS.CREATE.color)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -153,14 +146,7 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
|
|||||||
tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId)
|
tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
slug: slugSchema({ max: 64 }).describe(SECRET_TAGS.UPDATE.slug),
|
||||||
.string()
|
|
||||||
.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
.describe(SECRET_TAGS.UPDATE.slug)
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens."
|
|
||||||
}),
|
|
||||||
color: z.string().trim().describe(SECRET_TAGS.UPDATE.color)
|
color: z.string().trim().describe(SECRET_TAGS.UPDATE.color)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SlackIntegrationsSchema, WorkflowIntegrationsSchema } from "@app/db/schemas";
|
import { SlackIntegrationsSchema, WorkflowIntegrationsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -35,12 +35,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
slug: z
|
slug: slugSchema({ max: 64 }),
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
}),
|
|
||||||
description: z.string().optional()
|
description: z.string().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -288,13 +283,7 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
|||||||
id: z.string()
|
id: z.string()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z
|
slug: slugSchema({ max: 64 }).optional(),
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
description: z.string().optional()
|
description: z.string().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import { Authenticator } from "@fastify/passport";
|
import { Authenticator } from "@fastify/passport";
|
||||||
import fastifySession from "@fastify/session";
|
import fastifySession from "@fastify/session";
|
||||||
|
import RedisStore from "connect-redis";
|
||||||
import { Strategy as GitHubStrategy } from "passport-github";
|
import { Strategy as GitHubStrategy } from "passport-github";
|
||||||
import { Strategy as GitLabStrategy } from "passport-gitlab2";
|
import { Strategy as GitLabStrategy } from "passport-gitlab2";
|
||||||
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
||||||
@@ -23,8 +24,22 @@ import { OrgAuthMethod } from "@app/services/org/org-types";
|
|||||||
|
|
||||||
export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const passport = new Authenticator({ key: "sso", userProperty: "passportUser" });
|
const passport = new Authenticator({ key: "sso", userProperty: "passportUser" });
|
||||||
await server.register(fastifySession, { secret: appCfg.COOKIE_SECRET_SIGN_KEY });
|
const redisStore = new RedisStore({
|
||||||
|
client: server.redis,
|
||||||
|
prefix: "oauth-session:",
|
||||||
|
ttl: 600 // 10 minutes
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.register(fastifySession, {
|
||||||
|
secret: appCfg.COOKIE_SECRET_SIGN_KEY,
|
||||||
|
store: redisStore,
|
||||||
|
cookie: {
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
sameSite: "lax" // we want cookies to be sent to Infisical in redirects originating from IDP server
|
||||||
|
}
|
||||||
|
});
|
||||||
await server.register(passport.initialize());
|
await server.register(passport.initialize());
|
||||||
await server.register(passport.secureSession());
|
await server.register(passport.secureSession());
|
||||||
// passport oauth strategy for Google
|
// passport oauth strategy for Google
|
||||||
@@ -37,11 +52,15 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientID: appCfg.CLIENT_ID_GOOGLE_LOGIN as string,
|
clientID: appCfg.CLIENT_ID_GOOGLE_LOGIN as string,
|
||||||
clientSecret: appCfg.CLIENT_SECRET_GOOGLE_LOGIN as string,
|
clientSecret: appCfg.CLIENT_SECRET_GOOGLE_LOGIN as string,
|
||||||
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/google`,
|
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/google`,
|
||||||
scope: ["profile", " email"]
|
scope: ["profile", " email"],
|
||||||
|
state: true
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req, _accessToken, _refreshToken, profile, cb) => {
|
async (req, _accessToken, _refreshToken, profile, cb) => {
|
||||||
try {
|
try {
|
||||||
|
// @ts-expect-error this is because this is express type and not fastify
|
||||||
|
const callbackPort = req.session.get("callbackPort");
|
||||||
|
|
||||||
const email = profile?.emails?.[0]?.value;
|
const email = profile?.emails?.[0]?.value;
|
||||||
if (!email)
|
if (!email)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -54,7 +73,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
firstName: profile?.name?.givenName || "",
|
firstName: profile?.name?.givenName || "",
|
||||||
lastName: profile?.name?.familyName || "",
|
lastName: profile?.name?.familyName || "",
|
||||||
authMethod: AuthMethod.GOOGLE,
|
authMethod: AuthMethod.GOOGLE,
|
||||||
callbackPort: req.query.state as string
|
callbackPort
|
||||||
});
|
});
|
||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -76,10 +95,14 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientID: appCfg.CLIENT_ID_GITHUB_LOGIN as string,
|
clientID: appCfg.CLIENT_ID_GITHUB_LOGIN as string,
|
||||||
clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN as string,
|
clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN as string,
|
||||||
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/github`,
|
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/github`,
|
||||||
scope: ["user:email"]
|
scope: ["user:email"],
|
||||||
|
// akhilmhdh: because the ts type for this is outdated by the maintainer
|
||||||
|
state: true as unknown as string
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req, accessToken, _refreshToken, profile, cb) => {
|
async (req, accessToken, _refreshToken, profile, cb) => {
|
||||||
|
// @ts-expect-error this is because this is express type and not fastify
|
||||||
|
const callbackPort = req.session.get("callbackPort");
|
||||||
try {
|
try {
|
||||||
const ghEmails = await fetchGithubEmails(accessToken);
|
const ghEmails = await fetchGithubEmails(accessToken);
|
||||||
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
|
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
|
||||||
@@ -88,7 +111,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
firstName: profile.displayName,
|
firstName: profile.displayName,
|
||||||
lastName: "",
|
lastName: "",
|
||||||
authMethod: AuthMethod.GITHUB,
|
authMethod: AuthMethod.GITHUB,
|
||||||
callbackPort: req.query.state as string
|
callbackPort
|
||||||
});
|
});
|
||||||
return cb(null, { isUserCompleted, providerAuthToken });
|
return cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -112,17 +135,20 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientID: appCfg.CLIENT_ID_GITLAB_LOGIN,
|
clientID: appCfg.CLIENT_ID_GITLAB_LOGIN,
|
||||||
clientSecret: appCfg.CLIENT_SECRET_GITLAB_LOGIN,
|
clientSecret: appCfg.CLIENT_SECRET_GITLAB_LOGIN,
|
||||||
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/gitlab`,
|
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/gitlab`,
|
||||||
baseURL: appCfg.CLIENT_GITLAB_LOGIN_URL
|
baseURL: appCfg.CLIENT_GITLAB_LOGIN_URL,
|
||||||
|
state: true
|
||||||
},
|
},
|
||||||
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
|
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
|
||||||
try {
|
try {
|
||||||
|
const callbackPort = req.session.get("callbackPort");
|
||||||
|
|
||||||
const email = profile.emails[0].value;
|
const email = profile.emails[0].value;
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
||||||
email,
|
email,
|
||||||
firstName: profile.displayName,
|
firstName: profile.displayName,
|
||||||
lastName: "",
|
lastName: "",
|
||||||
authMethod: AuthMethod.GITLAB,
|
authMethod: AuthMethod.GITLAB,
|
||||||
callbackPort: req.query.state as string
|
callbackPort
|
||||||
});
|
});
|
||||||
|
|
||||||
return cb(null, { isUserCompleted, providerAuthToken });
|
return cb(null, { isUserCompleted, providerAuthToken });
|
||||||
@@ -143,17 +169,24 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
callback_port: z.string().optional()
|
callback_port: z.string().optional()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
preValidation: (req, res) =>
|
preValidation: [
|
||||||
(
|
async (req, res) => {
|
||||||
passport.authenticate("google", {
|
const { callback_port: callbackPort } = req.query;
|
||||||
scope: ["profile", "email"],
|
// ensure fresh session state per login attempt
|
||||||
session: false,
|
await req.session.regenerate();
|
||||||
state: req.query.callback_port,
|
if (callbackPort) {
|
||||||
authInfo: false
|
req.session.set("callbackPort", callbackPort);
|
||||||
// this is due to zod type difference
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
return (
|
||||||
}) as any
|
passport.authenticate("google", {
|
||||||
)(req, res),
|
scope: ["profile", "email"],
|
||||||
|
authInfo: false
|
||||||
|
// this is due to zod type difference
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
}) as any
|
||||||
|
)(req, res);
|
||||||
|
}
|
||||||
|
],
|
||||||
handler: () => {}
|
handler: () => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -166,7 +199,8 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
authInfo: false
|
authInfo: false
|
||||||
// this is due to zod type difference
|
// this is due to zod type difference
|
||||||
}) as never,
|
}) as never,
|
||||||
handler: (req, res) => {
|
handler: async (req, res) => {
|
||||||
|
await req.session.destroy();
|
||||||
if (req.passportUser.isUserCompleted) {
|
if (req.passportUser.isUserCompleted) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
||||||
@@ -186,15 +220,24 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
callback_port: z.string().optional()
|
callback_port: z.string().optional()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
preValidation: (req, res) =>
|
preValidation: [
|
||||||
(
|
async (req, res) => {
|
||||||
passport.authenticate("github", {
|
const { callback_port: callbackPort } = req.query;
|
||||||
session: false,
|
// ensure fresh session state per login attempt
|
||||||
state: req.query.callback_port,
|
await req.session.regenerate();
|
||||||
authInfo: false
|
if (callbackPort) {
|
||||||
// this is due to zod type difference
|
req.session.set("callbackPort", callbackPort);
|
||||||
}) as any
|
}
|
||||||
)(req, res),
|
|
||||||
|
return (
|
||||||
|
passport.authenticate("github", {
|
||||||
|
session: false,
|
||||||
|
authInfo: false
|
||||||
|
// this is due to zod type difference
|
||||||
|
}) as any
|
||||||
|
)(req, res);
|
||||||
|
}
|
||||||
|
],
|
||||||
handler: () => {}
|
handler: () => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -245,7 +288,8 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
authInfo: false
|
authInfo: false
|
||||||
// this is due to zod type difference
|
// this is due to zod type difference
|
||||||
}) as any,
|
}) as any,
|
||||||
handler: (req, res) => {
|
handler: async (req, res) => {
|
||||||
|
await req.session.destroy();
|
||||||
if (req.passportUser.isUserCompleted) {
|
if (req.passportUser.isUserCompleted) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
||||||
@@ -265,16 +309,25 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
callback_port: z.string().optional()
|
callback_port: z.string().optional()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
preValidation: (req, res) =>
|
preValidation: [
|
||||||
(
|
async (req, res) => {
|
||||||
passport.authenticate("gitlab", {
|
const { callback_port: callbackPort } = req.query;
|
||||||
session: false,
|
// ensure fresh session state per login attempt
|
||||||
state: req.query.callback_port,
|
await req.session.regenerate();
|
||||||
authInfo: false
|
if (callbackPort) {
|
||||||
// this is due to zod type difference
|
req.session.set("callbackPort", callbackPort);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
}
|
||||||
}) as any
|
|
||||||
)(req, res),
|
return (
|
||||||
|
passport.authenticate("gitlab", {
|
||||||
|
session: false,
|
||||||
|
authInfo: false
|
||||||
|
// this is due to zod type difference
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
}) as any
|
||||||
|
)(req, res);
|
||||||
|
}
|
||||||
|
],
|
||||||
handler: () => {}
|
handler: () => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -288,7 +341,8 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
// this is due to zod type difference
|
// this is due to zod type difference
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
}) as any,
|
}) as any,
|
||||||
handler: (req, res) => {
|
handler: async (req, res) => {
|
||||||
|
await req.session.destroy();
|
||||||
if (req.passportUser.isUserCompleted) {
|
if (req.passportUser.isUserCompleted) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -15,6 +14,7 @@ import { sanitizedSshCertificate } from "@app/ee/services/ssh-certificate/ssh-ce
|
|||||||
import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-schema";
|
import { sanitizedSshCertificateTemplate } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-schema";
|
||||||
import { PROJECTS } from "@app/lib/api-docs";
|
import { PROJECTS } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -30,14 +30,6 @@ const projectWithEnv = SanitizedProjectSchema.extend({
|
|||||||
environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array()
|
environments: z.object({ name: z.string(), slug: z.string(), id: z.string() }).array()
|
||||||
});
|
});
|
||||||
|
|
||||||
const slugSchema = z
|
|
||||||
.string()
|
|
||||||
.min(5)
|
|
||||||
.max(36)
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be at least 5 character but no more than 36"
|
|
||||||
});
|
|
||||||
|
|
||||||
export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||||
/* Get project key */
|
/* Get project key */
|
||||||
server.route({
|
server.route({
|
||||||
@@ -165,21 +157,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
projectName: z.string().trim().describe(PROJECTS.CREATE.projectName),
|
projectName: z.string().trim().describe(PROJECTS.CREATE.projectName),
|
||||||
projectDescription: z.string().trim().optional().describe(PROJECTS.CREATE.projectDescription),
|
projectDescription: z.string().trim().optional().describe(PROJECTS.CREATE.projectDescription),
|
||||||
slug: z
|
slug: slugSchema({ min: 5, max: 36 }).optional().describe(PROJECTS.CREATE.slug),
|
||||||
.string()
|
|
||||||
.min(5)
|
|
||||||
.max(36)
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe(PROJECTS.CREATE.slug),
|
|
||||||
kmsKeyId: z.string().optional(),
|
kmsKeyId: z.string().optional(),
|
||||||
template: z
|
template: slugSchema({ field: "Template Name", max: 64 })
|
||||||
.string()
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Template name must be in slug format"
|
|
||||||
})
|
|
||||||
.optional()
|
.optional()
|
||||||
.default(InfisicalProjectTemplate.Default)
|
.default(InfisicalProjectTemplate.Default)
|
||||||
.describe(PROJECTS.CREATE.template)
|
.describe(PROJECTS.CREATE.template)
|
||||||
@@ -247,7 +227,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: slugSchema.describe("The slug of the project to delete.")
|
slug: slugSchema({ min: 5, max: 36 }).describe("The slug of the project to delete.")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: SanitizedProjectSchema
|
200: SanitizedProjectSchema
|
||||||
@@ -281,7 +261,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: slugSchema.describe("The slug of the project to get.")
|
slug: slugSchema({ min: 5, max: 36 }).describe("The slug of the project to get.")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: projectWithEnv
|
200: projectWithEnv
|
||||||
@@ -314,7 +294,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: slugSchema.describe("The slug of the project to update.")
|
slug: slugSchema({ min: 5, max: 36 }).describe("The slug of the project to update.")
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().optional().describe(PROJECTS.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECTS.UPDATE.name),
|
||||||
@@ -357,7 +337,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: slugSchema.describe(PROJECTS.LIST_CAS.slug)
|
slug: slugSchema({ min: 5, max: 36 }).describe(PROJECTS.LIST_CAS.slug)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional().describe(PROJECTS.LIST_CAS.status),
|
status: z.enum([CaStatus.ACTIVE, CaStatus.PENDING_CERTIFICATE]).optional().describe(PROJECTS.LIST_CAS.status),
|
||||||
@@ -398,7 +378,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: slugSchema.describe(PROJECTS.LIST_CERTIFICATES.slug)
|
slug: slugSchema({ min: 5, max: 36 }).describe(PROJECTS.LIST_CERTIFICATES.slug)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
friendlyName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.friendlyName),
|
friendlyName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.friendlyName),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ProjectMembershipRole } from "@app/db/schemas";
|
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||||
@@ -61,7 +61,12 @@ export const identityProjectServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Identity, {
|
||||||
|
identityId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const existingIdentity = await identityProjectDAL.findOne({ identityId, projectId });
|
const existingIdentity = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (existingIdentity)
|
if (existingIdentity)
|
||||||
@@ -161,7 +166,10 @@ export const identityProjectServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
|
);
|
||||||
|
|
||||||
const projectIdentity = await identityProjectDAL.findOne({ identityId, projectId });
|
const projectIdentity = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!projectIdentity)
|
if (!projectIdentity)
|
||||||
@@ -253,7 +261,11 @@ export const identityProjectServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Delete,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
|
);
|
||||||
|
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityId,
|
identityId,
|
||||||
@@ -317,7 +329,11 @@ export const identityProjectServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
|
);
|
||||||
|
|
||||||
const [identityMembership] = await identityProjectDAL.findByProjectId(projectId, { identityId });
|
const [identityMembership] = await identityProjectDAL.findByProjectId(projectId, { identityId });
|
||||||
if (!identityMembership)
|
if (!identityMembership)
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import {
|
|||||||
TOctopusDeployVariableSet,
|
TOctopusDeployVariableSet,
|
||||||
TSaveIntegrationAccessTokenDTO,
|
TSaveIntegrationAccessTokenDTO,
|
||||||
TTeamCityBuildConfig,
|
TTeamCityBuildConfig,
|
||||||
|
TUpdateIntegrationAuthDTO,
|
||||||
TVercelBranches
|
TVercelBranches
|
||||||
} from "./integration-auth-types";
|
} from "./integration-auth-types";
|
||||||
import { getIntegrationOptions, Integrations, IntegrationUrls } from "./integration-list";
|
import { getIntegrationOptions, Integrations, IntegrationUrls } from "./integration-list";
|
||||||
@@ -368,6 +369,148 @@ export const integrationAuthServiceFactory = ({
|
|||||||
return integrationAuthDAL.create(updateDoc);
|
return integrationAuthDAL.create(updateDoc);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateIntegrationAuth = async ({
|
||||||
|
integrationAuthId,
|
||||||
|
refreshToken,
|
||||||
|
actorId,
|
||||||
|
integration: newIntegration,
|
||||||
|
url,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
accessId,
|
||||||
|
namespace,
|
||||||
|
accessToken,
|
||||||
|
awsAssumeIamRoleArn
|
||||||
|
}: TUpdateIntegrationAuthDTO) => {
|
||||||
|
const integrationAuth = await integrationAuthDAL.findById(integrationAuthId);
|
||||||
|
if (!integrationAuth) {
|
||||||
|
throw new NotFoundError({ message: `Integration auth with id ${integrationAuthId} not found.` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
integrationAuth.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
|
const { projectId } = integrationAuth;
|
||||||
|
const integration = newIntegration || integrationAuth.integration;
|
||||||
|
|
||||||
|
const updateDoc: TIntegrationAuthsInsert = {
|
||||||
|
projectId,
|
||||||
|
integration,
|
||||||
|
namespace,
|
||||||
|
url,
|
||||||
|
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
||||||
|
keyEncoding: SecretKeyEncoding.UTF8,
|
||||||
|
...(integration === Integrations.GCP_SECRET_MANAGER
|
||||||
|
? {
|
||||||
|
metadata: {
|
||||||
|
authMethod: "serviceAccount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
};
|
||||||
|
|
||||||
|
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId);
|
||||||
|
if (shouldUseSecretV2Bridge) {
|
||||||
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
if (refreshToken) {
|
||||||
|
const tokenDetails = await exchangeRefresh(
|
||||||
|
integration,
|
||||||
|
refreshToken,
|
||||||
|
url,
|
||||||
|
updateDoc.metadata as Record<string, string>
|
||||||
|
);
|
||||||
|
const refreshEncToken = secretManagerEncryptor({
|
||||||
|
plainText: Buffer.from(tokenDetails.refreshToken)
|
||||||
|
}).cipherTextBlob;
|
||||||
|
updateDoc.encryptedRefresh = refreshEncToken;
|
||||||
|
|
||||||
|
const accessEncToken = secretManagerEncryptor({
|
||||||
|
plainText: Buffer.from(tokenDetails.accessToken)
|
||||||
|
}).cipherTextBlob;
|
||||||
|
updateDoc.encryptedAccess = accessEncToken;
|
||||||
|
updateDoc.accessExpiresAt = tokenDetails.accessExpiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) {
|
||||||
|
if (accessToken) {
|
||||||
|
const accessEncToken = secretManagerEncryptor({
|
||||||
|
plainText: Buffer.from(accessToken)
|
||||||
|
}).cipherTextBlob;
|
||||||
|
updateDoc.encryptedAccess = accessEncToken;
|
||||||
|
updateDoc.encryptedAwsAssumeIamRoleArn = null;
|
||||||
|
}
|
||||||
|
if (accessId) {
|
||||||
|
const accessEncToken = secretManagerEncryptor({
|
||||||
|
plainText: Buffer.from(accessId)
|
||||||
|
}).cipherTextBlob;
|
||||||
|
updateDoc.encryptedAccessId = accessEncToken;
|
||||||
|
updateDoc.encryptedAwsAssumeIamRoleArn = null;
|
||||||
|
}
|
||||||
|
if (awsAssumeIamRoleArn) {
|
||||||
|
const awsAssumeIamRoleArnEncrypted = secretManagerEncryptor({
|
||||||
|
plainText: Buffer.from(awsAssumeIamRoleArn)
|
||||||
|
}).cipherTextBlob;
|
||||||
|
updateDoc.encryptedAwsAssumeIamRoleArn = awsAssumeIamRoleArnEncrypted;
|
||||||
|
updateDoc.encryptedAccess = null;
|
||||||
|
updateDoc.encryptedAccessId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!botKey) throw new NotFoundError({ message: `Project bot key for project with ID '${projectId}' not found` });
|
||||||
|
if (refreshToken) {
|
||||||
|
const tokenDetails = await exchangeRefresh(
|
||||||
|
integration,
|
||||||
|
refreshToken,
|
||||||
|
url,
|
||||||
|
updateDoc.metadata as Record<string, string>
|
||||||
|
);
|
||||||
|
const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey);
|
||||||
|
updateDoc.refreshIV = refreshEncToken.iv;
|
||||||
|
updateDoc.refreshTag = refreshEncToken.tag;
|
||||||
|
updateDoc.refreshCiphertext = refreshEncToken.ciphertext;
|
||||||
|
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey);
|
||||||
|
updateDoc.accessIV = accessEncToken.iv;
|
||||||
|
updateDoc.accessTag = accessEncToken.tag;
|
||||||
|
updateDoc.accessCiphertext = accessEncToken.ciphertext;
|
||||||
|
|
||||||
|
updateDoc.accessExpiresAt = tokenDetails.accessExpiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) {
|
||||||
|
if (accessToken) {
|
||||||
|
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessToken, botKey);
|
||||||
|
updateDoc.accessIV = accessEncToken.iv;
|
||||||
|
updateDoc.accessTag = accessEncToken.tag;
|
||||||
|
updateDoc.accessCiphertext = accessEncToken.ciphertext;
|
||||||
|
}
|
||||||
|
if (accessId) {
|
||||||
|
const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessId, botKey);
|
||||||
|
updateDoc.accessIdIV = accessEncToken.iv;
|
||||||
|
updateDoc.accessIdTag = accessEncToken.tag;
|
||||||
|
updateDoc.accessIdCiphertext = accessEncToken.ciphertext;
|
||||||
|
}
|
||||||
|
if (awsAssumeIamRoleArn) {
|
||||||
|
const awsAssumeIamRoleArnEnc = encryptSymmetric128BitHexKeyUTF8(awsAssumeIamRoleArn, botKey);
|
||||||
|
updateDoc.awsAssumeIamRoleArnCipherText = awsAssumeIamRoleArnEnc.ciphertext;
|
||||||
|
updateDoc.awsAssumeIamRoleArnIV = awsAssumeIamRoleArnEnc.iv;
|
||||||
|
updateDoc.awsAssumeIamRoleArnTag = awsAssumeIamRoleArnEnc.tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return integrationAuthDAL.updateById(integrationAuthId, updateDoc);
|
||||||
|
};
|
||||||
|
|
||||||
// helper function
|
// helper function
|
||||||
const getIntegrationAccessToken = async (
|
const getIntegrationAccessToken = async (
|
||||||
integrationAuth: TIntegrationAuths,
|
integrationAuth: TIntegrationAuths,
|
||||||
@@ -1615,6 +1758,7 @@ export const integrationAuthServiceFactory = ({
|
|||||||
getIntegrationAuth,
|
getIntegrationAuth,
|
||||||
oauthExchange,
|
oauthExchange,
|
||||||
saveIntegrationToken,
|
saveIntegrationToken,
|
||||||
|
updateIntegrationAuth,
|
||||||
deleteIntegrationAuthById,
|
deleteIntegrationAuthById,
|
||||||
deleteIntegrationAuths,
|
deleteIntegrationAuths,
|
||||||
getIntegrationAuthTeams,
|
getIntegrationAuthTeams,
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ export type TSaveIntegrationAccessTokenDTO = {
|
|||||||
awsAssumeIamRoleArn?: string;
|
awsAssumeIamRoleArn?: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TUpdateIntegrationAuthDTO = Omit<TSaveIntegrationAccessTokenDTO, "projectId" | "integration"> & {
|
||||||
|
integrationAuthId: string;
|
||||||
|
integration?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TDeleteIntegrationAuthsDTO = TProjectPermission & {
|
export type TDeleteIntegrationAuthsDTO = TProjectPermission & {
|
||||||
integration: string;
|
integration: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|||||||
@@ -151,7 +151,9 @@ export const integrationServiceFactory = ({
|
|||||||
isActive,
|
isActive,
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
metadata
|
region,
|
||||||
|
metadata,
|
||||||
|
path
|
||||||
}: TUpdateIntegrationDTO) => {
|
}: TUpdateIntegrationDTO) => {
|
||||||
const integration = await integrationDAL.findById(id);
|
const integration = await integrationDAL.findById(id);
|
||||||
if (!integration) throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
|
if (!integration) throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
|
||||||
@@ -192,7 +194,9 @@ export const integrationServiceFactory = ({
|
|||||||
appId,
|
appId,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
owner,
|
owner,
|
||||||
|
region,
|
||||||
secretPath,
|
secretPath,
|
||||||
|
path,
|
||||||
metadata: {
|
metadata: {
|
||||||
...(integration.metadata as object),
|
...(integration.metadata as object),
|
||||||
...metadata
|
...metadata
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export type TUpdateIntegrationDTO = {
|
|||||||
appId?: string;
|
appId?: string;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
|
region?: string;
|
||||||
|
path?: string;
|
||||||
targetEnvironment?: string;
|
targetEnvironment?: string;
|
||||||
owner?: string;
|
owner?: string;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { KmsKeysSchema, TKmsRootConfig } from "@app/db/schemas";
|
import { KmsKeysSchema, TKmsRootConfig } from "@app/db/schemas";
|
||||||
import { AwsKmsProviderFactory } from "@app/ee/services/external-kms/providers/aws-kms";
|
import { AwsKmsProviderFactory } from "@app/ee/services/external-kms/providers/aws-kms";
|
||||||
|
import { GcpKmsProviderFactory } from "@app/ee/services/external-kms/providers/gcp-kms";
|
||||||
import {
|
import {
|
||||||
ExternalKmsAwsSchema,
|
ExternalKmsAwsSchema,
|
||||||
|
ExternalKmsGcpSchema,
|
||||||
KmsProviders,
|
KmsProviders,
|
||||||
TExternalKmsProviderFns
|
TExternalKmsProviderFns
|
||||||
} from "@app/ee/services/external-kms/providers/model";
|
} from "@app/ee/services/external-kms/providers/model";
|
||||||
@@ -291,6 +293,16 @@ export const kmsServiceFactory = ({
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case KmsProviders.Gcp: {
|
||||||
|
const decryptedProviderInput = await ExternalKmsGcpSchema.parseAsync(
|
||||||
|
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
|
||||||
|
);
|
||||||
|
|
||||||
|
externalKms = await GcpKmsProviderFactory({
|
||||||
|
inputs: decryptedProviderInput
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid KMS provider.");
|
throw new Error("Invalid KMS provider.");
|
||||||
}
|
}
|
||||||
@@ -353,6 +365,16 @@ export const kmsServiceFactory = ({
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case KmsProviders.Gcp: {
|
||||||
|
const decryptedProviderInput = await ExternalKmsGcpSchema.parseAsync(
|
||||||
|
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
|
||||||
|
);
|
||||||
|
|
||||||
|
externalKms = await GcpKmsProviderFactory({
|
||||||
|
inputs: decryptedProviderInput
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid KMS provider.");
|
throw new Error("Invalid KMS provider.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
|
||||||
import { OrgMembershipRole, ProjectMembershipRole, ProjectVersion, TProjectEnvironments } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectVersion, TProjectEnvironments } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@@ -12,7 +12,6 @@ import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-cer
|
|||||||
import { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal";
|
import { TSshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal";
|
||||||
import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal";
|
import { TSshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal";
|
||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
|
||||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
@@ -382,20 +381,6 @@ export const projectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the role permission for the identity
|
|
||||||
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
|
|
||||||
OrgMembershipRole.Member,
|
|
||||||
organization.id
|
|
||||||
);
|
|
||||||
|
|
||||||
// Identity has to be at least a member in order to create projects
|
|
||||||
const hasPrivilege = isAtLeastAsPrivileged(permission, rolePermission);
|
|
||||||
if (!hasPrivilege)
|
|
||||||
throw new ForbiddenRequestError({
|
|
||||||
message: "Failed to add identity to project with more privileged role"
|
|
||||||
});
|
|
||||||
const isCustomRole = Boolean(customRole);
|
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.create(
|
const identityProjectMembership = await identityProjectDAL.create(
|
||||||
{
|
{
|
||||||
identityId: actorId,
|
identityId: actorId,
|
||||||
@@ -407,8 +392,7 @@ export const projectServiceFactory = ({
|
|||||||
await identityProjectMembershipRoleDAL.create(
|
await identityProjectMembershipRoleDAL.create(
|
||||||
{
|
{
|
||||||
projectMembershipId: identityProjectMembership.id,
|
projectMembershipId: identityProjectMembership.id,
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : ProjectMembershipRole.Admin,
|
role: ProjectMembershipRole.Admin
|
||||||
customRoleId: customRole?.id
|
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -932,8 +932,12 @@ export const secretQueueFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const message =
|
const message =
|
||||||
(err instanceof AxiosError ? JSON.stringify(err?.response?.data) : (err as Error)?.message) ||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
"Unknown error occurred.";
|
(err instanceof AxiosError
|
||||||
|
? err?.response?.data
|
||||||
|
? JSON.stringify(err?.response?.data)
|
||||||
|
: err?.message
|
||||||
|
: (err as Error)?.message) || "Unknown error occurred.";
|
||||||
|
|
||||||
await auditLogService.createAuditLog({
|
await auditLogService.createAuditLog({
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Join your organization on Infisical</h2>
|
<h2>Join your organization on Infisical</h2>
|
||||||
<p>{{inviterFirstName}} ({{inviterUsername}}) has invited you to their Infisical organization — {{organizationName}}</p>
|
<p>{{inviterFirstName}} ({{inviterUsername}}) has invited you to their Infisical organization named {{organizationName}}</p>
|
||||||
<a href="{{callback_url}}?token={{token}}{{#if metadata}}&metadata={{metadata}}{{/if}}&to={{email}}&organization_id={{organizationId}}">Join now</a>
|
<a href="{{callback_url}}?token={{token}}{{#if metadata}}&metadata={{metadata}}{{/if}}&to={{email}}&organization_id={{organizationId}}">Click to join</a>
|
||||||
<h3>What is Infisical?</h3>
|
<h3>What is Infisical?</h3>
|
||||||
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
|
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Join your team on Infisical</h2>
|
<h2>Join your team on Infisical</h2>
|
||||||
<p>You have been invited to a new Infisical project — {{workspaceName}}</p>
|
<p>You have been invited to a new Infisical project named {{workspaceName}}</p>
|
||||||
<a href="{{callback_url}}">Join now</a>
|
<a href="{{callback_url}}">Click to join</a>
|
||||||
<h3>What is Infisical?</h3>
|
<h3>What is Infisical?</h3>
|
||||||
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets
|
<p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets
|
||||||
and configs.</p>
|
and configs.</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -74,22 +74,22 @@ Next, you will need to follow the steps listed below to add AWS KMS for your org
|
|||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Navigate to the organization settings and select the 'Encryption' tab.">
|
<Step title="Navigate to the organization settings and select the 'Encryption' tab.">
|
||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Click on the 'Add' button">
|
<Step title="Click on the 'Add' button">
|
||||||

|

|
||||||
Click the 'Add' button to begin adding a new external KMS.
|
Click the 'Add' button to begin adding a new external KMS.
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Select 'AWS KMS'">
|
<Step title="Select 'AWS KMS'">
|
||||||

|

|
||||||
Choose 'AWS KMS' from the list of encryption providers.
|
Choose 'AWS KMS' from the list of encryption providers.
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Provide the inputs for AWS KMS">
|
<Step title="Provide the inputs for AWS KMS">
|
||||||
Selecting AWS as the provider will require you input the following fields.
|
Selecting AWS as the provider will require you input the following fields.
|
||||||
|
|
||||||
<ParamField path="Alias" type="string" required>
|
<ParamField path="Alias" type="string" required>
|
||||||
Name for referencing the AWS KMS key within the organization.
|
Name for referencing the AWS KMS key within the organization.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="Description" type="string">
|
<ParamField path="Description" type="string">
|
||||||
Short description of the AWS KMS key.
|
Short description of the AWS KMS key.
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
title: "GCP Key Management Service"
|
||||||
|
description: "Learn how to manage encryption using GCP KMS"
|
||||||
|
---
|
||||||
|
|
||||||
|
To enhance the security of your Infisical projects, you can now encrypt your secrets using an external Key Management Service (KMS).
|
||||||
|
When external KMS is configured for your project, all encryption and decryption operations will be handled by the chosen KMS.
|
||||||
|
This guide will walk you through the steps needed to configure external KMS support with Google Cloud KMS.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before you begin, you'll first need to set up a GCP Service Account, add a KMS key and set the required permissions.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Create a GCP Service Account">
|
||||||
|
1. Navigate to the [Create Service Account](https://console.cloud.google.com/iam-admin/serviceaccounts/create) page in your GCP Console.
|
||||||
|

|
||||||
|
|
||||||
|
2. Give the service account a suitable **name** and **description**. Then click **Create and Continue**.
|
||||||
|
3. Under **Grant this service account access to project**, click **Select a role** and select the
|
||||||
|
**Cloud KMS Viewer** and **Cloud KMS CryptoKey Encrypter/Decrypter*** roles, then click **Continue**.
|
||||||
|

|
||||||
|
3. You can skip the **Grant users access to this service account** options.
|
||||||
|
4. Click Done.
|
||||||
|
5. You should see the service account in the list of service accounts. Click it to view the service account details.
|
||||||
|
6. Select the **Keys** tab, click **Add Key**, select **Create new key**, select **JSON** as the key type, then click **Create**.
|
||||||
|
7. You will be prompted to download a JSON file that we will need later on.
|
||||||
|
<Info>
|
||||||
|
Remember to keep the JSON file in a secure location. It will be used to authenticate your GCP service account.
|
||||||
|
|
||||||
|
Once you have successfully set up GCP KMS with Infisical, you should permanently delete the JSON file.
|
||||||
|
</Info>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Add a GCP KMS Key">
|
||||||
|
1. Navigate to the [KMS](https://console.cloud.google.com/security/kms) page in your GCP Console.
|
||||||
|
<Info>
|
||||||
|
If you have not used GCP KMS before, you will be redirected to the **Cloud Key Management Service (KMS) API** page.
|
||||||
|
|
||||||
|
Click **Enable** to enable the KMS API, then continue the steps below.
|
||||||
|
|
||||||
|
It may take a few minutes for the API to be enabled and KMS section of the Cloud Console to become viewable.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
2. In the KMS section, click **Create Key Ring**.
|
||||||
|

|
||||||
|
|
||||||
|
3. Give the key ring a **Name** and select a **Region**, then click **Create**.
|
||||||
|
<Info>
|
||||||
|
We don't currently support multi-region key rings.
|
||||||
|
</Info>
|
||||||
|
|
||||||
|
4. On the "Create Key" page, give the key a **Name** and set the **Protection Level** based on your requirements (or use default *Software*), then click **Continue**.
|
||||||
|
|
||||||
|
5. Under **Key Material**, select **Generated Key**, then click **Continue**.
|
||||||
|
|
||||||
|
6. Under **Purpose**, select **Symmetric encrypt/decrypt**, then click **Continue**.
|
||||||
|
|
||||||
|
7. For **Key Rotation Period**, select **Never (manual rotation)**, then click **Continue** followed by **Create**.
|
||||||
|
|
||||||
|
8. You should see the key in the list of keys. We're now ready to set it up in Infisical.
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Setup GCP KMS in the Organization Settings
|
||||||
|
|
||||||
|
Next, you will need to follow the steps listed below to add GCP KMS for your organization.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to the organization settings and select the 'Encryption' tab.">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Click on the 'Add' button">
|
||||||
|

|
||||||
|
Click the 'Add' button to begin adding a new external KMS.
|
||||||
|
</Step>
|
||||||
|
<Step title="Select 'GCP KMS'">
|
||||||
|

|
||||||
|
Choose 'GCP KMS' from the list of encryption providers.
|
||||||
|
</Step>
|
||||||
|
<Step title="Provide the inputs for GCP KMS">
|
||||||
|
|
||||||
|

|
||||||
|
Selecting GCP as the provider will require you input the following fields.
|
||||||
|
|
||||||
|
<ParamField path="Alias" type="string" required>
|
||||||
|
Name for referencing the GCP KMS key within the organization.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="Description" type="string">
|
||||||
|
Short description of the GCP KMS key.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="GCP Region" type="dropdown" required>
|
||||||
|
The GCP region where the GCP KMS key ring is located.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="Service Account Credential JSON" type="file" required>
|
||||||
|
Upload the JSON file you downloaded earlier when creating the GCP service account.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
<ParamField path="GCP Key Name" type="dropdown" required>
|
||||||
|
This field will be populated with the list of GCP KMS keys in the selected region. Select the key you created earlier.
|
||||||
|
</ParamField>
|
||||||
|
|
||||||
|
</Step>
|
||||||
|
<Step title="Click Save">
|
||||||
|
Save your configuration to apply the settings.
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
You now have a GCP KMS Key configured at the organization level. You can assign these GCP KMS keys to existing Infisical projects by visiting the 'Project Settings' page.
|
||||||
|
|
||||||
|
## Assign GCP KMS Key to an Existing Project
|
||||||
|
|
||||||
|
To assign the GCP KMS key you added to your organization, follow the steps below.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Open Project Settings and select to the Encryption Tab">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Under the Key Management section, select your newly added GCP KMS key from the dropdown">
|
||||||
|

|
||||||
|
Choose the GCP KMS key you configured earlier.
|
||||||
|
</Step>
|
||||||
|
<Step title="Click Save">
|
||||||
|
Once you have selected the KMS of choice, click save.
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
@@ -25,4 +25,4 @@ For existing projects, you can configure the KMS from the Project Settings page.
|
|||||||
|
|
||||||
## External KMS
|
## External KMS
|
||||||
|
|
||||||
Infisical supports the use of external KMS solutions to enhance security and compliance. You can configure your project to use services like [AWS Key Management Service](./aws-kms) for managing encryption.
|
Infisical supports the use of external KMS solutions to enhance security and compliance. You can configure your project to use services like [AWS Key Management Service](./aws-kms) or [GCP Key Management Service](./gcp-kms) for managing encryption.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 348 KiB |
|
After Width: | Height: | Size: 590 KiB |
|
Before Width: | Height: | Size: 694 KiB After Width: | Height: | Size: 694 KiB |
|
Before Width: | Height: | Size: 482 KiB After Width: | Height: | Size: 482 KiB |
|
After Width: | Height: | Size: 611 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 978 KiB |
|
After Width: | Height: | Size: 974 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 122 KiB |
@@ -162,6 +162,10 @@ spec:
|
|||||||
secretName: managed-secret
|
secretName: managed-secret
|
||||||
secretNamespace: default
|
secretNamespace: default
|
||||||
creationPolicy: "Orphan" ## Owner | Orphan
|
creationPolicy: "Orphan" ## Owner | Orphan
|
||||||
|
# template:
|
||||||
|
# includeAllSecrets: true
|
||||||
|
# data:
|
||||||
|
# CUSTOM_KEY: "{{ .KEY.SecretPath }} {{ .KEY.Value }}"
|
||||||
# secretType: kubernetes.io/dockerconfigjson
|
# secretType: kubernetes.io/dockerconfigjson
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -674,6 +678,51 @@ The namespace of the managed Kubernetes secret to be created.
|
|||||||
<Accordion title="managedSecretReference.secretType">
|
<Accordion title="managedSecretReference.secretType">
|
||||||
Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets.
|
Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets.
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
<Accordion title="managedSecretReference.template">
|
||||||
|
Templates enable you to transform data from Infisical before storing it as a Kubernetes Secret.
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="managedSecretReference.template.includeAllSecrets">
|
||||||
|
When set to true, this option injects all secrets retrieved from Infisical into your configuration.
|
||||||
|
Secrets defined in the template will override the automatically injected secrets.
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="managedSecretReference.template.data">
|
||||||
|
Define secret keys and their corresponding templates.
|
||||||
|
Each data value uses a Golang template with access to all secrets retrieved from the specified scope.
|
||||||
|
|
||||||
|
Secrets are structured as follows:
|
||||||
|
```golang
|
||||||
|
type TemplateSecret struct {
|
||||||
|
Value string `json:"value"`
|
||||||
|
SecretPath string `json:"secretPath"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example template configuration:
|
||||||
|
```golang
|
||||||
|
managedSecretReference:
|
||||||
|
secretName: managed-secret
|
||||||
|
secretNamespace: default
|
||||||
|
template:
|
||||||
|
includeAllSecrets: true
|
||||||
|
data:
|
||||||
|
NEW_KEY: "{{ .KEY1.SecretPath }} {{ .KEY1.Value }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
When you run the following command:
|
||||||
|
```bash
|
||||||
|
kubectl get secret managed-secret -o jsonpath='{.data}'
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll receive Kubernetes secrets output that includes the NEW_KEY:
|
||||||
|
```bash
|
||||||
|
{... "KEY":"d29ybGQ=","NEW_KEY":"LyBoZWxsbw=="}
|
||||||
|
```
|
||||||
|
|
||||||
|
When you set `includeAllSecrets` as `false` the Kubernetes secrets outputs will be:
|
||||||
|
```bash
|
||||||
|
{"NEW_KEY":"LyBoZWxsbw=="}
|
||||||
|
```
|
||||||
|
</Accordion>
|
||||||
<Accordion title="managedSecretReference.creationPolicy">
|
<Accordion title="managedSecretReference.creationPolicy">
|
||||||
Creation polices allow you to control whether or not owner references should be added to the managed Kubernetes secret that is generated by the Infisical operator.
|
Creation polices allow you to control whether or not owner references should be added to the managed Kubernetes secret that is generated by the Infisical operator.
|
||||||
This is useful for tools such as ArgoCD, where every resource requires an owner reference; otherwise, it will be pruned automatically.
|
This is useful for tools such as ArgoCD, where every resource requires an owner reference; otherwise, it will be pruned automatically.
|
||||||
|
|||||||
@@ -32,7 +32,10 @@
|
|||||||
"thumbsRating": true
|
"thumbsRating": true
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"baseUrl": ["https://app.infisical.com", "http://localhost:8080"]
|
"baseUrl": [
|
||||||
|
"https://app.infisical.com",
|
||||||
|
"http://localhost:8080"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"topbarLinks": [
|
"topbarLinks": [
|
||||||
{
|
{
|
||||||
@@ -73,7 +76,9 @@
|
|||||||
"documentation/getting-started/introduction",
|
"documentation/getting-started/introduction",
|
||||||
{
|
{
|
||||||
"group": "Quickstart",
|
"group": "Quickstart",
|
||||||
"pages": ["documentation/guides/local-development"]
|
"pages": [
|
||||||
|
"documentation/guides/local-development"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Guides",
|
"group": "Guides",
|
||||||
@@ -127,7 +132,8 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"documentation/platform/kms-configuration/overview",
|
"documentation/platform/kms-configuration/overview",
|
||||||
"documentation/platform/kms-configuration/aws-kms",
|
"documentation/platform/kms-configuration/aws-kms",
|
||||||
"documentation/platform/kms-configuration/aws-hsm"
|
"documentation/platform/kms-configuration/aws-hsm",
|
||||||
|
"documentation/platform/kms-configuration/gcp-kms"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -461,20 +467,24 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Build Tool Integrations",
|
"group": "Build Tool Integrations",
|
||||||
"pages": ["integrations/build-tools/gradle"]
|
"pages": [
|
||||||
|
"integrations/build-tools/gradle"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "",
|
"group": "",
|
||||||
"pages": ["sdks/overview"]
|
"pages": [
|
||||||
|
"sdks/overview"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "SDK's",
|
"group": "SDK's",
|
||||||
"pages": [
|
"pages": [
|
||||||
"sdks/languages/node",
|
"sdks/languages/node",
|
||||||
"sdks/languages/python",
|
"sdks/languages/python",
|
||||||
|
"sdks/languages/java",
|
||||||
"sdks/languages/go",
|
"sdks/languages/go",
|
||||||
"sdks/languages/ruby",
|
"sdks/languages/ruby",
|
||||||
"sdks/languages/java",
|
|
||||||
"sdks/languages/csharp"
|
"sdks/languages/csharp"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -485,7 +495,9 @@
|
|||||||
"api-reference/overview/authentication",
|
"api-reference/overview/authentication",
|
||||||
{
|
{
|
||||||
"group": "Examples",
|
"group": "Examples",
|
||||||
"pages": ["api-reference/overview/examples/integration"]
|
"pages": [
|
||||||
|
"api-reference/overview/examples/integration"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -760,11 +772,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Service Tokens",
|
"group": "Service Tokens",
|
||||||
"pages": ["api-reference/endpoints/service-tokens/get"]
|
"pages": [
|
||||||
|
"api-reference/endpoints/service-tokens/get"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Audit Logs",
|
"group": "Audit Logs",
|
||||||
"pages": ["api-reference/endpoints/audit-logs/export-audit-log"]
|
"pages": [
|
||||||
|
"api-reference/endpoints/audit-logs/export-audit-log"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -863,7 +879,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "",
|
"group": "",
|
||||||
"pages": ["changelog/overview"]
|
"pages": [
|
||||||
|
"changelog/overview"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Contributing",
|
"group": "Contributing",
|
||||||
@@ -887,7 +905,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "Contributing to SDK",
|
"group": "Contributing to SDK",
|
||||||
"pages": ["contributing/sdk/developing"]
|
"pages": [
|
||||||
|
"contributing/sdk/developing"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -911,13 +931,22 @@
|
|||||||
{
|
{
|
||||||
"title": "PRODUCT",
|
"title": "PRODUCT",
|
||||||
"links": [
|
"links": [
|
||||||
{ "label": "Secret Management", "url": "https://infisical.com/" },
|
{
|
||||||
{ "label": "Secret Scanning", "url": "https://infisical.com/radar" },
|
"label": "Secret Management",
|
||||||
|
"url": "https://infisical.com/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Secret Scanning",
|
||||||
|
"url": "https://infisical.com/radar"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "Share Secrets",
|
"label": "Share Secrets",
|
||||||
"url": "https://app.infisical.com/share-secret"
|
"url": "https://app.infisical.com/share-secret"
|
||||||
},
|
},
|
||||||
{ "label": "Pricing", "url": "https://infisical.com/pricing" },
|
{
|
||||||
|
"label": "Pricing",
|
||||||
|
"url": "https://infisical.com/pricing"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "Security",
|
"label": "Security",
|
||||||
"url": "https://infisical.com/docs/internals/security"
|
"url": "https://infisical.com/docs/internals/security"
|
||||||
@@ -1061,4 +1090,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: "Infisical Java SDK"
|
title: "Infisical Java SDK"
|
||||||
sidebarTitle: "Java"
|
sidebarTitle: "Java"
|
||||||
|
url: "https://github.com/Infisical/java-sdk?tab=readme-ov-file#infisical-nodejs-sdk"
|
||||||
icon: "java"
|
icon: "java"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
{
|
||||||
|
/*
|
||||||
If you're working with Java, the official [Infisical Java SDK](https://github.com/Infisical/sdk/tree/main/languages/java) package is the easiest way to fetch and work with secrets for your application.
|
If you're working with Java, the official [Infisical Java SDK](https://github.com/Infisical/sdk/tree/main/languages/java) package is the easiest way to fetch and work with secrets for your application.
|
||||||
|
|
||||||
- [Maven Package](https://github.com/Infisical/sdk/packages/2019741)
|
- [Maven Package](https://github.com/Infisical/sdk/packages/2019741)
|
||||||
@@ -568,4 +571,5 @@ String decryptedString = client.decryptSymmetric(decryptOptions);
|
|||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
#### Returns (string)
|
#### Returns (string)
|
||||||
`Plaintext` (string): The decrypted plaintext.
|
`Plaintext` (string): The decrypted plaintext.
|
||||||
|
*/}
|
||||||
@@ -16,7 +16,7 @@ From local development to production, Infisical SDKs provide the easiest way for
|
|||||||
<Card href="https://github.com/Infisical/python-sdk-official" title="Python" icon="python" color="#4c8abe">
|
<Card href="https://github.com/Infisical/python-sdk-official" title="Python" icon="python" color="#4c8abe">
|
||||||
Manage secrets for your Python application on demand
|
Manage secrets for your Python application on demand
|
||||||
</Card>
|
</Card>
|
||||||
<Card href="/sdks/languages/java" title="Java" icon="java" color="#e41f23">
|
<Card href="https://github.com/Infisical/java-sdk?tab=readme-ov-file#infisical-nodejs-sdk" title="Java" icon="java" color="#e41f23">
|
||||||
Manage secrets for your Java application on demand
|
Manage secrets for your Java application on demand
|
||||||
</Card>
|
</Card>
|
||||||
<Card href="/sdks/languages/go" title="Go" icon="golang" color="#367B99">
|
<Card href="/sdks/languages/go" title="Go" icon="golang" color="#367B99">
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
"react-mailchimp-subscribe": "^2.1.3",
|
"react-mailchimp-subscribe": "^2.1.3",
|
||||||
"react-markdown": "^8.0.3",
|
"react-markdown": "^8.0.3",
|
||||||
"react-redux": "^8.0.2",
|
"react-redux": "^8.0.2",
|
||||||
"react-select": "^5.8.3",
|
"react-select": "^5.8.1",
|
||||||
"react-table": "^7.8.0",
|
"react-table": "^7.8.0",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"sanitize-html": "^2.12.1",
|
"sanitize-html": "^2.12.1",
|
||||||
|
|||||||