mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-18 01:29:25 +00:00
Compare commits
145 Commits
feat/add-k
...
daniel/cli
Author | SHA1 | Date | |
---|---|---|---|
22f32e060b | |||
b4f26aac25 | |||
b634a6c371 | |||
080ae5ce6f | |||
3b28e946cf | |||
4db82e37c1 | |||
3a8789af76 | |||
79ebfc92e9 | |||
ffca4aa054 | |||
52b3f7e8c8 | |||
9de33d8c23 | |||
97aed61c54 | |||
972dbac7db | |||
5c0e265703 | |||
4efbb8dca6 | |||
09db9e340b | |||
5e3d4edec9 | |||
86348eb434 | |||
d31d28666a | |||
3362ec29cd | |||
3a0e2bf88b | |||
86862b932c | |||
85fefb2a82 | |||
858ec2095e | |||
a5bb80d2cf | |||
3156057278 | |||
b5da1d7a6c | |||
8fa8161602 | |||
b12aca62ff | |||
c9cd843184 | |||
47442b16f5 | |||
0bdb5d3f19 | |||
cd9ab0024e | |||
f4bed26781 | |||
75e9ea9c5d | |||
d0c10838e1 | |||
4dc587576b | |||
7097731539 | |||
4261281b0f | |||
ff7ff06a6a | |||
6cbeb4ddf9 | |||
5a07c3d1d4 | |||
d96e880015 | |||
4df6c8c2cc | |||
70860e0d26 | |||
3f3b81f9bf | |||
5181cac9c8 | |||
5af39b1a40 | |||
a9723134f9 | |||
fe237fbf4a | |||
98e79207cc | |||
26375715e4 | |||
5c435f7645 | |||
f7a9e13209 | |||
04908edb5b | |||
e8753a3ce8 | |||
1947989ca5 | |||
a47e6910b1 | |||
78c4a591a9 | |||
f6b7717517 | |||
476671e6ef | |||
b21a5b6425 | |||
66a5691ffd | |||
6bdf62d453 | |||
652a48b520 | |||
3148c54e18 | |||
bd4cf64fc6 | |||
f4e3d7d576 | |||
8298f9974f | |||
da347e96e1 | |||
5df96234a0 | |||
e78682560c | |||
1602fac5ca | |||
0100bf7032 | |||
e2c49878c6 | |||
e74117b7fd | |||
335aada941 | |||
b949fe06c3 | |||
28e539c481 | |||
5c4c881b60 | |||
8ffb92bfb3 | |||
db9a1726c2 | |||
15986633c7 | |||
c4809bbb54 | |||
6305aab0d1 | |||
456493ff5a | |||
5fe93dc35a | |||
5e0e7763a3 | |||
f663d1d4a6 | |||
48619ed24c | |||
21fb8df39b | |||
f03a7cc249 | |||
d08510ebe4 | |||
767159bf8f | |||
98457cdb34 | |||
8ed8f1200d | |||
30252c2bcb | |||
cc3551c417 | |||
accb21f7ed | |||
8f010e740f | |||
f3768c90c7 | |||
3190ff2eb1 | |||
c7ec825830 | |||
5b7f445e33 | |||
7fe53ab00e | |||
8168b5faf8 | |||
8b9e035bf6 | |||
d36d0784ca | |||
f3a84f6001 | |||
13672481a8 | |||
4f26b43789 | |||
4817eb2fc6 | |||
c623c615a1 | |||
034a8112b7 | |||
5fc6fd71ce | |||
f45c917922 | |||
debef510e4 | |||
e5bc609a2a | |||
b812761bdd | |||
14362dbe6a | |||
b7b90aea33 | |||
14cc21787d | |||
f551806737 | |||
28a3bf0b94 | |||
5712c24370 | |||
4a391c7ac2 | |||
2b21c9d348 | |||
2b948a18f3 | |||
f06004370d | |||
2493bbbc97 | |||
44aa743d56 | |||
fefb71dd86 | |||
1748052cb0 | |||
c01a98ccf1 | |||
9ea9f90928 | |||
6319f53802 | |||
8bfd3913da | |||
9e1d38a27b | |||
78d5bc823d | |||
e8d424bbb0 | |||
f0c52cc8da | |||
e58dbe853e | |||
f493a617b1 | |||
32a3e1d200 | |||
c6e56f0380 |
2
backend/src/@types/fastify.d.ts
vendored
2
backend/src/@types/fastify.d.ts
vendored
@ -83,6 +83,7 @@ import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-servi
|
|||||||
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
||||||
import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
||||||
import { TPkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
|
import { TPkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
|
||||||
|
import { TPkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service";
|
||||||
import { TProjectServiceFactory } from "@app/services/project/project-service";
|
import { TProjectServiceFactory } from "@app/services/project/project-service";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
|
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
|
||||||
@ -271,6 +272,7 @@ declare module "fastify" {
|
|||||||
assumePrivileges: TAssumePrivilegeServiceFactory;
|
assumePrivileges: TAssumePrivilegeServiceFactory;
|
||||||
githubOrgSync: TGithubOrgSyncServiceFactory;
|
githubOrgSync: TGithubOrgSyncServiceFactory;
|
||||||
internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory;
|
internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory;
|
||||||
|
pkiTemplate: TPkiTemplatesServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
17
backend/src/@types/knex.d.ts
vendored
17
backend/src/@types/knex.d.ts
vendored
@ -6,6 +6,9 @@ import {
|
|||||||
TAccessApprovalPoliciesApprovers,
|
TAccessApprovalPoliciesApprovers,
|
||||||
TAccessApprovalPoliciesApproversInsert,
|
TAccessApprovalPoliciesApproversInsert,
|
||||||
TAccessApprovalPoliciesApproversUpdate,
|
TAccessApprovalPoliciesApproversUpdate,
|
||||||
|
TAccessApprovalPoliciesBypassers,
|
||||||
|
TAccessApprovalPoliciesBypassersInsert,
|
||||||
|
TAccessApprovalPoliciesBypassersUpdate,
|
||||||
TAccessApprovalPoliciesInsert,
|
TAccessApprovalPoliciesInsert,
|
||||||
TAccessApprovalPoliciesUpdate,
|
TAccessApprovalPoliciesUpdate,
|
||||||
TAccessApprovalRequests,
|
TAccessApprovalRequests,
|
||||||
@ -276,6 +279,9 @@ import {
|
|||||||
TSecretApprovalPoliciesApprovers,
|
TSecretApprovalPoliciesApprovers,
|
||||||
TSecretApprovalPoliciesApproversInsert,
|
TSecretApprovalPoliciesApproversInsert,
|
||||||
TSecretApprovalPoliciesApproversUpdate,
|
TSecretApprovalPoliciesApproversUpdate,
|
||||||
|
TSecretApprovalPoliciesBypassers,
|
||||||
|
TSecretApprovalPoliciesBypassersInsert,
|
||||||
|
TSecretApprovalPoliciesBypassersUpdate,
|
||||||
TSecretApprovalPoliciesInsert,
|
TSecretApprovalPoliciesInsert,
|
||||||
TSecretApprovalPoliciesUpdate,
|
TSecretApprovalPoliciesUpdate,
|
||||||
TSecretApprovalRequests,
|
TSecretApprovalRequests,
|
||||||
@ -820,6 +826,12 @@ declare module "knex/types/tables" {
|
|||||||
TAccessApprovalPoliciesApproversUpdate
|
TAccessApprovalPoliciesApproversUpdate
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
[TableName.AccessApprovalPolicyBypasser]: KnexOriginal.CompositeTableType<
|
||||||
|
TAccessApprovalPoliciesBypassers,
|
||||||
|
TAccessApprovalPoliciesBypassersInsert,
|
||||||
|
TAccessApprovalPoliciesBypassersUpdate
|
||||||
|
>;
|
||||||
|
|
||||||
[TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType<
|
[TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType<
|
||||||
TAccessApprovalRequests,
|
TAccessApprovalRequests,
|
||||||
TAccessApprovalRequestsInsert,
|
TAccessApprovalRequestsInsert,
|
||||||
@ -843,6 +855,11 @@ declare module "knex/types/tables" {
|
|||||||
TSecretApprovalPoliciesApproversInsert,
|
TSecretApprovalPoliciesApproversInsert,
|
||||||
TSecretApprovalPoliciesApproversUpdate
|
TSecretApprovalPoliciesApproversUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SecretApprovalPolicyBypasser]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretApprovalPoliciesBypassers,
|
||||||
|
TSecretApprovalPoliciesBypassersInsert,
|
||||||
|
TSecretApprovalPoliciesBypassersUpdate
|
||||||
|
>;
|
||||||
[TableName.SecretApprovalRequest]: KnexOriginal.CompositeTableType<
|
[TableName.SecretApprovalRequest]: KnexOriginal.CompositeTableType<
|
||||||
TSecretApprovalRequests,
|
TSecretApprovalRequests,
|
||||||
TSecretApprovalRequestsInsert,
|
TSecretApprovalRequestsInsert,
|
||||||
|
48
backend/src/db/migrations/20250527030702_policy-bypassers.ts
Normal file
48
backend/src/db/migrations/20250527030702_policy-bypassers.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.AccessApprovalPolicyBypasser))) {
|
||||||
|
await knex.schema.createTable(TableName.AccessApprovalPolicyBypasser, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
|
||||||
|
t.uuid("bypasserGroupId").nullable();
|
||||||
|
t.foreign("bypasserGroupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
||||||
|
|
||||||
|
t.uuid("bypasserUserId").nullable();
|
||||||
|
t.foreign("bypasserUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
|
||||||
|
t.uuid("policyId").notNullable();
|
||||||
|
t.foreign("policyId").references("id").inTable(TableName.AccessApprovalPolicy).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
await createOnUpdateTrigger(knex, TableName.AccessApprovalPolicyBypasser);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicyBypasser))) {
|
||||||
|
await knex.schema.createTable(TableName.SecretApprovalPolicyBypasser, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
|
||||||
|
t.uuid("bypasserGroupId").nullable();
|
||||||
|
t.foreign("bypasserGroupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
||||||
|
|
||||||
|
t.uuid("bypasserUserId").nullable();
|
||||||
|
t.foreign("bypasserUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
|
||||||
|
t.uuid("policyId").notNullable();
|
||||||
|
t.foreign("policyId").references("id").inTable(TableName.SecretApprovalPolicy).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicyBypasser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretApprovalPolicyBypasser);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.AccessApprovalPolicyBypasser);
|
||||||
|
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SecretApprovalPolicyBypasser);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.AccessApprovalPolicyBypasser);
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityAccessToken, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityUniversalAuth, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityAwsAuth, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityOidcAuth, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityAzureAuth, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAzureAuth, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityGcpAuth, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityGcpAuth, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityJwtAuth, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityJwtAuth, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityLdapAuth, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityOciAuth, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityOciAuth, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityTokenAuth, "accessTokenPeriod"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityTokenAuth, (t) => {
|
||||||
|
t.bigInteger("accessTokenPeriod").defaultTo(0).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityAccessToken, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityUniversalAuth, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityUniversalAuth, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityAwsAuth, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityOidcAuth, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityAzureAuth, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAzureAuth, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityGcpAuth, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityGcpAuth, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityJwtAuth, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityJwtAuth, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityLdapAuth, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityOciAuth, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityOciAuth, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityTokenAuth, "accessTokenPeriod")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityTokenAuth, (t) => {
|
||||||
|
t.dropColumn("accessTokenPeriod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasNameCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "name");
|
||||||
|
if (hasNameCol) {
|
||||||
|
const templates = await knex(TableName.CertificateTemplate).select("id", "name");
|
||||||
|
await Promise.all(
|
||||||
|
templates.map((el) => {
|
||||||
|
const slugifiedName = el.name
|
||||||
|
? slugify(`${el.name.slice(0, 16)}-${alphaNumericNanoId(8)}`)
|
||||||
|
: slugify(alphaNumericNanoId(12));
|
||||||
|
|
||||||
|
return knex(TableName.CertificateTemplate).where({ id: el.id }).update({ name: slugifiedName });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(): Promise<void> {}
|
@ -0,0 +1,27 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
const hasEncryptedSalt = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSalt");
|
||||||
|
|
||||||
|
if (hasEncryptedSalt) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
|
t.dropColumn("encryptedSalt");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
const hasEncryptedSalt = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSalt");
|
||||||
|
|
||||||
|
if (!hasEncryptedSalt) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
|
t.binary("encryptedSalt").nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { ApprovalStatus } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasPrivilegeDeletedAtColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.AccessApprovalRequest,
|
||||||
|
"privilegeDeletedAt"
|
||||||
|
);
|
||||||
|
const hasStatusColumn = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "status");
|
||||||
|
|
||||||
|
if (!hasPrivilegeDeletedAtColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
|
||||||
|
t.timestamp("privilegeDeletedAt").nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasStatusColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
|
||||||
|
t.string("status").defaultTo(ApprovalStatus.PENDING).notNullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update existing rows based on business logic
|
||||||
|
// If privilegeId is not null, set status to "approved"
|
||||||
|
await knex(TableName.AccessApprovalRequest).whereNotNull("privilegeId").update({ status: ApprovalStatus.APPROVED });
|
||||||
|
|
||||||
|
// If privilegeId is null and there's a rejected reviewer, set to "rejected"
|
||||||
|
const rejectedRequestIds = await knex(TableName.AccessApprovalRequestReviewer)
|
||||||
|
.select("requestId")
|
||||||
|
.where("status", "rejected")
|
||||||
|
.distinct()
|
||||||
|
.pluck("requestId");
|
||||||
|
|
||||||
|
if (rejectedRequestIds.length > 0) {
|
||||||
|
await knex(TableName.AccessApprovalRequest)
|
||||||
|
.whereNull("privilegeId")
|
||||||
|
.whereIn("id", rejectedRequestIds)
|
||||||
|
.update({ status: ApprovalStatus.REJECTED });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasPrivilegeDeletedAtColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.AccessApprovalRequest,
|
||||||
|
"privilegeDeletedAt"
|
||||||
|
);
|
||||||
|
const hasStatusColumn = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "status");
|
||||||
|
|
||||||
|
if (hasPrivilegeDeletedAtColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
|
||||||
|
t.dropColumn("privilegeDeletedAt");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasStatusColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.AccessApprovalRequest, (t) => {
|
||||||
|
t.dropColumn("status");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
26
backend/src/db/schemas/access-approval-policies-bypassers.ts
Normal file
26
backend/src/db/schemas/access-approval-policies-bypassers.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const AccessApprovalPoliciesBypassersSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
bypasserGroupId: z.string().uuid().nullable().optional(),
|
||||||
|
bypasserUserId: z.string().uuid().nullable().optional(),
|
||||||
|
policyId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TAccessApprovalPoliciesBypassers = z.infer<typeof AccessApprovalPoliciesBypassersSchema>;
|
||||||
|
export type TAccessApprovalPoliciesBypassersInsert = Omit<
|
||||||
|
z.input<typeof AccessApprovalPoliciesBypassersSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TAccessApprovalPoliciesBypassersUpdate = Partial<
|
||||||
|
Omit<z.input<typeof AccessApprovalPoliciesBypassersSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
@ -18,7 +18,9 @@ export const AccessApprovalRequestsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
requestedByUserId: z.string().uuid(),
|
requestedByUserId: z.string().uuid(),
|
||||||
note: z.string().nullable().optional()
|
note: z.string().nullable().optional(),
|
||||||
|
privilegeDeletedAt: z.date().nullable().optional(),
|
||||||
|
status: z.string().default("pending")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>;
|
export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>;
|
||||||
|
@ -21,7 +21,8 @@ export const IdentityAccessTokensSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
name: z.string().nullable().optional(),
|
name: z.string().nullable().optional(),
|
||||||
authMethod: z.string()
|
authMethod: z.string(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>;
|
export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>;
|
||||||
|
@ -19,7 +19,8 @@ export const IdentityAwsAuthsSchema = z.object({
|
|||||||
type: z.string(),
|
type: z.string(),
|
||||||
stsEndpoint: z.string(),
|
stsEndpoint: z.string(),
|
||||||
allowedPrincipalArns: z.string(),
|
allowedPrincipalArns: z.string(),
|
||||||
allowedAccountIds: z.string()
|
allowedAccountIds: z.string(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityAwsAuths = z.infer<typeof IdentityAwsAuthsSchema>;
|
export type TIdentityAwsAuths = z.infer<typeof IdentityAwsAuthsSchema>;
|
||||||
|
@ -18,7 +18,8 @@ export const IdentityAzureAuthsSchema = z.object({
|
|||||||
identityId: z.string().uuid(),
|
identityId: z.string().uuid(),
|
||||||
tenantId: z.string(),
|
tenantId: z.string(),
|
||||||
resource: z.string(),
|
resource: z.string(),
|
||||||
allowedServicePrincipalIds: z.string()
|
allowedServicePrincipalIds: z.string(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityAzureAuths = z.infer<typeof IdentityAzureAuthsSchema>;
|
export type TIdentityAzureAuths = z.infer<typeof IdentityAzureAuthsSchema>;
|
||||||
|
@ -19,7 +19,8 @@ export const IdentityGcpAuthsSchema = z.object({
|
|||||||
type: z.string(),
|
type: z.string(),
|
||||||
allowedServiceAccounts: z.string().nullable().optional(),
|
allowedServiceAccounts: z.string().nullable().optional(),
|
||||||
allowedProjects: z.string().nullable().optional(),
|
allowedProjects: z.string().nullable().optional(),
|
||||||
allowedZones: z.string().nullable().optional()
|
allowedZones: z.string().nullable().optional(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityGcpAuths = z.infer<typeof IdentityGcpAuthsSchema>;
|
export type TIdentityGcpAuths = z.infer<typeof IdentityGcpAuthsSchema>;
|
||||||
|
@ -25,7 +25,8 @@ export const IdentityJwtAuthsSchema = z.object({
|
|||||||
boundClaims: z.unknown(),
|
boundClaims: z.unknown(),
|
||||||
boundSubject: z.string(),
|
boundSubject: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityJwtAuths = z.infer<typeof IdentityJwtAuthsSchema>;
|
export type TIdentityJwtAuths = z.infer<typeof IdentityJwtAuthsSchema>;
|
||||||
|
@ -30,7 +30,8 @@ export const IdentityKubernetesAuthsSchema = z.object({
|
|||||||
allowedAudience: z.string(),
|
allowedAudience: z.string(),
|
||||||
encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(),
|
encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(),
|
||||||
encryptedKubernetesCaCertificate: zodBuffer.nullable().optional(),
|
encryptedKubernetesCaCertificate: zodBuffer.nullable().optional(),
|
||||||
gatewayId: z.string().uuid().nullable().optional()
|
gatewayId: z.string().uuid().nullable().optional(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>;
|
export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>;
|
||||||
|
@ -24,7 +24,8 @@ export const IdentityLdapAuthsSchema = z.object({
|
|||||||
searchFilter: z.string(),
|
searchFilter: z.string(),
|
||||||
allowedFields: z.unknown().nullable().optional(),
|
allowedFields: z.unknown().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;
|
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;
|
||||||
|
@ -18,7 +18,8 @@ export const IdentityOciAuthsSchema = z.object({
|
|||||||
identityId: z.string().uuid(),
|
identityId: z.string().uuid(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
tenancyOcid: z.string(),
|
tenancyOcid: z.string(),
|
||||||
allowedUsernames: z.string().nullable().optional()
|
allowedUsernames: z.string().nullable().optional(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityOciAuths = z.infer<typeof IdentityOciAuthsSchema>;
|
export type TIdentityOciAuths = z.infer<typeof IdentityOciAuthsSchema>;
|
||||||
|
@ -27,7 +27,8 @@ export const IdentityOidcAuthsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
encryptedCaCertificate: zodBuffer.nullable().optional(),
|
encryptedCaCertificate: zodBuffer.nullable().optional(),
|
||||||
claimMetadataMapping: z.unknown().nullable().optional()
|
claimMetadataMapping: z.unknown().nullable().optional(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityOidcAuths = z.infer<typeof IdentityOidcAuthsSchema>;
|
export type TIdentityOidcAuths = z.infer<typeof IdentityOidcAuthsSchema>;
|
||||||
|
@ -15,7 +15,8 @@ export const IdentityTokenAuthsSchema = z.object({
|
|||||||
accessTokenTrustedIps: z.unknown(),
|
accessTokenTrustedIps: z.unknown(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
identityId: z.string().uuid()
|
identityId: z.string().uuid(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityTokenAuths = z.infer<typeof IdentityTokenAuthsSchema>;
|
export type TIdentityTokenAuths = z.infer<typeof IdentityTokenAuthsSchema>;
|
||||||
|
@ -17,7 +17,8 @@ export const IdentityUniversalAuthsSchema = z.object({
|
|||||||
accessTokenTrustedIps: z.unknown(),
|
accessTokenTrustedIps: z.unknown(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
identityId: z.string().uuid()
|
identityId: z.string().uuid(),
|
||||||
|
accessTokenPeriod: z.coerce.number().default(0)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityUniversalAuths = z.infer<typeof IdentityUniversalAuthsSchema>;
|
export type TIdentityUniversalAuths = z.infer<typeof IdentityUniversalAuthsSchema>;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export * from "./access-approval-policies";
|
export * from "./access-approval-policies";
|
||||||
export * from "./access-approval-policies-approvers";
|
export * from "./access-approval-policies-approvers";
|
||||||
|
export * from "./access-approval-policies-bypassers";
|
||||||
export * from "./access-approval-requests";
|
export * from "./access-approval-requests";
|
||||||
export * from "./access-approval-requests-reviewers";
|
export * from "./access-approval-requests-reviewers";
|
||||||
export * from "./api-keys";
|
export * from "./api-keys";
|
||||||
@ -92,6 +93,7 @@ export * from "./saml-configs";
|
|||||||
export * from "./scim-tokens";
|
export * from "./scim-tokens";
|
||||||
export * from "./secret-approval-policies";
|
export * from "./secret-approval-policies";
|
||||||
export * from "./secret-approval-policies-approvers";
|
export * from "./secret-approval-policies-approvers";
|
||||||
|
export * from "./secret-approval-policies-bypassers";
|
||||||
export * from "./secret-approval-request-secret-tags";
|
export * from "./secret-approval-request-secret-tags";
|
||||||
export * from "./secret-approval-request-secret-tags-v2";
|
export * from "./secret-approval-request-secret-tags-v2";
|
||||||
export * from "./secret-approval-requests";
|
export * from "./secret-approval-requests";
|
||||||
|
@ -95,10 +95,12 @@ export enum TableName {
|
|||||||
ScimToken = "scim_tokens",
|
ScimToken = "scim_tokens",
|
||||||
AccessApprovalPolicy = "access_approval_policies",
|
AccessApprovalPolicy = "access_approval_policies",
|
||||||
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
|
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
|
||||||
|
AccessApprovalPolicyBypasser = "access_approval_policies_bypassers",
|
||||||
AccessApprovalRequest = "access_approval_requests",
|
AccessApprovalRequest = "access_approval_requests",
|
||||||
AccessApprovalRequestReviewer = "access_approval_requests_reviewers",
|
AccessApprovalRequestReviewer = "access_approval_requests_reviewers",
|
||||||
SecretApprovalPolicy = "secret_approval_policies",
|
SecretApprovalPolicy = "secret_approval_policies",
|
||||||
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
||||||
|
SecretApprovalPolicyBypasser = "secret_approval_policies_bypassers",
|
||||||
SecretApprovalRequest = "secret_approval_requests",
|
SecretApprovalRequest = "secret_approval_requests",
|
||||||
SecretApprovalRequestReviewer = "secret_approval_requests_reviewers",
|
SecretApprovalRequestReviewer = "secret_approval_requests_reviewers",
|
||||||
SecretApprovalRequestSecret = "secret_approval_requests_secrets",
|
SecretApprovalRequestSecret = "secret_approval_requests_secrets",
|
||||||
|
26
backend/src/db/schemas/secret-approval-policies-bypassers.ts
Normal file
26
backend/src/db/schemas/secret-approval-policies-bypassers.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const SecretApprovalPoliciesBypassersSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
bypasserGroupId: z.string().uuid().nullable().optional(),
|
||||||
|
bypasserUserId: z.string().uuid().nullable().optional(),
|
||||||
|
policyId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretApprovalPoliciesBypassers = z.infer<typeof SecretApprovalPoliciesBypassersSchema>;
|
||||||
|
export type TSecretApprovalPoliciesBypassersInsert = Omit<
|
||||||
|
z.input<typeof SecretApprovalPoliciesBypassersSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TSecretApprovalPoliciesBypassersUpdate = Partial<
|
||||||
|
Omit<z.input<typeof SecretApprovalPoliciesBypassersSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
@ -28,7 +28,6 @@ export const SecretSharingSchema = z.object({
|
|||||||
encryptedSecret: zodBuffer.nullable().optional(),
|
encryptedSecret: zodBuffer.nullable().optional(),
|
||||||
identifier: z.string().nullable().optional(),
|
identifier: z.string().nullable().optional(),
|
||||||
type: z.string().default("share"),
|
type: z.string().default("share"),
|
||||||
encryptedSalt: zodBuffer.nullable().optional(),
|
|
||||||
authorizedEmails: z.unknown().nullable().optional()
|
authorizedEmails: z.unknown().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ApproverType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
import { EnforcementLevel } from "@app/lib/types";
|
||||||
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";
|
||||||
@ -24,10 +24,19 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
approvers: z
|
approvers: z
|
||||||
.discriminatedUnion("type", [
|
.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
])
|
])
|
||||||
.array()
|
.array()
|
||||||
|
.max(100, "Cannot have more than 100 approvers")
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
|
bypassers: z
|
||||||
|
.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
||||||
|
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
|
])
|
||||||
|
.array()
|
||||||
|
.max(100, "Cannot have more than 100 bypassers")
|
||||||
|
.optional(),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
@ -72,7 +81,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.object({ type: z.nativeEnum(ApproverType), id: z.string().nullable().optional() })
|
.object({ type: z.nativeEnum(ApproverType), id: z.string().nullable().optional() })
|
||||||
.array()
|
.array()
|
||||||
.nullable()
|
.nullable()
|
||||||
.optional()
|
.optional(),
|
||||||
|
bypassers: z.object({ type: z.nativeEnum(BypasserType), id: z.string().nullable().optional() }).array()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.nullable()
|
.nullable()
|
||||||
@ -143,10 +153,19 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
approvers: z
|
approvers: z
|
||||||
.discriminatedUnion("type", [
|
.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
])
|
])
|
||||||
.array()
|
.array()
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" })
|
||||||
|
.max(100, "Cannot have more than 100 approvers"),
|
||||||
|
bypassers: z
|
||||||
|
.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
||||||
|
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
|
])
|
||||||
|
.array()
|
||||||
|
.max(100, "Cannot have more than 100 bypassers")
|
||||||
|
.optional(),
|
||||||
approvals: z.number().min(1).optional(),
|
approvals: z.number().min(1).optional(),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
@ -220,6 +239,15 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.nullable()
|
.nullable()
|
||||||
|
.optional(),
|
||||||
|
bypassers: z
|
||||||
|
.object({
|
||||||
|
type: z.nativeEnum(BypasserType),
|
||||||
|
id: z.string().nullable().optional(),
|
||||||
|
name: z.string().nullable().optional()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.nullable()
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -113,6 +113,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
approvals: z.number(),
|
approvals: z.number(),
|
||||||
approvers: z.string().array(),
|
approvers: z.string().array(),
|
||||||
|
bypassers: z.string().array(),
|
||||||
secretPath: z.string().nullish(),
|
secretPath: z.string().nullish(),
|
||||||
envId: z.string(),
|
envId: z.string(),
|
||||||
enforcementLevel: z.string(),
|
enforcementLevel: z.string(),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ApproverType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
import { ApproverType, BypasserType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
import { EnforcementLevel } from "@app/lib/types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@ -30,10 +30,19 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
approvers: z
|
approvers: z
|
||||||
.discriminatedUnion("type", [
|
.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
])
|
])
|
||||||
.array()
|
.array()
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" })
|
||||||
|
.max(100, "Cannot have more than 100 approvers"),
|
||||||
|
bypassers: z
|
||||||
|
.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
||||||
|
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
|
])
|
||||||
|
.array()
|
||||||
|
.max(100, "Cannot have more than 100 bypassers")
|
||||||
|
.optional(),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||||
allowedSelfApprovals: z.boolean().default(true)
|
allowedSelfApprovals: z.boolean().default(true)
|
||||||
@ -75,10 +84,19 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
approvers: z
|
approvers: z
|
||||||
.discriminatedUnion("type", [
|
.discriminatedUnion("type", [
|
||||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
])
|
])
|
||||||
.array()
|
.array()
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" })
|
||||||
|
.max(100, "Cannot have more than 100 approvers"),
|
||||||
|
bypassers: z
|
||||||
|
.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
||||||
|
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
|
||||||
|
])
|
||||||
|
.array()
|
||||||
|
.max(100, "Cannot have more than 100 bypassers")
|
||||||
|
.optional(),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
secretPath: z
|
secretPath: z
|
||||||
.string()
|
.string()
|
||||||
@ -157,6 +175,12 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
id: z.string().nullable().optional(),
|
id: z.string().nullable().optional(),
|
||||||
type: z.nativeEnum(ApproverType)
|
type: z.nativeEnum(ApproverType)
|
||||||
})
|
})
|
||||||
|
.array(),
|
||||||
|
bypassers: z
|
||||||
|
.object({
|
||||||
|
id: z.string().nullable().optional(),
|
||||||
|
type: z.nativeEnum(BypasserType)
|
||||||
|
})
|
||||||
.array()
|
.array()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
@ -193,7 +217,14 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.object({
|
.object({
|
||||||
id: z.string().nullable().optional(),
|
id: z.string().nullable().optional(),
|
||||||
type: z.nativeEnum(ApproverType),
|
type: z.nativeEnum(ApproverType),
|
||||||
name: z.string().nullable().optional()
|
username: z.string().nullable().optional()
|
||||||
|
})
|
||||||
|
.array(),
|
||||||
|
bypassers: z
|
||||||
|
.object({
|
||||||
|
id: z.string().nullable().optional(),
|
||||||
|
type: z.nativeEnum(BypasserType),
|
||||||
|
username: z.string().nullable().optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
})
|
})
|
||||||
|
@ -47,6 +47,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
userId: z.string().nullable().optional()
|
userId: z.string().nullable().optional()
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
|
bypassers: z
|
||||||
|
.object({
|
||||||
|
userId: z.string().nullable().optional()
|
||||||
|
})
|
||||||
|
.array(),
|
||||||
secretPath: z.string().optional().nullable(),
|
secretPath: z.string().optional().nullable(),
|
||||||
enforcementLevel: z.string(),
|
enforcementLevel: z.string(),
|
||||||
deletedAt: z.date().nullish(),
|
deletedAt: z.date().nullish(),
|
||||||
@ -266,6 +271,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
approvals: z.number(),
|
approvals: z.number(),
|
||||||
approvers: approvalRequestUser.array(),
|
approvers: approvalRequestUser.array(),
|
||||||
|
bypassers: approvalRequestUser.array(),
|
||||||
secretPath: z.string().optional().nullable(),
|
secretPath: z.string().optional().nullable(),
|
||||||
enforcementLevel: z.string(),
|
enforcementLevel: z.string(),
|
||||||
deletedAt: z.date().nullish(),
|
deletedAt: z.date().nullish(),
|
||||||
|
@ -5,6 +5,7 @@ import { registerAwsIamUserSecretRotationRouter } from "./aws-iam-user-secret-ro
|
|||||||
import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-rotation-router";
|
import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-rotation-router";
|
||||||
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
||||||
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
||||||
|
import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router";
|
||||||
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
||||||
|
|
||||||
export * from "./secret-rotation-v2-router";
|
export * from "./secret-rotation-v2-router";
|
||||||
@ -15,6 +16,7 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
|
|||||||
> = {
|
> = {
|
||||||
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
||||||
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
||||||
|
[SecretRotation.MySqlCredentials]: registerMySqlCredentialsRotationRouter,
|
||||||
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
||||||
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
||||||
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import {
|
||||||
|
CreateMySqlCredentialsRotationSchema,
|
||||||
|
MySqlCredentialsRotationSchema,
|
||||||
|
UpdateMySqlCredentialsRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials";
|
||||||
|
|
||||||
|
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
||||||
|
|
||||||
|
export const registerMySqlCredentialsRotationRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSecretRotationEndpoints({
|
||||||
|
type: SecretRotation.MySqlCredentials,
|
||||||
|
server,
|
||||||
|
responseSchema: MySqlCredentialsRotationSchema,
|
||||||
|
createSchema: CreateMySqlCredentialsRotationSchema,
|
||||||
|
updateSchema: UpdateMySqlCredentialsRotationSchema,
|
||||||
|
generatedCredentialsSchema: SqlCredentialsRotationGeneratedCredentialsSchema
|
||||||
|
});
|
@ -6,6 +6,7 @@ import { AwsIamUserSecretRotationListItemSchema } from "@app/ee/services/secret-
|
|||||||
import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||||
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
|
import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||||
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||||
import { ApiDocsTags, SecretRotations } from "@app/lib/api-docs";
|
import { ApiDocsTags, SecretRotations } from "@app/lib/api-docs";
|
||||||
@ -16,6 +17,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
||||||
PostgresCredentialsRotationListItemSchema,
|
PostgresCredentialsRotationListItemSchema,
|
||||||
MsSqlCredentialsRotationListItemSchema,
|
MsSqlCredentialsRotationListItemSchema,
|
||||||
|
MySqlCredentialsRotationListItemSchema,
|
||||||
Auth0ClientSecretRotationListItemSchema,
|
Auth0ClientSecretRotationListItemSchema,
|
||||||
AzureClientSecretRotationListItemSchema,
|
AzureClientSecretRotationListItemSchema,
|
||||||
AwsIamUserSecretRotationListItemSchema,
|
AwsIamUserSecretRotationListItemSchema,
|
||||||
|
@ -8,3 +8,10 @@ export const accessApprovalPolicyApproverDALFactory = (db: TDbClient) => {
|
|||||||
const accessApprovalPolicyApproverOrm = ormify(db, TableName.AccessApprovalPolicyApprover);
|
const accessApprovalPolicyApproverOrm = ormify(db, TableName.AccessApprovalPolicyApprover);
|
||||||
return { ...accessApprovalPolicyApproverOrm };
|
return { ...accessApprovalPolicyApproverOrm };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TAccessApprovalPolicyBypasserDALFactory = ReturnType<typeof accessApprovalPolicyBypasserDALFactory>;
|
||||||
|
|
||||||
|
export const accessApprovalPolicyBypasserDALFactory = (db: TDbClient) => {
|
||||||
|
const accessApprovalPolicyBypasserOrm = ormify(db, TableName.AccessApprovalPolicyBypasser);
|
||||||
|
return { ...accessApprovalPolicyBypasserOrm };
|
||||||
|
};
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { AccessApprovalPoliciesSchema, TableName, TAccessApprovalPolicies } from "@app/db/schemas";
|
import { AccessApprovalPoliciesSchema, TableName, TAccessApprovalPolicies, TUsers } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
||||||
|
|
||||||
import { ApproverType } from "./access-approval-policy-types";
|
import { ApproverType, BypasserType } from "./access-approval-policy-types";
|
||||||
|
|
||||||
export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>;
|
export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>;
|
||||||
|
|
||||||
@ -34,9 +34,22 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
||||||
)
|
)
|
||||||
.leftJoin(TableName.Users, `${TableName.AccessApprovalPolicyApprover}.approverUserId`, `${TableName.Users}.id`)
|
.leftJoin(TableName.Users, `${TableName.AccessApprovalPolicyApprover}.approverUserId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.AccessApprovalPolicyBypasser,
|
||||||
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
|
`${TableName.AccessApprovalPolicyBypasser}.policyId`
|
||||||
|
)
|
||||||
|
.leftJoin<TUsers>(
|
||||||
|
db(TableName.Users).as("bypasserUsers"),
|
||||||
|
`${TableName.AccessApprovalPolicyBypasser}.bypasserUserId`,
|
||||||
|
`bypasserUsers.id`
|
||||||
|
)
|
||||||
.select(tx.ref("username").withSchema(TableName.Users).as("approverUsername"))
|
.select(tx.ref("username").withSchema(TableName.Users).as("approverUsername"))
|
||||||
|
.select(tx.ref("username").withSchema("bypasserUsers").as("bypasserUsername"))
|
||||||
.select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
.select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||||
.select(tx.ref("approverGroupId").withSchema(TableName.AccessApprovalPolicyApprover))
|
.select(tx.ref("approverGroupId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||||
|
.select(tx.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
||||||
|
.select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
||||||
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
||||||
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
||||||
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
|
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
|
||||||
@ -129,6 +142,23 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
id,
|
id,
|
||||||
type: ApproverType.Group
|
type: ApproverType.Group
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({ bypasserUserId: id, bypasserUsername }) => ({
|
||||||
|
id,
|
||||||
|
type: BypasserType.User,
|
||||||
|
name: bypasserUsername
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserGroupId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({ bypasserGroupId: id }) => ({
|
||||||
|
id,
|
||||||
|
type: BypasserType.Group
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -144,5 +174,28 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
return softDeletedPolicy;
|
return softDeletedPolicy;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...accessApprovalPolicyOrm, find, findById, softDeleteById };
|
const findLastValidPolicy = async ({ envId, secretPath }: { envId: string; secretPath: string }, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const result = await (tx || db.replicaNode())(TableName.AccessApprovalPolicy)
|
||||||
|
.where(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
buildFindFilter(
|
||||||
|
{
|
||||||
|
envId,
|
||||||
|
secretPath
|
||||||
|
},
|
||||||
|
TableName.AccessApprovalPolicy
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy("deletedAt", "desc")
|
||||||
|
.orderByRaw(`"deletedAt" IS NULL`)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "FindLastValidPolicy" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...accessApprovalPolicyOrm, find, findById, softDeleteById, findLastValidPolicy };
|
||||||
};
|
};
|
||||||
|
@ -2,8 +2,9 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionApprovalActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
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";
|
||||||
@ -14,10 +15,14 @@ import { TAccessApprovalRequestReviewerDALFactory } from "../access-approval-req
|
|||||||
import { ApprovalStatus } from "../access-approval-request/access-approval-request-types";
|
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 { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal";
|
import {
|
||||||
|
TAccessApprovalPolicyApproverDALFactory,
|
||||||
|
TAccessApprovalPolicyBypasserDALFactory
|
||||||
|
} from "./access-approval-policy-approver-dal";
|
||||||
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
||||||
import {
|
import {
|
||||||
ApproverType,
|
ApproverType,
|
||||||
|
BypasserType,
|
||||||
TCreateAccessApprovalPolicy,
|
TCreateAccessApprovalPolicy,
|
||||||
TDeleteAccessApprovalPolicy,
|
TDeleteAccessApprovalPolicy,
|
||||||
TGetAccessApprovalPolicyByIdDTO,
|
TGetAccessApprovalPolicyByIdDTO,
|
||||||
@ -32,12 +37,14 @@ type TAccessApprovalPolicyServiceFactoryDep = {
|
|||||||
accessApprovalPolicyDAL: TAccessApprovalPolicyDALFactory;
|
accessApprovalPolicyDAL: TAccessApprovalPolicyDALFactory;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findOne">;
|
||||||
accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory;
|
accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory;
|
||||||
|
accessApprovalPolicyBypasserDAL: TAccessApprovalPolicyBypasserDALFactory;
|
||||||
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">;
|
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update" | "find">;
|
||||||
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update">;
|
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update">;
|
||||||
|
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
|
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
|
||||||
@ -45,6 +52,7 @@ export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprov
|
|||||||
export const accessApprovalPolicyServiceFactory = ({
|
export const accessApprovalPolicyServiceFactory = ({
|
||||||
accessApprovalPolicyDAL,
|
accessApprovalPolicyDAL,
|
||||||
accessApprovalPolicyApproverDAL,
|
accessApprovalPolicyApproverDAL,
|
||||||
|
accessApprovalPolicyBypasserDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
@ -52,7 +60,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
userDAL,
|
userDAL,
|
||||||
accessApprovalRequestDAL,
|
accessApprovalRequestDAL,
|
||||||
additionalPrivilegeDAL,
|
additionalPrivilegeDAL,
|
||||||
accessApprovalRequestReviewerDAL
|
accessApprovalRequestReviewerDAL,
|
||||||
|
orgMembershipDAL
|
||||||
}: TAccessApprovalPolicyServiceFactoryDep) => {
|
}: TAccessApprovalPolicyServiceFactoryDep) => {
|
||||||
const createAccessApprovalPolicy = async ({
|
const createAccessApprovalPolicy = async ({
|
||||||
name,
|
name,
|
||||||
@ -63,6 +72,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
approvals,
|
approvals,
|
||||||
approvers,
|
approvers,
|
||||||
|
bypassers,
|
||||||
projectSlug,
|
projectSlug,
|
||||||
environment,
|
environment,
|
||||||
enforcementLevel,
|
enforcementLevel,
|
||||||
@ -82,7 +92,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
const userApproverNames = approvers
|
const userApproverNames = approvers
|
||||||
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined))
|
.map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
|
||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
|
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
|
||||||
@ -98,7 +108,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionApprovalActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
||||||
@ -147,6 +157,44 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
.map((user) => user.id);
|
.map((user) => user.id);
|
||||||
verifyAllApprovers.push(...verifyGroupApprovers);
|
verifyAllApprovers.push(...verifyGroupApprovers);
|
||||||
|
|
||||||
|
let groupBypassers: string[] = [];
|
||||||
|
let bypasserUserIds: string[] = [];
|
||||||
|
|
||||||
|
if (bypassers && bypassers.length) {
|
||||||
|
groupBypassers = bypassers
|
||||||
|
.filter((bypasser) => bypasser.type === BypasserType.Group)
|
||||||
|
.map((bypasser) => bypasser.id) as string[];
|
||||||
|
|
||||||
|
const userBypassers = bypassers
|
||||||
|
.filter((bypasser) => bypasser.type === BypasserType.User)
|
||||||
|
.map((bypasser) => bypasser.id)
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
const userBypasserNames = bypassers
|
||||||
|
.map((bypasser) => (bypasser.type === BypasserType.User ? bypasser.username : undefined))
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
bypasserUserIds = userBypassers;
|
||||||
|
if (userBypasserNames.length) {
|
||||||
|
const bypasserUsers = await userDAL.find({
|
||||||
|
$in: {
|
||||||
|
username: userBypasserNames
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const bypasserNamesFromDb = bypasserUsers.map((user) => user.username);
|
||||||
|
const invalidUsernames = userBypasserNames.filter((username) => !bypasserNamesFromDb.includes(username));
|
||||||
|
|
||||||
|
if (invalidUsernames.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Invalid bypasser user: ${invalidUsernames.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bypasserUserIds = bypasserUserIds.concat(bypasserUsers.map((user) => user.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await accessApprovalPolicyDAL.create(
|
const doc = await accessApprovalPolicyDAL.create(
|
||||||
{
|
{
|
||||||
@ -159,6 +207,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
if (approverUserIds.length) {
|
if (approverUserIds.length) {
|
||||||
await accessApprovalPolicyApproverDAL.insertMany(
|
await accessApprovalPolicyApproverDAL.insertMany(
|
||||||
approverUserIds.map((userId) => ({
|
approverUserIds.map((userId) => ({
|
||||||
@ -179,8 +228,29 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bypasserUserIds.length) {
|
||||||
|
await accessApprovalPolicyBypasserDAL.insertMany(
|
||||||
|
bypasserUserIds.map((userId) => ({
|
||||||
|
bypasserUserId: userId,
|
||||||
|
policyId: doc.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupBypassers.length) {
|
||||||
|
await accessApprovalPolicyBypasserDAL.insertMany(
|
||||||
|
groupBypassers.map((groupId) => ({
|
||||||
|
bypasserGroupId: groupId,
|
||||||
|
policyId: doc.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ...accessApproval, environment: env, projectId: project.id };
|
return { ...accessApproval, environment: env, projectId: project.id };
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -211,6 +281,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const updateAccessApprovalPolicy = async ({
|
const updateAccessApprovalPolicy = async ({
|
||||||
policyId,
|
policyId,
|
||||||
approvers,
|
approvers,
|
||||||
|
bypassers,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
actorId,
|
actorId,
|
||||||
@ -231,15 +302,15 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
const userApproverNames = approvers
|
const userApproverNames = approvers
|
||||||
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined))
|
.map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
|
||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
||||||
const currentAppovals = approvals || accessApprovalPolicy.approvals;
|
const currentApprovals = approvals || accessApprovalPolicy.approvals;
|
||||||
if (
|
if (
|
||||||
groupApprovers?.length === 0 &&
|
groupApprovers?.length === 0 &&
|
||||||
userApprovers &&
|
userApprovers &&
|
||||||
currentAppovals > userApprovers.length + userApproverNames.length
|
currentApprovals > userApprovers.length + userApproverNames.length
|
||||||
) {
|
) {
|
||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
}
|
}
|
||||||
@ -256,10 +327,79 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
ProjectPermissionApprovalActions.Edit,
|
|
||||||
ProjectPermissionSub.SecretApproval
|
let groupBypassers: string[] = [];
|
||||||
);
|
let bypasserUserIds: string[] = [];
|
||||||
|
|
||||||
|
if (bypassers && bypassers.length) {
|
||||||
|
groupBypassers = bypassers
|
||||||
|
.filter((bypasser) => bypasser.type === BypasserType.Group)
|
||||||
|
.map((bypasser) => bypasser.id) as string[];
|
||||||
|
|
||||||
|
groupBypassers = [...new Set(groupBypassers)];
|
||||||
|
|
||||||
|
const userBypassers = bypassers
|
||||||
|
.filter((bypasser) => bypasser.type === BypasserType.User)
|
||||||
|
.map((bypasser) => bypasser.id)
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
const userBypasserNames = bypassers
|
||||||
|
.map((bypasser) => (bypasser.type === BypasserType.User ? bypasser.username : undefined))
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
bypasserUserIds = userBypassers;
|
||||||
|
if (userBypasserNames.length) {
|
||||||
|
const bypasserUsers = await userDAL.find({
|
||||||
|
$in: {
|
||||||
|
username: userBypasserNames
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const bypasserNamesFromDb = bypasserUsers.map((user) => user.username);
|
||||||
|
const invalidUsernames = userBypasserNames.filter((username) => !bypasserNamesFromDb.includes(username));
|
||||||
|
|
||||||
|
if (invalidUsernames.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Invalid bypasser user: ${invalidUsernames.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bypasserUserIds = [...new Set(bypasserUserIds.concat(bypasserUsers.map((user) => user.id)))];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate user bypassers
|
||||||
|
if (bypasserUserIds.length > 0) {
|
||||||
|
const orgMemberships = await orgMembershipDAL.find({
|
||||||
|
$in: { userId: bypasserUserIds },
|
||||||
|
orgId: actorOrgId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (orgMemberships.length !== bypasserUserIds.length) {
|
||||||
|
const foundUserIdsInOrg = new Set(orgMemberships.map((mem) => mem.userId));
|
||||||
|
const missingUserIds = bypasserUserIds.filter((id) => !foundUserIdsInOrg.has(id));
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `One or more specified bypasser users are not part of the organization or do not exist. Invalid or non-member user IDs: ${missingUserIds.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate group bypassers
|
||||||
|
if (groupBypassers.length > 0) {
|
||||||
|
const orgGroups = await groupDAL.find({
|
||||||
|
$in: { id: groupBypassers },
|
||||||
|
orgId: actorOrgId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (orgGroups.length !== groupBypassers.length) {
|
||||||
|
const foundGroupIdsInOrg = new Set(orgGroups.map((group) => group.id));
|
||||||
|
const missingGroupIds = groupBypassers.filter((id) => !foundGroupIdsInOrg.has(id));
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `One or more specified bypasser groups are not part of the organization or do not exist. Invalid or non-member group IDs: ${missingGroupIds.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updatedPolicy = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
const updatedPolicy = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await accessApprovalPolicyDAL.updateById(
|
const doc = await accessApprovalPolicyDAL.updateById(
|
||||||
@ -316,6 +456,28 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await accessApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
|
||||||
|
|
||||||
|
if (bypasserUserIds.length) {
|
||||||
|
await accessApprovalPolicyBypasserDAL.insertMany(
|
||||||
|
bypasserUserIds.map((userId) => ({
|
||||||
|
bypasserUserId: userId,
|
||||||
|
policyId: doc.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupBypassers.length) {
|
||||||
|
await accessApprovalPolicyBypasserDAL.insertMany(
|
||||||
|
groupBypassers.map((groupId) => ({
|
||||||
|
bypasserGroupId: groupId,
|
||||||
|
policyId: doc.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -344,7 +506,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionApprovalActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -435,10 +597,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
ProjectPermissionApprovalActions.Read,
|
|
||||||
ProjectPermissionSub.SecretApproval
|
|
||||||
);
|
|
||||||
|
|
||||||
return policy;
|
return policy;
|
||||||
};
|
};
|
||||||
|
@ -18,11 +18,20 @@ export enum ApproverType {
|
|||||||
User = "user"
|
User = "user"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum BypasserType {
|
||||||
|
Group = "group",
|
||||||
|
User = "user"
|
||||||
|
}
|
||||||
|
|
||||||
export type TCreateAccessApprovalPolicy = {
|
export type TCreateAccessApprovalPolicy = {
|
||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||||
|
bypassers?: (
|
||||||
|
| { type: BypasserType.Group; id: string }
|
||||||
|
| { type: BypasserType.User; id?: string; username?: string }
|
||||||
|
)[];
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
name: string;
|
name: string;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
@ -32,7 +41,11 @@ export type TCreateAccessApprovalPolicy = {
|
|||||||
export type TUpdateAccessApprovalPolicy = {
|
export type TUpdateAccessApprovalPolicy = {
|
||||||
policyId: string;
|
policyId: string;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||||
|
bypassers?: (
|
||||||
|
| { type: BypasserType.Group; id: string }
|
||||||
|
| { type: BypasserType.User; id?: string; username?: string }
|
||||||
|
)[];
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { AccessApprovalRequestsSchema, TableName, TAccessApprovalRequests, TUsers } from "@app/db/schemas";
|
import {
|
||||||
|
AccessApprovalRequestsSchema,
|
||||||
|
TableName,
|
||||||
|
TAccessApprovalRequests,
|
||||||
|
TUserGroupMembership,
|
||||||
|
TUsers
|
||||||
|
} from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
||||||
|
|
||||||
@ -28,12 +34,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.AccessApprovalRequest}.policyId`,
|
`${TableName.AccessApprovalRequest}.policyId`,
|
||||||
`${TableName.AccessApprovalPolicy}.id`
|
`${TableName.AccessApprovalPolicy}.id`
|
||||||
)
|
)
|
||||||
|
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.AccessApprovalRequestReviewer,
|
TableName.AccessApprovalRequestReviewer,
|
||||||
`${TableName.AccessApprovalRequest}.id`,
|
`${TableName.AccessApprovalRequest}.id`,
|
||||||
`${TableName.AccessApprovalRequestReviewer}.requestId`
|
`${TableName.AccessApprovalRequestReviewer}.requestId`
|
||||||
)
|
)
|
||||||
|
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.AccessApprovalPolicyApprover,
|
TableName.AccessApprovalPolicyApprover,
|
||||||
`${TableName.AccessApprovalPolicy}.id`,
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
@ -46,6 +52,17 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
.leftJoin(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
.leftJoin(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||||
|
|
||||||
|
.leftJoin(
|
||||||
|
TableName.AccessApprovalPolicyBypasser,
|
||||||
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
|
`${TableName.AccessApprovalPolicyBypasser}.policyId`
|
||||||
|
)
|
||||||
|
.leftJoin<TUserGroupMembership>(
|
||||||
|
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
|
||||||
|
`${TableName.AccessApprovalPolicyBypasser}.bypasserGroupId`,
|
||||||
|
`bypasserUserGroupMembership.groupId`
|
||||||
|
)
|
||||||
|
|
||||||
.join<TUsers>(
|
.join<TUsers>(
|
||||||
db(TableName.Users).as("requestedByUser"),
|
db(TableName.Users).as("requestedByUser"),
|
||||||
`${TableName.AccessApprovalRequest}.requestedByUserId`,
|
`${TableName.AccessApprovalRequest}.requestedByUserId`,
|
||||||
@ -69,6 +86,9 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||||
.select(db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"))
|
.select(db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"))
|
||||||
|
|
||||||
|
.select(db.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
||||||
|
.select(db.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"))
|
||||||
|
|
||||||
.select(
|
.select(
|
||||||
db.ref("projectId").withSchema(TableName.Environment),
|
db.ref("projectId").withSchema(TableName.Environment),
|
||||||
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||||
@ -145,7 +165,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
isApproved: !!doc.policyDeletedAt || !!doc.privilegeId
|
isApproved: !!doc.policyDeletedAt || !!doc.privilegeId || doc.status !== ApprovalStatus.PENDING
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
@ -158,6 +178,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
key: "approverGroupUserId",
|
key: "approverGroupUserId",
|
||||||
label: "approvers" as const,
|
label: "approvers" as const,
|
||||||
mapper: ({ approverGroupUserId }) => approverGroupUserId
|
mapper: ({ approverGroupUserId }) => approverGroupUserId
|
||||||
|
},
|
||||||
|
{ key: "bypasserUserId", label: "bypassers" as const, mapper: ({ bypasserUserId }) => bypasserUserId },
|
||||||
|
{
|
||||||
|
key: "bypasserGroupUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({ bypasserGroupUserId }) => bypasserGroupUserId
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -166,7 +192,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
return formattedDocs.map((doc) => ({
|
return formattedDocs.map((doc) => ({
|
||||||
...doc,
|
...doc,
|
||||||
policy: { ...doc.policy, approvers: doc.approvers }
|
policy: { ...doc.policy, approvers: doc.approvers, bypassers: doc.bypassers }
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindRequestsWithPrivilege" });
|
throw new DatabaseError({ error, name: "FindRequestsWithPrivilege" });
|
||||||
@ -193,7 +219,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.AccessApprovalPolicy}.id`,
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
||||||
)
|
)
|
||||||
|
|
||||||
.leftJoin<TUsers>(
|
.leftJoin<TUsers>(
|
||||||
db(TableName.Users).as("accessApprovalPolicyApproverUser"),
|
db(TableName.Users).as("accessApprovalPolicyApproverUser"),
|
||||||
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
|
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
|
||||||
@ -204,13 +229,33 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.AccessApprovalPolicyApprover}.approverGroupId`,
|
`${TableName.AccessApprovalPolicyApprover}.approverGroupId`,
|
||||||
`${TableName.UserGroupMembership}.groupId`
|
`${TableName.UserGroupMembership}.groupId`
|
||||||
)
|
)
|
||||||
|
|
||||||
.leftJoin<TUsers>(
|
.leftJoin<TUsers>(
|
||||||
db(TableName.Users).as("accessApprovalPolicyGroupApproverUser"),
|
db(TableName.Users).as("accessApprovalPolicyGroupApproverUser"),
|
||||||
`${TableName.UserGroupMembership}.userId`,
|
`${TableName.UserGroupMembership}.userId`,
|
||||||
"accessApprovalPolicyGroupApproverUser.id"
|
"accessApprovalPolicyGroupApproverUser.id"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.leftJoin(
|
||||||
|
TableName.AccessApprovalPolicyBypasser,
|
||||||
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
|
`${TableName.AccessApprovalPolicyBypasser}.policyId`
|
||||||
|
)
|
||||||
|
.leftJoin<TUsers>(
|
||||||
|
db(TableName.Users).as("accessApprovalPolicyBypasserUser"),
|
||||||
|
`${TableName.AccessApprovalPolicyBypasser}.bypasserUserId`,
|
||||||
|
"accessApprovalPolicyBypasserUser.id"
|
||||||
|
)
|
||||||
|
.leftJoin<TUserGroupMembership>(
|
||||||
|
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
|
||||||
|
`${TableName.AccessApprovalPolicyBypasser}.bypasserGroupId`,
|
||||||
|
`bypasserUserGroupMembership.groupId`
|
||||||
|
)
|
||||||
|
.leftJoin<TUsers>(
|
||||||
|
db(TableName.Users).as("accessApprovalPolicyGroupBypasserUser"),
|
||||||
|
`bypasserUserGroupMembership.userId`,
|
||||||
|
"accessApprovalPolicyGroupBypasserUser.id"
|
||||||
|
)
|
||||||
|
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.AccessApprovalRequestReviewer,
|
TableName.AccessApprovalRequestReviewer,
|
||||||
`${TableName.AccessApprovalRequest}.id`,
|
`${TableName.AccessApprovalRequest}.id`,
|
||||||
@ -241,6 +286,18 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
tx.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
|
tx.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
|
||||||
tx.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
|
tx.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
|
||||||
|
|
||||||
|
// Bypassers
|
||||||
|
tx.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser),
|
||||||
|
tx.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"),
|
||||||
|
tx.ref("email").withSchema("accessApprovalPolicyBypasserUser").as("bypasserEmail"),
|
||||||
|
tx.ref("email").withSchema("accessApprovalPolicyGroupBypasserUser").as("bypasserGroupEmail"),
|
||||||
|
tx.ref("username").withSchema("accessApprovalPolicyBypasserUser").as("bypasserUsername"),
|
||||||
|
tx.ref("username").withSchema("accessApprovalPolicyGroupBypasserUser").as("bypasserGroupUsername"),
|
||||||
|
tx.ref("firstName").withSchema("accessApprovalPolicyBypasserUser").as("bypasserFirstName"),
|
||||||
|
tx.ref("firstName").withSchema("accessApprovalPolicyGroupBypasserUser").as("bypasserGroupFirstName"),
|
||||||
|
tx.ref("lastName").withSchema("accessApprovalPolicyBypasserUser").as("bypasserLastName"),
|
||||||
|
tx.ref("lastName").withSchema("accessApprovalPolicyGroupBypasserUser").as("bypasserGroupLastName"),
|
||||||
|
|
||||||
tx.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer),
|
tx.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer),
|
||||||
|
|
||||||
tx.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"),
|
tx.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"),
|
||||||
@ -265,7 +322,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
try {
|
try {
|
||||||
const sql = findQuery({ [`${TableName.AccessApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode());
|
const sql = findQuery({ [`${TableName.AccessApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode());
|
||||||
const docs = await sql;
|
const docs = await sql;
|
||||||
const formatedDoc = sqlNestRelationships({
|
const formattedDoc = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
parentMapper: (el) => ({
|
parentMapper: (el) => ({
|
||||||
@ -335,13 +392,51 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
lastName,
|
lastName,
|
||||||
username
|
username
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({
|
||||||
|
bypasserUserId,
|
||||||
|
bypasserEmail: email,
|
||||||
|
bypasserUsername: username,
|
||||||
|
bypasserLastName: lastName,
|
||||||
|
bypasserFirstName: firstName
|
||||||
|
}) => ({
|
||||||
|
userId: bypasserUserId,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
username
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserGroupUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({
|
||||||
|
userId,
|
||||||
|
bypasserGroupEmail: email,
|
||||||
|
bypasserGroupUsername: username,
|
||||||
|
bypasserGroupLastName: lastName,
|
||||||
|
bypasserFirstName: firstName
|
||||||
|
}) => ({
|
||||||
|
userId,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
username
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
if (!formatedDoc?.[0]) return;
|
if (!formattedDoc?.[0]) return;
|
||||||
return {
|
return {
|
||||||
...formatedDoc[0],
|
...formattedDoc[0],
|
||||||
policy: { ...formatedDoc[0].policy, approvers: formatedDoc[0].approvers }
|
policy: {
|
||||||
|
...formattedDoc[0].policy,
|
||||||
|
approvers: formattedDoc[0].approvers,
|
||||||
|
bypassers: formattedDoc[0].bypassers
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindByIdAccessApprovalRequest" });
|
throw new DatabaseError({ error, name: "FindByIdAccessApprovalRequest" });
|
||||||
@ -392,14 +487,20 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// an approval is pending if there is no reviewer rejections and no privilege ID is set
|
// an approval is pending if there is no reviewer rejections, no privilege ID is set and the status is pending
|
||||||
const pendingApprovals = formattedRequests.filter(
|
const pendingApprovals = formattedRequests.filter(
|
||||||
(req) => !req.privilegeId && !req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED)
|
(req) =>
|
||||||
|
!req.privilegeId &&
|
||||||
|
!req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) &&
|
||||||
|
req.status === ApprovalStatus.PENDING
|
||||||
);
|
);
|
||||||
|
|
||||||
// an approval is finalized if there are any rejections or a privilege ID is set
|
// an approval is finalized if there are any rejections, a privilege ID is set or the number of approvals is equal to the number of approvals required
|
||||||
const finalizedApprovals = formattedRequests.filter(
|
const finalizedApprovals = formattedRequests.filter(
|
||||||
(req) => req.privilegeId || req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED)
|
(req) =>
|
||||||
|
req.privilegeId ||
|
||||||
|
req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED) ||
|
||||||
|
req.status !== ApprovalStatus.PENDING
|
||||||
);
|
);
|
||||||
|
|
||||||
return { pendingCount: pendingApprovals.length, finalizedCount: finalizedApprovals.length };
|
return { pendingCount: pendingApprovals.length, finalizedCount: finalizedApprovals.length };
|
||||||
|
@ -23,7 +23,6 @@ import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-poli
|
|||||||
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
|
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
|
||||||
import { TGroupDALFactory } from "../group/group-dal";
|
import { TGroupDALFactory } from "../group/group-dal";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionApprovalActions, ProjectPermissionSub } from "../permission/project-permission";
|
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types";
|
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types";
|
||||||
import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal";
|
import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal";
|
||||||
@ -57,7 +56,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
| "findOne"
|
| "findOne"
|
||||||
| "getCount"
|
| "getCount"
|
||||||
>;
|
>;
|
||||||
accessApprovalPolicyDAL: Pick<TAccessApprovalPolicyDALFactory, "findOne" | "find">;
|
accessApprovalPolicyDAL: Pick<TAccessApprovalPolicyDALFactory, "findOne" | "find" | "findLastValidPolicy">;
|
||||||
accessApprovalRequestReviewerDAL: Pick<
|
accessApprovalRequestReviewerDAL: Pick<
|
||||||
TAccessApprovalRequestReviewerDALFactory,
|
TAccessApprovalRequestReviewerDALFactory,
|
||||||
"create" | "find" | "findOne" | "transaction"
|
"create" | "find" | "findOne" | "transaction"
|
||||||
@ -132,7 +131,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
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 policy = await accessApprovalPolicyDAL.findOne({
|
const policy = await accessApprovalPolicyDAL.findLastValidPolicy({
|
||||||
envId: environment.id,
|
envId: environment.id,
|
||||||
secretPath
|
secretPath
|
||||||
});
|
});
|
||||||
@ -204,7 +203,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const isRejected = reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED);
|
const isRejected = reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED);
|
||||||
|
|
||||||
if (!isRejected) {
|
if (!isRejected && duplicateRequest.status === ApprovalStatus.PENDING) {
|
||||||
throw new BadRequestError({ message: "You already have a pending access request with the same criteria" });
|
throw new BadRequestError({ message: "You already have a pending access request with the same criteria" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -340,7 +339,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { membership, hasRole, permission } = await permissionService.getProjectPermission({
|
const { membership, hasRole } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId: accessApprovalRequest.projectId,
|
projectId: accessApprovalRequest.projectId,
|
||||||
@ -355,13 +354,13 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const isSelfApproval = actorId === accessApprovalRequest.requestedByUserId;
|
const isSelfApproval = actorId === accessApprovalRequest.requestedByUserId;
|
||||||
const isSoftEnforcement = policy.enforcementLevel === EnforcementLevel.Soft;
|
const isSoftEnforcement = policy.enforcementLevel === EnforcementLevel.Soft;
|
||||||
const canBypassApproval = permission.can(
|
const canBypass = !policy.bypassers.length || policy.bypassers.some((bypasser) => bypasser.userId === actorId);
|
||||||
ProjectPermissionApprovalActions.AllowAccessBypass,
|
const cannotBypassUnderSoftEnforcement = !(isSoftEnforcement && canBypass);
|
||||||
ProjectPermissionSub.SecretApproval
|
|
||||||
);
|
|
||||||
const cannotBypassUnderSoftEnforcement = !(isSoftEnforcement && canBypassApproval);
|
|
||||||
|
|
||||||
if (!policy.allowedSelfApprovals && isSelfApproval && cannotBypassUnderSoftEnforcement) {
|
const isApprover = policy.approvers.find((approver) => approver.userId === actorId);
|
||||||
|
|
||||||
|
// If user is (not an approver OR cant self approve) AND can't bypass policy
|
||||||
|
if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to review access approval request. Users are not authorized to review their own request."
|
message: "Failed to review access approval request. Users are not authorized to review their own request."
|
||||||
});
|
});
|
||||||
@ -370,7 +369,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user
|
accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user
|
||||||
!policy.approvers.find((approver) => approver.userId === actorId) // The request isn't performed by an assigned approver
|
!isApprover // The request isn't performed by an assigned approver
|
||||||
) {
|
) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not authorized to approve this request" });
|
throw new ForbiddenRequestError({ message: "You are not authorized to approve this request" });
|
||||||
}
|
}
|
||||||
@ -478,7 +477,11 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
privilegeIdToSet = privilege.id;
|
privilegeIdToSet = privilege.id;
|
||||||
}
|
}
|
||||||
await accessApprovalRequestDAL.updateById(accessApprovalRequest.id, { privilegeId: privilegeIdToSet }, tx);
|
await accessApprovalRequestDAL.updateById(
|
||||||
|
accessApprovalRequest.id,
|
||||||
|
{ privilegeId: privilegeIdToSet, status: ApprovalStatus.APPROVED },
|
||||||
|
tx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import { TIdentityOrgDALFactory } from "@app/services/identity/identity-org-dal"
|
|||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
import { OrgPermissionBillingActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums";
|
import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums";
|
||||||
import { TLicenseDALFactory } from "./license-dal";
|
import { TLicenseDALFactory } from "./license-dal";
|
||||||
@ -288,7 +288,7 @@ export const licenseServiceFactory = ({
|
|||||||
billingCycle
|
billingCycle
|
||||||
}: TOrgPlansTableDTO) => {
|
}: TOrgPlansTableDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
|
||||||
const { data } = await licenseServerCloudApi.request.get(
|
const { data } = await licenseServerCloudApi.request.get(
|
||||||
`/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
|
`/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
|
||||||
);
|
);
|
||||||
@ -310,8 +310,10 @@ export const licenseServiceFactory = ({
|
|||||||
success_url
|
success_url
|
||||||
}: TStartOrgTrialDTO) => {
|
}: TStartOrgTrialDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
OrgPermissionBillingActions.ManageBilling,
|
||||||
|
OrgPermissionSubjects.Billing
|
||||||
|
);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -338,8 +340,10 @@ export const licenseServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TCreateOrgPortalSession) => {
|
}: TCreateOrgPortalSession) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
OrgPermissionBillingActions.ManageBilling,
|
||||||
|
OrgPermissionSubjects.Billing
|
||||||
|
);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -385,7 +389,7 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -413,7 +417,7 @@ export const licenseServiceFactory = ({
|
|||||||
// returns org current plan feature table
|
// returns org current plan feature table
|
||||||
const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -484,7 +488,7 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -509,7 +513,10 @@ export const licenseServiceFactory = ({
|
|||||||
email
|
email
|
||||||
}: TUpdateOrgBillingDetailsDTO) => {
|
}: TUpdateOrgBillingDetailsDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionBillingActions.ManageBilling,
|
||||||
|
OrgPermissionSubjects.Billing
|
||||||
|
);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -529,7 +536,7 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => {
|
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -556,7 +563,10 @@ export const licenseServiceFactory = ({
|
|||||||
cancel_url
|
cancel_url
|
||||||
}: TAddOrgPmtMethodDTO) => {
|
}: TAddOrgPmtMethodDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionBillingActions.ManageBilling,
|
||||||
|
OrgPermissionSubjects.Billing
|
||||||
|
);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -585,7 +595,10 @@ export const licenseServiceFactory = ({
|
|||||||
pmtMethodId
|
pmtMethodId
|
||||||
}: TDelOrgPmtMethodDTO) => {
|
}: TDelOrgPmtMethodDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionBillingActions.ManageBilling,
|
||||||
|
OrgPermissionSubjects.Billing
|
||||||
|
);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -602,7 +615,7 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
|
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -620,7 +633,10 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
|
const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionBillingActions.ManageBilling,
|
||||||
|
OrgPermissionSubjects.Billing
|
||||||
|
);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -641,7 +657,10 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
|
const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionBillingActions.ManageBilling,
|
||||||
|
OrgPermissionSubjects.Billing
|
||||||
|
);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -658,7 +677,7 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => {
|
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
@ -675,7 +694,7 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => {
|
const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
|
@ -44,7 +44,6 @@ import {
|
|||||||
TOidcLoginDTO,
|
TOidcLoginDTO,
|
||||||
TUpdateOidcCfgDTO
|
TUpdateOidcCfgDTO
|
||||||
} from "./oidc-config-types";
|
} from "./oidc-config-types";
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
|
|
||||||
type TOidcConfigServiceFactoryDep = {
|
type TOidcConfigServiceFactoryDep = {
|
||||||
userDAL: Pick<
|
userDAL: Pick<
|
||||||
@ -700,7 +699,6 @@ export const oidcConfigServiceFactory = ({
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(_req: any, tokenSet: TokenSet, cb: any) => {
|
(_req: any, tokenSet: TokenSet, cb: any) => {
|
||||||
const claims = tokenSet.claims();
|
const claims = tokenSet.claims();
|
||||||
logger.info(`User OIDC claims received for [orgId=${org.id}]`, JSON.stringify(claims, null, 2));
|
|
||||||
if (!claims.email || !claims.given_name) {
|
if (!claims.email || !claims.given_name) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Invalid request. Missing email or first name"
|
message: "Invalid request. Missing email or first name"
|
||||||
|
@ -2,7 +2,6 @@ import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability"
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionApprovalActions,
|
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
ProjectPermissionCmekActions,
|
ProjectPermissionCmekActions,
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
@ -11,6 +10,7 @@ import {
|
|||||||
ProjectPermissionKmipActions,
|
ProjectPermissionKmipActions,
|
||||||
ProjectPermissionMemberActions,
|
ProjectPermissionMemberActions,
|
||||||
ProjectPermissionPkiSubscriberActions,
|
ProjectPermissionPkiSubscriberActions,
|
||||||
|
ProjectPermissionPkiTemplateActions,
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionSecretActions,
|
||||||
ProjectPermissionSecretRotationActions,
|
ProjectPermissionSecretRotationActions,
|
||||||
ProjectPermissionSecretSyncActions,
|
ProjectPermissionSecretSyncActions,
|
||||||
@ -36,7 +36,6 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionSub.AuditLogs,
|
ProjectPermissionSub.AuditLogs,
|
||||||
ProjectPermissionSub.IpAllowList,
|
ProjectPermissionSub.IpAllowList,
|
||||||
ProjectPermissionSub.CertificateAuthorities,
|
ProjectPermissionSub.CertificateAuthorities,
|
||||||
ProjectPermissionSub.CertificateTemplates,
|
|
||||||
ProjectPermissionSub.PkiAlerts,
|
ProjectPermissionSub.PkiAlerts,
|
||||||
ProjectPermissionSub.PkiCollections,
|
ProjectPermissionSub.PkiCollections,
|
||||||
ProjectPermissionSub.SshCertificateAuthorities,
|
ProjectPermissionSub.SshCertificateAuthorities,
|
||||||
@ -57,12 +56,22 @@ const buildAdminPermissionRules = () => {
|
|||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionApprovalActions.Read,
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
ProjectPermissionApprovalActions.Edit,
|
ProjectPermissionPkiTemplateActions.Edit,
|
||||||
ProjectPermissionApprovalActions.Create,
|
ProjectPermissionPkiTemplateActions.Create,
|
||||||
ProjectPermissionApprovalActions.Delete,
|
ProjectPermissionPkiTemplateActions.Delete,
|
||||||
ProjectPermissionApprovalActions.AllowChangeBypass,
|
ProjectPermissionPkiTemplateActions.IssueCert,
|
||||||
ProjectPermissionApprovalActions.AllowAccessBypass
|
ProjectPermissionPkiTemplateActions.ListCerts
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.CertificateTemplates
|
||||||
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
@ -255,7 +264,7 @@ const buildMemberPermissionRules = () => {
|
|||||||
ProjectPermissionSub.SecretImports
|
ProjectPermissionSub.SecretImports
|
||||||
);
|
);
|
||||||
|
|
||||||
can([ProjectPermissionApprovalActions.Read], ProjectPermissionSub.SecretApproval);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
|
||||||
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
|
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
||||||
@ -351,7 +360,7 @@ const buildMemberPermissionRules = () => {
|
|||||||
ProjectPermissionSub.Certificates
|
ProjectPermissionSub.Certificates
|
||||||
);
|
);
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateTemplates);
|
can([ProjectPermissionPkiTemplateActions.Read], ProjectPermissionSub.CertificateTemplates);
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
|
||||||
@ -403,7 +412,7 @@ const buildViewerPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
||||||
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
||||||
can(ProjectPermissionApprovalActions.Read, ProjectPermissionSub.SecretApproval);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
|
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||||
@ -420,6 +429,7 @@ const buildViewerPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
||||||
can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates);
|
can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates);
|
||||||
|
can(ProjectPermissionPkiTemplateActions.Read, ProjectPermissionSub.CertificateTemplates);
|
||||||
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
|
||||||
|
@ -67,6 +67,11 @@ export enum OrgPermissionGroupActions {
|
|||||||
RemoveMembers = "remove-members"
|
RemoveMembers = "remove-members"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OrgPermissionBillingActions {
|
||||||
|
Read = "read",
|
||||||
|
ManageBilling = "manage-billing"
|
||||||
|
}
|
||||||
|
|
||||||
export enum OrgPermissionSubjects {
|
export enum OrgPermissionSubjects {
|
||||||
Workspace = "workspace",
|
Workspace = "workspace",
|
||||||
Role = "role",
|
Role = "role",
|
||||||
@ -107,7 +112,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
||||||
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
|
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
| [OrgPermissionBillingActions, OrgPermissionSubjects.Billing]
|
||||||
| [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity]
|
| [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||||
@ -298,10 +303,8 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionGroupActions.AddMembers, OrgPermissionSubjects.Groups);
|
can(OrgPermissionGroupActions.AddMembers, OrgPermissionSubjects.Groups);
|
||||||
can(OrgPermissionGroupActions.RemoveMembers, OrgPermissionSubjects.Groups);
|
can(OrgPermissionGroupActions.RemoveMembers, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
can(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
|
can(OrgPermissionBillingActions.ManageBilling, OrgPermissionSubjects.Billing);
|
||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
|
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Billing);
|
|
||||||
|
|
||||||
can(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
can(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
|
||||||
can(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
can(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||||
@ -362,7 +365,7 @@ const buildMemberPermission = () => {
|
|||||||
can(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
|
can(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
can(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||||
|
@ -34,15 +34,6 @@ export enum ProjectPermissionSecretActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionApprovalActions {
|
|
||||||
Read = "read",
|
|
||||||
Create = "create",
|
|
||||||
Edit = "edit",
|
|
||||||
Delete = "delete",
|
|
||||||
AllowChangeBypass = "allow-change-bypass",
|
|
||||||
AllowAccessBypass = "allow-access-bypass"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ProjectPermissionCmekActions {
|
export enum ProjectPermissionCmekActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
@ -96,6 +87,15 @@ export enum ProjectPermissionSshHostActions {
|
|||||||
IssueHostCert = "issue-host-cert"
|
IssueHostCert = "issue-host-cert"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionPkiTemplateActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
IssueCert = "issue-cert",
|
||||||
|
ListCerts = "list-certs"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionPkiSubscriberActions {
|
export enum ProjectPermissionPkiSubscriberActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
@ -209,6 +209,11 @@ export type SshHostSubjectFields = {
|
|||||||
hostname: string;
|
hostname: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type PkiTemplateSubjectFields = {
|
||||||
|
name: string;
|
||||||
|
// (dangtony98): consider adding [commonName] as a subject field in the future
|
||||||
|
};
|
||||||
|
|
||||||
export type PkiSubscriberSubjectFields = {
|
export type PkiSubscriberSubjectFields = {
|
||||||
name: string;
|
name: string;
|
||||||
// (dangtony98): consider adding [commonName] as a subject field in the future
|
// (dangtony98): consider adding [commonName] as a subject field in the future
|
||||||
@ -251,7 +256,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.IpAllowList]
|
| [ProjectPermissionActions, ProjectPermissionSub.IpAllowList]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Settings]
|
| [ProjectPermissionActions, ProjectPermissionSub.Settings]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
||||||
| [ProjectPermissionApprovalActions, ProjectPermissionSub.SecretApproval]
|
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
||||||
| [
|
| [
|
||||||
ProjectPermissionSecretRotationActions,
|
ProjectPermissionSecretRotationActions,
|
||||||
(
|
(
|
||||||
@ -265,7 +270,13 @@ export type ProjectPermissionSet =
|
|||||||
]
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
||||||
| [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates]
|
| [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
| [
|
||||||
|
ProjectPermissionPkiTemplateActions,
|
||||||
|
(
|
||||||
|
| ProjectPermissionSub.CertificateTemplates
|
||||||
|
| (ForcedSubject<ProjectPermissionSub.CertificateTemplates> & PkiTemplateSubjectFields)
|
||||||
|
)
|
||||||
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
|
||||||
@ -445,10 +456,25 @@ const PkiSubscriberConditionSchema = z
|
|||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
|
const PkiTemplateConditionSchema = z
|
||||||
|
.object({
|
||||||
|
name: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB],
|
||||||
|
[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."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionApprovalActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
@ -536,12 +562,6 @@ const GeneralPermissionSchema = [
|
|||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
z.object({
|
|
||||||
subject: z.literal(ProjectPermissionSub.CertificateTemplates).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
|
subject: z
|
||||||
.literal(ProjectPermissionSub.SshCertificateAuthorities)
|
.literal(ProjectPermissionSub.SshCertificateAuthorities)
|
||||||
@ -719,6 +739,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.CertificateTemplates).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionPkiTemplateActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
conditions: PkiTemplateConditionSchema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.SecretRotation).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.SecretRotation).describe("The entity this permission pertains to."),
|
||||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
@ -729,6 +759,7 @@ 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()
|
||||||
}),
|
}),
|
||||||
|
|
||||||
...GeneralPermissionSchema
|
...GeneralPermissionSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/per
|
|||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
|
|
||||||
|
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
|
||||||
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import {
|
import {
|
||||||
@ -16,6 +17,7 @@ import {
|
|||||||
ProjectPermissionSet,
|
ProjectPermissionSet,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "../permission/project-permission";
|
} from "../permission/project-permission";
|
||||||
|
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
||||||
import {
|
import {
|
||||||
ProjectUserAdditionalPrivilegeTemporaryMode,
|
ProjectUserAdditionalPrivilegeTemporaryMode,
|
||||||
@ -30,6 +32,7 @@ type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
|||||||
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType<
|
export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType<
|
||||||
@ -44,7 +47,8 @@ const unpackPermissions = (permissions: unknown) =>
|
|||||||
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||||
projectUserAdditionalPrivilegeDAL,
|
projectUserAdditionalPrivilegeDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
permissionService
|
permissionService,
|
||||||
|
accessApprovalRequestDAL
|
||||||
}: TProjectUserAdditionalPrivilegeServiceFactoryDep) => {
|
}: TProjectUserAdditionalPrivilegeServiceFactoryDep) => {
|
||||||
const create = async ({
|
const create = async ({
|
||||||
slug,
|
slug,
|
||||||
@ -279,6 +283,15 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
await accessApprovalRequestDAL.update(
|
||||||
|
{
|
||||||
|
privilegeId: userPrivilege.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
privilegeDeletedAt: new Date(),
|
||||||
|
status: ApprovalStatus.REJECTED
|
||||||
|
}
|
||||||
|
);
|
||||||
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||||
return {
|
return {
|
||||||
...deletedPrivilege,
|
...deletedPrivilege,
|
||||||
|
@ -8,3 +8,10 @@ export const secretApprovalPolicyApproverDALFactory = (db: TDbClient) => {
|
|||||||
const sapApproverOrm = ormify(db, TableName.SecretApprovalPolicyApprover);
|
const sapApproverOrm = ormify(db, TableName.SecretApprovalPolicyApprover);
|
||||||
return sapApproverOrm;
|
return sapApproverOrm;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TSecretApprovalPolicyBypasserDALFactory = ReturnType<typeof secretApprovalPolicyBypasserDALFactory>;
|
||||||
|
|
||||||
|
export const secretApprovalPolicyBypasserDALFactory = (db: TDbClient) => {
|
||||||
|
const sapBypasserOrm = ormify(db, TableName.SecretApprovalPolicyBypasser);
|
||||||
|
return sapBypasserOrm;
|
||||||
|
};
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { SecretApprovalPoliciesSchema, TableName, TSecretApprovalPolicies, TUsers } from "@app/db/schemas";
|
import {
|
||||||
|
SecretApprovalPoliciesSchema,
|
||||||
|
TableName,
|
||||||
|
TSecretApprovalPolicies,
|
||||||
|
TUserGroupMembership,
|
||||||
|
TUsers
|
||||||
|
} from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
||||||
|
|
||||||
import { ApproverType } from "../access-approval-policy/access-approval-policy-types";
|
import { ApproverType, BypasserType } from "../access-approval-policy/access-approval-policy-types";
|
||||||
|
|
||||||
export type TSecretApprovalPolicyDALFactory = ReturnType<typeof secretApprovalPolicyDALFactory>;
|
export type TSecretApprovalPolicyDALFactory = ReturnType<typeof secretApprovalPolicyDALFactory>;
|
||||||
|
|
||||||
@ -43,6 +49,22 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalPolicyApprover}.approverUserId`,
|
`${TableName.SecretApprovalPolicyApprover}.approverUserId`,
|
||||||
"secretApprovalPolicyApproverUser.id"
|
"secretApprovalPolicyApproverUser.id"
|
||||||
)
|
)
|
||||||
|
// Bypasser
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretApprovalPolicyBypasser,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
|
`${TableName.SecretApprovalPolicyBypasser}.policyId`
|
||||||
|
)
|
||||||
|
.leftJoin<TUserGroupMembership>(
|
||||||
|
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
|
||||||
|
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
|
||||||
|
`bypasserUserGroupMembership.groupId`
|
||||||
|
)
|
||||||
|
.leftJoin<TUsers>(
|
||||||
|
db(TableName.Users).as("secretApprovalPolicyBypasserUser"),
|
||||||
|
`${TableName.SecretApprovalPolicyBypasser}.bypasserUserId`,
|
||||||
|
"secretApprovalPolicyBypasserUser.id"
|
||||||
|
)
|
||||||
.leftJoin<TUsers>(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
.leftJoin<TUsers>(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||||
.select(
|
.select(
|
||||||
tx.ref("id").withSchema("secretApprovalPolicyApproverUser").as("approverUserId"),
|
tx.ref("id").withSchema("secretApprovalPolicyApproverUser").as("approverUserId"),
|
||||||
@ -58,6 +80,20 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
tx.ref("firstName").withSchema(TableName.Users).as("approverGroupFirstName"),
|
tx.ref("firstName").withSchema(TableName.Users).as("approverGroupFirstName"),
|
||||||
tx.ref("lastName").withSchema(TableName.Users).as("approverGroupLastName")
|
tx.ref("lastName").withSchema(TableName.Users).as("approverGroupLastName")
|
||||||
)
|
)
|
||||||
|
.select(
|
||||||
|
tx.ref("id").withSchema("secretApprovalPolicyBypasserUser").as("bypasserUserId"),
|
||||||
|
tx.ref("email").withSchema("secretApprovalPolicyBypasserUser").as("bypasserEmail"),
|
||||||
|
tx.ref("firstName").withSchema("secretApprovalPolicyBypasserUser").as("bypasserFirstName"),
|
||||||
|
tx.ref("username").withSchema("secretApprovalPolicyBypasserUser").as("bypasserUsername"),
|
||||||
|
tx.ref("lastName").withSchema("secretApprovalPolicyBypasserUser").as("bypasserLastName")
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
tx.ref("bypasserGroupId").withSchema(TableName.SecretApprovalPolicyBypasser),
|
||||||
|
tx.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"),
|
||||||
|
tx.ref("email").withSchema(TableName.Users).as("bypasserGroupEmail"),
|
||||||
|
tx.ref("firstName").withSchema(TableName.Users).as("bypasserGroupFirstName"),
|
||||||
|
tx.ref("lastName").withSchema(TableName.Users).as("bypasserGroupLastName")
|
||||||
|
)
|
||||||
.select(
|
.select(
|
||||||
tx.ref("name").withSchema(TableName.Environment).as("envName"),
|
tx.ref("name").withSchema(TableName.Environment).as("envName"),
|
||||||
tx.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
tx.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||||
@ -143,7 +179,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
label: "approvers" as const,
|
label: "approvers" as const,
|
||||||
mapper: ({ approverUserId: id, approverUsername }) => ({
|
mapper: ({ approverUserId: id, approverUsername }) => ({
|
||||||
type: ApproverType.User,
|
type: ApproverType.User,
|
||||||
name: approverUsername,
|
username: approverUsername,
|
||||||
id
|
id
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -155,6 +191,23 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
id
|
id
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({ bypasserUserId: id, bypasserUsername }) => ({
|
||||||
|
type: BypasserType.User,
|
||||||
|
username: bypasserUsername,
|
||||||
|
id
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserGroupId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({ bypasserGroupId: id }) => ({
|
||||||
|
type: BypasserType.Group,
|
||||||
|
id
|
||||||
|
})
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "approverUserId",
|
key: "approverUserId",
|
||||||
label: "userApprovers" as const,
|
label: "userApprovers" as const,
|
||||||
|
@ -3,18 +3,21 @@ import picomatch from "picomatch";
|
|||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionApprovalActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { containsGlobPatterns } from "@app/lib/picomatch";
|
import { containsGlobPatterns } from "@app/lib/picomatch";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
import { ApproverType } from "../access-approval-policy/access-approval-policy-types";
|
import { ApproverType, BypasserType } 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 { TSecretApprovalRequestDALFactory } from "../secret-approval-request/secret-approval-request-dal";
|
||||||
import { RequestState } from "../secret-approval-request/secret-approval-request-types";
|
import { RequestState } from "../secret-approval-request/secret-approval-request-types";
|
||||||
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
import {
|
||||||
|
TSecretApprovalPolicyApproverDALFactory,
|
||||||
|
TSecretApprovalPolicyBypasserDALFactory
|
||||||
|
} from "./secret-approval-policy-approver-dal";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
||||||
import {
|
import {
|
||||||
TCreateSapDTO,
|
TCreateSapDTO,
|
||||||
@ -36,6 +39,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
|
|||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
userDAL: Pick<TUserDALFactory, "find">;
|
userDAL: Pick<TUserDALFactory, "find">;
|
||||||
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
||||||
|
secretApprovalPolicyBypasserDAL: TSecretApprovalPolicyBypasserDALFactory;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">;
|
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">;
|
||||||
};
|
};
|
||||||
@ -46,6 +50,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyApproverDAL,
|
secretApprovalPolicyApproverDAL,
|
||||||
|
secretApprovalPolicyBypasserDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
@ -59,6 +64,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
approvals,
|
approvals,
|
||||||
approvers,
|
approvers,
|
||||||
|
bypassers,
|
||||||
projectId,
|
projectId,
|
||||||
secretPath,
|
secretPath,
|
||||||
environment,
|
environment,
|
||||||
@ -74,7 +80,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
const userApproverNames = approvers
|
const userApproverNames = approvers
|
||||||
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined))
|
.map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
|
||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
if (!groupApprovers.length && approvals > approvers.length)
|
if (!groupApprovers.length && approvals > approvers.length)
|
||||||
@ -89,7 +95,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionApprovalActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -107,6 +113,44 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
|
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let groupBypassers: string[] = [];
|
||||||
|
let bypasserUserIds: string[] = [];
|
||||||
|
|
||||||
|
if (bypassers && bypassers.length) {
|
||||||
|
groupBypassers = bypassers
|
||||||
|
.filter((bypasser) => bypasser.type === BypasserType.Group)
|
||||||
|
.map((bypasser) => bypasser.id) as string[];
|
||||||
|
|
||||||
|
const userBypassers = bypassers
|
||||||
|
.filter((bypasser) => bypasser.type === BypasserType.User)
|
||||||
|
.map((bypasser) => bypasser.id)
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
const userBypasserNames = bypassers
|
||||||
|
.map((bypasser) => (bypasser.type === BypasserType.User ? bypasser.username : undefined))
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
bypasserUserIds = userBypassers;
|
||||||
|
if (userBypasserNames.length) {
|
||||||
|
const bypasserUsers = await userDAL.find({
|
||||||
|
$in: {
|
||||||
|
username: userBypasserNames
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const bypasserNamesFromDb = bypasserUsers.map((user) => user.username);
|
||||||
|
const invalidUsernames = userBypasserNames.filter((username) => !bypasserNamesFromDb.includes(username));
|
||||||
|
|
||||||
|
if (invalidUsernames.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Invalid bypasser user: ${invalidUsernames.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bypasserUserIds = bypasserUserIds.concat(bypasserUsers.map((user) => user.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalPolicyDAL.create(
|
const doc = await secretApprovalPolicyDAL.create(
|
||||||
{
|
{
|
||||||
@ -158,6 +202,27 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (bypasserUserIds.length) {
|
||||||
|
await secretApprovalPolicyBypasserDAL.insertMany(
|
||||||
|
bypasserUserIds.map((userId) => ({
|
||||||
|
bypasserUserId: userId,
|
||||||
|
policyId: doc.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupBypassers.length) {
|
||||||
|
await secretApprovalPolicyBypasserDAL.insertMany(
|
||||||
|
groupBypassers.map((groupId) => ({
|
||||||
|
bypasserGroupId: groupId,
|
||||||
|
policyId: doc.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -166,6 +231,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
const updateSecretApprovalPolicy = async ({
|
const updateSecretApprovalPolicy = async ({
|
||||||
approvers,
|
approvers,
|
||||||
|
bypassers,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
actorId,
|
actorId,
|
||||||
@ -186,7 +252,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
const userApproverNames = approvers
|
const userApproverNames = approvers
|
||||||
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined))
|
.map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
|
||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
||||||
@ -204,10 +270,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
ProjectPermissionApprovalActions.Edit,
|
|
||||||
ProjectPermissionSub.SecretApproval
|
|
||||||
);
|
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
if (!plan.secretApproval) {
|
if (!plan.secretApproval) {
|
||||||
@ -217,6 +280,44 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let groupBypassers: string[] = [];
|
||||||
|
let bypasserUserIds: string[] = [];
|
||||||
|
|
||||||
|
if (bypassers && bypassers.length) {
|
||||||
|
groupBypassers = bypassers
|
||||||
|
.filter((bypasser) => bypasser.type === BypasserType.Group)
|
||||||
|
.map((bypasser) => bypasser.id) as string[];
|
||||||
|
|
||||||
|
const userBypassers = bypassers
|
||||||
|
.filter((bypasser) => bypasser.type === BypasserType.User)
|
||||||
|
.map((bypasser) => bypasser.id)
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
const userBypasserNames = bypassers
|
||||||
|
.map((bypasser) => (bypasser.type === BypasserType.User ? bypasser.username : undefined))
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
bypasserUserIds = userBypassers;
|
||||||
|
if (userBypasserNames.length) {
|
||||||
|
const bypasserUsers = await userDAL.find({
|
||||||
|
$in: {
|
||||||
|
username: userBypasserNames
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const bypasserNamesFromDb = bypasserUsers.map((user) => user.username);
|
||||||
|
const invalidUsernames = userBypasserNames.filter((username) => !bypasserNamesFromDb.includes(username));
|
||||||
|
|
||||||
|
if (invalidUsernames.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Invalid bypasser user: ${invalidUsernames.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bypasserUserIds = bypasserUserIds.concat(bypasserUsers.map((user) => user.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalPolicyDAL.updateById(
|
const doc = await secretApprovalPolicyDAL.updateById(
|
||||||
secretApprovalPolicy.id,
|
secretApprovalPolicy.id,
|
||||||
@ -275,6 +376,28 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await secretApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
|
||||||
|
|
||||||
|
if (bypasserUserIds.length) {
|
||||||
|
await secretApprovalPolicyBypasserDAL.insertMany(
|
||||||
|
bypasserUserIds.map((userId) => ({
|
||||||
|
bypasserUserId: userId,
|
||||||
|
policyId: doc.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupBypassers.length) {
|
||||||
|
await secretApprovalPolicyBypasserDAL.insertMany(
|
||||||
|
groupBypassers.map((groupId) => ({
|
||||||
|
bypasserGroupId: groupId,
|
||||||
|
policyId: doc.id
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -304,7 +427,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionApprovalActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -343,10 +466,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
ProjectPermissionApprovalActions.Read,
|
|
||||||
ProjectPermissionSub.SecretApproval
|
|
||||||
);
|
|
||||||
|
|
||||||
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
|
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
|
||||||
return sapPolicies;
|
return sapPolicies;
|
||||||
@ -419,10 +539,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
ProjectPermissionApprovalActions.Read,
|
|
||||||
ProjectPermissionSub.SecretApproval
|
|
||||||
);
|
|
||||||
|
|
||||||
return sapPolicy;
|
return sapPolicy;
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
|
import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { ApproverType } from "../access-approval-policy/access-approval-policy-types";
|
import { ApproverType, BypasserType } from "../access-approval-policy/access-approval-policy-types";
|
||||||
|
|
||||||
export type TCreateSapDTO = {
|
export type TCreateSapDTO = {
|
||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath?: string | null;
|
secretPath?: string | null;
|
||||||
environment: string;
|
environment: string;
|
||||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||||
|
bypassers?: (
|
||||||
|
| { type: BypasserType.Group; id: string }
|
||||||
|
| { type: BypasserType.User; id?: string; username?: string }
|
||||||
|
)[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
@ -17,7 +21,11 @@ export type TUpdateSapDTO = {
|
|||||||
secretPolicyId: string;
|
secretPolicyId: string;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
secretPath?: string | null;
|
secretPath?: string | null;
|
||||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||||
|
bypassers?: (
|
||||||
|
| { type: BypasserType.Group; id: string }
|
||||||
|
| { type: BypasserType.User; id?: string; username?: string }
|
||||||
|
)[];
|
||||||
name?: string;
|
name?: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
allowedSelfApprovals?: boolean;
|
allowedSelfApprovals?: boolean;
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
TableName,
|
TableName,
|
||||||
TSecretApprovalRequests,
|
TSecretApprovalRequests,
|
||||||
TSecretApprovalRequestsSecrets,
|
TSecretApprovalRequestsSecrets,
|
||||||
|
TUserGroupMembership,
|
||||||
TUsers
|
TUsers
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
@ -58,16 +59,36 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalPolicyApprover}.approverUserId`,
|
`${TableName.SecretApprovalPolicyApprover}.approverUserId`,
|
||||||
"secretApprovalPolicyApproverUser.id"
|
"secretApprovalPolicyApproverUser.id"
|
||||||
)
|
)
|
||||||
.leftJoin(
|
.leftJoin<TUserGroupMembership>(
|
||||||
TableName.UserGroupMembership,
|
db(TableName.UserGroupMembership).as("approverUserGroupMembership"),
|
||||||
`${TableName.SecretApprovalPolicyApprover}.approverGroupId`,
|
`${TableName.SecretApprovalPolicyApprover}.approverGroupId`,
|
||||||
`${TableName.UserGroupMembership}.groupId`
|
`approverUserGroupMembership.groupId`
|
||||||
)
|
)
|
||||||
.leftJoin<TUsers>(
|
.leftJoin<TUsers>(
|
||||||
db(TableName.Users).as("secretApprovalPolicyGroupApproverUser"),
|
db(TableName.Users).as("secretApprovalPolicyGroupApproverUser"),
|
||||||
`${TableName.UserGroupMembership}.userId`,
|
`approverUserGroupMembership.userId`,
|
||||||
`secretApprovalPolicyGroupApproverUser.id`
|
`secretApprovalPolicyGroupApproverUser.id`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretApprovalPolicyBypasser,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
|
`${TableName.SecretApprovalPolicyBypasser}.policyId`
|
||||||
|
)
|
||||||
|
.leftJoin<TUsers>(
|
||||||
|
db(TableName.Users).as("secretApprovalPolicyBypasserUser"),
|
||||||
|
`${TableName.SecretApprovalPolicyBypasser}.bypasserUserId`,
|
||||||
|
"secretApprovalPolicyBypasserUser.id"
|
||||||
|
)
|
||||||
|
.leftJoin<TUserGroupMembership>(
|
||||||
|
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
|
||||||
|
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
|
||||||
|
`bypasserUserGroupMembership.groupId`
|
||||||
|
)
|
||||||
|
.leftJoin<TUsers>(
|
||||||
|
db(TableName.Users).as("secretApprovalPolicyGroupBypasserUser"),
|
||||||
|
`bypasserUserGroupMembership.userId`,
|
||||||
|
`secretApprovalPolicyGroupBypasserUser.id`
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SecretApprovalRequestReviewer,
|
TableName.SecretApprovalRequestReviewer,
|
||||||
`${TableName.SecretApprovalRequest}.id`,
|
`${TableName.SecretApprovalRequest}.id`,
|
||||||
@ -81,7 +102,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
||||||
.select(
|
.select(
|
||||||
tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||||
tx.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"),
|
tx.ref("userId").withSchema("approverUserGroupMembership").as("approverGroupUserId"),
|
||||||
tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"),
|
tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"),
|
||||||
tx.ref("email").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupEmail"),
|
tx.ref("email").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupEmail"),
|
||||||
tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"),
|
tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"),
|
||||||
@ -90,6 +111,20 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
tx.ref("firstName").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupFirstName"),
|
tx.ref("firstName").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupFirstName"),
|
||||||
tx.ref("lastName").withSchema("secretApprovalPolicyApproverUser").as("approverLastName"),
|
tx.ref("lastName").withSchema("secretApprovalPolicyApproverUser").as("approverLastName"),
|
||||||
tx.ref("lastName").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupLastName"),
|
tx.ref("lastName").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupLastName"),
|
||||||
|
|
||||||
|
// Bypasser fields
|
||||||
|
tx.ref("bypasserUserId").withSchema(TableName.SecretApprovalPolicyBypasser),
|
||||||
|
tx.ref("bypasserGroupId").withSchema(TableName.SecretApprovalPolicyBypasser),
|
||||||
|
tx.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"),
|
||||||
|
tx.ref("email").withSchema("secretApprovalPolicyBypasserUser").as("bypasserEmail"),
|
||||||
|
tx.ref("email").withSchema("secretApprovalPolicyGroupBypasserUser").as("bypasserGroupEmail"),
|
||||||
|
tx.ref("username").withSchema("secretApprovalPolicyBypasserUser").as("bypasserUsername"),
|
||||||
|
tx.ref("username").withSchema("secretApprovalPolicyGroupBypasserUser").as("bypasserGroupUsername"),
|
||||||
|
tx.ref("firstName").withSchema("secretApprovalPolicyBypasserUser").as("bypasserFirstName"),
|
||||||
|
tx.ref("firstName").withSchema("secretApprovalPolicyGroupBypasserUser").as("bypasserGroupFirstName"),
|
||||||
|
tx.ref("lastName").withSchema("secretApprovalPolicyBypasserUser").as("bypasserLastName"),
|
||||||
|
tx.ref("lastName").withSchema("secretApprovalPolicyGroupBypasserUser").as("bypasserGroupLastName"),
|
||||||
|
|
||||||
tx.ref("email").withSchema("statusChangedByUser").as("statusChangedByUserEmail"),
|
tx.ref("email").withSchema("statusChangedByUser").as("statusChangedByUserEmail"),
|
||||||
tx.ref("username").withSchema("statusChangedByUser").as("statusChangedByUserUsername"),
|
tx.ref("username").withSchema("statusChangedByUser").as("statusChangedByUserUsername"),
|
||||||
tx.ref("firstName").withSchema("statusChangedByUser").as("statusChangedByUserFirstName"),
|
tx.ref("firstName").withSchema("statusChangedByUser").as("statusChangedByUserFirstName"),
|
||||||
@ -121,7 +156,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
try {
|
try {
|
||||||
const sql = findQuery({ [`${TableName.SecretApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode());
|
const sql = findQuery({ [`${TableName.SecretApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode());
|
||||||
const docs = await sql;
|
const docs = await sql;
|
||||||
const formatedDoc = sqlNestRelationships({
|
const formattedDoc = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
parentMapper: (el) => ({
|
parentMapper: (el) => ({
|
||||||
@ -203,13 +238,51 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
lastName,
|
lastName,
|
||||||
username
|
username
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({
|
||||||
|
bypasserUserId: userId,
|
||||||
|
bypasserEmail: email,
|
||||||
|
bypasserUsername: username,
|
||||||
|
bypasserLastName: lastName,
|
||||||
|
bypasserFirstName: firstName
|
||||||
|
}) => ({
|
||||||
|
userId,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
username
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserGroupUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({
|
||||||
|
bypasserGroupUserId: userId,
|
||||||
|
bypasserGroupEmail: email,
|
||||||
|
bypasserGroupUsername: username,
|
||||||
|
bypasserGroupLastName: lastName,
|
||||||
|
bypasserGroupFirstName: firstName
|
||||||
|
}) => ({
|
||||||
|
userId,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
username
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
if (!formatedDoc?.[0]) return;
|
if (!formattedDoc?.[0]) return;
|
||||||
return {
|
return {
|
||||||
...formatedDoc[0],
|
...formattedDoc[0],
|
||||||
policy: { ...formatedDoc[0].policy, approvers: formatedDoc[0].approvers }
|
policy: {
|
||||||
|
...formattedDoc[0].policy,
|
||||||
|
approvers: formattedDoc[0].approvers,
|
||||||
|
bypassers: formattedDoc[0].bypassers
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindByIdSAR" });
|
throw new DatabaseError({ error, name: "FindByIdSAR" });
|
||||||
@ -291,6 +364,16 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalPolicyApprover}.approverGroupId`,
|
`${TableName.SecretApprovalPolicyApprover}.approverGroupId`,
|
||||||
`${TableName.UserGroupMembership}.groupId`
|
`${TableName.UserGroupMembership}.groupId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretApprovalPolicyBypasser,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
|
`${TableName.SecretApprovalPolicyBypasser}.policyId`
|
||||||
|
)
|
||||||
|
.leftJoin<TUserGroupMembership>(
|
||||||
|
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
|
||||||
|
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
|
||||||
|
`bypasserUserGroupMembership.groupId`
|
||||||
|
)
|
||||||
.join<TUsers>(
|
.join<TUsers>(
|
||||||
db(TableName.Users).as("committerUser"),
|
db(TableName.Users).as("committerUser"),
|
||||||
`${TableName.SecretApprovalRequest}.committerUserId`,
|
`${TableName.SecretApprovalRequest}.committerUserId`,
|
||||||
@ -342,6 +425,11 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||||
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||||
db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"),
|
db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"),
|
||||||
|
|
||||||
|
// Bypasser fields
|
||||||
|
db.ref("bypasserUserId").withSchema(TableName.SecretApprovalPolicyBypasser),
|
||||||
|
db.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"),
|
||||||
|
|
||||||
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
||||||
db.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
db.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
||||||
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
||||||
@ -355,7 +443,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
.from<Awaited<typeof query>[number]>("w")
|
.from<Awaited<typeof query>[number]>("w")
|
||||||
.where("w.rank", ">=", offset)
|
.where("w.rank", ">=", offset)
|
||||||
.andWhere("w.rank", "<", offset + limit);
|
.andWhere("w.rank", "<", offset + limit);
|
||||||
const formatedDoc = sqlNestRelationships({
|
const formattedDoc = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
parentMapper: (el) => ({
|
parentMapper: (el) => ({
|
||||||
@ -403,12 +491,22 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
key: "approverGroupUserId",
|
key: "approverGroupUserId",
|
||||||
label: "approvers" as const,
|
label: "approvers" as const,
|
||||||
mapper: ({ approverGroupUserId }) => ({ userId: approverGroupUserId })
|
mapper: ({ approverGroupUserId }) => ({ userId: approverGroupUserId })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({ bypasserUserId }) => ({ userId: bypasserUserId })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserGroupUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({ bypasserGroupUserId }) => ({ userId: bypasserGroupUserId })
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
return formatedDoc.map((el) => ({
|
return formattedDoc.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
policy: { ...el.policy, approvers: el.approvers }
|
policy: { ...el.policy, approvers: el.approvers, bypassers: el.bypassers }
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindSAR" });
|
throw new DatabaseError({ error, name: "FindSAR" });
|
||||||
@ -440,6 +538,16 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretApprovalPolicyApprover}.approverGroupId`,
|
`${TableName.SecretApprovalPolicyApprover}.approverGroupId`,
|
||||||
`${TableName.UserGroupMembership}.groupId`
|
`${TableName.UserGroupMembership}.groupId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretApprovalPolicyBypasser,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
|
`${TableName.SecretApprovalPolicyBypasser}.policyId`
|
||||||
|
)
|
||||||
|
.leftJoin<TUserGroupMembership>(
|
||||||
|
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
|
||||||
|
`${TableName.SecretApprovalPolicyBypasser}.bypasserGroupId`,
|
||||||
|
`bypasserUserGroupMembership.groupId`
|
||||||
|
)
|
||||||
.join<TUsers>(
|
.join<TUsers>(
|
||||||
db(TableName.Users).as("committerUser"),
|
db(TableName.Users).as("committerUser"),
|
||||||
`${TableName.SecretApprovalRequest}.committerUserId`,
|
`${TableName.SecretApprovalRequest}.committerUserId`,
|
||||||
@ -491,6 +599,11 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||||
db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"),
|
db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"),
|
||||||
|
|
||||||
|
// Bypasser
|
||||||
|
db.ref("bypasserUserId").withSchema(TableName.SecretApprovalPolicyBypasser),
|
||||||
|
db.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"),
|
||||||
|
|
||||||
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
||||||
db.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
db.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
||||||
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
||||||
@ -504,7 +617,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
.from<Awaited<typeof query>[number]>("w")
|
.from<Awaited<typeof query>[number]>("w")
|
||||||
.where("w.rank", ">=", offset)
|
.where("w.rank", ">=", offset)
|
||||||
.andWhere("w.rank", "<", offset + limit);
|
.andWhere("w.rank", "<", offset + limit);
|
||||||
const formatedDoc = sqlNestRelationships({
|
const formattedDoc = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
parentMapper: (el) => ({
|
parentMapper: (el) => ({
|
||||||
@ -554,12 +667,24 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
mapper: ({ approverGroupUserId }) => ({
|
mapper: ({ approverGroupUserId }) => ({
|
||||||
userId: approverGroupUserId
|
userId: approverGroupUserId
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({ bypasserUserId }) => ({ userId: bypasserUserId })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "bypasserGroupUserId",
|
||||||
|
label: "bypassers" as const,
|
||||||
|
mapper: ({ bypasserGroupUserId }) => ({
|
||||||
|
userId: bypasserGroupUserId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
return formatedDoc.map((el) => ({
|
return formattedDoc.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
policy: { ...el.policy, approvers: el.approvers }
|
policy: { ...el.policy, approvers: el.approvers, bypassers: el.bypassers }
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindSAR" });
|
throw new DatabaseError({ error, name: "FindSAR" });
|
||||||
|
@ -62,11 +62,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { throwIfMissingSecretReadValueOrDescribePermission } from "../permission/permission-fns";
|
import { throwIfMissingSecretReadValueOrDescribePermission } from "../permission/permission-fns";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
ProjectPermissionApprovalActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "../permission/project-permission";
|
|
||||||
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
||||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||||
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
||||||
@ -501,14 +497,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { policy, folderId, projectId } = secretApprovalRequest;
|
const { policy, folderId, projectId, bypassers } = secretApprovalRequest;
|
||||||
if (policy.deletedAt) {
|
if (policy.deletedAt) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "The policy associated with this secret approval request has been deleted."
|
message: "The policy associated with this secret approval request has been deleted."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasRole, permission } = await permissionService.getProjectPermission({
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
actor: ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
@ -534,14 +530,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
approverId ? reviewers[approverId] === ApprovalStatus.APPROVED : false
|
approverId ? reviewers[approverId] === ApprovalStatus.APPROVED : false
|
||||||
).length;
|
).length;
|
||||||
const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft;
|
const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft;
|
||||||
|
const canBypass = !bypassers.length || bypassers.some((bypasser) => bypasser.userId === actorId);
|
||||||
|
|
||||||
if (
|
if (!hasMinApproval && !(isSoftEnforcement && canBypass))
|
||||||
!hasMinApproval &&
|
|
||||||
!(
|
|
||||||
isSoftEnforcement &&
|
|
||||||
permission.can(ProjectPermissionApprovalActions.AllowChangeBypass, ProjectPermissionSub.SecretApproval)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||||
|
|
||||||
const { botKey, shouldUseSecretV2Bridge, project } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge, project } = await projectBotService.getBotKey(projectId);
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./mysql-credentials-rotation-constants";
|
||||||
|
export * from "./mysql-credentials-rotation-schemas";
|
||||||
|
export * from "./mysql-credentials-rotation-types";
|
@ -0,0 +1,23 @@
|
|||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const MYSQL_CREDENTIALS_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
||||||
|
name: "MySQL Credentials",
|
||||||
|
type: SecretRotation.MySqlCredentials,
|
||||||
|
connection: AppConnection.MySql,
|
||||||
|
template: {
|
||||||
|
createUserStatement: `-- create user
|
||||||
|
CREATE USER 'infisical_user'@'%' IDENTIFIED BY 'temporary_password';
|
||||||
|
|
||||||
|
-- grant all privileges
|
||||||
|
GRANT ALL PRIVILEGES ON my_database.* TO 'infisical_user'@'%';
|
||||||
|
|
||||||
|
-- apply the privilege changes
|
||||||
|
FLUSH PRIVILEGES;`,
|
||||||
|
secretsMapping: {
|
||||||
|
username: "MYSQL_USERNAME",
|
||||||
|
password: "MYSQL_PASSWORD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,41 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||||
|
import {
|
||||||
|
BaseCreateSecretRotationSchema,
|
||||||
|
BaseSecretRotationSchema,
|
||||||
|
BaseUpdateSecretRotationSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
|
||||||
|
import {
|
||||||
|
SqlCredentialsRotationParametersSchema,
|
||||||
|
SqlCredentialsRotationSecretsMappingSchema,
|
||||||
|
SqlCredentialsRotationTemplateSchema
|
||||||
|
} from "@app/ee/services/secret-rotation-v2/shared/sql-credentials";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const MySqlCredentialsRotationSchema = BaseSecretRotationSchema(SecretRotation.MySqlCredentials).extend({
|
||||||
|
type: z.literal(SecretRotation.MySqlCredentials),
|
||||||
|
parameters: SqlCredentialsRotationParametersSchema,
|
||||||
|
secretsMapping: SqlCredentialsRotationSecretsMappingSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateMySqlCredentialsRotationSchema = BaseCreateSecretRotationSchema(
|
||||||
|
SecretRotation.MySqlCredentials
|
||||||
|
).extend({
|
||||||
|
parameters: SqlCredentialsRotationParametersSchema,
|
||||||
|
secretsMapping: SqlCredentialsRotationSecretsMappingSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateMySqlCredentialsRotationSchema = BaseUpdateSecretRotationSchema(
|
||||||
|
SecretRotation.MySqlCredentials
|
||||||
|
).extend({
|
||||||
|
parameters: SqlCredentialsRotationParametersSchema.optional(),
|
||||||
|
secretsMapping: SqlCredentialsRotationSecretsMappingSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const MySqlCredentialsRotationListItemSchema = z.object({
|
||||||
|
name: z.literal("MySQL Credentials"),
|
||||||
|
connection: z.literal(AppConnection.MySql),
|
||||||
|
type: z.literal(SecretRotation.MySqlCredentials),
|
||||||
|
template: SqlCredentialsRotationTemplateSchema
|
||||||
|
});
|
@ -0,0 +1,19 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TMySqlConnection } from "@app/services/app-connection/mysql";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateMySqlCredentialsRotationSchema,
|
||||||
|
MySqlCredentialsRotationListItemSchema,
|
||||||
|
MySqlCredentialsRotationSchema
|
||||||
|
} from "./mysql-credentials-rotation-schemas";
|
||||||
|
|
||||||
|
export type TMySqlCredentialsRotation = z.infer<typeof MySqlCredentialsRotationSchema>;
|
||||||
|
|
||||||
|
export type TMySqlCredentialsRotationInput = z.infer<typeof CreateMySqlCredentialsRotationSchema>;
|
||||||
|
|
||||||
|
export type TMySqlCredentialsRotationListItem = z.infer<typeof MySqlCredentialsRotationListItemSchema>;
|
||||||
|
|
||||||
|
export type TMySqlCredentialsRotationWithConnection = TMySqlCredentialsRotation & {
|
||||||
|
connection: TMySqlConnection;
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
export enum SecretRotation {
|
export enum SecretRotation {
|
||||||
PostgresCredentials = "postgres-credentials",
|
PostgresCredentials = "postgres-credentials",
|
||||||
MsSqlCredentials = "mssql-credentials",
|
MsSqlCredentials = "mssql-credentials",
|
||||||
|
MySqlCredentials = "mysql-credentials",
|
||||||
Auth0ClientSecret = "auth0-client-secret",
|
Auth0ClientSecret = "auth0-client-secret",
|
||||||
AzureClientSecret = "azure-client-secret",
|
AzureClientSecret = "azure-client-secret",
|
||||||
AwsIamUserSecret = "aws-iam-user-secret",
|
AwsIamUserSecret = "aws-iam-user-secret",
|
||||||
|
@ -9,6 +9,7 @@ import { AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION } from "./aws-iam-user-secret"
|
|||||||
import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret";
|
import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret";
|
||||||
import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password";
|
import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password";
|
||||||
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
||||||
|
import { MYSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mysql-credentials";
|
||||||
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
||||||
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
||||||
import { TSecretRotationV2ServiceFactoryDep } from "./secret-rotation-v2-service";
|
import { TSecretRotationV2ServiceFactoryDep } from "./secret-rotation-v2-service";
|
||||||
@ -23,6 +24,7 @@ import {
|
|||||||
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
|
const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2ListItem> = {
|
||||||
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
[SecretRotation.PostgresCredentials]: POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
[SecretRotation.MsSqlCredentials]: MSSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
|
[SecretRotation.MySqlCredentials]: MYSQL_CREDENTIALS_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||||
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,
|
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,
|
||||||
|
@ -4,6 +4,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
|
|||||||
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
||||||
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
[SecretRotation.PostgresCredentials]: "PostgreSQL Credentials",
|
||||||
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
[SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials",
|
||||||
|
[SecretRotation.MySqlCredentials]: "MySQL Credentials",
|
||||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
||||||
[SecretRotation.AzureClientSecret]: "Azure Client Secret",
|
[SecretRotation.AzureClientSecret]: "Azure Client Secret",
|
||||||
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
|
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
|
||||||
@ -13,6 +14,7 @@ export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
|||||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||||
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
[SecretRotation.PostgresCredentials]: AppConnection.Postgres,
|
||||||
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
[SecretRotation.MsSqlCredentials]: AppConnection.MsSql,
|
||||||
|
[SecretRotation.MySqlCredentials]: AppConnection.MySql,
|
||||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
||||||
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
||||||
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
||||||
|
@ -120,6 +120,7 @@ type TRotationFactoryImplementation = TRotationFactory<
|
|||||||
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
|
const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplementation> = {
|
||||||
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.PostgresCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.MsSqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
|
[SecretRotation.MySqlCredentials]: sqlCredentialsRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,
|
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,
|
||||||
|
@ -39,6 +39,12 @@ import {
|
|||||||
TMsSqlCredentialsRotationListItem,
|
TMsSqlCredentialsRotationListItem,
|
||||||
TMsSqlCredentialsRotationWithConnection
|
TMsSqlCredentialsRotationWithConnection
|
||||||
} from "./mssql-credentials";
|
} from "./mssql-credentials";
|
||||||
|
import {
|
||||||
|
TMySqlCredentialsRotation,
|
||||||
|
TMySqlCredentialsRotationInput,
|
||||||
|
TMySqlCredentialsRotationListItem,
|
||||||
|
TMySqlCredentialsRotationWithConnection
|
||||||
|
} from "./mysql-credentials";
|
||||||
import {
|
import {
|
||||||
TPostgresCredentialsRotation,
|
TPostgresCredentialsRotation,
|
||||||
TPostgresCredentialsRotationInput,
|
TPostgresCredentialsRotationInput,
|
||||||
@ -51,6 +57,7 @@ import { SecretRotation } from "./secret-rotation-v2-enums";
|
|||||||
export type TSecretRotationV2 =
|
export type TSecretRotationV2 =
|
||||||
| TPostgresCredentialsRotation
|
| TPostgresCredentialsRotation
|
||||||
| TMsSqlCredentialsRotation
|
| TMsSqlCredentialsRotation
|
||||||
|
| TMySqlCredentialsRotation
|
||||||
| TAuth0ClientSecretRotation
|
| TAuth0ClientSecretRotation
|
||||||
| TAzureClientSecretRotation
|
| TAzureClientSecretRotation
|
||||||
| TLdapPasswordRotation
|
| TLdapPasswordRotation
|
||||||
@ -59,6 +66,7 @@ export type TSecretRotationV2 =
|
|||||||
export type TSecretRotationV2WithConnection =
|
export type TSecretRotationV2WithConnection =
|
||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
| TMsSqlCredentialsRotationWithConnection
|
| TMsSqlCredentialsRotationWithConnection
|
||||||
|
| TMySqlCredentialsRotationWithConnection
|
||||||
| TAuth0ClientSecretRotationWithConnection
|
| TAuth0ClientSecretRotationWithConnection
|
||||||
| TAzureClientSecretRotationWithConnection
|
| TAzureClientSecretRotationWithConnection
|
||||||
| TLdapPasswordRotationWithConnection
|
| TLdapPasswordRotationWithConnection
|
||||||
@ -74,6 +82,7 @@ export type TSecretRotationV2GeneratedCredentials =
|
|||||||
export type TSecretRotationV2Input =
|
export type TSecretRotationV2Input =
|
||||||
| TPostgresCredentialsRotationInput
|
| TPostgresCredentialsRotationInput
|
||||||
| TMsSqlCredentialsRotationInput
|
| TMsSqlCredentialsRotationInput
|
||||||
|
| TMySqlCredentialsRotationInput
|
||||||
| TAuth0ClientSecretRotationInput
|
| TAuth0ClientSecretRotationInput
|
||||||
| TAzureClientSecretRotationInput
|
| TAzureClientSecretRotationInput
|
||||||
| TLdapPasswordRotationInput
|
| TLdapPasswordRotationInput
|
||||||
@ -82,6 +91,7 @@ export type TSecretRotationV2Input =
|
|||||||
export type TSecretRotationV2ListItem =
|
export type TSecretRotationV2ListItem =
|
||||||
| TPostgresCredentialsRotationListItem
|
| TPostgresCredentialsRotationListItem
|
||||||
| TMsSqlCredentialsRotationListItem
|
| TMsSqlCredentialsRotationListItem
|
||||||
|
| TMySqlCredentialsRotationListItem
|
||||||
| TAuth0ClientSecretRotationListItem
|
| TAuth0ClientSecretRotationListItem
|
||||||
| TAzureClientSecretRotationListItem
|
| TAzureClientSecretRotationListItem
|
||||||
| TLdapPasswordRotationListItem
|
| TLdapPasswordRotationListItem
|
||||||
|
@ -4,6 +4,7 @@ import { Auth0ClientSecretRotationSchema } from "@app/ee/services/secret-rotatio
|
|||||||
import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/azure-client-secret";
|
||||||
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||||
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
|
import { MySqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||||
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
|
|
||||||
import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret";
|
import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret";
|
||||||
@ -11,6 +12,7 @@ import { AwsIamUserSecretRotationSchema } from "./aws-iam-user-secret";
|
|||||||
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
||||||
PostgresCredentialsRotationSchema,
|
PostgresCredentialsRotationSchema,
|
||||||
MsSqlCredentialsRotationSchema,
|
MsSqlCredentialsRotationSchema,
|
||||||
|
MySqlCredentialsRotationSchema,
|
||||||
Auth0ClientSecretRotationSchema,
|
Auth0ClientSecretRotationSchema,
|
||||||
AzureClientSecretRotationSchema,
|
AzureClientSecretRotationSchema,
|
||||||
LdapPasswordRotationSchema,
|
LdapPasswordRotationSchema,
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TMsSqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
import { TMsSqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||||
|
import { TMySqlCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||||
import { TPostgresCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
import { TPostgresCredentialsRotationWithConnection } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||||
|
|
||||||
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "./sql-credentials-rotation-schemas";
|
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "./sql-credentials-rotation-schemas";
|
||||||
|
|
||||||
export type TSqlCredentialsRotationWithConnection =
|
export type TSqlCredentialsRotationWithConnection =
|
||||||
| TPostgresCredentialsRotationWithConnection
|
| TPostgresCredentialsRotationWithConnection
|
||||||
| TMsSqlCredentialsRotationWithConnection;
|
| TMsSqlCredentialsRotationWithConnection
|
||||||
|
| TMySqlCredentialsRotationWithConnection;
|
||||||
|
|
||||||
export type TSqlCredentialsRotationGeneratedCredentials = z.infer<
|
export type TSqlCredentialsRotationGeneratedCredentials = z.infer<
|
||||||
typeof SqlCredentialsRotationGeneratedCredentialsSchema
|
typeof SqlCredentialsRotationGeneratedCredentialsSchema
|
||||||
|
@ -171,6 +171,13 @@ export const getDbSetQuery = (db: TDbProviderClients, variables: { username: str
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (db === TDbProviderClients.MySql) {
|
||||||
|
return {
|
||||||
|
query: `ALTER USER ??@'%' IDENTIFIED BY '${variables.password}'`,
|
||||||
|
variables: [variables.username]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// add more based on client
|
// add more based on client
|
||||||
return {
|
return {
|
||||||
query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`,
|
query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`,
|
||||||
|
@ -147,7 +147,9 @@ export const UNIVERSAL_AUTH = {
|
|||||||
accessTokenMaxTTL:
|
accessTokenMaxTTL:
|
||||||
"The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.",
|
"The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.",
|
||||||
accessTokenNumUsesLimit:
|
accessTokenNumUsesLimit:
|
||||||
"The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses."
|
"The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses.",
|
||||||
|
accessTokenPeriod:
|
||||||
|
"The period for an access token in seconds. This value will be referenced at renewal time. Default value is 0."
|
||||||
},
|
},
|
||||||
RETRIEVE: {
|
RETRIEVE: {
|
||||||
identityId: "The ID of the identity to retrieve the auth method for."
|
identityId: "The ID of the identity to retrieve the auth method for."
|
||||||
@ -161,7 +163,8 @@ export const UNIVERSAL_AUTH = {
|
|||||||
accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
|
||||||
|
accessTokenPeriod: "The new period for an access token in seconds."
|
||||||
},
|
},
|
||||||
CREATE_CLIENT_SECRET: {
|
CREATE_CLIENT_SECRET: {
|
||||||
identityId: "The ID of the identity to create a client secret for.",
|
identityId: "The ID of the identity to create a client secret for.",
|
||||||
|
@ -6,7 +6,10 @@ import { z } from "zod";
|
|||||||
import { registerCertificateEstRouter } from "@app/ee/routes/est/certificate-est-router";
|
import { registerCertificateEstRouter } from "@app/ee/routes/est/certificate-est-router";
|
||||||
import { registerV1EERoutes } from "@app/ee/routes/v1";
|
import { registerV1EERoutes } from "@app/ee/routes/v1";
|
||||||
import { registerV2EERoutes } from "@app/ee/routes/v2";
|
import { registerV2EERoutes } from "@app/ee/routes/v2";
|
||||||
import { accessApprovalPolicyApproverDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal";
|
import {
|
||||||
|
accessApprovalPolicyApproverDALFactory,
|
||||||
|
accessApprovalPolicyBypasserDALFactory
|
||||||
|
} from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal";
|
||||||
import { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal";
|
import { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal";
|
||||||
import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
||||||
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
|
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
|
||||||
@ -67,7 +70,10 @@ import { samlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-d
|
|||||||
import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||||
import { scimDALFactory } from "@app/ee/services/scim/scim-dal";
|
import { scimDALFactory } from "@app/ee/services/scim/scim-dal";
|
||||||
import { scimServiceFactory } from "@app/ee/services/scim/scim-service";
|
import { scimServiceFactory } from "@app/ee/services/scim/scim-service";
|
||||||
import { secretApprovalPolicyApproverDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal";
|
import {
|
||||||
|
secretApprovalPolicyApproverDALFactory,
|
||||||
|
secretApprovalPolicyBypasserDALFactory
|
||||||
|
} from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal";
|
||||||
import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
|
import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
|
||||||
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||||
import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
|
||||||
@ -205,6 +211,8 @@ import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-co
|
|||||||
import { pkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
|
import { pkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
|
||||||
import { pkiSubscriberQueueServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-queue";
|
import { pkiSubscriberQueueServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-queue";
|
||||||
import { pkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
|
import { pkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
|
||||||
|
import { pkiTemplatesDALFactory } from "@app/services/pki-templates/pki-templates-dal";
|
||||||
|
import { pkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service";
|
||||||
import { projectDALFactory } from "@app/services/project/project-dal";
|
import { projectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { projectQueueFactory } from "@app/services/project/project-queue";
|
import { projectQueueFactory } from "@app/services/project/project-queue";
|
||||||
import { projectServiceFactory } from "@app/services/project/project-service";
|
import { projectServiceFactory } from "@app/services/project/project-service";
|
||||||
@ -385,9 +393,11 @@ export const registerRoutes = async (
|
|||||||
const accessApprovalPolicyDAL = accessApprovalPolicyDALFactory(db);
|
const accessApprovalPolicyDAL = accessApprovalPolicyDALFactory(db);
|
||||||
const accessApprovalRequestDAL = accessApprovalRequestDALFactory(db);
|
const accessApprovalRequestDAL = accessApprovalRequestDALFactory(db);
|
||||||
const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db);
|
const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db);
|
||||||
|
const accessApprovalPolicyBypasserDAL = accessApprovalPolicyBypasserDALFactory(db);
|
||||||
const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db);
|
const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db);
|
||||||
|
|
||||||
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
|
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
|
||||||
|
const sapBypasserDAL = secretApprovalPolicyBypasserDALFactory(db);
|
||||||
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
|
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
|
||||||
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
|
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
|
||||||
const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db);
|
const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db);
|
||||||
@ -519,6 +529,7 @@ export const registerRoutes = async (
|
|||||||
const secretApprovalPolicyService = secretApprovalPolicyServiceFactory({
|
const secretApprovalPolicyService = secretApprovalPolicyServiceFactory({
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
||||||
|
secretApprovalPolicyBypasserDAL: sapBypasserDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
@ -794,7 +805,8 @@ export const registerRoutes = async (
|
|||||||
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
|
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectUserAdditionalPrivilegeDAL
|
projectUserAdditionalPrivilegeDAL,
|
||||||
|
accessApprovalRequestDAL
|
||||||
});
|
});
|
||||||
const projectKeyService = projectKeyServiceFactory({
|
const projectKeyService = projectKeyServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
@ -838,6 +850,7 @@ export const registerRoutes = async (
|
|||||||
const pkiCollectionDAL = pkiCollectionDALFactory(db);
|
const pkiCollectionDAL = pkiCollectionDALFactory(db);
|
||||||
const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db);
|
const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db);
|
||||||
const pkiSubscriberDAL = pkiSubscriberDALFactory(db);
|
const pkiSubscriberDAL = pkiSubscriberDALFactory(db);
|
||||||
|
const pkiTemplatesDAL = pkiTemplatesDALFactory(db);
|
||||||
|
|
||||||
const certificateService = certificateServiceFactory({
|
const certificateService = certificateServiceFactory({
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
@ -1218,6 +1231,7 @@ export const registerRoutes = async (
|
|||||||
const accessApprovalPolicyService = accessApprovalPolicyServiceFactory({
|
const accessApprovalPolicyService = accessApprovalPolicyServiceFactory({
|
||||||
accessApprovalPolicyDAL,
|
accessApprovalPolicyDAL,
|
||||||
accessApprovalPolicyApproverDAL,
|
accessApprovalPolicyApproverDAL,
|
||||||
|
accessApprovalPolicyBypasserDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
@ -1226,7 +1240,8 @@ export const registerRoutes = async (
|
|||||||
userDAL,
|
userDAL,
|
||||||
accessApprovalRequestDAL,
|
accessApprovalRequestDAL,
|
||||||
additionalPrivilegeDAL: projectUserAdditionalPrivilegeDAL,
|
additionalPrivilegeDAL: projectUserAdditionalPrivilegeDAL,
|
||||||
accessApprovalRequestReviewerDAL
|
accessApprovalRequestReviewerDAL,
|
||||||
|
orgMembershipDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
|
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
|
||||||
@ -1743,6 +1758,21 @@ export const registerRoutes = async (
|
|||||||
internalCaFns
|
internalCaFns
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const pkiTemplateService = pkiTemplatesServiceFactory({
|
||||||
|
pkiTemplatesDAL,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
permissionService,
|
||||||
|
internalCaFns
|
||||||
|
});
|
||||||
|
|
||||||
await secretRotationV2QueueServiceFactory({
|
await secretRotationV2QueueServiceFactory({
|
||||||
secretRotationV2Service,
|
secretRotationV2Service,
|
||||||
secretRotationV2DAL,
|
secretRotationV2DAL,
|
||||||
@ -1836,6 +1866,7 @@ export const registerRoutes = async (
|
|||||||
pkiAlert: pkiAlertService,
|
pkiAlert: pkiAlertService,
|
||||||
pkiCollection: pkiCollectionService,
|
pkiCollection: pkiCollectionService,
|
||||||
pkiSubscriber: pkiSubscriberService,
|
pkiSubscriber: pkiSubscriberService,
|
||||||
|
pkiTemplate: pkiTemplateService,
|
||||||
secretScanning: secretScanningService,
|
secretScanning: secretScanningService,
|
||||||
license: licenseService,
|
license: licenseService,
|
||||||
trustedIp: trustedIpService,
|
trustedIp: trustedIpService,
|
||||||
|
@ -43,6 +43,7 @@ import {
|
|||||||
} from "@app/services/app-connection/humanitec";
|
} from "@app/services/app-connection/humanitec";
|
||||||
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
|
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
|
||||||
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
|
||||||
|
import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql";
|
||||||
import {
|
import {
|
||||||
PostgresConnectionListItemSchema,
|
PostgresConnectionListItemSchema,
|
||||||
SanitizedPostgresConnectionSchema
|
SanitizedPostgresConnectionSchema
|
||||||
@ -75,6 +76,7 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedVercelConnectionSchema.options,
|
...SanitizedVercelConnectionSchema.options,
|
||||||
...SanitizedPostgresConnectionSchema.options,
|
...SanitizedPostgresConnectionSchema.options,
|
||||||
...SanitizedMsSqlConnectionSchema.options,
|
...SanitizedMsSqlConnectionSchema.options,
|
||||||
|
...SanitizedMySqlConnectionSchema.options,
|
||||||
...SanitizedCamundaConnectionSchema.options,
|
...SanitizedCamundaConnectionSchema.options,
|
||||||
...SanitizedAuth0ConnectionSchema.options,
|
...SanitizedAuth0ConnectionSchema.options,
|
||||||
...SanitizedHCVaultConnectionSchema.options,
|
...SanitizedHCVaultConnectionSchema.options,
|
||||||
@ -98,6 +100,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
VercelConnectionListItemSchema,
|
VercelConnectionListItemSchema,
|
||||||
PostgresConnectionListItemSchema,
|
PostgresConnectionListItemSchema,
|
||||||
MsSqlConnectionListItemSchema,
|
MsSqlConnectionListItemSchema,
|
||||||
|
MySqlConnectionListItemSchema,
|
||||||
CamundaConnectionListItemSchema,
|
CamundaConnectionListItemSchema,
|
||||||
Auth0ConnectionListItemSchema,
|
Auth0ConnectionListItemSchema,
|
||||||
HCVaultConnectionListItemSchema,
|
HCVaultConnectionListItemSchema,
|
||||||
|
@ -15,6 +15,7 @@ import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
|
|||||||
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
||||||
import { registerLdapConnectionRouter } from "./ldap-connection-router";
|
import { registerLdapConnectionRouter } from "./ldap-connection-router";
|
||||||
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
||||||
|
import { registerMySqlConnectionRouter } from "./mysql-connection-router";
|
||||||
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
|
||||||
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
|
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
|
||||||
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
|
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
|
||||||
@ -37,6 +38,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.Vercel]: registerVercelConnectionRouter,
|
[AppConnection.Vercel]: registerVercelConnectionRouter,
|
||||||
[AppConnection.Postgres]: registerPostgresConnectionRouter,
|
[AppConnection.Postgres]: registerPostgresConnectionRouter,
|
||||||
[AppConnection.MsSql]: registerMsSqlConnectionRouter,
|
[AppConnection.MsSql]: registerMsSqlConnectionRouter,
|
||||||
|
[AppConnection.MySql]: registerMySqlConnectionRouter,
|
||||||
[AppConnection.Camunda]: registerCamundaConnectionRouter,
|
[AppConnection.Camunda]: registerCamundaConnectionRouter,
|
||||||
[AppConnection.Windmill]: registerWindmillConnectionRouter,
|
[AppConnection.Windmill]: registerWindmillConnectionRouter,
|
||||||
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
|
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateMySqlConnectionSchema,
|
||||||
|
SanitizedMySqlConnectionSchema,
|
||||||
|
UpdateMySqlConnectionSchema
|
||||||
|
} from "@app/services/app-connection/mysql";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerMySqlConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.MySql,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedMySqlConnectionSchema,
|
||||||
|
createSchema: CreateMySqlConnectionSchema,
|
||||||
|
updateSchema: UpdateMySqlConnectionSchema
|
||||||
|
});
|
||||||
|
};
|
@ -5,6 +5,7 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
|||||||
import { ApiDocsTags, CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
|
import { ApiDocsTags, CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
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 { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
@ -72,7 +73,7 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
caId: z.string().describe(CERTIFICATE_TEMPLATES.CREATE.caId),
|
caId: z.string().describe(CERTIFICATE_TEMPLATES.CREATE.caId),
|
||||||
pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.CREATE.pkiCollectionId),
|
pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.CREATE.pkiCollectionId),
|
||||||
name: z.string().min(1).describe(CERTIFICATE_TEMPLATES.CREATE.name),
|
name: slugSchema().describe(CERTIFICATE_TEMPLATES.CREATE.name),
|
||||||
commonName: validateTemplateRegexField.describe(CERTIFICATE_TEMPLATES.CREATE.commonName),
|
commonName: validateTemplateRegexField.describe(CERTIFICATE_TEMPLATES.CREATE.commonName),
|
||||||
subjectAlternativeName: validateTemplateRegexField.describe(
|
subjectAlternativeName: validateTemplateRegexField.describe(
|
||||||
CERTIFICATE_TEMPLATES.CREATE.subjectAlternativeName
|
CERTIFICATE_TEMPLATES.CREATE.subjectAlternativeName
|
||||||
@ -141,7 +142,7 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
caId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.caId),
|
caId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.caId),
|
||||||
pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.pkiCollectionId),
|
pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.pkiCollectionId),
|
||||||
name: z.string().min(1).optional().describe(CERTIFICATE_TEMPLATES.UPDATE.name),
|
name: slugSchema().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.name),
|
||||||
commonName: validateTemplateRegexField.optional().describe(CERTIFICATE_TEMPLATES.UPDATE.commonName),
|
commonName: validateTemplateRegexField.optional().describe(CERTIFICATE_TEMPLATES.UPDATE.commonName),
|
||||||
subjectAlternativeName: validateTemplateRegexField
|
subjectAlternativeName: validateTemplateRegexField
|
||||||
.optional()
|
.optional()
|
||||||
|
@ -47,8 +47,15 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { identityUa, accessToken, identityAccessToken, validClientSecretInfo, identityMembershipOrg } =
|
const {
|
||||||
await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp);
|
identityUa,
|
||||||
|
accessToken,
|
||||||
|
identityAccessToken,
|
||||||
|
validClientSecretInfo,
|
||||||
|
identityMembershipOrg,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenMaxTTL
|
||||||
|
} = await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp);
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
@ -63,11 +70,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accessToken,
|
accessToken,
|
||||||
tokenType: "Bearer" as const,
|
tokenType: "Bearer" as const,
|
||||||
expiresIn: identityUa.accessTokenTTL,
|
expiresIn: accessTokenTTL,
|
||||||
accessTokenMaxTTL: identityUa.accessTokenMaxTTL
|
accessTokenMaxTTL
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -128,7 +136,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
.int()
|
.int()
|
||||||
.min(0)
|
.min(0)
|
||||||
.default(0)
|
.default(0)
|
||||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit)
|
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit),
|
||||||
|
accessTokenPeriod: z.number().int().min(0).default(0).describe(UNIVERSAL_AUTH.ATTACH.accessTokenPeriod)
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
@ -227,7 +236,14 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
.min(0)
|
.min(0)
|
||||||
.max(315360000)
|
.max(315360000)
|
||||||
.optional()
|
.optional()
|
||||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL)
|
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL),
|
||||||
|
accessTokenPeriod: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.max(315360000)
|
||||||
|
.optional()
|
||||||
|
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenPeriod)
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
@ -62,9 +62,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
hashedHex: z.string().min(1).optional(),
|
hashedHex: z.string().min(1).optional(),
|
||||||
password: z.string().optional(),
|
password: z.string().optional()
|
||||||
email: z.string().optional(),
|
|
||||||
hash: z.string().optional()
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -91,8 +89,7 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
|||||||
hashedHex: req.body.hashedHex,
|
hashedHex: req.body.hashedHex,
|
||||||
password: req.body.password,
|
password: req.body.password,
|
||||||
orgId: req.permission?.orgId,
|
orgId: req.permission?.orgId,
|
||||||
email: req.body.email,
|
actorId: req.permission?.id
|
||||||
hash: req.body.hash
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sharedSecret.secret?.orgId) {
|
if (sharedSecret.secret?.orgId) {
|
||||||
@ -156,7 +153,13 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
|
|||||||
expiresAt: z.string(),
|
expiresAt: z.string(),
|
||||||
expiresAfterViews: z.number().min(1).optional(),
|
expiresAfterViews: z.number().min(1).optional(),
|
||||||
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization),
|
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization),
|
||||||
emails: z.string().email().array().max(100).optional()
|
emails: z
|
||||||
|
.string()
|
||||||
|
.email()
|
||||||
|
.array()
|
||||||
|
.max(100)
|
||||||
|
.optional()
|
||||||
|
.transform((val) => (val ? [...new Set(val)] : undefined))
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -5,6 +5,7 @@ import { registerIdentityProjectRouter } from "./identity-project-router";
|
|||||||
import { registerMfaRouter } from "./mfa-router";
|
import { registerMfaRouter } from "./mfa-router";
|
||||||
import { registerOrgRouter } from "./organization-router";
|
import { registerOrgRouter } from "./organization-router";
|
||||||
import { registerPasswordRouter } from "./password-router";
|
import { registerPasswordRouter } from "./password-router";
|
||||||
|
import { registerPkiTemplatesRouter } from "./pki-templates-router";
|
||||||
import { registerProjectMembershipRouter } from "./project-membership-router";
|
import { registerProjectMembershipRouter } from "./project-membership-router";
|
||||||
import { registerProjectRouter } from "./project-router";
|
import { registerProjectRouter } from "./project-router";
|
||||||
import { registerServiceTokenRouter } from "./service-token-router";
|
import { registerServiceTokenRouter } from "./service-token-router";
|
||||||
@ -15,7 +16,15 @@ export const registerV2Routes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerUserRouter, { prefix: "/users" });
|
await server.register(registerUserRouter, { prefix: "/users" });
|
||||||
await server.register(registerServiceTokenRouter, { prefix: "/service-token" });
|
await server.register(registerServiceTokenRouter, { prefix: "/service-token" });
|
||||||
await server.register(registerPasswordRouter, { prefix: "/password" });
|
await server.register(registerPasswordRouter, { prefix: "/password" });
|
||||||
await server.register(registerCaRouter, { prefix: "/pki/ca" });
|
|
||||||
|
await server.register(
|
||||||
|
async (pkiRouter) => {
|
||||||
|
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
|
||||||
|
await pkiRouter.register(registerPkiTemplatesRouter, { prefix: "/certificate-templates" });
|
||||||
|
},
|
||||||
|
{ prefix: "/pki" }
|
||||||
|
);
|
||||||
|
|
||||||
await server.register(
|
await server.register(
|
||||||
async (orgRouter) => {
|
async (orgRouter) => {
|
||||||
await orgRouter.register(registerOrgRouter);
|
await orgRouter.register(registerOrgRouter);
|
||||||
|
309
backend/src/server/routes/v2/pki-templates-router.ts
Normal file
309
backend/src/server/routes/v2/pki-templates-router.ts
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { CertificateTemplatesSchema } from "@app/db/schemas";
|
||||||
|
import { ApiDocsTags } from "@app/lib/api-docs";
|
||||||
|
import { ms } from "@app/lib/ms";
|
||||||
|
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 { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
|
import {
|
||||||
|
validateAltNamesField,
|
||||||
|
validateCaDateField
|
||||||
|
} from "@app/services/certificate-authority/certificate-authority-validators";
|
||||||
|
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
||||||
|
|
||||||
|
export const registerPkiTemplatesRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||||
|
body: z.object({
|
||||||
|
name: slugSchema(),
|
||||||
|
caName: slugSchema({ field: "caName" }),
|
||||||
|
projectId: z.string(),
|
||||||
|
commonName: validateTemplateRegexField,
|
||||||
|
subjectAlternativeName: validateTemplateRegexField,
|
||||||
|
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
||||||
|
keyUsages: z
|
||||||
|
.nativeEnum(CertKeyUsage)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]),
|
||||||
|
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional().default([])
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificateTemplate: CertificateTemplatesSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const certificateTemplate = await server.services.pkiTemplate.createTemplate({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return { certificateTemplate };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:templateName",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||||
|
params: z.object({
|
||||||
|
templateName: slugSchema()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
name: slugSchema().optional(),
|
||||||
|
caName: slugSchema(),
|
||||||
|
projectId: z.string(),
|
||||||
|
commonName: validateTemplateRegexField.optional(),
|
||||||
|
subjectAlternativeName: validateTemplateRegexField.optional(),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
|
.optional(),
|
||||||
|
keyUsages: z
|
||||||
|
.nativeEnum(CertKeyUsage)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]),
|
||||||
|
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional().default([])
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificateTemplate: CertificateTemplatesSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const certificateTemplate = await server.services.pkiTemplate.updateTemplate({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
templateName: req.params.templateName,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return { certificateTemplate };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:templateName",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||||
|
params: z.object({
|
||||||
|
templateName: z.string().min(1)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificateTemplate: CertificateTemplatesSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const certificateTemplate = await server.services.pkiTemplate.deleteTemplate({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
templateName: req.params.templateName,
|
||||||
|
projectId: req.body.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { certificateTemplate };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:templateName",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||||
|
params: z.object({
|
||||||
|
templateName: slugSchema()
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
projectId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificateTemplate: CertificateTemplatesSchema.extend({
|
||||||
|
ca: z.object({ id: z.string(), name: z.string() })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const certificateTemplate = await server.services.pkiTemplate.getTemplateByName({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
templateName: req.params.templateName,
|
||||||
|
projectId: req.query.projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { certificateTemplate };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||||
|
querystring: z.object({
|
||||||
|
projectId: z.string(),
|
||||||
|
limit: z.coerce.number().default(100),
|
||||||
|
offset: z.coerce.number().default(0)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificateTemplates: CertificateTemplatesSchema.extend({
|
||||||
|
ca: z.object({ id: z.string(), name: z.string() })
|
||||||
|
}).array(),
|
||||||
|
totalCount: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { certificateTemplates, totalCount } = await server.services.pkiTemplate.listTemplate({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
|
||||||
|
return { certificateTemplates, totalCount };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:templateName/issue-certificate",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||||
|
params: z.object({
|
||||||
|
templateName: slugSchema()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string(),
|
||||||
|
commonName: validateTemplateRegexField,
|
||||||
|
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
||||||
|
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
|
||||||
|
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional(),
|
||||||
|
notBefore: validateCaDateField.optional(),
|
||||||
|
notAfter: validateCaDateField.optional(),
|
||||||
|
altNames: validateAltNamesField
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificate: z.string().trim(),
|
||||||
|
issuingCaCertificate: z.string().trim(),
|
||||||
|
certificateChain: z.string().trim(),
|
||||||
|
privateKey: z.string().trim(),
|
||||||
|
serialNumber: z.string().trim()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const data = await server.services.pkiTemplate.issueCertificate({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
templateName: req.params.templateName,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:templateName/sign-certificate",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||||
|
params: z.object({
|
||||||
|
templateName: slugSchema()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string(),
|
||||||
|
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
||||||
|
csr: z.string().trim().min(1).max(4096)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificate: z.string().trim(),
|
||||||
|
issuingCaCertificate: z.string().trim(),
|
||||||
|
certificateChain: z.string().trim(),
|
||||||
|
serialNumber: z.string().trim()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const data = await server.services.pkiTemplate.signCertificate({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
templateName: req.params.templateName,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -11,6 +11,7 @@ export enum AppConnection {
|
|||||||
Vercel = "vercel",
|
Vercel = "vercel",
|
||||||
Postgres = "postgres",
|
Postgres = "postgres",
|
||||||
MsSql = "mssql",
|
MsSql = "mssql",
|
||||||
|
MySql = "mysql",
|
||||||
Camunda = "camunda",
|
Camunda = "camunda",
|
||||||
Windmill = "windmill",
|
Windmill = "windmill",
|
||||||
Auth0 = "auth0",
|
Auth0 = "auth0",
|
||||||
|
@ -64,6 +64,8 @@ import {
|
|||||||
} from "./humanitec";
|
} from "./humanitec";
|
||||||
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
|
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
|
||||||
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
|
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
|
||||||
|
import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums";
|
||||||
|
import { getMySqlConnectionListItem } from "./mysql/mysql-connection-fns";
|
||||||
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
|
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
|
||||||
import {
|
import {
|
||||||
getTeamCityConnectionListItem,
|
getTeamCityConnectionListItem,
|
||||||
@ -96,6 +98,7 @@ export const listAppConnectionOptions = () => {
|
|||||||
getVercelConnectionListItem(),
|
getVercelConnectionListItem(),
|
||||||
getPostgresConnectionListItem(),
|
getPostgresConnectionListItem(),
|
||||||
getMsSqlConnectionListItem(),
|
getMsSqlConnectionListItem(),
|
||||||
|
getMySqlConnectionListItem(),
|
||||||
getCamundaConnectionListItem(),
|
getCamundaConnectionListItem(),
|
||||||
getAzureClientSecretsConnectionListItem(),
|
getAzureClientSecretsConnectionListItem(),
|
||||||
getWindmillConnectionListItem(),
|
getWindmillConnectionListItem(),
|
||||||
@ -166,6 +169,7 @@ export const validateAppConnectionCredentials = async (
|
|||||||
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Humanitec]: validateHumanitecConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Postgres]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.MsSql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.MySql]: validateSqlConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Camunda]: validateCamundaConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
@ -208,6 +212,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
return "API Token";
|
return "API Token";
|
||||||
case PostgresConnectionMethod.UsernameAndPassword:
|
case PostgresConnectionMethod.UsernameAndPassword:
|
||||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||||
|
case MySqlConnectionMethod.UsernameAndPassword:
|
||||||
return "Username & Password";
|
return "Username & Password";
|
||||||
case WindmillConnectionMethod.AccessToken:
|
case WindmillConnectionMethod.AccessToken:
|
||||||
case HCVaultConnectionMethod.AccessToken:
|
case HCVaultConnectionMethod.AccessToken:
|
||||||
@ -259,6 +264,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.Humanitec]: platformManagedCredentialsNotSupported,
|
[AppConnection.Humanitec]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
[AppConnection.Postgres]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||||
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
[AppConnection.MsSql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||||
|
[AppConnection.MySql]: transferSqlConnectionCredentialsToPlatform as TAppConnectionTransitionCredentialsToPlatform,
|
||||||
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
|
[AppConnection.TerraformCloud]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
|
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
|
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
|
||||||
|
@ -13,6 +13,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.Vercel]: "Vercel",
|
[AppConnection.Vercel]: "Vercel",
|
||||||
[AppConnection.Postgres]: "PostgreSQL",
|
[AppConnection.Postgres]: "PostgreSQL",
|
||||||
[AppConnection.MsSql]: "Microsoft SQL Server",
|
[AppConnection.MsSql]: "Microsoft SQL Server",
|
||||||
|
[AppConnection.MySql]: "MySQL",
|
||||||
[AppConnection.Camunda]: "Camunda",
|
[AppConnection.Camunda]: "Camunda",
|
||||||
[AppConnection.Windmill]: "Windmill",
|
[AppConnection.Windmill]: "Windmill",
|
||||||
[AppConnection.Auth0]: "Auth0",
|
[AppConnection.Auth0]: "Auth0",
|
||||||
@ -43,5 +44,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
|
|||||||
[AppConnection.LDAP]: AppConnectionPlanType.Regular,
|
[AppConnection.LDAP]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.TeamCity]: AppConnectionPlanType.Regular,
|
[AppConnection.TeamCity]: AppConnectionPlanType.Regular,
|
||||||
[AppConnection.OCI]: AppConnectionPlanType.Enterprise,
|
[AppConnection.OCI]: AppConnectionPlanType.Enterprise,
|
||||||
[AppConnection.OnePass]: AppConnectionPlanType.Regular
|
[AppConnection.OnePass]: AppConnectionPlanType.Regular,
|
||||||
|
[AppConnection.MySql]: AppConnectionPlanType.Regular
|
||||||
};
|
};
|
||||||
|
@ -55,6 +55,7 @@ import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
|
|||||||
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
||||||
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
||||||
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||||
|
import { ValidateMySqlConnectionCredentialsSchema } from "./mysql";
|
||||||
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
|
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
|
||||||
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
|
import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity";
|
||||||
import { teamcityConnectionService } from "./teamcity/teamcity-connection-service";
|
import { teamcityConnectionService } from "./teamcity/teamcity-connection-service";
|
||||||
@ -86,6 +87,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.Vercel]: ValidateVercelConnectionCredentialsSchema,
|
[AppConnection.Vercel]: ValidateVercelConnectionCredentialsSchema,
|
||||||
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
|
[AppConnection.Postgres]: ValidatePostgresConnectionCredentialsSchema,
|
||||||
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
|
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
|
||||||
|
[AppConnection.MySql]: ValidateMySqlConnectionCredentialsSchema,
|
||||||
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
|
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
|
||||||
[AppConnection.AzureClientSecrets]: ValidateAzureClientSecretsConnectionCredentialsSchema,
|
[AppConnection.AzureClientSecrets]: ValidateAzureClientSecretsConnectionCredentialsSchema,
|
||||||
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
||||||
|
@ -88,6 +88,7 @@ import {
|
|||||||
TValidateLdapConnectionCredentialsSchema
|
TValidateLdapConnectionCredentialsSchema
|
||||||
} from "./ldap";
|
} from "./ldap";
|
||||||
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||||
|
import { TMySqlConnection, TMySqlConnectionInput, TValidateMySqlConnectionCredentialsSchema } from "./mysql";
|
||||||
import {
|
import {
|
||||||
TPostgresConnection,
|
TPostgresConnection,
|
||||||
TPostgresConnectionInput,
|
TPostgresConnectionInput,
|
||||||
@ -130,6 +131,7 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TVercelConnection
|
| TVercelConnection
|
||||||
| TPostgresConnection
|
| TPostgresConnection
|
||||||
| TMsSqlConnection
|
| TMsSqlConnection
|
||||||
|
| TMySqlConnection
|
||||||
| TCamundaConnection
|
| TCamundaConnection
|
||||||
| TAzureClientSecretsConnection
|
| TAzureClientSecretsConnection
|
||||||
| TWindmillConnection
|
| TWindmillConnection
|
||||||
@ -143,7 +145,7 @@ export type TAppConnection = { id: string } & (
|
|||||||
|
|
||||||
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
||||||
|
|
||||||
export type TSqlConnection = TPostgresConnection | TMsSqlConnection;
|
export type TSqlConnection = TPostgresConnection | TMsSqlConnection | TMySqlConnection;
|
||||||
|
|
||||||
export type TAppConnectionInput = { id: string } & (
|
export type TAppConnectionInput = { id: string } & (
|
||||||
| TAwsConnectionInput
|
| TAwsConnectionInput
|
||||||
@ -157,6 +159,7 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TVercelConnectionInput
|
| TVercelConnectionInput
|
||||||
| TPostgresConnectionInput
|
| TPostgresConnectionInput
|
||||||
| TMsSqlConnectionInput
|
| TMsSqlConnectionInput
|
||||||
|
| TMySqlConnectionInput
|
||||||
| TCamundaConnectionInput
|
| TCamundaConnectionInput
|
||||||
| TAzureClientSecretsConnectionInput
|
| TAzureClientSecretsConnectionInput
|
||||||
| TWindmillConnectionInput
|
| TWindmillConnectionInput
|
||||||
@ -168,7 +171,7 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TOnePassConnectionInput
|
| TOnePassConnectionInput
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput;
|
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput | TMySqlConnectionInput;
|
||||||
|
|
||||||
export type TCreateAppConnectionDTO = Pick<
|
export type TCreateAppConnectionDTO = Pick<
|
||||||
TAppConnectionInput,
|
TAppConnectionInput,
|
||||||
@ -211,6 +214,7 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidateHumanitecConnectionCredentialsSchema
|
| TValidateHumanitecConnectionCredentialsSchema
|
||||||
| TValidatePostgresConnectionCredentialsSchema
|
| TValidatePostgresConnectionCredentialsSchema
|
||||||
| TValidateMsSqlConnectionCredentialsSchema
|
| TValidateMsSqlConnectionCredentialsSchema
|
||||||
|
| TValidateMySqlConnectionCredentialsSchema
|
||||||
| TValidateCamundaConnectionCredentialsSchema
|
| TValidateCamundaConnectionCredentialsSchema
|
||||||
| TValidateVercelConnectionCredentialsSchema
|
| TValidateVercelConnectionCredentialsSchema
|
||||||
| TValidateTerraformCloudConnectionCredentialsSchema
|
| TValidateTerraformCloudConnectionCredentialsSchema
|
||||||
|
4
backend/src/services/app-connection/mysql/index.ts
Normal file
4
backend/src/services/app-connection/mysql/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./mysql-connection-enums";
|
||||||
|
export * from "./mysql-connection-fns";
|
||||||
|
export * from "./mysql-connection-schemas";
|
||||||
|
export * from "./mysql-connection-types";
|
@ -0,0 +1,3 @@
|
|||||||
|
export enum MySqlConnectionMethod {
|
||||||
|
UsernameAndPassword = "username-and-password"
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { MySqlConnectionMethod } from "./mysql-connection-enums";
|
||||||
|
|
||||||
|
export const getMySqlConnectionListItem = () => {
|
||||||
|
return {
|
||||||
|
name: "MySQL" as const,
|
||||||
|
app: AppConnection.MySql as const,
|
||||||
|
methods: Object.values(MySqlConnectionMethod) as [MySqlConnectionMethod.UsernameAndPassword],
|
||||||
|
supportsPlatformManagement: true as const
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,66 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { AppConnections } from "@app/lib/api-docs";
|
||||||
|
import {
|
||||||
|
BaseAppConnectionSchema,
|
||||||
|
GenericCreateAppConnectionFieldsSchema,
|
||||||
|
GenericUpdateAppConnectionFieldsSchema
|
||||||
|
} from "@app/services/app-connection/app-connection-schemas";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import { BaseSqlUsernameAndPasswordConnectionSchema } from "../shared/sql";
|
||||||
|
import { MySqlConnectionMethod } from "./mysql-connection-enums";
|
||||||
|
|
||||||
|
export const MySqlConnectionAccessTokenCredentialsSchema = BaseSqlUsernameAndPasswordConnectionSchema;
|
||||||
|
|
||||||
|
const BaseMySqlConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.MySql) });
|
||||||
|
|
||||||
|
export const MySqlConnectionSchema = BaseMySqlConnectionSchema.extend({
|
||||||
|
method: z.literal(MySqlConnectionMethod.UsernameAndPassword),
|
||||||
|
credentials: MySqlConnectionAccessTokenCredentialsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SanitizedMySqlConnectionSchema = z.discriminatedUnion("method", [
|
||||||
|
BaseMySqlConnectionSchema.extend({
|
||||||
|
method: z.literal(MySqlConnectionMethod.UsernameAndPassword),
|
||||||
|
credentials: MySqlConnectionAccessTokenCredentialsSchema.pick({
|
||||||
|
host: true,
|
||||||
|
database: true,
|
||||||
|
port: true,
|
||||||
|
username: true,
|
||||||
|
sslEnabled: true,
|
||||||
|
sslRejectUnauthorized: true,
|
||||||
|
sslCertificate: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const ValidateMySqlConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
|
z.object({
|
||||||
|
method: z
|
||||||
|
.literal(MySqlConnectionMethod.UsernameAndPassword)
|
||||||
|
.describe(AppConnections.CREATE(AppConnection.MySql).method),
|
||||||
|
credentials: MySqlConnectionAccessTokenCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.MySql).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const CreateMySqlConnectionSchema = ValidateMySqlConnectionCredentialsSchema.and(
|
||||||
|
GenericCreateAppConnectionFieldsSchema(AppConnection.MySql, { supportsPlatformManagedCredentials: true })
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateMySqlConnectionSchema = z
|
||||||
|
.object({
|
||||||
|
credentials: MySqlConnectionAccessTokenCredentialsSchema.optional().describe(
|
||||||
|
AppConnections.UPDATE(AppConnection.MySql).credentials
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.MySql, { supportsPlatformManagedCredentials: true }));
|
||||||
|
|
||||||
|
export const MySqlConnectionListItemSchema = z.object({
|
||||||
|
name: z.literal("MySQL"),
|
||||||
|
app: z.literal(AppConnection.MySql),
|
||||||
|
methods: z.nativeEnum(MySqlConnectionMethod).array(),
|
||||||
|
supportsPlatformManagement: z.literal(true)
|
||||||
|
});
|
@ -0,0 +1,16 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateMySqlConnectionSchema,
|
||||||
|
MySqlConnectionSchema,
|
||||||
|
ValidateMySqlConnectionCredentialsSchema
|
||||||
|
} from "./mysql-connection-schemas";
|
||||||
|
|
||||||
|
export type TMySqlConnection = z.infer<typeof MySqlConnectionSchema>;
|
||||||
|
|
||||||
|
export type TMySqlConnectionInput = z.infer<typeof CreateMySqlConnectionSchema> & {
|
||||||
|
app: AppConnection.MySql;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TValidateMySqlConnectionCredentialsSchema = typeof ValidateMySqlConnectionCredentialsSchema;
|
@ -15,7 +15,8 @@ const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
|||||||
|
|
||||||
const SQL_CONNECTION_CLIENT_MAP = {
|
const SQL_CONNECTION_CLIENT_MAP = {
|
||||||
[AppConnection.Postgres]: "pg",
|
[AppConnection.Postgres]: "pg",
|
||||||
[AppConnection.MsSql]: "mssql"
|
[AppConnection.MsSql]: "mssql",
|
||||||
|
[AppConnection.MySql]: "mysql2"
|
||||||
};
|
};
|
||||||
|
|
||||||
const getConnectionConfig = ({
|
const getConnectionConfig = ({
|
||||||
@ -45,6 +46,17 @@ const getConnectionConfig = ({
|
|||||||
: { encrypt: false }
|
: { encrypt: false }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case AppConnection.MySql: {
|
||||||
|
return {
|
||||||
|
ssl: sslEnabled
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: sslRejectUnauthorized,
|
||||||
|
ca: sslCertificate,
|
||||||
|
servername: host
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
};
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled SQL Connection Config: ${app as AppConnection}`);
|
throw new Error(`Unhandled SQL Connection Config: ${app as AppConnection}`);
|
||||||
}
|
}
|
||||||
@ -101,7 +113,8 @@ export const SQL_CONNECTION_ALTER_LOGIN_STATEMENT: Record<
|
|||||||
(credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => [string, Knex.RawBinding]
|
(credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => [string, Knex.RawBinding]
|
||||||
> = {
|
> = {
|
||||||
[AppConnection.Postgres]: ({ username, password }) => [`ALTER USER ?? WITH PASSWORD '${password}';`, [username]],
|
[AppConnection.Postgres]: ({ username, password }) => [`ALTER USER ?? WITH PASSWORD '${password}';`, [username]],
|
||||||
[AppConnection.MsSql]: ({ username, password }) => [`ALTER LOGIN ?? WITH PASSWORD = '${password}';`, [username]]
|
[AppConnection.MsSql]: ({ username, password }) => [`ALTER LOGIN ?? WITH PASSWORD = '${password}';`, [username]],
|
||||||
|
[AppConnection.MySql]: ({ username, password }) => [`ALTER USER ??@'%' IDENTIFIED BY '${password}';`, [username]]
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transferSqlConnectionCredentialsToPlatform = async (
|
export const transferSqlConnectionCredentialsToPlatform = async (
|
||||||
|
@ -311,7 +311,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatedCa = await internalCertificateAuthorityService.updateCaById({
|
const updatedCa = await internalCertificateAuthorityService.updateCaById({
|
||||||
...configuration,
|
|
||||||
isInternal: true,
|
isInternal: true,
|
||||||
enableDirectIssuance,
|
enableDirectIssuance,
|
||||||
caId: certificateAuthority.id,
|
caId: certificateAuthority.id,
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
/* eslint-disable no-bitwise */
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import { KeyObject } from "crypto";
|
import { KeyObject } from "crypto";
|
||||||
|
import RE2 from "re2";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TPkiSubscribers } from "@app/db/schemas";
|
import { TCertificateTemplates, TPkiSubscribers } from "@app/db/schemas";
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
@ -31,6 +33,7 @@ import {
|
|||||||
keyAlgorithmToAlgCfg
|
keyAlgorithmToAlgCfg
|
||||||
} from "../certificate-authority-fns";
|
} from "../certificate-authority-fns";
|
||||||
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
||||||
|
import { TIssueCertWithTemplateDTO } from "./internal-certificate-authority-types";
|
||||||
|
|
||||||
type TInternalCertificateAuthorityFnsDeps = {
|
type TInternalCertificateAuthorityFnsDeps = {
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa" | "findById">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa" | "findById">;
|
||||||
@ -257,7 +260,274 @@ export const InternalCertificateAuthorityFns = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const issueCertificateWithTemplate = async (
|
||||||
|
ca: Awaited<ReturnType<TCertificateAuthorityDALFactory["findByIdWithAssociatedCa"]>>,
|
||||||
|
certificateTemplate: TCertificateTemplates,
|
||||||
|
{ altNames, commonName, ttl, extendedKeyUsages, keyUsages, notAfter, notBefore }: TIssueCertWithTemplateDTO
|
||||||
|
) => {
|
||||||
|
if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" });
|
||||||
|
if (!ca.internalCa?.activeCaCertId)
|
||||||
|
throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
|
||||||
|
const caCert = await certificateAuthorityCertDAL.findById(ca.internalCa.activeCaCertId);
|
||||||
|
|
||||||
|
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||||
|
projectId: ca.projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedCaCert = await kmsDecryptor({
|
||||||
|
cipherTextBlob: caCert.encryptedCertificate
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCertObj = new x509.X509Certificate(decryptedCaCert);
|
||||||
|
const notBeforeDate = notBefore ? new Date(notBefore) : new Date();
|
||||||
|
|
||||||
|
let notAfterDate = new Date(new Date().setFullYear(new Date().getFullYear() + 1));
|
||||||
|
if (notAfter) {
|
||||||
|
notAfterDate = new Date(notAfter);
|
||||||
|
} else if (ttl) {
|
||||||
|
notAfterDate = new Date(new Date().getTime() + ms(ttl));
|
||||||
|
}
|
||||||
|
|
||||||
|
const caCertNotBeforeDate = new Date(caCertObj.notBefore);
|
||||||
|
const caCertNotAfterDate = new Date(caCertObj.notAfter);
|
||||||
|
|
||||||
|
// check not before constraint
|
||||||
|
if (notBeforeDate < caCertNotBeforeDate) {
|
||||||
|
throw new BadRequestError({ message: "notBefore date is before CA certificate's notBefore date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// check not after constraint
|
||||||
|
if (notAfterDate > caCertNotAfterDate) {
|
||||||
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const commonNameRegex = new RE2(certificateTemplate.commonName);
|
||||||
|
if (!commonNameRegex.test(commonName)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid common name based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notAfterDate.getTime() - notBeforeDate.getTime() > ms(certificateTemplate.ttl)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid validity date based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const subjectAlternativeNameRegex = new RE2(certificateTemplate.subjectAlternativeName);
|
||||||
|
altNames.split(",").forEach((altName) => {
|
||||||
|
if (!subjectAlternativeNameRegex.test(altName)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid subject alternative name based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm);
|
||||||
|
const leafKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
||||||
|
|
||||||
|
const csrObj = await x509.Pkcs10CertificateRequestGenerator.create({
|
||||||
|
name: `CN=${commonName}`,
|
||||||
|
keys: leafKeys,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
extensions: [
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment)
|
||||||
|
],
|
||||||
|
attributes: [new x509.ChallengePasswordAttribute("password")]
|
||||||
|
});
|
||||||
|
|
||||||
|
const { caPrivateKey, caSecret } = await getCaCredentials({
|
||||||
|
caId: ca.id,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
||||||
|
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
||||||
|
|
||||||
|
const extensions: x509.Extension[] = [
|
||||||
|
new x509.BasicConstraintsExtension(false),
|
||||||
|
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
||||||
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
||||||
|
new x509.AuthorityInfoAccessExtension({
|
||||||
|
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
||||||
|
}),
|
||||||
|
new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy
|
||||||
|
];
|
||||||
|
|
||||||
|
let selectedKeyUsages: CertKeyUsage[] = keyUsages ?? [];
|
||||||
|
if (keyUsages === undefined && !certificateTemplate) {
|
||||||
|
selectedKeyUsages = [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyUsages === undefined && certificateTemplate) {
|
||||||
|
selectedKeyUsages = (certificateTemplate.keyUsages ?? []) as CertKeyUsage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyUsages?.length && certificateTemplate) {
|
||||||
|
const validKeyUsages = certificateTemplate.keyUsages || [];
|
||||||
|
if (keyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid key usage value based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectedKeyUsages = keyUsages;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0);
|
||||||
|
if (keyUsagesBitValue) {
|
||||||
|
extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle extended key usages
|
||||||
|
let selectedExtendedKeyUsages: CertExtendedKeyUsage[] = extendedKeyUsages ?? [];
|
||||||
|
if (extendedKeyUsages === undefined && certificateTemplate) {
|
||||||
|
selectedExtendedKeyUsages = (certificateTemplate.extendedKeyUsages ?? []) as CertExtendedKeyUsage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extendedKeyUsages?.length && certificateTemplate) {
|
||||||
|
const validExtendedKeyUsages = certificateTemplate.extendedKeyUsages || [];
|
||||||
|
if (extendedKeyUsages.some((eku) => !validExtendedKeyUsages.includes(eku))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid extended key usage value based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectedExtendedKeyUsages = extendedKeyUsages;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedExtendedKeyUsages.length) {
|
||||||
|
extensions.push(
|
||||||
|
new x509.ExtendedKeyUsageExtension(
|
||||||
|
selectedExtendedKeyUsages.map((eku) => x509.ExtendedKeyUsage[eku]),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let altNamesArray: { type: "email" | "dns"; value: string }[] = [];
|
||||||
|
|
||||||
|
if (altNames) {
|
||||||
|
altNamesArray = altNames.split(",").map((altName) => {
|
||||||
|
if (z.string().email().safeParse(altName).success) {
|
||||||
|
return { type: "email", value: altName };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFQDN(altName, { allow_wildcard: true })) {
|
||||||
|
return { type: "dns", value: altName };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
|
||||||
|
});
|
||||||
|
|
||||||
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
|
extensions.push(altNamesExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
const serialNumber = createSerialNumber();
|
||||||
|
const leafCert = await x509.X509CertificateGenerator.create({
|
||||||
|
serialNumber,
|
||||||
|
subject: csrObj.subject,
|
||||||
|
issuer: caCertObj.subject,
|
||||||
|
notBefore: notBeforeDate,
|
||||||
|
notAfter: notAfterDate,
|
||||||
|
signingKey: caPrivateKey,
|
||||||
|
publicKey: csrObj.publicKey,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
extensions
|
||||||
|
});
|
||||||
|
|
||||||
|
const skLeafObj = KeyObject.from(leafKeys.privateKey);
|
||||||
|
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
|
||||||
|
|
||||||
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
|
||||||
|
});
|
||||||
|
const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(skLeaf)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
||||||
|
caCertId: caCert.id,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const certificateChainPem = `${issuingCaCertificate}\n${caCertChain}`.trim();
|
||||||
|
|
||||||
|
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(certificateChainPem)
|
||||||
|
});
|
||||||
|
|
||||||
|
await certificateDAL.transaction(async (tx) => {
|
||||||
|
const cert = await certificateDAL.create(
|
||||||
|
{
|
||||||
|
caId: ca.id,
|
||||||
|
caCertId: caCert.id,
|
||||||
|
status: CertStatus.ACTIVE,
|
||||||
|
friendlyName: commonName,
|
||||||
|
commonName,
|
||||||
|
altNames,
|
||||||
|
serialNumber,
|
||||||
|
notBefore: notBeforeDate,
|
||||||
|
notAfter: notAfterDate,
|
||||||
|
keyUsages: selectedKeyUsages,
|
||||||
|
extendedKeyUsages: selectedExtendedKeyUsages,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
certificateTemplateId: certificateTemplate.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await certificateBodyDAL.create(
|
||||||
|
{
|
||||||
|
certId: cert.id,
|
||||||
|
encryptedCertificate,
|
||||||
|
encryptedCertificateChain
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await certificateSecretDAL.create(
|
||||||
|
{
|
||||||
|
certId: cert.id,
|
||||||
|
encryptedPrivateKey
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
issueCertificate
|
certificate: leafCert.toString("pem"),
|
||||||
|
certificateChain: certificateChainPem,
|
||||||
|
issuingCaCertificate,
|
||||||
|
privateKey: skLeaf,
|
||||||
|
serialNumber,
|
||||||
|
ca,
|
||||||
|
template: certificateTemplate
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
issueCertificate,
|
||||||
|
issueCertificateWithTemplate
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -55,8 +55,4 @@ export const CreateInternalCertificateAuthoritySchema = GenericCreateCertificate
|
|||||||
configuration: InternalCertificateAuthorityConfigurationSchema
|
configuration: InternalCertificateAuthorityConfigurationSchema
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UpdateInternalCertificateAuthoritySchema = GenericUpdateCertificateAuthorityFieldsSchema(
|
export const UpdateInternalCertificateAuthoritySchema = GenericUpdateCertificateAuthorityFieldsSchema(CaType.INTERNAL);
|
||||||
CaType.INTERNAL
|
|
||||||
).extend({
|
|
||||||
configuration: InternalCertificateAuthorityConfigurationSchema.optional()
|
|
||||||
});
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import crypto, { KeyObject } from "crypto";
|
import crypto, { KeyObject } from "crypto";
|
||||||
@ -16,6 +16,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
|
ProjectPermissionPkiTemplateActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
|
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
|
||||||
@ -1952,15 +1953,15 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.CertificateManager
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionSub.CertificateTemplates
|
|
||||||
);
|
|
||||||
|
|
||||||
const certificateTemplates = await certificateTemplateDAL.find({ caId });
|
const certificateTemplates = await certificateTemplateDAL.find({ caId });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificateTemplates,
|
certificateTemplates: certificateTemplates.filter((el) =>
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name: el.name })
|
||||||
|
)
|
||||||
|
),
|
||||||
ca: expandInternalCa(ca)
|
ca: expandInternalCa(ca)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -221,3 +221,13 @@ export type TOrderCertificateForSubscriberDTO = {
|
|||||||
subscriberId: string;
|
subscriberId: string;
|
||||||
caType: CaType;
|
caType: CaType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TIssueCertWithTemplateDTO = {
|
||||||
|
commonName: string;
|
||||||
|
altNames: string;
|
||||||
|
ttl: string;
|
||||||
|
notBefore?: string;
|
||||||
|
notAfter?: string;
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
|
};
|
||||||
|
@ -18,3 +18,20 @@ export const sanitizedCertificateTemplate = CertificateTemplatesSchema.pick({
|
|||||||
caName: z.string()
|
caName: z.string()
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const sanitizedCertificateTemplateV2 = CertificateTemplatesSchema.pick({
|
||||||
|
id: true,
|
||||||
|
caId: true,
|
||||||
|
name: true,
|
||||||
|
commonName: true,
|
||||||
|
subjectAlternativeName: true,
|
||||||
|
pkiCollectionId: true,
|
||||||
|
ttl: true,
|
||||||
|
keyUsages: true,
|
||||||
|
extendedKeyUsages: true
|
||||||
|
}).merge(
|
||||||
|
z.object({
|
||||||
|
projectId: z.string(),
|
||||||
|
caName: z.string()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
import { ActionProjectType, TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
|
import { ActionProjectType, TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionPkiTemplateActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
|
import { extractX509CertFromChain } from "@app/lib/certificates/extract-certificate";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@ -78,8 +81,8 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionPkiTemplateActions.Create,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, { name })
|
||||||
);
|
);
|
||||||
|
|
||||||
return certificateTemplateDAL.transaction(async (tx) => {
|
return certificateTemplateDAL.transaction(async (tx) => {
|
||||||
@ -140,8 +143,8 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionPkiTemplateActions.Edit,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, { name: certTemplate.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
if (caId) {
|
if (caId) {
|
||||||
@ -153,6 +156,13 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiTemplateActions.Create,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return certificateTemplateDAL.transaction(async (tx) => {
|
return certificateTemplateDAL.transaction(async (tx) => {
|
||||||
await certificateTemplateDAL.updateById(
|
await certificateTemplateDAL.updateById(
|
||||||
certTemplate.id,
|
certTemplate.id,
|
||||||
@ -198,8 +208,8 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionPkiTemplateActions.Delete,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, { name: certTemplate.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
await certificateTemplateDAL.deleteById(certTemplate.id);
|
await certificateTemplateDAL.deleteById(certTemplate.id);
|
||||||
@ -225,8 +235,8 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, { name: certTemplate.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
return certTemplate;
|
return certTemplate;
|
||||||
@ -267,8 +277,8 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionPkiTemplateActions.Edit,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, { name: certTemplate.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@ -350,8 +360,8 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionPkiTemplateActions.Edit,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, { name: certTemplate.name })
|
||||||
);
|
);
|
||||||
|
|
||||||
const originalCaEstConfig = await certificateTemplateEstConfigDAL.findOne({
|
const originalCaEstConfig = await certificateTemplateEstConfigDAL.findOne({
|
||||||
@ -430,8 +440,8 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionPkiTemplateActions.Edit,
|
||||||
ProjectPermissionSub.CertificateTemplates
|
subject(ProjectPermissionSub.CertificateTemplates, { name: certTemplate.name })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,10 +96,15 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
}
|
}
|
||||||
await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses });
|
await validateAccessTokenExp({ ...identityAccessToken, accessTokenNumUses });
|
||||||
|
|
||||||
const { accessTokenMaxTTL, createdAt: accessTokenCreatedAt, accessTokenTTL } = identityAccessToken;
|
const {
|
||||||
|
accessTokenMaxTTL,
|
||||||
|
createdAt: accessTokenCreatedAt,
|
||||||
|
accessTokenTTL,
|
||||||
|
accessTokenPeriod
|
||||||
|
} = identityAccessToken;
|
||||||
|
|
||||||
// max ttl checks - will it go above max ttl
|
// Only enforce Max TTL for non-periodic tokens
|
||||||
if (Number(accessTokenMaxTTL) > 0) {
|
if (Number(accessTokenMaxTTL) > 0 && Number(accessTokenPeriod) === 0) {
|
||||||
const accessTokenCreated = new Date(accessTokenCreatedAt);
|
const accessTokenCreated = new Date(accessTokenCreatedAt);
|
||||||
const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000;
|
const ttlInMilliseconds = Number(accessTokenMaxTTL) * 1000;
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
@ -125,6 +130,18 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
accessTokenLastRenewedAt: new Date()
|
accessTokenLastRenewedAt: new Date()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ttl = Number(accessTokenTTL);
|
||||||
|
const period = Number(accessTokenPeriod);
|
||||||
|
|
||||||
|
let expiresIn: number | undefined;
|
||||||
|
if (period > 0) {
|
||||||
|
expiresIn = period;
|
||||||
|
} else if (ttl > 0) {
|
||||||
|
expiresIn = ttl;
|
||||||
|
} else {
|
||||||
|
expiresIn = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const renewedToken = jwt.sign(
|
const renewedToken = jwt.sign(
|
||||||
{
|
{
|
||||||
identityId: decodedToken.identityId,
|
identityId: decodedToken.identityId,
|
||||||
@ -133,12 +150,7 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
|
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
|
||||||
} as TIdentityAccessTokenJwtPayload,
|
} as TIdentityAccessTokenJwtPayload,
|
||||||
appCfg.AUTH_SECRET,
|
appCfg.AUTH_SECRET,
|
||||||
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
|
expiresIn !== undefined ? { expiresIn } : undefined
|
||||||
Number(identityAccessToken.accessTokenTTL) === 0
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
expiresIn: Number(identityAccessToken.accessTokenTTL)
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return { accessToken: renewedToken, identityAccessToken: updatedIdentityAccessToken };
|
return { accessToken: renewedToken, identityAccessToken: updatedIdentityAccessToken };
|
||||||
|
@ -2,6 +2,7 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import axios, { AxiosError } from "axios";
|
import axios, { AxiosError } from "axios";
|
||||||
import https from "https";
|
import https from "https";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
import RE2 from "re2";
|
||||||
|
|
||||||
import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas";
|
import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas";
|
||||||
import { TGatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
|
import { TGatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
|
||||||
@ -185,7 +186,13 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const [k8sHost, k8sPort] = identityKubernetesAuth.kubernetesHost.split(":");
|
let { kubernetesHost } = identityKubernetesAuth;
|
||||||
|
|
||||||
|
if (kubernetesHost.startsWith("https://") || kubernetesHost.startsWith("http://")) {
|
||||||
|
kubernetesHost = new RE2("^https?:\\/\\/").replace(kubernetesHost, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [k8sHost, k8sPort] = kubernetesHost.split(":");
|
||||||
|
|
||||||
const data = identityKubernetesAuth.gatewayId
|
const data = identityKubernetesAuth.gatewayId
|
||||||
? await $gatewayProxyWrapper(
|
? await $gatewayProxyWrapper(
|
||||||
|
@ -114,21 +114,36 @@ export const identityUaServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const accessTokenTTLParams =
|
||||||
|
Number(identityUa.accessTokenPeriod) === 0
|
||||||
|
? {
|
||||||
|
accessTokenTTL: identityUa.accessTokenTTL,
|
||||||
|
accessTokenMaxTTL: identityUa.accessTokenMaxTTL
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
accessTokenTTL: identityUa.accessTokenPeriod,
|
||||||
|
// Setting Max TTL to 2 × period ensures that clients can always renew their token
|
||||||
|
// at least once, and matches client logic that checks if renewing would exceed Max TTL.
|
||||||
|
accessTokenMaxTTL: 2 * identityUa.accessTokenPeriod
|
||||||
|
};
|
||||||
|
|
||||||
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
|
const identityAccessToken = await identityUaDAL.transaction(async (tx) => {
|
||||||
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx);
|
const uaClientSecretDoc = await identityUaClientSecretDAL.incrementUsage(validClientSecretInfo!.id, tx);
|
||||||
|
|
||||||
const newToken = await identityAccessTokenDAL.create(
|
const newToken = await identityAccessTokenDAL.create(
|
||||||
{
|
{
|
||||||
identityId: identityUa.identityId,
|
identityId: identityUa.identityId,
|
||||||
isAccessTokenRevoked: false,
|
isAccessTokenRevoked: false,
|
||||||
identityUAClientSecretId: uaClientSecretDoc.id,
|
identityUAClientSecretId: uaClientSecretDoc.id,
|
||||||
accessTokenTTL: identityUa.accessTokenTTL,
|
|
||||||
accessTokenMaxTTL: identityUa.accessTokenMaxTTL,
|
|
||||||
accessTokenNumUses: 0,
|
accessTokenNumUses: 0,
|
||||||
accessTokenNumUsesLimit: identityUa.accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit: identityUa.accessTokenNumUsesLimit,
|
||||||
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH
|
accessTokenPeriod: identityUa.accessTokenPeriod,
|
||||||
|
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
|
||||||
|
...accessTokenTTLParams
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
return newToken;
|
return newToken;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -149,7 +164,14 @@ export const identityUaServiceFactory = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return { accessToken, identityUa, validClientSecretInfo, identityAccessToken, identityMembershipOrg };
|
return {
|
||||||
|
accessToken,
|
||||||
|
identityUa,
|
||||||
|
validClientSecretInfo,
|
||||||
|
identityAccessToken,
|
||||||
|
identityMembershipOrg,
|
||||||
|
...accessTokenTTLParams
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachUniversalAuth = async ({
|
const attachUniversalAuth = async ({
|
||||||
@ -163,7 +185,8 @@ export const identityUaServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
isActorSuperAdmin
|
isActorSuperAdmin,
|
||||||
|
accessTokenPeriod
|
||||||
}: TAttachUaDTO) => {
|
}: TAttachUaDTO) => {
|
||||||
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
|
||||||
|
|
||||||
@ -232,7 +255,8 @@ export const identityUaServiceFactory = ({
|
|||||||
accessTokenMaxTTL,
|
accessTokenMaxTTL,
|
||||||
accessTokenTTL,
|
accessTokenTTL,
|
||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps)
|
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
|
||||||
|
accessTokenPeriod
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@ -248,6 +272,7 @@ export const identityUaServiceFactory = ({
|
|||||||
accessTokenTTL,
|
accessTokenTTL,
|
||||||
accessTokenTrustedIps,
|
accessTokenTrustedIps,
|
||||||
clientSecretTrustedIps,
|
clientSecretTrustedIps,
|
||||||
|
accessTokenPeriod,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
@ -324,6 +349,7 @@ export const identityUaServiceFactory = ({
|
|||||||
accessTokenMaxTTL,
|
accessTokenMaxTTL,
|
||||||
accessTokenTTL,
|
accessTokenTTL,
|
||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
|
accessTokenPeriod,
|
||||||
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
|
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
|
||||||
? JSON.stringify(reformattedAccessTokenTrustedIps)
|
? JSON.stringify(reformattedAccessTokenTrustedIps)
|
||||||
: undefined
|
: undefined
|
||||||
|
@ -5,6 +5,7 @@ export type TAttachUaDTO = {
|
|||||||
accessTokenTTL: number;
|
accessTokenTTL: number;
|
||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
|
accessTokenPeriod: number;
|
||||||
clientSecretTrustedIps: { ipAddress: string }[];
|
clientSecretTrustedIps: { ipAddress: string }[];
|
||||||
accessTokenTrustedIps: { ipAddress: string }[];
|
accessTokenTrustedIps: { ipAddress: string }[];
|
||||||
isActorSuperAdmin?: boolean;
|
isActorSuperAdmin?: boolean;
|
||||||
@ -15,6 +16,7 @@ export type TUpdateUaDTO = {
|
|||||||
accessTokenTTL?: number;
|
accessTokenTTL?: number;
|
||||||
accessTokenMaxTTL?: number;
|
accessTokenMaxTTL?: number;
|
||||||
accessTokenNumUsesLimit?: number;
|
accessTokenNumUsesLimit?: number;
|
||||||
|
accessTokenPeriod?: number;
|
||||||
clientSecretTrustedIps?: { ipAddress: string }[];
|
clientSecretTrustedIps?: { ipAddress: string }[];
|
||||||
accessTokenTrustedIps?: { ipAddress: string }[];
|
accessTokenTrustedIps?: { ipAddress: string }[];
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -137,6 +137,15 @@ export const pkiSubscriberServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
|
if (!ca) {
|
||||||
|
throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ca.projectId !== projectId) {
|
||||||
|
throw new BadRequestError({ message: "CA does not belong to the project" });
|
||||||
|
}
|
||||||
|
|
||||||
const newSubscriber = await pkiSubscriberDAL.create({
|
const newSubscriber = await pkiSubscriberDAL.create({
|
||||||
caId,
|
caId,
|
||||||
projectId,
|
projectId,
|
||||||
@ -245,6 +254,17 @@ export const pkiSubscriberServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (caId) {
|
||||||
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
|
if (!ca) {
|
||||||
|
throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ca.projectId !== projectId) {
|
||||||
|
throw new BadRequestError({ message: "CA does not belong to the project" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updatedSubscriber = await pkiSubscriberDAL.updateById(subscriber.id, {
|
const updatedSubscriber = await pkiSubscriberDAL.updateById(subscriber.id, {
|
||||||
caId,
|
caId,
|
||||||
name,
|
name,
|
||||||
|
102
backend/src/services/pki-templates/pki-templates-dal.ts
Normal file
102
backend/src/services/pki-templates/pki-templates-dal.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
import { Tables } from "knex/types/tables";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt, TFindReturn } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TPkiTemplatesDALFactory = ReturnType<typeof pkiTemplatesDALFactory>;
|
||||||
|
|
||||||
|
export const pkiTemplatesDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.CertificateTemplate);
|
||||||
|
|
||||||
|
const findOne = async (
|
||||||
|
filter: Partial<Tables[TableName.CertificateTemplate]["base"] & { projectId: string }>,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const { projectId, ...templateFilters } = filter;
|
||||||
|
const res = await (tx || db.replicaNode())(TableName.CertificateTemplate)
|
||||||
|
.join(
|
||||||
|
TableName.CertificateAuthority,
|
||||||
|
`${TableName.CertificateAuthority}.id`,
|
||||||
|
`${TableName.CertificateTemplate}.caId`
|
||||||
|
)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
.where(buildFindFilter(templateFilters, TableName.CertificateTemplate))
|
||||||
|
.where((qb) => {
|
||||||
|
if (projectId) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
void qb.where(buildFindFilter({ projectId }, TableName.CertificateAuthority));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.select(selectAllTableCols(TableName.CertificateTemplate))
|
||||||
|
.select(db.ref("name").withSchema(TableName.CertificateAuthority).as("caName"))
|
||||||
|
.select(db.ref("projectId").withSchema(TableName.CertificateAuthority))
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (!res) return undefined;
|
||||||
|
|
||||||
|
return { ...res, ca: { id: res.caId, name: res.caName } };
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find one" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const find = async <
|
||||||
|
TCount extends boolean = false,
|
||||||
|
TCountDistinct extends keyof Tables[TableName.CertificateTemplate]["base"] | undefined = undefined
|
||||||
|
>(
|
||||||
|
filter: TFindFilter<Tables[TableName.CertificateTemplate]["base"]> & { projectId: string },
|
||||||
|
{
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
sort,
|
||||||
|
count,
|
||||||
|
tx,
|
||||||
|
countDistinct
|
||||||
|
}: TFindOpt<Tables[TableName.CertificateTemplate]["base"], TCount, TCountDistinct> = {}
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const { projectId, ...templateFilters } = filter;
|
||||||
|
|
||||||
|
const query = (tx || db.replicaNode())(TableName.CertificateTemplate)
|
||||||
|
.join(
|
||||||
|
TableName.CertificateAuthority,
|
||||||
|
`${TableName.CertificateAuthority}.id`,
|
||||||
|
`${TableName.CertificateTemplate}.caId`
|
||||||
|
)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
.where(buildFindFilter(templateFilters, TableName.CertificateTemplate))
|
||||||
|
.where((qb) => {
|
||||||
|
if (projectId) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
void qb.where(buildFindFilter({ projectId }, TableName.CertificateAuthority));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.select(selectAllTableCols(TableName.CertificateTemplate))
|
||||||
|
.select(db.ref("projectId").withSchema(TableName.CertificateAuthority))
|
||||||
|
.select(db.ref("name").withSchema(TableName.CertificateAuthority).as("caName"));
|
||||||
|
|
||||||
|
if (countDistinct) {
|
||||||
|
void query.countDistinct(countDistinct);
|
||||||
|
} else if (count) {
|
||||||
|
void query.select(db.raw("COUNT(*) OVER() AS count"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit) void query.limit(limit);
|
||||||
|
if (offset) void query.offset(offset);
|
||||||
|
if (sort) {
|
||||||
|
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = (await query) as TFindReturn<typeof query, TCountDistinct extends undefined ? TCount : true>;
|
||||||
|
return res.map((el) => ({ ...el, ca: { id: el.caId, name: el.caName } }));
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find one" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...orm, find, findOne };
|
||||||
|
};
|
644
backend/src/services/pki-templates/pki-templates-service.ts
Normal file
644
backend/src/services/pki-templates/pki-templates-service.ts
Normal file
@ -0,0 +1,644 @@
|
|||||||
|
/* eslint-disable no-bitwise */
|
||||||
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
import * as x509 from "@peculiar/x509";
|
||||||
|
import RE2 from "re2";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import {
|
||||||
|
ProjectPermissionPkiTemplateActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { ms } from "@app/lib/ms";
|
||||||
|
|
||||||
|
import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal";
|
||||||
|
import { TCertificateDALFactory } from "../certificate/certificate-dal";
|
||||||
|
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
|
||||||
|
import {
|
||||||
|
CertExtendedKeyUsage,
|
||||||
|
CertExtendedKeyUsageOIDToName,
|
||||||
|
CertKeyAlgorithm,
|
||||||
|
CertKeyUsage,
|
||||||
|
CertStatus
|
||||||
|
} from "../certificate/certificate-types";
|
||||||
|
import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal";
|
||||||
|
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
||||||
|
import { CaStatus } from "../certificate-authority/certificate-authority-enums";
|
||||||
|
import {
|
||||||
|
createSerialNumber,
|
||||||
|
expandInternalCa,
|
||||||
|
getCaCertChain,
|
||||||
|
getCaCredentials,
|
||||||
|
keyAlgorithmToAlgCfg,
|
||||||
|
parseDistinguishedName
|
||||||
|
} from "../certificate-authority/certificate-authority-fns";
|
||||||
|
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority/certificate-authority-secret-dal";
|
||||||
|
import { InternalCertificateAuthorityFns } from "../certificate-authority/internal/internal-certificate-authority-fns";
|
||||||
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
|
import { getProjectKmsCertificateKeyId } from "../project/project-fns";
|
||||||
|
import { TPkiTemplatesDALFactory } from "./pki-templates-dal";
|
||||||
|
import {
|
||||||
|
TCreatePkiTemplateDTO,
|
||||||
|
TDeletePkiTemplateDTO,
|
||||||
|
TGetPkiTemplateDTO,
|
||||||
|
TIssueCertPkiTemplateDTO,
|
||||||
|
TListPkiTemplateDTO,
|
||||||
|
TSignCertPkiTemplateDTO,
|
||||||
|
TUpdatePkiTemplateDTO
|
||||||
|
} from "./pki-templates-types";
|
||||||
|
|
||||||
|
type TPkiTemplatesServiceFactoryDep = {
|
||||||
|
pkiTemplatesDAL: TPkiTemplatesDALFactory;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
certificateAuthorityDAL: Pick<
|
||||||
|
TCertificateAuthorityDALFactory,
|
||||||
|
| "findByIdWithAssociatedCa"
|
||||||
|
| "findById"
|
||||||
|
| "transaction"
|
||||||
|
| "create"
|
||||||
|
| "updateById"
|
||||||
|
| "findWithAssociatedCa"
|
||||||
|
| "findOne"
|
||||||
|
>;
|
||||||
|
internalCaFns: ReturnType<typeof InternalCertificateAuthorityFns>;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey" | "encryptWithKmsKey">;
|
||||||
|
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||||
|
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
|
||||||
|
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
|
||||||
|
certificateDAL: Pick<
|
||||||
|
TCertificateDALFactory,
|
||||||
|
"create" | "transaction" | "countCertificatesForPkiSubscriber" | "findLatestActiveCertForSubscriber" | "find"
|
||||||
|
>;
|
||||||
|
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create" | "findOne">;
|
||||||
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create" | "findOne">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction" | "findById" | "find">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TPkiTemplatesServiceFactory = ReturnType<typeof pkiTemplatesServiceFactory>;
|
||||||
|
|
||||||
|
export const pkiTemplatesServiceFactory = ({
|
||||||
|
pkiTemplatesDAL,
|
||||||
|
permissionService,
|
||||||
|
internalCaFns,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
certificateAuthorityCrlDAL,
|
||||||
|
certificateDAL,
|
||||||
|
certificateBodyDAL,
|
||||||
|
kmsService,
|
||||||
|
projectDAL
|
||||||
|
}: TPkiTemplatesServiceFactoryDep) => {
|
||||||
|
const createTemplate = async ({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
caName,
|
||||||
|
commonName,
|
||||||
|
extendedKeyUsages,
|
||||||
|
keyUsages,
|
||||||
|
name,
|
||||||
|
subjectAlternativeName,
|
||||||
|
ttl,
|
||||||
|
projectId
|
||||||
|
}: TCreatePkiTemplateDTO) => {
|
||||||
|
const ca = await certificateAuthorityDAL.findOne({ name: caName, projectId });
|
||||||
|
if (!ca) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `CA with name ${caName} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiTemplateActions.Create,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name })
|
||||||
|
);
|
||||||
|
|
||||||
|
const existingTemplate = await pkiTemplatesDAL.findOne({ name, projectId: ca.projectId });
|
||||||
|
if (existingTemplate) {
|
||||||
|
throw new BadRequestError({ message: `Template with name ${name} already exists.` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTemplate = await pkiTemplatesDAL.create({
|
||||||
|
caId: ca.id,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
subjectAlternativeName,
|
||||||
|
ttl,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
|
});
|
||||||
|
return newTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTemplate = async ({
|
||||||
|
templateName,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
caName,
|
||||||
|
commonName,
|
||||||
|
extendedKeyUsages,
|
||||||
|
keyUsages,
|
||||||
|
name,
|
||||||
|
subjectAlternativeName,
|
||||||
|
ttl,
|
||||||
|
projectId
|
||||||
|
}: TUpdatePkiTemplateDTO) => {
|
||||||
|
const certTemplate = await pkiTemplatesDAL.findOne({ name: templateName, projectId });
|
||||||
|
if (!certTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Certificate template with name ${templateName} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: certTemplate.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiTemplateActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name: templateName })
|
||||||
|
);
|
||||||
|
|
||||||
|
let caId;
|
||||||
|
if (caName) {
|
||||||
|
const ca = await certificateAuthorityDAL.findOne({ name: caName, projectId });
|
||||||
|
if (!ca || ca.projectId !== certTemplate.projectId) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `CA with name ${caName} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
caId = ca.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiTemplateActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name })
|
||||||
|
);
|
||||||
|
|
||||||
|
const existingTemplate = await pkiTemplatesDAL.findOne({ name, projectId });
|
||||||
|
if (existingTemplate && existingTemplate.id !== certTemplate.id) {
|
||||||
|
throw new BadRequestError({ message: `Template with name ${name} already exists.` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedTemplate = await pkiTemplatesDAL.updateById(certTemplate.id, {
|
||||||
|
caId,
|
||||||
|
name,
|
||||||
|
commonName,
|
||||||
|
subjectAlternativeName,
|
||||||
|
ttl,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
|
});
|
||||||
|
return updatedTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteTemplate = async ({
|
||||||
|
templateName,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
projectId
|
||||||
|
}: TDeletePkiTemplateDTO) => {
|
||||||
|
const certTemplate = await pkiTemplatesDAL.findOne({ name: templateName, projectId });
|
||||||
|
if (!certTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Certificate template with name ${templateName} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: certTemplate.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiTemplateActions.Delete,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name: templateName })
|
||||||
|
);
|
||||||
|
|
||||||
|
const deletedTemplate = await pkiTemplatesDAL.deleteById(certTemplate.id);
|
||||||
|
return deletedTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTemplateByName = async ({
|
||||||
|
templateName,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
projectId
|
||||||
|
}: TGetPkiTemplateDTO) => {
|
||||||
|
const certTemplate = await pkiTemplatesDAL.findOne({ name: templateName, projectId });
|
||||||
|
if (!certTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Certificate template with name ${templateName} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: certTemplate.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name: templateName })
|
||||||
|
);
|
||||||
|
|
||||||
|
return certTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listTemplate = async ({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
projectId,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
}: TListPkiTemplateDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
const certTemplate = await pkiTemplatesDAL.find({ projectId }, { limit, offset, count: true });
|
||||||
|
return {
|
||||||
|
certificateTemplates: certTemplate.filter((el) =>
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name: el.name })
|
||||||
|
)
|
||||||
|
),
|
||||||
|
totalCount: Number(certTemplate?.[0]?.count ?? 0)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const issueCertificate = async ({
|
||||||
|
templateName,
|
||||||
|
projectId,
|
||||||
|
commonName,
|
||||||
|
altNames,
|
||||||
|
ttl,
|
||||||
|
notBefore,
|
||||||
|
notAfter,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
|
}: TIssueCertPkiTemplateDTO) => {
|
||||||
|
const certTemplate = await pkiTemplatesDAL.findOne({ name: templateName, projectId });
|
||||||
|
if (!certTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Certificate template with name ${templateName} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: certTemplate.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiTemplateActions.IssueCert,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name: templateName })
|
||||||
|
);
|
||||||
|
|
||||||
|
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(certTemplate.caId);
|
||||||
|
if (ca.internalCa?.id) {
|
||||||
|
return internalCaFns.issueCertificateWithTemplate(ca, certTemplate, {
|
||||||
|
altNames,
|
||||||
|
commonName,
|
||||||
|
ttl,
|
||||||
|
extendedKeyUsages,
|
||||||
|
keyUsages,
|
||||||
|
notAfter,
|
||||||
|
notBefore
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: "CA does not support immediate issuance of certificates" });
|
||||||
|
};
|
||||||
|
|
||||||
|
const signCertificate = async ({
|
||||||
|
templateName,
|
||||||
|
csr,
|
||||||
|
projectId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
ttl
|
||||||
|
}: TSignCertPkiTemplateDTO) => {
|
||||||
|
const certTemplate = await pkiTemplatesDAL.findOne({ name: templateName, projectId });
|
||||||
|
if (!certTemplate) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Certificate template with name ${templateName} not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: certTemplate.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionPkiTemplateActions.IssueCert,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name: templateName })
|
||||||
|
);
|
||||||
|
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(certTemplate.caId);
|
||||||
|
if (!ca?.internalCa) throw new NotFoundError({ message: `CA with ID '${certTemplate.caId}' not found` });
|
||||||
|
|
||||||
|
if (ca.status !== CaStatus.ACTIVE) throw new BadRequestError({ message: "CA is not active" });
|
||||||
|
if (!ca.internalCa?.activeCaCertId)
|
||||||
|
throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
|
||||||
|
const caCert = await certificateAuthorityCertDAL.findById(ca.internalCa.activeCaCertId);
|
||||||
|
|
||||||
|
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
|
||||||
|
projectId: ca.projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedCaCert = await kmsDecryptor({
|
||||||
|
cipherTextBlob: caCert.encryptedCertificate
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCertObj = new x509.X509Certificate(decryptedCaCert);
|
||||||
|
const notBeforeDate = new Date();
|
||||||
|
const notAfterDate = new Date(new Date().getTime() + ms(ttl ?? "0"));
|
||||||
|
const caCertNotBeforeDate = new Date(caCertObj.notBefore);
|
||||||
|
const caCertNotAfterDate = new Date(caCertObj.notAfter);
|
||||||
|
|
||||||
|
// check not before constraint
|
||||||
|
if (notBeforeDate < caCertNotBeforeDate) {
|
||||||
|
throw new BadRequestError({ message: "notBefore date is before CA certificate's notBefore date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// check not after constraint
|
||||||
|
if (notAfterDate > caCertNotAfterDate) {
|
||||||
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const alg = keyAlgorithmToAlgCfg(ca.internalCa.keyAlgorithm as CertKeyAlgorithm);
|
||||||
|
|
||||||
|
const csrObj = new x509.Pkcs10CertificateRequest(csr);
|
||||||
|
const dn = parseDistinguishedName(csrObj.subject);
|
||||||
|
const cn = dn.commonName;
|
||||||
|
if (!cn)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Missing common name on CSR"
|
||||||
|
});
|
||||||
|
|
||||||
|
const commonNameRegex = new RE2(certTemplate.commonName);
|
||||||
|
if (!commonNameRegex.test(cn)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid common name based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ms(ttl) > ms(certTemplate.ttl)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid validity date based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { caPrivateKey, caSecret } = await getCaCredentials({
|
||||||
|
caId: ca.id,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthoritySecretDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
||||||
|
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
||||||
|
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
||||||
|
|
||||||
|
const extensions: x509.Extension[] = [
|
||||||
|
new x509.BasicConstraintsExtension(false),
|
||||||
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
||||||
|
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
||||||
|
new x509.AuthorityInfoAccessExtension({
|
||||||
|
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
||||||
|
}),
|
||||||
|
new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy
|
||||||
|
];
|
||||||
|
|
||||||
|
// handle key usages
|
||||||
|
const csrKeyUsageExtension = csrObj.getExtension("2.5.29.15") as x509.KeyUsagesExtension | undefined; // Better to type as optional
|
||||||
|
let selectedKeyUsages: CertKeyUsage[] = [];
|
||||||
|
if (csrKeyUsageExtension && csrKeyUsageExtension.usages) {
|
||||||
|
selectedKeyUsages = Object.values(CertKeyUsage).filter(
|
||||||
|
(keyUsage) => (x509.KeyUsageFlags[keyUsage] & csrKeyUsageExtension.usages) !== 0
|
||||||
|
);
|
||||||
|
const validKeyUsages = certTemplate.keyUsages || [];
|
||||||
|
if (selectedKeyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid key usage value based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0);
|
||||||
|
if (keyUsagesBitValue) {
|
||||||
|
extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle extended key usage
|
||||||
|
const csrExtendedKeyUsageExtension = csrObj.getExtension("2.5.29.37") as x509.ExtendedKeyUsageExtension | undefined;
|
||||||
|
let selectedExtendedKeyUsages: CertExtendedKeyUsage[] = [];
|
||||||
|
if (csrExtendedKeyUsageExtension && csrExtendedKeyUsageExtension.usages.length > 0) {
|
||||||
|
selectedExtendedKeyUsages = csrExtendedKeyUsageExtension.usages.map(
|
||||||
|
(ekuOid) => CertExtendedKeyUsageOIDToName[ekuOid as string]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (selectedExtendedKeyUsages.some((eku) => !certTemplate?.extendedKeyUsages?.includes(eku))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid extended key usage value based on subscriber's specified extended key usages"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedExtendedKeyUsages.length) {
|
||||||
|
extensions.push(
|
||||||
|
new x509.ExtendedKeyUsageExtension(
|
||||||
|
selectedExtendedKeyUsages.map((eku) => x509.ExtendedKeyUsage[eku]),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to read from CSR if altNames is not explicitly provided
|
||||||
|
let altNamesArray: {
|
||||||
|
type: "email" | "dns";
|
||||||
|
value: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
const sanExtension = csrObj.extensions.find((ext) => ext.type === "2.5.29.17");
|
||||||
|
if (sanExtension) {
|
||||||
|
const sanNames = new x509.GeneralNames(sanExtension.value);
|
||||||
|
|
||||||
|
altNamesArray = sanNames.items
|
||||||
|
.filter((value) => value.type === "email" || value.type === "dns")
|
||||||
|
.map((name) => ({
|
||||||
|
type: name.type as "email" | "dns",
|
||||||
|
value: name.value
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (altNamesArray.length) {
|
||||||
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
|
extensions.push(altNamesExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
const subjectAlternativeNameRegex = new RE2(certTemplate.subjectAlternativeName);
|
||||||
|
altNamesArray.forEach((altName) => {
|
||||||
|
if (!subjectAlternativeNameRegex.test(altName.value)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid subject alternative name based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const serialNumber = createSerialNumber();
|
||||||
|
const leafCert = await x509.X509CertificateGenerator.create({
|
||||||
|
serialNumber,
|
||||||
|
subject: csrObj.subject,
|
||||||
|
issuer: caCertObj.subject,
|
||||||
|
notBefore: notBeforeDate,
|
||||||
|
notAfter: notAfterDate,
|
||||||
|
signingKey: caPrivateKey,
|
||||||
|
publicKey: csrObj.publicKey,
|
||||||
|
signingAlgorithm: alg,
|
||||||
|
extensions
|
||||||
|
});
|
||||||
|
|
||||||
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKmsId
|
||||||
|
});
|
||||||
|
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
|
||||||
|
});
|
||||||
|
|
||||||
|
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
||||||
|
caCertId: ca.internalCa.activeCaCertId,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const certificateChainPem = `${issuingCaCertificate}\n${caCertChain}`.trim();
|
||||||
|
|
||||||
|
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(certificateChainPem)
|
||||||
|
});
|
||||||
|
|
||||||
|
await certificateDAL.transaction(async (tx) => {
|
||||||
|
const cert = await certificateDAL.create(
|
||||||
|
{
|
||||||
|
caId: ca.id,
|
||||||
|
caCertId: caCert.id,
|
||||||
|
status: CertStatus.ACTIVE,
|
||||||
|
friendlyName: cn,
|
||||||
|
commonName: cn,
|
||||||
|
altNames: altNamesArray.map((el) => el.value).join(","),
|
||||||
|
serialNumber,
|
||||||
|
notBefore: notBeforeDate,
|
||||||
|
notAfter: notAfterDate,
|
||||||
|
keyUsages: selectedKeyUsages,
|
||||||
|
extendedKeyUsages: selectedExtendedKeyUsages,
|
||||||
|
projectId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await certificateBodyDAL.create(
|
||||||
|
{
|
||||||
|
certId: cert.id,
|
||||||
|
encryptedCertificate,
|
||||||
|
encryptedCertificateChain
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
return cert;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate: leafCert.toString("pem"),
|
||||||
|
certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(),
|
||||||
|
issuingCaCertificate,
|
||||||
|
serialNumber,
|
||||||
|
ca: expandInternalCa(ca),
|
||||||
|
commonName: cn,
|
||||||
|
template: certTemplate
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createTemplate,
|
||||||
|
updateTemplate,
|
||||||
|
getTemplateByName,
|
||||||
|
listTemplate,
|
||||||
|
deleteTemplate,
|
||||||
|
signCertificate,
|
||||||
|
issueCertificate
|
||||||
|
};
|
||||||
|
};
|
53
backend/src/services/pki-templates/pki-templates-types.ts
Normal file
53
backend/src/services/pki-templates/pki-templates-types.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
|
export type TCreatePkiTemplateDTO = {
|
||||||
|
caName: string;
|
||||||
|
name: string;
|
||||||
|
commonName: string;
|
||||||
|
subjectAlternativeName: string;
|
||||||
|
ttl: string;
|
||||||
|
keyUsages: CertKeyUsage[];
|
||||||
|
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TUpdatePkiTemplateDTO = {
|
||||||
|
templateName: string;
|
||||||
|
caName?: string;
|
||||||
|
name?: string;
|
||||||
|
commonName?: string;
|
||||||
|
subjectAlternativeName?: string;
|
||||||
|
ttl?: string;
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TListPkiTemplateDTO = {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TGetPkiTemplateDTO = {
|
||||||
|
templateName: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TDeletePkiTemplateDTO = {
|
||||||
|
templateName: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TIssueCertPkiTemplateDTO = {
|
||||||
|
templateName: string;
|
||||||
|
commonName: string;
|
||||||
|
altNames: string;
|
||||||
|
ttl: string;
|
||||||
|
notBefore?: string;
|
||||||
|
notAfter?: string;
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export type TSignCertPkiTemplateDTO = {
|
||||||
|
templateName: string;
|
||||||
|
csr: string;
|
||||||
|
ttl: string;
|
||||||
|
} & TProjectPermission;
|
@ -17,6 +17,7 @@ import {
|
|||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
ProjectPermissionPkiSubscriberActions,
|
ProjectPermissionPkiSubscriberActions,
|
||||||
|
ProjectPermissionPkiTemplateActions,
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionSecretActions,
|
||||||
ProjectPermissionSshHostActions,
|
ProjectPermissionSshHostActions,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
@ -1131,15 +1132,15 @@ export const projectServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.CertificateManager
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
});
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
ProjectPermissionSub.CertificateTemplates
|
|
||||||
);
|
|
||||||
|
|
||||||
const certificateTemplates = await certificateTemplateDAL.getCertTemplatesByProjectId(projectId);
|
const certificateTemplates = await certificateTemplateDAL.getCertTemplatesByProjectId(projectId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificateTemplates
|
certificateTemplates: certificateTemplates.filter((el) =>
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionPkiTemplateActions.Read,
|
||||||
|
subject(ProjectPermissionSub.CertificateTemplates, { name: el.name })
|
||||||
|
)
|
||||||
|
)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -115,8 +115,6 @@ export const secretSharingServiceFactory = ({
|
|||||||
|
|
||||||
const encryptWithRoot = kmsService.encryptWithRootKey();
|
const encryptWithRoot = kmsService.encryptWithRootKey();
|
||||||
|
|
||||||
let salt: string | undefined;
|
|
||||||
let encryptedSalt: Buffer | undefined;
|
|
||||||
const orgEmails = [];
|
const orgEmails = [];
|
||||||
|
|
||||||
if (emails && emails.length > 0) {
|
if (emails && emails.length > 0) {
|
||||||
@ -133,10 +131,6 @@ export const secretSharingServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate salt for signing email hashes (if emails are provided)
|
|
||||||
salt = crypto.randomBytes(32).toString("hex");
|
|
||||||
encryptedSalt = encryptWithRoot(Buffer.from(salt));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const encryptedSecret = encryptWithRoot(Buffer.from(secretValue));
|
const encryptedSecret = encryptWithRoot(Buffer.from(secretValue));
|
||||||
@ -158,14 +152,13 @@ export const secretSharingServiceFactory = ({
|
|||||||
userId: actorId,
|
userId: actorId,
|
||||||
orgId,
|
orgId,
|
||||||
accessType,
|
accessType,
|
||||||
authorizedEmails: emails && emails.length > 0 ? JSON.stringify(emails) : undefined,
|
authorizedEmails: emails && emails.length > 0 ? JSON.stringify(emails) : undefined
|
||||||
encryptedSalt
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const idToReturn = `${Buffer.from(newSharedSecret.identifier!, "hex").toString("base64url")}`;
|
const idToReturn = `${Buffer.from(newSharedSecret.identifier!, "hex").toString("base64url")}`;
|
||||||
|
|
||||||
// Loop through recipients and send out emails with unique access links
|
// Loop through recipients and send out emails with unique access links
|
||||||
if (emails && salt) {
|
if (emails) {
|
||||||
const user = await userDAL.findById(actorId);
|
const user = await userDAL.findById(actorId);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@ -174,9 +167,6 @@ export const secretSharingServiceFactory = ({
|
|||||||
|
|
||||||
for await (const email of emails) {
|
for await (const email of emails) {
|
||||||
try {
|
try {
|
||||||
const hmac = crypto.createHmac("sha256", salt).update(email);
|
|
||||||
const hash = hmac.digest("hex");
|
|
||||||
|
|
||||||
// Only show the username to emails which are part of the organization
|
// Only show the username to emails which are part of the organization
|
||||||
const respondentUsername = orgEmails.includes(email) ? user.username : undefined;
|
const respondentUsername = orgEmails.includes(email) ? user.username : undefined;
|
||||||
|
|
||||||
@ -186,7 +176,7 @@ export const secretSharingServiceFactory = ({
|
|||||||
substitutions: {
|
substitutions: {
|
||||||
name,
|
name,
|
||||||
respondentUsername,
|
respondentUsername,
|
||||||
secretRequestUrl: `${appCfg.SITE_URL}/shared/secret/${idToReturn}?email=${encodeURIComponent(email)}&hash=${hash}`
|
secretRequestUrl: `${appCfg.SITE_URL}/shared/secret/${idToReturn}`
|
||||||
},
|
},
|
||||||
template: SmtpTemplates.SecretRequestCompleted
|
template: SmtpTemplates.SecretRequestCompleted
|
||||||
});
|
});
|
||||||
@ -474,9 +464,8 @@ export const secretSharingServiceFactory = ({
|
|||||||
sharedSecretId,
|
sharedSecretId,
|
||||||
hashedHex,
|
hashedHex,
|
||||||
orgId,
|
orgId,
|
||||||
password,
|
actorId,
|
||||||
email,
|
password
|
||||||
hash
|
|
||||||
}: TGetActiveSharedSecretByIdDTO) => {
|
}: TGetActiveSharedSecretByIdDTO) => {
|
||||||
const sharedSecret = isUuidV4(sharedSecretId)
|
const sharedSecret = isUuidV4(sharedSecretId)
|
||||||
? await secretSharingDAL.findOne({
|
? await secretSharingDAL.findOne({
|
||||||
@ -506,6 +495,17 @@ export const secretSharingServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError();
|
throw new ForbiddenRequestError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the secret was shared with specific emails, verify that the current user's session email is authorized
|
||||||
|
if (sharedSecret.authorizedEmails && (sharedSecret.authorizedEmails as string[]).length > 0) {
|
||||||
|
if (!actorId) throw new UnauthorizedError();
|
||||||
|
|
||||||
|
const user = await userDAL.findById(actorId);
|
||||||
|
if (!user || !user.email) throw new UnauthorizedError();
|
||||||
|
|
||||||
|
if (!(sharedSecret.authorizedEmails as string[]).includes(user.email))
|
||||||
|
throw new UnauthorizedError({ message: "Email not authorized to view secret" });
|
||||||
|
}
|
||||||
|
|
||||||
// all secrets pass through here, meaning we check if its expired first and then check if it needs verification
|
// all secrets pass through here, meaning we check if its expired first and then check if it needs verification
|
||||||
// or can be safely sent to the client.
|
// or can be safely sent to the client.
|
||||||
if (expiresAt !== null && expiresAt < new Date()) {
|
if (expiresAt !== null && expiresAt < new Date()) {
|
||||||
@ -524,31 +524,6 @@ export const secretSharingServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptWithRoot = kmsService.decryptWithRootKey();
|
|
||||||
|
|
||||||
if (sharedSecret.authorizedEmails && sharedSecret.encryptedSalt) {
|
|
||||||
// Verify both params were passed
|
|
||||||
if (!email || !hash) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "This secret is email protected. Parameters must include email and hash."
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify that email is authorized to view shared secret
|
|
||||||
} else if (!(sharedSecret.authorizedEmails as string[]).includes(email)) {
|
|
||||||
throw new UnauthorizedError({ message: "Email not authorized to view secret" });
|
|
||||||
|
|
||||||
// Verify that hash matches
|
|
||||||
} else {
|
|
||||||
const salt = decryptWithRoot(sharedSecret.encryptedSalt).toString();
|
|
||||||
const hmac = crypto.createHmac("sha256", salt).update(email);
|
|
||||||
const rebuiltHash = hmac.digest("hex");
|
|
||||||
|
|
||||||
if (rebuiltHash !== hash) {
|
|
||||||
throw new UnauthorizedError({ message: "Email not authorized to view secret" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Password checks
|
// Password checks
|
||||||
const isPasswordProtected = Boolean(sharedSecret.password);
|
const isPasswordProtected = Boolean(sharedSecret.password);
|
||||||
const hasProvidedPassword = Boolean(password);
|
const hasProvidedPassword = Boolean(password);
|
||||||
@ -561,6 +536,8 @@ export const secretSharingServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const decryptWithRoot = kmsService.decryptWithRootKey();
|
||||||
|
|
||||||
// If encryptedSecret is set, we know that this secret has been encrypted using KMS, and we can therefore do server-side decryption.
|
// If encryptedSecret is set, we know that this secret has been encrypted using KMS, and we can therefore do server-side decryption.
|
||||||
let decryptedSecretValue: Buffer | undefined;
|
let decryptedSecretValue: Buffer | undefined;
|
||||||
if (sharedSecret.encryptedSecret) {
|
if (sharedSecret.encryptedSecret) {
|
||||||
|
@ -37,11 +37,8 @@ export type TGetActiveSharedSecretByIdDTO = {
|
|||||||
sharedSecretId: string;
|
sharedSecretId: string;
|
||||||
hashedHex?: string;
|
hashedHex?: string;
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
|
actorId?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
|
|
||||||
// For secrets shared with specific emails
|
|
||||||
email?: string;
|
|
||||||
hash?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TValidateActiveSharedSecretDTO = TGetActiveSharedSecretByIdDTO & {
|
export type TValidateActiveSharedSecretDTO = TGetActiveSharedSecretByIdDTO & {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user