1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-21 13:36:03 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
1a8ac35b82 test 2024-07-11 14:28:38 +08:00
223 changed files with 3111 additions and 8813 deletions
backend
package-lock.jsonpackage.json
src
@types
db
ee
lib
server/routes
services
cli
company
docs
api-reference/endpoints/folders
cli/commands
documentation/platform/identities
integrations/platforms
mint.json
self-hosting
configuration
ee.mdx
frontend
package-lock.jsonpackage.json
src
components/v2
DeleteActionModal
FormControl
helpers
hooks/api
layouts/AppLayout
pages/org/[id]
memberships/[membershipId]
overview
roles/[roleId]
views
Org
Project/MembersPage
SecretApprovalPage
SecretOverviewPage/components/SecretOverviewTableRow
ShareSecretPublicPage/components
admin/DashboardPage
helm-charts/secrets-operator
k8-operator

1062
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -106,7 +106,6 @@
},
"dependencies": {
"@aws-sdk/client-iam": "^3.525.0",
"@aws-sdk/client-kms": "^3.609.0",
"@aws-sdk/client-secrets-manager": "^3.504.0",
"@aws-sdk/client-sts": "^3.600.0",
"@casl/ability": "^6.5.0",

@ -9,7 +9,6 @@ import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream
import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service";
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service";
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
@ -164,7 +163,6 @@ declare module "fastify" {
secretSharing: TSecretSharingServiceFactory;
rateLimit: TRateLimitServiceFactory;
userEngagement: TUserEngagementServiceFactory;
externalKms: TExternalKmsServiceFactory;
};
// this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer

@ -59,9 +59,6 @@ import {
TDynamicSecrets,
TDynamicSecretsInsert,
TDynamicSecretsUpdate,
TExternalKms,
TExternalKmsInsert,
TExternalKmsUpdate,
TGitAppInstallSessions,
TGitAppInstallSessionsInsert,
TGitAppInstallSessionsUpdate,
@ -128,9 +125,6 @@ import {
TIntegrations,
TIntegrationsInsert,
TIntegrationsUpdate,
TInternalKms,
TInternalKmsInsert,
TInternalKmsUpdate,
TKmsKeys,
TKmsKeysInsert,
TKmsKeysUpdate,
@ -662,8 +656,6 @@ declare module "knex/types/tables" {
TKmsRootConfigInsert,
TKmsRootConfigUpdate
>;
[TableName.InternalKms]: KnexOriginal.CompositeTableType<TInternalKms, TInternalKmsInsert, TInternalKmsUpdate>;
[TableName.ExternalKms]: KnexOriginal.CompositeTableType<TExternalKms, TExternalKmsInsert, TExternalKmsUpdate>;
[TableName.KmsKey]: KnexOriginal.CompositeTableType<TKmsKeys, TKmsKeysInsert, TKmsKeysUpdate>;
[TableName.KmsKeyVersion]: KnexOriginal.CompositeTableType<
TKmsKeyVersions,

@ -1,256 +0,0 @@
import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TableName } from "../schemas";
const createInternalKmsTableAndBackfillData = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
// building the internal kms table by filling from old kms table
if (doesOldKmsKeyTableExist && !doesInternalKmsTableExist) {
await knex.schema.createTable(TableName.InternalKms, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.binary("encryptedKey").notNullable();
tb.string("encryptionAlgorithm").notNullable();
tb.integer("version").defaultTo(1).notNullable();
tb.uuid("kmsKeyId").unique().notNullable();
tb.foreign("kmsKeyId").references("id").inTable(TableName.KmsKey).onDelete("CASCADE");
});
// copy the old kms and backfill
const oldKmsKey = await knex(TableName.KmsKey).select("version", "encryptedKey", "encryptionAlgorithm", "id");
if (oldKmsKey.length) {
await knex(TableName.InternalKms).insert(
oldKmsKey.map((el) => ({
encryptionAlgorithm: el.encryptionAlgorithm,
encryptedKey: el.encryptedKey,
kmsKeyId: el.id,
version: el.version
}))
);
}
}
};
const renameKmsKeyVersionTableAsInternalKmsKeyVersion = async (knex: Knex) => {
const doesOldKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.KmsKeyVersion);
const doesNewKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.InternalKmsKeyVersion);
if (doesOldKmsKeyVersionTableExist && !doesNewKmsKeyVersionTableExist) {
// because we haven't started using versioning for kms thus no data exist
await knex.schema.renameTable(TableName.KmsKeyVersion, TableName.InternalKmsKeyVersion);
const hasKmsKeyIdColumn = await knex.schema.hasColumn(TableName.InternalKmsKeyVersion, "kmsKeyId");
const hasInternalKmsIdColumn = await knex.schema.hasColumn(TableName.InternalKmsKeyVersion, "internalKmsId");
await knex.schema.alterTable(TableName.InternalKmsKeyVersion, (tb) => {
if (hasKmsKeyIdColumn) tb.dropColumn("kmsKeyId");
if (!hasInternalKmsIdColumn) {
tb.uuid("internalKmsId").notNullable();
tb.foreign("internalKmsId").references("id").inTable(TableName.InternalKms).onDelete("CASCADE");
}
});
}
};
const createExternalKmsKeyTable = async (knex: Knex) => {
const doesExternalKmsServiceExist = await knex.schema.hasTable(TableName.ExternalKms);
if (!doesExternalKmsServiceExist) {
await knex.schema.createTable(TableName.ExternalKms, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.string("provider").notNullable();
tb.binary("encryptedProviderInputs").notNullable();
tb.string("status");
tb.string("statusDetails");
tb.uuid("kmsKeyId").unique().notNullable();
tb.foreign("kmsKeyId").references("id").inTable(TableName.KmsKey).onDelete("CASCADE");
});
}
};
const removeNonRequiredFieldsFromKmsKeyTableAndBackfillRequiredData = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
// building the internal kms table by filling from old kms table
if (doesOldKmsKeyTableExist) {
const hasSlugColumn = await knex.schema.hasColumn(TableName.KmsKey, "slug");
const hasEncryptedKeyColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptedKey");
const hasEncryptionAlgorithmColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptionAlgorithm");
const hasVersionColumn = await knex.schema.hasColumn(TableName.KmsKey, "version");
const hasTimestamps = await knex.schema.hasColumn(TableName.KmsKey, "createdAt");
const hasProjectId = await knex.schema.hasColumn(TableName.KmsKey, "projectId");
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
if (!hasSlugColumn) tb.string("slug", 32);
if (hasEncryptedKeyColumn) tb.dropColumn("encryptedKey");
if (hasEncryptionAlgorithmColumn) tb.dropColumn("encryptionAlgorithm");
if (hasVersionColumn) tb.dropColumn("version");
if (!hasTimestamps) tb.timestamps(true, true, true);
});
// backfill all org id in kms key because its gonna be changed to non nullable
if (hasProjectId && hasOrgId) {
await knex(TableName.KmsKey)
.whereNull("orgId")
.update({
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
orgId: knex(TableName.Project)
.select("orgId")
.where("id", knex.raw("??", [`${TableName.KmsKey}.projectId`]))
});
}
// backfill slugs in kms
const missingSlugs = await knex(TableName.KmsKey).whereNull("slug").select("id");
if (missingSlugs.length) {
await knex(TableName.KmsKey)
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
.insert(missingSlugs.map(({ id }) => ({ id, slug: slugify(alphaNumericNanoId(8).toLowerCase()) })))
.onConflict("id")
.merge();
}
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
if (hasOrgId) tb.uuid("orgId").notNullable().alter();
tb.string("slug", 32).notNullable().alter();
if (hasProjectId) tb.dropColumn("projectId");
if (hasOrgId) tb.unique(["orgId", "slug"]);
});
}
};
/*
* The goal for this migration is split the existing kms key into three table
* the kms-key table would be a container table that contains
* the internal kms key table and external kms table
*/
export async function up(knex: Knex): Promise<void> {
await createInternalKmsTableAndBackfillData(knex);
await renameKmsKeyVersionTableAsInternalKmsKeyVersion(knex);
await removeNonRequiredFieldsFromKmsKeyTableAndBackfillRequiredData(knex);
await createExternalKmsKeyTable(knex);
const doesOrgKmsKeyExist = await knex.schema.hasColumn(TableName.Organization, "kmsDefaultKeyId");
if (!doesOrgKmsKeyExist) {
await knex.schema.alterTable(TableName.Organization, (tb) => {
tb.uuid("kmsDefaultKeyId").nullable();
tb.foreign("kmsDefaultKeyId").references("id").inTable(TableName.KmsKey);
});
}
const doesProjectKmsSecretManagerKeyExist = await knex.schema.hasColumn(TableName.Project, "kmsSecretManagerKeyId");
if (!doesProjectKmsSecretManagerKeyExist) {
await knex.schema.alterTable(TableName.Project, (tb) => {
tb.uuid("kmsSecretManagerKeyId").nullable();
tb.foreign("kmsSecretManagerKeyId").references("id").inTable(TableName.KmsKey);
});
}
}
const renameInternalKmsKeyVersionBackToKmsKeyVersion = async (knex: Knex) => {
const doesInternalKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.InternalKmsKeyVersion);
const doesKmsKeyVersionTableExist = await knex.schema.hasTable(TableName.KmsKeyVersion);
if (doesInternalKmsKeyVersionTableExist && !doesKmsKeyVersionTableExist) {
// because we haven't started using versioning for kms thus no data exist
await knex.schema.renameTable(TableName.InternalKmsKeyVersion, TableName.KmsKeyVersion);
const hasInternalKmsIdColumn = await knex.schema.hasColumn(TableName.KmsKeyVersion, "internalKmsId");
const hasKmsKeyIdColumn = await knex.schema.hasColumn(TableName.KmsKeyVersion, "kmsKeyId");
await knex.schema.alterTable(TableName.KmsKeyVersion, (tb) => {
if (hasInternalKmsIdColumn) tb.dropColumn("internalKmsId");
if (!hasKmsKeyIdColumn) {
tb.uuid("kmsKeyId").notNullable();
tb.foreign("kmsKeyId").references("id").inTable(TableName.KmsKey).onDelete("CASCADE");
}
});
}
};
const bringBackKmsKeyFields = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
if (doesOldKmsKeyTableExist && doesInternalKmsTableExist) {
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
const hasEncryptedKeyColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptedKey");
const hasEncryptionAlgorithmColumn = await knex.schema.hasColumn(TableName.KmsKey, "encryptionAlgorithm");
const hasVersionColumn = await knex.schema.hasColumn(TableName.KmsKey, "version");
const hasNullableOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
const hasProjectIdColumn = await knex.schema.hasColumn(TableName.KmsKey, "projectId");
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
if (!hasEncryptedKeyColumn) tb.binary("encryptedKey");
if (!hasEncryptionAlgorithmColumn) tb.string("encryptionAlgorithm");
if (!hasVersionColumn) tb.integer("version").defaultTo(1);
if (hasNullableOrgId) tb.uuid("orgId").nullable().alter();
if (!hasProjectIdColumn) {
tb.string("projectId");
tb.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
}
if (hasSlug) tb.dropColumn("slug");
});
}
};
const backfillKmsKeyFromInternalKmsTable = async (knex: Knex) => {
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
if (doesInternalKmsTableExist && doesOldKmsKeyTableExist) {
// backfill kms key with internal kms data
await knex(TableName.KmsKey).update({
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
encryptedKey: knex(TableName.InternalKms)
.select("encryptedKey")
.where("kmsKeyId", knex.raw("??", [`${TableName.KmsKey}.id`])),
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
encryptionAlgorithm: knex(TableName.InternalKms)
.select("encryptionAlgorithm")
.where("kmsKeyId", knex.raw("??", [`${TableName.KmsKey}.id`])),
// eslint-disable-next-line
// @ts-ignore because generate schema happens after this
projectId: knex(TableName.Project)
.select("id")
.where("kmsCertificateKeyId", knex.raw("??", [`${TableName.KmsKey}.id`]))
});
}
};
export async function down(knex: Knex): Promise<void> {
const doesOrgKmsKeyExist = await knex.schema.hasColumn(TableName.Organization, "kmsDefaultKeyId");
if (doesOrgKmsKeyExist) {
await knex.schema.alterTable(TableName.Organization, (tb) => {
tb.dropColumn("kmsDefaultKeyId");
});
}
const doesProjectKmsSecretManagerKeyExist = await knex.schema.hasColumn(TableName.Project, "kmsSecretManagerKeyId");
if (doesProjectKmsSecretManagerKeyExist) {
await knex.schema.alterTable(TableName.Project, (tb) => {
tb.dropColumn("kmsSecretManagerKeyId");
});
}
await renameInternalKmsKeyVersionBackToKmsKeyVersion(knex);
await bringBackKmsKeyFields(knex);
await backfillKmsKeyFromInternalKmsTable(knex);
const doesOldKmsKeyTableExist = await knex.schema.hasTable(TableName.KmsKey);
if (doesOldKmsKeyTableExist) {
await knex.schema.alterTable(TableName.KmsKey, (tb) => {
tb.binary("encryptedKey").notNullable().alter();
tb.string("encryptionAlgorithm").notNullable().alter();
});
}
const doesInternalKmsTableExist = await knex.schema.hasTable(TableName.InternalKms);
if (doesInternalKmsTableExist) await knex.schema.dropTable(TableName.InternalKms);
const doesExternalKmsServiceExist = await knex.schema.hasTable(TableName.ExternalKms);
if (doesExternalKmsServiceExist) await knex.schema.dropTable(TableName.ExternalKms);
}

@ -1,25 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.OrgMembership)) {
const doesUserIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "userId");
const doesOrgIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "orgId");
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
t.boolean("isActive").notNullable().defaultTo(true);
if (doesUserIdExist && doesOrgIdExist) t.index(["userId", "orgId"]);
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.OrgMembership)) {
const doesUserIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "userId");
const doesOrgIdExist = await knex.schema.hasColumn(TableName.OrgMembership, "orgId");
await knex.schema.alterTable(TableName.OrgMembership, (t) => {
t.dropColumn("isActive");
if (doesUserIdExist && doesOrgIdExist) t.dropIndex(["userId", "orgId"]);
});
}
}

@ -1,23 +0,0 @@
import { Knex } from "knex";
import { EnforcementLevel } from "@app/lib/types";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.SecretApprovalPolicy, "enforcementLevel");
if (!hasColumn) {
await knex.schema.table(TableName.SecretApprovalPolicy, (table) => {
table.string("enforcementLevel", 10).notNullable().defaultTo(EnforcementLevel.Hard);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.SecretApprovalPolicy, "enforcementLevel");
if (hasColumn) {
await knex.schema.table(TableName.SecretApprovalPolicy, (table) => {
table.dropColumn("enforcementLevel");
});
}
}

@ -1,23 +0,0 @@
import { Knex } from "knex";
import { EnforcementLevel } from "@app/lib/types";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.AccessApprovalPolicy, "enforcementLevel");
if (!hasColumn) {
await knex.schema.table(TableName.AccessApprovalPolicy, (table) => {
table.string("enforcementLevel", 10).notNullable().defaultTo(EnforcementLevel.Hard);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.AccessApprovalPolicy, "enforcementLevel");
if (hasColumn) {
await knex.schema.table(TableName.AccessApprovalPolicy, (table) => {
table.dropColumn("enforcementLevel");
});
}
}

@ -5,8 +5,6 @@
import { z } from "zod";
import { EnforcementLevel } from "@app/lib/types";
import { TImmutableDBKeys } from "./models";
export const AccessApprovalPoliciesSchema = z.object({
@ -16,8 +14,7 @@ export const AccessApprovalPoliciesSchema = z.object({
secretPath: z.string().nullable().optional(),
envId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
updatedAt: z.date()
});
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;

@ -1,23 +0,0 @@
// 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 { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const ExternalKmsSchema = z.object({
id: z.string().uuid(),
provider: z.string(),
encryptedProviderInputs: zodBuffer,
status: z.string().nullable().optional(),
statusDetails: z.string().nullable().optional(),
kmsKeyId: z.string().uuid()
});
export type TExternalKms = z.infer<typeof ExternalKmsSchema>;
export type TExternalKmsInsert = Omit<z.input<typeof ExternalKmsSchema>, TImmutableDBKeys>;
export type TExternalKmsUpdate = Partial<Omit<z.input<typeof ExternalKmsSchema>, TImmutableDBKeys>>;

@ -17,7 +17,6 @@ export * from "./certificate-secrets";
export * from "./certificates";
export * from "./dynamic-secret-leases";
export * from "./dynamic-secrets";
export * from "./external-kms";
export * from "./git-app-install-sessions";
export * from "./git-app-org";
export * from "./group-project-membership-roles";
@ -40,7 +39,6 @@ export * from "./identity-universal-auths";
export * from "./incident-contacts";
export * from "./integration-auths";
export * from "./integrations";
export * from "./internal-kms";
export * from "./kms-key-versions";
export * from "./kms-keys";
export * from "./kms-root-config";

@ -1,21 +0,0 @@
// 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 { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const InternalKmsKeyVersionSchema = z.object({
id: z.string().uuid(),
encryptedKey: zodBuffer,
version: z.number(),
internalKmsId: z.string().uuid()
});
export type TInternalKmsKeyVersion = z.infer<typeof InternalKmsKeyVersionSchema>;
export type TInternalKmsKeyVersionInsert = Omit<z.input<typeof InternalKmsKeyVersionSchema>, TImmutableDBKeys>;
export type TInternalKmsKeyVersionUpdate = Partial<Omit<z.input<typeof InternalKmsKeyVersionSchema>, TImmutableDBKeys>>;

@ -1,22 +0,0 @@
// 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 { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const InternalKmsSchema = z.object({
id: z.string().uuid(),
encryptedKey: zodBuffer,
encryptionAlgorithm: z.string(),
version: z.number().default(1),
kmsKeyId: z.string().uuid()
});
export type TInternalKms = z.infer<typeof InternalKmsSchema>;
export type TInternalKmsInsert = Omit<z.input<typeof InternalKmsSchema>, TImmutableDBKeys>;
export type TInternalKmsUpdate = Partial<Omit<z.input<typeof InternalKmsSchema>, TImmutableDBKeys>>;

@ -5,17 +5,20 @@
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const KmsKeysSchema = z.object({
id: z.string().uuid(),
encryptedKey: zodBuffer,
encryptionAlgorithm: z.string(),
version: z.number().default(1),
description: z.string().nullable().optional(),
isDisabled: z.boolean().default(false).nullable().optional(),
isReserved: z.boolean().default(true).nullable().optional(),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
slug: z.string()
projectId: z.string().nullable().optional(),
orgId: z.string().uuid().nullable().optional()
});
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;

@ -96,10 +96,6 @@ export enum TableName {
// KMS Service
KmsServerRootConfig = "kms_root_config",
KmsKey = "kms_keys",
ExternalKms = "external_kms",
InternalKms = "internal_kms",
InternalKmsKeyVersion = "internal_kms_key_version",
// @depreciated
KmsKeyVersion = "kms_key_versions"
}

@ -17,8 +17,7 @@ export const OrgMembershipsSchema = z.object({
userId: z.string().uuid().nullable().optional(),
orgId: z.string().uuid(),
roleId: z.string().uuid().nullable().optional(),
projectFavorites: z.string().array().nullable().optional(),
isActive: z.boolean()
projectFavorites: z.string().array().nullable().optional()
});
export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;

@ -15,8 +15,7 @@ export const OrganizationsSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
authEnforced: z.boolean().default(false).nullable().optional(),
scimEnabled: z.boolean().default(false).nullable().optional(),
kmsDefaultKeyId: z.string().uuid().nullable().optional()
scimEnabled: z.boolean().default(false).nullable().optional()
});
export type TOrganizations = z.infer<typeof OrganizationsSchema>;

@ -19,8 +19,7 @@ export const ProjectsSchema = z.object({
upgradeStatus: z.string().nullable().optional(),
pitVersionLimit: z.number().default(10),
kmsCertificateKeyId: z.string().uuid().nullable().optional(),
auditLogsRetentionDays: z.number().nullable().optional(),
kmsSecretManagerKeyId: z.string().uuid().nullable().optional()
auditLogsRetentionDays: z.number().nullable().optional()
});
export type TProjects = z.infer<typeof ProjectsSchema>;

@ -14,8 +14,7 @@ export const SecretApprovalPoliciesSchema = z.object({
approvals: z.number().default(1),
envId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
enforcementLevel: z.string().default("hard")
updatedAt: z.date()
});
export type TSecretApprovalPolicies = z.infer<typeof SecretApprovalPoliciesSchema>;

@ -29,8 +29,7 @@ export async function seed(knex: Knex): Promise<void> {
role: OrgMembershipRole.Admin,
orgId: org.id,
status: OrgMembershipStatus.Accepted,
userId: user.id,
isActive: true
userId: user.id
}
]);
}

@ -1,7 +1,6 @@
import { nanoid } from "nanoid";
import { z } from "zod";
import { EnforcementLevel } from "@app/lib/types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
@ -18,8 +17,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
secretPath: z.string().trim().default("/"),
environment: z.string(),
approvers: z.string().array().min(1),
approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
approvals: z.number().min(1).default(1)
})
.refine((data) => data.approvals <= data.approvers.length, {
path: ["approvals"],
@ -40,8 +38,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
actorOrgId: req.permission.orgId,
...req.body,
projectSlug: req.body.projectSlug,
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`,
enforcementLevel: req.body.enforcementLevel
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`
});
return { approval };
}
@ -118,8 +115,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
.optional()
.transform((val) => (val === "" ? "/" : val)),
approvers: z.string().array().min(1),
approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
approvals: z.number().min(1).default(1)
})
.refine((data) => data.approvals <= data.approvers.length, {
path: ["approvals"],

@ -99,8 +99,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
approvals: z.number(),
approvers: z.string().array(),
secretPath: z.string().nullish(),
envId: z.string(),
enforcementLevel: z.string()
envId: z.string()
}),
reviewers: z
.object({

@ -1,190 +0,0 @@
import { z } from "zod";
import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
import {
ExternalKmsAwsSchema,
ExternalKmsInputSchema,
ExternalKmsInputUpdateSchema
} from "@app/ee/services/external-kms/providers/model";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
const sanitizedExternalSchema = KmsKeysSchema.extend({
external: ExternalKmsSchema.pick({
id: true,
status: true,
statusDetails: true,
provider: true
})
});
const sanitizedExternalSchemaForGetById = KmsKeysSchema.extend({
external: ExternalKmsSchema.pick({
id: true,
status: true,
statusDetails: true,
provider: true
}).extend({
providerInput: ExternalKmsAwsSchema
})
});
export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
slug: z.string().min(1).trim().toLowerCase().optional(),
description: z.string().min(1).trim().optional(),
provider: ExternalKmsInputSchema
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.create({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.slug,
provider: req.body.provider,
description: req.body.description
});
return { externalKms };
}
});
server.route({
method: "PATCH",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
id: z.string().trim().min(1)
}),
body: z.object({
slug: z.string().min(1).trim().toLowerCase().optional(),
description: z.string().min(1).trim().optional(),
provider: ExternalKmsInputUpdateSchema
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.updateById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.slug,
provider: req.body.provider,
description: req.body.description,
id: req.params.id
});
return { externalKms };
}
});
server.route({
method: "DELETE",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
id: z.string().trim().min(1)
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.deleteById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});
return { externalKms };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
id: z.string().trim().min(1)
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchemaForGetById
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.findById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});
return { externalKms };
}
});
server.route({
method: "GET",
url: "/slug/:slug",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
slug: z.string().trim().min(1)
}),
response: {
200: z.object({
externalKms: sanitizedExternalSchemaForGetById
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.findBySlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.params.slug
});
return { externalKms };
}
});
};

@ -52,36 +52,6 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "GET",
url: "/:organizationId/roles/:roleId",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
organizationId: z.string().trim(),
roleId: z.string().trim()
}),
response: {
200: z.object({
role: OrgRolesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const role = await server.services.orgRole.getRole(
req.permission.id,
req.params.organizationId,
req.params.roleId,
req.permission.authMethod,
req.permission.orgId
);
return { role };
}
});
server.route({
method: "PATCH",
url: "/:organizationId/roles/:roleId",
@ -99,7 +69,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
.trim()
.optional()
.refine(
(val) => typeof val !== "undefined" && !Object.keys(OrgMembershipRole).includes(val),
(val) => typeof val === "undefined" || Object.keys(OrgMembershipRole).includes(val),
"Please choose a different slug, the slug you have entered is reserved."
)
.refine((val) => typeof val === "undefined" || slugify(val) === val, {

@ -186,13 +186,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
})
),
displayName: z.string().trim(),
active: z.boolean(),
groups: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim()
})
)
active: z.boolean()
})
}
},
@ -350,12 +344,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
schemas: z.array(z.string()),
id: z.string().trim(),
displayName: z.string().trim(),
members: z.array(
z.object({
value: z.string(),
display: z.string()
})
),
members: z.array(z.any()).length(0),
meta: z.object({
resourceType: z.string().trim()
})
@ -428,7 +417,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
displayName: z.string().trim(),
members: z.array(
z.object({
value: z.string(),
value: z.string(), // infisical orgMembershipId
display: z.string()
})
)
@ -486,13 +475,10 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
}),
z.object({
op: z.literal("add"),
path: z.string().trim(),
value: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim().optional()
})
)
value: z.object({
value: z.string().trim(),
display: z.string().trim().optional()
})
})
])
)
@ -583,13 +569,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
})
),
displayName: z.string().trim(),
active: z.boolean(),
groups: z.array(
z.object({
value: z.string().trim(),
display: z.string().trim()
})
)
active: z.boolean()
})
}
},

@ -2,7 +2,6 @@ import { nanoid } from "nanoid";
import { z } from "zod";
import { removeTrailingSlash } from "@app/lib/fn";
import { EnforcementLevel } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
@ -25,13 +24,11 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
.string()
.optional()
.nullable()
.default("/")
.transform((val) => (val ? removeTrailingSlash(val) : val)),
approvers: z.string().array().min(1),
approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
approverUserIds: z.string().array().min(1),
approvals: z.number().min(1).default(1)
})
.refine((data) => data.approvals <= data.approvers.length, {
.refine((data) => data.approvals <= data.approverUserIds.length, {
path: ["approvals"],
message: "The number of approvals should be lower than the number of approvers."
}),
@ -50,8 +47,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
actorOrgId: req.permission.orgId,
projectId: req.body.workspaceId,
...req.body,
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`,
enforcementLevel: req.body.enforcementLevel
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`
});
return { approval };
}
@ -70,17 +66,15 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
body: z
.object({
name: z.string().optional(),
approvers: z.string().array().min(1),
approverUserIds: z.string().array().min(1),
approvals: z.number().min(1).default(1),
secretPath: z
.string()
.optional()
.nullable()
.transform((val) => (val ? removeTrailingSlash(val) : val))
.transform((val) => (val === "" ? "/" : val)),
enforcementLevel: z.nativeEnum(EnforcementLevel).optional()
})
.refine((data) => data.approvals <= data.approvers.length, {
.refine((data) => data.approvals <= data.approverUserIds.length, {
path: ["approvals"],
message: "The number of approvals should be lower than the number of approvers."
}),

@ -49,8 +49,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
name: z.string(),
approvals: z.number(),
approvers: z.string().array(),
secretPath: z.string().optional().nullable(),
enforcementLevel: z.string()
secretPath: z.string().optional().nullable()
}),
committerUser: approvalRequestUser,
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
@ -249,8 +248,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
name: z.string(),
approvals: z.number(),
approvers: approvalRequestUser.array(),
secretPath: z.string().optional().nullable(),
enforcementLevel: z.string()
secretPath: z.string().optional().nullable()
}),
environment: z.string(),
statusChangedByUser: approvalRequestUser.optional(),

@ -47,8 +47,7 @@ export const accessApprovalPolicyServiceFactory = ({
approvals,
approvers,
projectSlug,
environment,
enforcementLevel
environment
}: TCreateAccessApprovalPolicy) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
@ -95,8 +94,7 @@ export const accessApprovalPolicyServiceFactory = ({
envId: env.id,
approvals,
secretPath,
name,
enforcementLevel
name
},
tx
);
@ -145,8 +143,7 @@ export const accessApprovalPolicyServiceFactory = ({
actor,
actorOrgId,
actorAuthMethod,
approvals,
enforcementLevel
approvals
}: TUpdateAccessApprovalPolicy) => {
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
@ -166,8 +163,7 @@ export const accessApprovalPolicyServiceFactory = ({
{
approvals,
secretPath,
name,
enforcementLevel
name
},
tx
);

@ -1,4 +1,4 @@
import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
import { TProjectPermission } from "@app/lib/types";
import { ActorAuthMethod } from "@app/services/auth/auth-type";
import { TPermissionServiceFactory } from "../permission/permission-service";
@ -20,7 +20,6 @@ export type TCreateAccessApprovalPolicy = {
approvers: string[];
projectSlug: string;
name: string;
enforcementLevel: EnforcementLevel;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateAccessApprovalPolicy = {
@ -29,7 +28,6 @@ export type TUpdateAccessApprovalPolicy = {
approvers?: string[];
secretPath?: string;
name?: string;
enforcementLevel?: EnforcementLevel;
} & Omit<TProjectPermission, "projectId">;
export type TDeleteAccessApprovalPolicy = {

@ -48,7 +48,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId")
)
@ -99,7 +98,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
name: doc.policyName,
approvals: doc.policyApprovals,
secretPath: doc.policySecretPath,
enforcementLevel: doc.policyEnforcementLevel,
envId: doc.policyEnvId
},
privilege: doc.privilegeId
@ -167,7 +165,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
tx.ref("projectId").withSchema(TableName.Environment),
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover)
);
@ -187,8 +184,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
id: el.policyId,
name: el.policyName,
approvals: el.policyApprovals,
secretPath: el.policySecretPath,
enforcementLevel: el.policyEnforcementLevel
secretPath: el.policySecretPath
}
}),
childrenMapper: [

@ -17,7 +17,7 @@ type TCertificateAuthorityCrlServiceFactoryDep = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
kmsService: Pick<TKmsServiceFactory, "decrypt" | "generateKmsKey">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
@ -68,11 +68,11 @@ export const certificateAuthorityCrlServiceFactory = ({
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
const decryptedCrl = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCrl.encryptedCrl
});
const decryptedCrl = kmsDecryptor({ cipherTextBlob: caCrl.encryptedCrl });
const crl = new x509.X509Crl(decryptedCrl);
const base64crl = crl.toString("base64");

@ -1,47 +0,0 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TKmsKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TExternalKmsDALFactory = ReturnType<typeof externalKmsDALFactory>;
export const externalKmsDALFactory = (db: TDbClient) => {
const externalKmsOrm = ormify(db, TableName.ExternalKms);
const find = async (filter: Partial<TKmsKeys>, tx?: Knex) => {
try {
const result = await (tx || db.replicaNode())(TableName.ExternalKms)
.join(TableName.KmsKey, `${TableName.KmsKey}.id`, `${TableName.ExternalKms}.kmsKeyId`)
.where(filter)
.select(selectAllTableCols(TableName.KmsKey))
.select(
db.ref("id").withSchema(TableName.ExternalKms).as("externalKmsId"),
db.ref("provider").withSchema(TableName.ExternalKms).as("externalKmsProvider"),
db.ref("encryptedProviderInputs").withSchema(TableName.ExternalKms).as("externalKmsEncryptedProviderInput"),
db.ref("status").withSchema(TableName.ExternalKms).as("externalKmsStatus"),
db.ref("statusDetails").withSchema(TableName.ExternalKms).as("externalKmsStatusDetails")
);
return result.map((el) => ({
id: el.id,
description: el.description,
isDisabled: el.isDisabled,
isReserved: el.isReserved,
orgId: el.orgId,
slug: el.slug,
externalKms: {
id: el.externalKmsId,
provider: el.externalKmsProvider,
status: el.externalKmsStatus,
statusDetails: el.externalKmsStatusDetails
}
}));
} catch (error) {
throw new DatabaseError({ error, name: "Find" });
}
};
return { ...externalKmsOrm, find };
};

@ -1,309 +0,0 @@
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { BadRequestError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TExternalKmsDALFactory } from "./external-kms-dal";
import {
TCreateExternalKmsDTO,
TDeleteExternalKmsDTO,
TGetExternalKmsByIdDTO,
TGetExternalKmsBySlugDTO,
TListExternalKmsDTO,
TUpdateExternalKmsDTO
} from "./external-kms-types";
import { AwsKmsProviderFactory } from "./providers/aws-kms";
import { ExternalKmsAwsSchema, KmsProviders } from "./providers/model";
type TExternalKmsServiceFactoryDep = {
externalKmsDAL: TExternalKmsDALFactory;
kmsService: Pick<TKmsServiceFactory, "getOrgKmsKeyId" | "encryptWithKmsKey" | "decryptWithKmsKey">;
kmsDAL: Pick<TKmsKeyDALFactory, "create" | "updateById" | "findById" | "deleteById" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
};
export type TExternalKmsServiceFactory = ReturnType<typeof externalKmsServiceFactory>;
export const externalKmsServiceFactory = ({
externalKmsDAL,
permissionService,
kmsService,
kmsDAL
}: TExternalKmsServiceFactoryDep) => {
const create = async ({
provider,
description,
actor,
slug,
actorId,
actorOrgId,
actorAuthMethod
}: TCreateExternalKmsDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const kmsSlug = slug ? slugify(slug) : slugify(alphaNumericNanoId(8).toLowerCase());
let sanitizedProviderInput = "";
switch (provider.type) {
case KmsProviders.Aws:
{
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs });
await externalKms.validateConnection();
// if missing kms key this generate a new kms key id and returns new provider input
const newProviderInput = await externalKms.generateInputKmsKey();
sanitizedProviderInput = JSON.stringify(newProviderInput);
}
break;
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId);
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: orgKmsKeyId
});
const { cipherTextBlob: encryptedProviderInputs } = kmsEncryptor({
plainText: Buffer.from(sanitizedProviderInput, "utf8")
});
const externalKms = await externalKmsDAL.transaction(async (tx) => {
const kms = await kmsDAL.create(
{
isReserved: false,
description,
slug: kmsSlug,
orgId: actorOrgId
},
tx
);
const externalKmsCfg = await externalKmsDAL.create(
{
provider: provider.type,
encryptedProviderInputs,
kmsKeyId: kms.id
},
tx
);
return { ...kms, external: externalKmsCfg };
});
return externalKms;
};
const updateById = async ({
provider,
description,
actor,
id: kmsId,
slug,
actorId,
actorOrgId,
actorAuthMethod
}: TUpdateExternalKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const kmsSlug = slug ? slugify(slug) : undefined;
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
let sanitizedProviderInput = "";
if (provider) {
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
});
switch (provider.type) {
case KmsProviders.Aws:
{
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
);
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
await externalKms.validateConnection();
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
}
break;
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
}
let encryptedProviderInputs: Buffer | undefined;
if (sanitizedProviderInput) {
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const { cipherTextBlob } = kmsEncryptor({
plainText: Buffer.from(sanitizedProviderInput, "utf8")
});
encryptedProviderInputs = cipherTextBlob;
}
const externalKms = await externalKmsDAL.transaction(async (tx) => {
const kms = await kmsDAL.updateById(
kmsDoc.id,
{
description,
slug: kmsSlug
},
tx
);
if (encryptedProviderInputs) {
const externalKmsCfg = await externalKmsDAL.updateById(
externalKmsDoc.id,
{
encryptedProviderInputs
},
tx
);
return { ...kms, external: externalKmsCfg };
}
return { ...kms, external: externalKmsDoc };
});
return externalKms;
};
const deleteById = async ({ actor, id: kmsId, actorId, actorOrgId, actorAuthMethod }: TDeleteExternalKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const externalKms = await externalKmsDAL.transaction(async (tx) => {
const kms = await kmsDAL.deleteById(kmsDoc.id, tx);
return { ...kms, external: externalKmsDoc };
});
return externalKms;
};
const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListExternalKmsDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDocs = await externalKmsDAL.find({ orgId: actorOrgId });
return externalKmsDocs;
};
const findById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id: kmsId }: TGetExternalKmsByIdDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
});
switch (externalKmsDoc.provider) {
case KmsProviders.Aws: {
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
);
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
}
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
};
const findBySlug = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
slug: kmsSlug
}: TGetExternalKmsBySlugDTO) => {
const kmsDoc = await kmsDAL.findOne({ slug: kmsSlug, orgId: actorOrgId });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
kmsDoc.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: orgDefaultKmsId
});
const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
});
switch (externalKmsDoc.provider) {
case KmsProviders.Aws: {
const decryptedProviderInput = await ExternalKmsAwsSchema.parseAsync(
JSON.parse(decryptedProviderInputBlob.toString("utf8"))
);
return { ...kmsDoc, external: { ...externalKmsDoc, providerInput: decryptedProviderInput } };
}
default:
throw new BadRequestError({ message: "external kms provided is invalid" });
}
};
return {
create,
updateById,
deleteById,
list,
findById,
findBySlug
};
};

@ -1,30 +0,0 @@
import { TOrgPermission } from "@app/lib/types";
import { TExternalKmsInputSchema, TExternalKmsInputUpdateSchema } from "./providers/model";
export type TCreateExternalKmsDTO = {
slug?: string;
description?: string;
provider: TExternalKmsInputSchema;
} & Omit<TOrgPermission, "orgId">;
export type TUpdateExternalKmsDTO = {
id: string;
slug?: string;
description?: string;
provider?: TExternalKmsInputUpdateSchema;
} & Omit<TOrgPermission, "orgId">;
export type TDeleteExternalKmsDTO = {
id: string;
} & Omit<TOrgPermission, "orgId">;
export type TListExternalKmsDTO = Omit<TOrgPermission, "orgId">;
export type TGetExternalKmsByIdDTO = {
id: string;
} & Omit<TOrgPermission, "orgId">;
export type TGetExternalKmsBySlugDTO = {
slug: string;
} & Omit<TOrgPermission, "orgId">;

@ -1,102 +0,0 @@
import { CreateKeyCommand, DecryptCommand, DescribeKeyCommand, EncryptCommand, KMSClient } from "@aws-sdk/client-kms";
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
import { randomUUID } from "crypto";
import { ExternalKmsAwsSchema, KmsAwsCredentialType, TExternalKmsAwsSchema, TExternalKmsProviderFns } from "./model";
const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => {
if (providerInputs.credential.type === KmsAwsCredentialType.AssumeRole) {
const awsCredential = providerInputs.credential.data;
const stsClient = new STSClient({
region: providerInputs.awsRegion
});
const command = new AssumeRoleCommand({
RoleArn: awsCredential.assumeRoleArn,
RoleSessionName: `infisical-kms-${randomUUID()}`,
DurationSeconds: 900, // 15mins
ExternalId: awsCredential.externalId
});
const response = await stsClient.send(command);
if (!response.Credentials?.AccessKeyId || !response.Credentials?.SecretAccessKey)
throw new Error("Failed to assume role");
const kmsClient = new KMSClient({
region: providerInputs.awsRegion,
credentials: {
accessKeyId: response.Credentials.AccessKeyId,
secretAccessKey: response.Credentials.SecretAccessKey,
sessionToken: response.Credentials.SessionToken,
expiration: response.Credentials.Expiration
}
});
return kmsClient;
}
const awsCredential = providerInputs.credential.data;
const kmsClient = new KMSClient({
region: providerInputs.awsRegion,
credentials: {
accessKeyId: awsCredential.accessKey,
secretAccessKey: awsCredential.secretKey
}
});
return kmsClient;
};
type AwsKmsProviderArgs = {
inputs: unknown;
};
type TAwsKmsProviderFactoryReturn = TExternalKmsProviderFns & {
generateInputKmsKey: () => Promise<TExternalKmsAwsSchema>;
};
export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Promise<TAwsKmsProviderFactoryReturn> => {
const providerInputs = await ExternalKmsAwsSchema.parseAsync(inputs);
const awsClient = await getAwsKmsClient(providerInputs);
const generateInputKmsKey = async () => {
if (providerInputs.kmsKeyId) return providerInputs;
const command = new CreateKeyCommand({ Tags: [{ TagKey: "author", TagValue: "infisical" }] });
const kmsKey = await awsClient.send(command);
if (!kmsKey.KeyMetadata?.KeyId) throw new Error("Failed to generate kms key");
return { ...providerInputs, kmsKeyId: kmsKey.KeyMetadata?.KeyId };
};
const validateConnection = async () => {
const command = new DescribeKeyCommand({
KeyId: providerInputs.kmsKeyId
});
const isConnected = await awsClient.send(command).then(() => true);
return isConnected;
};
const encrypt = async (data: Buffer) => {
const command = new EncryptCommand({
KeyId: providerInputs.kmsKeyId,
Plaintext: data
});
const encryptionCommand = await awsClient.send(command);
if (!encryptionCommand.CiphertextBlob) throw new Error("encryption failed");
return { encryptedBlob: Buffer.from(encryptionCommand.CiphertextBlob) };
};
const decrypt = async (encryptedBlob: Buffer) => {
const command = new DecryptCommand({
KeyId: providerInputs.kmsKeyId,
CiphertextBlob: encryptedBlob
});
const decryptionCommand = await awsClient.send(command);
if (!decryptionCommand.Plaintext) throw new Error("decryption failed");
return { data: Buffer.from(decryptionCommand.Plaintext) };
};
return {
generateInputKmsKey,
validateConnection,
encrypt,
decrypt
};
};

@ -1,61 +0,0 @@
import { z } from "zod";
export enum KmsProviders {
Aws = "aws"
}
export enum KmsAwsCredentialType {
AssumeRole = "assume-role",
AccessKey = "access-key"
}
export const ExternalKmsAwsSchema = z.object({
credential: z
.discriminatedUnion("type", [
z.object({
type: z.literal(KmsAwsCredentialType.AccessKey),
data: z.object({
accessKey: z.string().trim().min(1).describe("AWS user account access key"),
secretKey: z.string().trim().min(1).describe("AWS user account secret key")
})
}),
z.object({
type: z.literal(KmsAwsCredentialType.AssumeRole),
data: z.object({
assumeRoleArn: z.string().trim().min(1).describe("AWS user role to be assumed by infisical"),
externalId: z
.string()
.trim()
.min(1)
.optional()
.describe("AWS assume role external id for furthur security in authentication")
})
})
])
.describe("AWS credential information to connect"),
awsRegion: z.string().min(1).trim().describe("AWS region to connect"),
kmsKeyId: z
.string()
.trim()
.optional()
.describe("A pre existing AWS KMS key id to be used for encryption. If not provided a kms key will be generated.")
});
export type TExternalKmsAwsSchema = z.infer<typeof ExternalKmsAwsSchema>;
// The root schema of the JSON
export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema })
]);
export type TExternalKmsInputSchema = z.infer<typeof ExternalKmsInputSchema>;
export const ExternalKmsInputUpdateSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema.partial() })
]);
export type TExternalKmsInputUpdateSchema = z.infer<typeof ExternalKmsInputUpdateSchema>;
// generic function shared by all provider
export type TExternalKmsProviderFns = {
validateConnection: () => Promise<boolean>;
encrypt: (data: Buffer) => Promise<{ encryptedBlob: Buffer }>;
decrypt: (encryptedBlob: Buffer) => Promise<{ data: Buffer }>;
};

@ -162,60 +162,11 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
}
};
const findGroupMembershipsByUserIdInOrg = async (userId: string, orgId: string) => {
try {
const docs = await db
.replicaNode()(TableName.UserGroupMembership)
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
.join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.UserGroupMembership}.userId`, userId)
.where(`${TableName.Groups}.orgId`, orgId)
.select(
db.ref("id").withSchema(TableName.UserGroupMembership),
db.ref("groupId").withSchema(TableName.UserGroupMembership),
db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
db.ref("lastName").withSchema(TableName.Users).as("lastName")
);
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "Find group memberships by user id in org" });
}
};
const findGroupMembershipsByGroupIdInOrg = async (groupId: string, orgId: string) => {
try {
const docs = await db
.replicaNode()(TableName.UserGroupMembership)
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
.join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.Groups}.id`, groupId)
.where(`${TableName.Groups}.orgId`, orgId)
.select(
db.ref("id").withSchema(TableName.UserGroupMembership),
db.ref("groupId").withSchema(TableName.UserGroupMembership),
db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
db.ref("lastName").withSchema(TableName.Users).as("lastName")
);
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "Find group memberships by group id in org" });
}
};
return {
...userGroupMembershipOrm,
filterProjectsByUserMembership,
findUserGroupMembershipsInProject,
findGroupMembersNotInProject,
deletePendingUserGroupMembershipsByUserIds,
findGroupMembershipsByUserIdInOrg,
findGroupMembershipsByGroupIdInOrg
deletePendingUserGroupMembershipsByUserIds
};
};

@ -449,8 +449,7 @@ export const ldapConfigServiceFactory = ({
userId: userAlias.userId,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Accepted,
isActive: true
status: OrgMembershipStatus.Accepted
},
tx
);
@ -535,8 +534,7 @@ export const ldapConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);

@ -38,8 +38,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
has_used_trial: true,
secretApproval: false,
secretRotation: true,
caCrl: false,
instanceUserManagement: false
caCrl: false
});
export const setupLicenceRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {

@ -218,8 +218,6 @@ export const licenseServiceFactory = ({
} else if (instanceType === InstanceType.EnterpriseOnPrem) {
const usedSeats = await licenseDAL.countOfOrgMembers(null, tx);
const usedIdentitySeats = await licenseDAL.countOrgUsersAndIdentities(null, tx);
onPremFeatures.membersUsed = usedSeats;
onPremFeatures.identitiesUsed = usedIdentitySeats;
await licenseServerOnPremApi.request.patch(`/api/license/v1/license`, {
usedSeats,
usedIdentitySeats

@ -30,9 +30,9 @@ export type TFeatureSet = {
workspacesUsed: 0;
dynamicSecret: false;
memberLimit: null;
membersUsed: number;
membersUsed: 0;
identityLimit: null;
identitiesUsed: number;
identitiesUsed: 0;
environmentLimit: null;
environmentsUsed: 0;
secretVersioning: true;
@ -56,7 +56,6 @@ export type TFeatureSet = {
secretApproval: false;
secretRotation: true;
caCrl: false;
instanceUserManagement: false;
};
export type TOrgPlansTableDTO = {

@ -193,8 +193,7 @@ export const oidcConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);
@ -267,8 +266,7 @@ export const oidcConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);

@ -109,9 +109,6 @@ export const permissionServiceFactory = ({
authMethod: ActorAuthMethod,
userOrgId?: string
) => {
// when token is scoped, ensure the passed org id is same as user org id
if (userOrgId && userOrgId !== orgId)
throw new BadRequestError({ message: "Invalid user token. Scoped to different organization." });
const membership = await permissionDAL.getOrgPermission(userId, orgId);
if (!membership) throw new UnauthorizedError({ name: "User not in org" });
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {

@ -370,8 +370,7 @@ export const samlConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);
@ -458,8 +457,7 @@ export const samlConfigServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);

@ -32,19 +32,12 @@ export const parseScimFilter = (filterToParse: string | undefined) => {
return { [attributeName]: parsedValue.replace(/"/g, "") };
};
export function extractScimValueFromPath(path: string): string | null {
const regex = /members\[value eq "([^"]+)"\]/;
const match = path.match(regex);
return match ? match[1] : null;
}
export const buildScimUser = ({
orgMembershipId,
username,
email,
firstName,
lastName,
groups = [],
active
}: {
orgMembershipId: string;
@ -52,10 +45,6 @@ export const buildScimUser = ({
email?: string | null;
firstName: string;
lastName: string;
groups?: {
value: string;
display: string;
}[];
active: boolean;
}): TScimUser => {
const scimUser = {
@ -78,7 +67,7 @@ export const buildScimUser = ({
]
: [],
active,
groups,
groups: [],
meta: {
resourceType: "User",
location: null

@ -2,14 +2,13 @@ import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import jwt from "jsonwebtoken";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TOrgMemberships, TUsers } from "@app/db/schemas";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TOrgPermission } from "@app/lib/types";
import { AuthTokenType } from "@app/services/auth/auth-type";
@ -31,14 +30,7 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import {
buildScimGroup,
buildScimGroupList,
buildScimUser,
buildScimUserList,
extractScimValueFromPath,
parseScimFilter
} from "./scim-fns";
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
import {
TCreateScimGroupDTO,
TCreateScimTokenDTO,
@ -52,7 +44,6 @@ import {
TListScimUsers,
TListScimUsersDTO,
TReplaceScimUserDTO,
TScimGroup,
TScimTokenJwtPayload,
TUpdateScimGroupNamePatchDTO,
TUpdateScimGroupNamePutDTO,
@ -70,23 +61,17 @@ type TScimServiceFactoryDep = {
TOrgDALFactory,
"createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction" | "updateMembershipById"
>;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById" | "findById">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById">;
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
groupDAL: Pick<
TGroupDALFactory,
"create" | "findOne" | "findAllGroupMembers" | "delete" | "findGroups" | "transaction" | "updateById" | "update"
"create" | "findOne" | "findAllGroupMembers" | "update" | "delete" | "findGroups" | "transaction"
>;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,
| "find"
| "transaction"
| "insertMany"
| "filterProjectsByUserMembership"
| "delete"
| "findGroupMembershipsByUserIdInOrg"
| "findGroupMembershipsByGroupIdInOrg"
"find" | "transaction" | "insertMany" | "filterProjectsByUserMembership" | "delete"
>;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
@ -212,14 +197,14 @@ export const scimServiceFactory = ({
findOpts
);
const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email, isActive }) =>
const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email }) =>
buildScimUser({
orgMembershipId: id ?? "",
username: externalId ?? username,
firstName: firstName ?? "",
lastName: lastName ?? "",
email,
active: isActive
active: true
})
);
@ -255,22 +240,13 @@ export const scimServiceFactory = ({
status: 403
});
const groupMembershipsInOrg = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(
membership.userId,
orgId
);
return buildScimUser({
orgMembershipId: membership.id,
username: membership.externalId ?? membership.username,
email: membership.email ?? "",
firstName: membership.firstName as string,
lastName: membership.lastName as string,
active: membership.isActive,
groups: groupMembershipsInOrg.map((group) => ({
value: group.groupId,
display: group.groupName
}))
active: true
});
};
@ -320,8 +296,7 @@ export const scimServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);
@ -389,8 +364,7 @@ export const scimServiceFactory = ({
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);
@ -427,7 +401,7 @@ export const scimServiceFactory = ({
firstName: createdUser.firstName as string,
lastName: createdUser.lastName as string,
email: createdUser.email ?? "",
active: createdOrgMembership.isActive
active: true
});
};
@ -471,8 +445,14 @@ export const scimServiceFactory = ({
});
if (!active) {
await orgMembershipDAL.updateById(membership.id, {
isActive: false
await deleteOrgMembershipFn({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
});
}
@ -511,14 +491,17 @@ export const scimServiceFactory = ({
status: 403
});
await orgMembershipDAL.updateById(membership.id, {
isActive: active
});
const groupMembershipsInOrg = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(
membership.userId,
orgId
);
if (!active) {
await deleteOrgMembershipFn({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
});
}
return buildScimUser({
orgMembershipId: membership.id,
@ -526,11 +509,7 @@ export const scimServiceFactory = ({
email: membership.email,
firstName: membership.firstName as string,
lastName: membership.lastName as string,
active,
groups: groupMembershipsInOrg.map((group) => ({
value: group.groupId,
display: group.groupName
}))
active
});
};
@ -598,20 +577,13 @@ export const scimServiceFactory = ({
}
);
const scimGroups: TScimGroup[] = [];
for await (const group of groups) {
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
const scimGroup = buildScimGroup({
const scimGroups = groups.map((group) =>
buildScimGroup({
groupId: group.id,
name: group.name,
members: members.map((member) => ({
value: member.orgMembershipId,
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
}))
});
scimGroups.push(scimGroup);
}
members: [] // does this need to be populated?
})
);
return buildScimGroupList({
scimGroups,
@ -845,6 +817,7 @@ export const scimServiceFactory = ({
});
};
// TODO: add support for add/remove op
const updateScimGroupNamePatch = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.groups)
@ -867,64 +840,27 @@ export const scimServiceFactory = ({
status: 403
});
let group = await groupDAL.findOne({
id: groupId,
orgId
});
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
let group: TGroups | undefined;
for await (const operation of operations) {
switch (operation.op) {
case "replace": {
group = await groupDAL.updateById(group.id, {
name: operation.value.displayName
});
await groupDAL.update(
{
id: groupId,
orgId
},
{
name: operation.value.displayName
}
);
break;
}
case "add": {
try {
const orgMemberships = await orgMembershipDAL.find({
$in: {
id: operation.value.map((member) => member.value)
}
});
await addUsersToGroupByUserIds({
group,
userIds: orgMemberships.map((membership) => membership.userId as string),
userDAL,
userGroupMembershipDAL,
orgDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
projectBotDAL
});
} catch {
logger.info("Repeat SCIM user-group add operation");
}
// TODO
break;
}
case "remove": {
const orgMembershipId = extractScimValueFromPath(operation.path);
if (!orgMembershipId) throw new ScimRequestError({ detail: "Invalid path value", status: 400 });
const orgMembership = await orgMembershipDAL.findById(orgMembershipId);
if (!orgMembership) throw new ScimRequestError({ detail: "Org Membership Not Found", status: 400 });
await removeUsersFromGroupByUserIds({
group,
userIds: [orgMembership.userId as string],
userDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL
});
// TODO
break;
}
default: {
@ -936,15 +872,17 @@ export const scimServiceFactory = ({
}
}
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
return buildScimGroup({
groupId: group.id,
name: group.name,
members: members.map((member) => ({
value: member.orgMembershipId,
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
}))
members: []
});
};

@ -125,11 +125,10 @@ type TRemoveOp = {
type TAddOp = {
op: "add";
path: string;
value: {
value: string;
display?: string;
}[];
};
};
export type TDeleteScimGroupDTO = {
@ -158,10 +157,7 @@ export type TScimUser = {
type: string;
}[];
active: boolean;
groups: {
value: string;
display: string;
}[];
groups: string[];
meta: {
resourceType: string;
location: null;

@ -45,13 +45,12 @@ export const secretApprovalPolicyServiceFactory = ({
actorOrgId,
actorAuthMethod,
approvals,
approvers,
approverUserIds,
projectId,
secretPath,
environment,
enforcementLevel
environment
}: TCreateSapDTO) => {
if (approvals > approvers.length)
if (approvals > approverUserIds.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission } = await permissionService.getProjectPermission(
@ -74,13 +73,12 @@ export const secretApprovalPolicyServiceFactory = ({
envId: env.id,
approvals,
secretPath,
name,
enforcementLevel
name
},
tx
);
await secretApprovalPolicyApproverDAL.insertMany(
approvers.map((approverUserId) => ({
approverUserIds.map((approverUserId) => ({
approverUserId,
policyId: doc.id
})),
@ -92,7 +90,7 @@ export const secretApprovalPolicyServiceFactory = ({
};
const updateSecretApprovalPolicy = async ({
approvers,
approverUserIds,
secretPath,
name,
actorId,
@ -100,8 +98,7 @@ export const secretApprovalPolicyServiceFactory = ({
actorOrgId,
actorAuthMethod,
approvals,
secretPolicyId,
enforcementLevel
secretPolicyId
}: TUpdateSapDTO) => {
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
if (!secretApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
@ -121,15 +118,14 @@ export const secretApprovalPolicyServiceFactory = ({
{
approvals,
secretPath,
name,
enforcementLevel
name
},
tx
);
if (approvers) {
if (approverUserIds) {
await secretApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
await secretApprovalPolicyApproverDAL.insertMany(
approvers.map((approverUserId) => ({
approverUserIds.map((approverUserId) => ({
approverUserId,
policyId: doc.id
})),

@ -1,22 +1,20 @@
import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
import { TProjectPermission } from "@app/lib/types";
export type TCreateSapDTO = {
approvals: number;
secretPath?: string | null;
environment: string;
approvers: string[];
approverUserIds: string[];
projectId: string;
name: string;
enforcementLevel: EnforcementLevel;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateSapDTO = {
secretPolicyId: string;
approvals?: number;
secretPath?: string | null;
approvers: string[];
approverUserIds: string[];
name?: string;
enforcementLevel?: EnforcementLevel;
} & Omit<TProjectPermission, "projectId">;
export type TDeleteSapDTO = {

@ -94,7 +94,6 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
tx.ref("projectId").withSchema(TableName.Environment),
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals")
);
@ -129,8 +128,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
id: el.policyId,
name: el.policyName,
approvals: el.policyApprovals,
secretPath: el.policySecretPath,
enforcementLevel: el.policyEnforcementLevel
secretPath: el.policySecretPath
}
}),
childrenMapper: [
@ -284,7 +282,6 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank`
),
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
@ -311,8 +308,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
id: el.policyId,
name: el.policyName,
approvals: el.policyApprovals,
secretPath: el.policySecretPath,
enforcementLevel: el.policyEnforcementLevel
secretPath: el.policySecretPath
},
committerUser: {
userId: el.committerUserId,

@ -11,7 +11,6 @@ import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { groupBy, pick, unique } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { EnforcementLevel } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
@ -290,10 +289,7 @@ export const secretApprovalRequestServiceFactory = ({
({ userId: approverId }) => reviewers[approverId.toString()] === ApprovalStatus.APPROVED
).length;
const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft;
if (!hasMinApproval && !isSoftEnforcement)
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
if (!hasMinApproval) throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" });

@ -348,15 +348,10 @@ export const ORGANIZATIONS = {
LIST_USER_MEMBERSHIPS: {
organizationId: "The ID of the organization to get memberships from."
},
GET_USER_MEMBERSHIP: {
organizationId: "The ID of the organization to get the membership for.",
membershipId: "The ID of the membership to get."
},
UPDATE_USER_MEMBERSHIP: {
organizationId: "The ID of the organization to update the membership for.",
membershipId: "The ID of the membership to update.",
role: "The new role of the membership.",
isActive: "The active status of the membership"
role: "The new role of the membership."
},
DELETE_USER_MEMBERSHIP: {
organizationId: "The ID of the organization to delete the membership from.",
@ -520,9 +515,6 @@ export const FOLDERS = {
path: "The path to list folders from.",
directory: "The directory to list folders from. (Deprecated in favor of path)"
},
GET_BY_ID: {
folderId: "The id of the folder to get details."
},
CREATE: {
workspaceId: "The ID of the project to create the folder in.",
environment: "The slug of the environment to create the folder in.",

@ -42,8 +42,3 @@ export type RequiredKeys<T> = {
}[keyof T];
export type PickRequired<T> = Pick<T, RequiredKeys<T>>;
export enum EnforcementLevel {
Hard = "hard",
Soft = "soft"
}

@ -22,8 +22,6 @@ import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/pro
import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal";
import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue";
import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
import { externalKmsDALFactory } from "@app/ee/services/external-kms/external-kms-dal";
import { externalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
import { groupDALFactory } from "@app/ee/services/group/group-dal";
import { groupServiceFactory } from "@app/ee/services/group/group-service";
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@ -118,8 +116,7 @@ import { integrationDALFactory } from "@app/services/integration/integration-dal
import { integrationServiceFactory } from "@app/services/integration/integration-service";
import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal";
import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal";
import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal";
import { kmsDALFactory } from "@app/services/kms/kms-dal";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { kmsServiceFactory } from "@app/services/kms/kms-service";
import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal";
@ -291,9 +288,7 @@ export const registerRoutes = async (
const dynamicSecretDAL = dynamicSecretDALFactory(db);
const dynamicSecretLeaseDAL = dynamicSecretLeaseDALFactory(db);
const kmsDAL = kmskeyDALFactory(db);
const internalKmsDAL = internalKmsDALFactory(db);
const externalKmsDAL = externalKmsDALFactory(db);
const kmsDAL = kmsDALFactory(db);
const kmsRootConfigDAL = kmsRootConfigDALFactory(db);
const permissionService = permissionServiceFactory({
@ -307,16 +302,7 @@ export const registerRoutes = async (
const kmsService = kmsServiceFactory({
kmsRootConfigDAL,
keyStore,
kmsDAL,
internalKmsDAL,
orgDAL,
projectDAL
});
const externalKmsService = externalKmsServiceFactory({
kmsDAL,
kmsService,
permissionService,
externalKmsDAL
kmsDAL
});
const trustedIpService = trustedIpServiceFactory({
@ -345,7 +331,7 @@ export const registerRoutes = async (
permissionService,
secretApprovalPolicyDAL
});
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL });
const samlService = samlConfigServiceFactory({
permissionService,
@ -457,7 +443,6 @@ export const registerRoutes = async (
tokenService,
projectDAL,
projectMembershipDAL,
orgMembershipDAL,
projectKeyDAL,
smtpService,
userDAL,
@ -484,8 +469,7 @@ export const registerRoutes = async (
authService: loginService,
serverCfgDAL: superAdminDAL,
orgService,
keyStore,
licenseService
keyStore
});
const rateLimitService = rateLimitServiceFactory({
rateLimitDAL,
@ -660,8 +644,7 @@ export const registerRoutes = async (
const webhookService = webhookServiceFactory({
permissionService,
webhookDAL,
projectEnvDAL,
projectDAL
projectEnvDAL
});
const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService });
@ -1046,8 +1029,7 @@ export const registerRoutes = async (
projectUserAdditionalPrivilege: projectUserAdditionalPrivilegeService,
identityProjectAdditionalPrivilege: identityProjectAdditionalPrivilegeService,
secretSharing: secretSharingService,
userEngagement: userEngagementService,
externalKms: externalKmsService
userEngagement: userEngagementService
});
const cronJobs: CronJob[] = [];

@ -100,7 +100,6 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -109,7 +108,6 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -184,12 +182,11 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
.min(1)
.optional()
.describe(AWS_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(AWS_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z.number().int().min(0).optional().describe(AWS_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AWS_AUTH.UPDATE.accessTokenNumUsesLimit),
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -90,7 +90,6 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -99,7 +98,6 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -175,12 +173,11 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
.min(1)
.optional()
.describe(AZURE_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(AZURE_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z.number().int().min(0).optional().describe(AZURE_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AZURE_AUTH.UPDATE.accessTokenNumUsesLimit),
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -91,7 +91,6 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -100,7 +99,6 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -177,12 +175,11 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
.min(1)
.optional()
.describe(GCP_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(GCP_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z.number().int().min(0).optional().describe(GCP_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(GCP_AUTH.UPDATE.accessTokenNumUsesLimit),
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -106,7 +106,6 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -115,7 +114,6 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -198,13 +196,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
.min(1)
.optional()
.describe(KUBERNETES_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z
.number()
.int()
.min(0)
.max(315360000)
.optional()
.describe(KUBERNETES_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z.number().int().min(0).optional().describe(KUBERNETES_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z
.number()
.int()
@ -214,7 +206,6 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -106,7 +106,6 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -115,7 +114,6 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -203,7 +201,6 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -212,7 +209,6 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -51,6 +51,8 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
orgId: req.body.organizationId
});
console.log("test");
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.body.organizationId,

@ -39,7 +39,6 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -48,7 +47,6 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -119,12 +117,11 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
.min(1)
.optional()
.describe(TOKEN_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(TOKEN_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z.number().int().min(0).optional().describe(TOKEN_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(TOKEN_AUTH.UPDATE.accessTokenNumUsesLimit),
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})

@ -107,7 +107,6 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.number()
.int()
.min(1)
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
@ -116,7 +115,6 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -198,13 +196,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
.min(1)
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z
.number()
.int()
.min(0)
.max(315360000)
.optional()
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
accessTokenTTL: z.number().int().min(0).optional().describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z
.number()
.int()
@ -214,7 +206,6 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
@ -371,7 +362,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
body: z.object({
description: z.string().trim().default("").describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.description),
numUsesLimit: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.numUsesLimit),
ttl: z.number().min(0).max(315360000).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.ttl)
ttl: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.ttl)
}),
response: {
200: z.object({

@ -78,7 +78,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
project: ProjectsSchema.pick({ name: true, id: true }),
roles: z.array(
z.object({
id: z.string(),

@ -292,39 +292,4 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
return { folders };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
description: "Get folder by id",
security: [
{
bearerAuth: []
}
],
params: z.object({
id: z.string().trim().describe(FOLDERS.GET_BY_ID.folderId)
}),
response: {
200: z.object({
folder: SecretFoldersSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const folder = await server.services.folder.getFolderById({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});
return { folder };
}
});
};

@ -1,13 +1,6 @@
import { z } from "zod";
import {
OrganizationsSchema,
OrgMembershipsSchema,
ProjectMembershipsSchema,
ProjectsSchema,
UserEncryptionKeysSchema,
UsersSchema
} from "@app/db/schemas";
import { OrganizationsSchema, OrgMembershipsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { ORGANIZATIONS } from "@app/lib/api-docs";
import { creationLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -37,7 +30,6 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
user: UsersSchema.pick({
username: true,
email: true,
isEmailVerified: true,
firstName: true,
lastName: true,
id: true
@ -111,54 +103,6 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "GET",
url: "/:organizationId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
schema: {
description: "Get organization user membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
organizationId: z.string().trim().describe(ORGANIZATIONS.GET_USER_MEMBERSHIP.organizationId),
membershipId: z.string().trim().describe(ORGANIZATIONS.GET_USER_MEMBERSHIP.membershipId)
}),
response: {
200: z.object({
membership: OrgMembershipsSchema.merge(
z.object({
user: UsersSchema.pick({
username: true,
email: true,
isEmailVerified: true,
firstName: true,
lastName: true,
id: true
}).merge(z.object({ publicKey: z.string().nullable() }))
})
).omit({ createdAt: true, updatedAt: true })
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.org.getOrgMembership({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId,
membershipId: req.params.membershipId
});
return { membership };
}
});
server.route({
method: "PATCH",
url: "/:organizationId/memberships/:membershipId",
@ -177,8 +121,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
membershipId: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.membershipId)
}),
body: z.object({
role: z.string().trim().optional().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.role),
isActive: z.boolean().optional().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.isActive)
role: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.role)
}),
response: {
200: z.object({
@ -186,17 +129,17 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
if (req.auth.actor !== ActorType.USER) return;
const membership = await server.services.org.updateOrgMembership({
userId: req.permission.id,
role: req.body.role,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId,
membershipId: req.params.membershipId,
actorOrgId: req.permission.orgId,
...req.body
actorOrgId: req.permission.orgId
});
return { membership };
}
@ -240,69 +183,6 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
// TODO: re-think endpoint structure in future so users only need to pass in membershipId bc organizationId is redundant
method: "GET",
url: "/:organizationId/memberships/:membershipId/project-memberships",
config: {
rateLimit: writeLimit
},
schema: {
description: "Get project memberships given organization membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
organizationId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.organizationId),
membershipId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.membershipId)
}),
response: {
200: z.object({
memberships: ProjectMembershipsSchema.extend({
user: UsersSchema.pick({
email: true,
username: true,
firstName: true,
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
project: ProjectsSchema.pick({ name: true, id: true }),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
)
})
.omit({ createdAt: true, updatedAt: true })
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const memberships = await server.services.org.listProjectMembershipsByOrgMembershipId({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId,
orgMembershipId: req.params.membershipId
});
return { memberships };
}
});
server.route({
method: "POST",
url: "/",

@ -4,8 +4,7 @@ import bcrypt from "bcrypt";
import { TAuthTokens, TAuthTokenSessions } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { UnauthorizedError } from "@app/lib/errors";
import { AuthModeJwtTokenPayload } from "../auth/auth-type";
import { TUserDALFactory } from "../user/user-dal";
@ -15,7 +14,6 @@ import { TCreateTokenForUserDTO, TIssueAuthTokenDTO, TokenType, TValidateTokenFo
type TAuthTokenServiceFactoryDep = {
tokenDAL: TTokenDALFactory;
userDAL: Pick<TUserDALFactory, "findById" | "transaction">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOne">;
};
export type TAuthTokenServiceFactory = ReturnType<typeof tokenServiceFactory>;
@ -69,7 +67,7 @@ export const getTokenConfig = (tokenType: TokenType) => {
}
};
export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAuthTokenServiceFactoryDep) => {
export const tokenServiceFactory = ({ tokenDAL, userDAL }: TAuthTokenServiceFactoryDep) => {
const createTokenForUser = async ({ type, userId, orgId }: TCreateTokenForUserDTO) => {
const { token, ...tkCfg } = getTokenConfig(type);
const appCfg = getConfig();
@ -156,16 +154,6 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
const user = await userDAL.findById(session.userId);
if (!user || !user.isAccepted) throw new UnauthorizedError({ name: "Token user not found" });
if (token.organizationId) {
const orgMembership = await orgMembershipDAL.findOne({
userId: user.id,
orgId: token.organizationId
});
if (!orgMembership) throw new ForbiddenRequestError({ message: "User not member of organization" });
if (!orgMembership.isActive) throw new ForbiddenRequestError({ message: "User not active in organization" });
}
return { user, tokenVersionId: token.tokenVersionId, orgId: token.organizationId };
};

@ -75,10 +75,8 @@ export const getCaCredentials = async ({
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const decryptedPrivateKey = kmsDecryptor({
const decryptedPrivateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caSecret.encryptedPrivateKey
});
@ -125,17 +123,15 @@ export const getCaCertChain = async ({
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const decryptedCaCert = kmsDecryptor({
const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCert.encryptedCertificate
});
const caCertObj = new x509.X509Certificate(decryptedCaCert);
const decryptedChain = kmsDecryptor({
const decryptedChain = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCert.encryptedCertificateChain
});
@ -172,11 +168,8 @@ export const rebuildCaCrl = async ({
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const privateKey = kmsDecryptor({
const privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caSecret.encryptedPrivateKey
});
@ -207,10 +200,8 @@ export const rebuildCaCrl = async ({
signingKey: sk
});
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: keyId
});
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(new Uint8Array(crl.rawData))
});

@ -25,7 +25,7 @@ type TCertificateAuthorityQueueFactoryDep = {
certificateAuthoritySecretDAL: TCertificateAuthoritySecretDALFactory;
certificateDAL: TCertificateDALFactory;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
queueService: TQueueServiceFactory;
};
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
@ -88,10 +88,8 @@ export const certificateAuthorityQueueFactory = ({
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: keyId
});
const privateKey = kmsDecryptor({
const privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caSecret.encryptedPrivateKey
});
@ -122,10 +120,8 @@ export const certificateAuthorityQueueFactory = ({
signingKey: sk
});
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: keyId
});
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(new Uint8Array(crl.rawData))
});

@ -53,7 +53,7 @@ type TCertificateAuthorityServiceFactoryDep = {
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
@ -154,14 +154,11 @@ export const certificateAuthorityServiceFactory = ({
tx
);
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
const keyId = await getProjectKmsCertificateKeyId({
projectId: project.id,
projectDAL,
kmsService
});
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: certificateManagerKmsId
});
if (type === CaType.ROOT) {
// note: create self-signed cert only applicable for root CA
@ -181,11 +178,13 @@ export const certificateAuthorityServiceFactory = ({
]
});
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(new Uint8Array(cert.rawData))
});
const { cipherTextBlob: encryptedCertificateChain } = kmsEncryptor({
const { cipherTextBlob: encryptedCertificateChain } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.alloc(0)
});
@ -209,7 +208,8 @@ export const certificateAuthorityServiceFactory = ({
signingKey: keys.privateKey
});
const { cipherTextBlob: encryptedCrl } = kmsEncryptor({
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(new Uint8Array(crl.rawData))
});
@ -224,7 +224,8 @@ export const certificateAuthorityServiceFactory = ({
// https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
const skObj = KeyObject.from(keys.privateKey);
const { cipherTextBlob: encryptedPrivateKey } = kmsEncryptor({
const { cipherTextBlob: encryptedPrivateKey } = await kmsService.encrypt({
kmsId: keyId,
plainText: skObj.export({
type: "pkcs8",
format: "der"
@ -448,17 +449,15 @@ export const certificateAuthorityServiceFactory = ({
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
const decryptedCaCert = kmsDecryptor({
const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCert.encryptedCertificate
});
@ -606,20 +605,19 @@ export const certificateAuthorityServiceFactory = ({
dn: parentCertSubject
});
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const kmsEncryptor = await kmsService.encryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const { cipherTextBlob: encryptedCertificate } = kmsEncryptor({
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(new Uint8Array(certObj.rawData))
});
const { cipherTextBlob: encryptedCertificateChain } = kmsEncryptor({
const { cipherTextBlob: encryptedCertificateChain } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(certificateChain)
});
@ -684,16 +682,14 @@ export const certificateAuthorityServiceFactory = ({
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" });
const certificateManagerKmsId = await getProjectKmsCertificateKeyId({
const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: certificateManagerKmsId
});
const decryptedCaCert = kmsDecryptor({
const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCert.encryptedCertificate
});
@ -800,10 +796,8 @@ export const certificateAuthorityServiceFactory = ({
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 } = kmsEncryptor({
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
});

@ -95,7 +95,7 @@ export type TGetCaCredentialsDTO = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
kmsService: Pick<TKmsServiceFactory, "decrypt" | "generateKmsKey">;
};
export type TGetCaCertChainDTO = {
@ -103,7 +103,7 @@ export type TGetCaCertChainDTO = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
kmsService: Pick<TKmsServiceFactory, "decrypt" | "generateKmsKey">;
};
export type TRebuildCaCrlDTO = {
@ -113,7 +113,7 @@ export type TRebuildCaCrlDTO = {
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
certificateDAL: Pick<TCertificateDALFactory, "find">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decryptWithKmsKey" | "encryptWithKmsKey">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "decrypt" | "encrypt">;
};
export type TRotateCaCrlTriggerDTO = {

@ -25,7 +25,7 @@ type TCertificateServiceFactoryDep = {
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "update">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
@ -164,16 +164,14 @@ export const certificateServiceFactory = ({
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const kmsDecryptor = await kmsService.decryptWithKmsKey({
kmsId: certificateManagerKeyId
});
const decryptedCert = kmsDecryptor({
const decryptedCert = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: certBody.encryptedCertificate
});

@ -57,7 +57,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
`${TableName.IdentityOidcAuth}.identityId`
);
})
.leftJoin(TableName.IdentityTokenAuth, (qb) => {
.leftJoin(TableName.IdentityOidcAuth, (qb) => {
qb.on(`${TableName.Identity}.authMethod`, db.raw("?", [IdentityAuthMethod.TOKEN_AUTH])).andOn(
`${TableName.Identity}.id`,
`${TableName.IdentityTokenAuth}.identityId`

@ -78,10 +78,7 @@ export const identityAwsAuthServiceFactory = ({
.map((accountId) => accountId.trim())
.some((accountId) => accountId === Account);
if (!isAccountAllowed)
throw new ForbiddenRequestError({
message: "Access denied: AWS account ID not allowed."
});
if (!isAccountAllowed) throw new UnauthorizedError();
}
if (identityAwsAuth.allowedPrincipalArns) {
@ -97,10 +94,7 @@ export const identityAwsAuthServiceFactory = ({
return regex.test(extractPrincipalArn(Arn));
});
if (!isArnAllowed)
throw new ForbiddenRequestError({
message: "Access denied: AWS principal ARN not allowed."
});
if (!isArnAllowed) throw new UnauthorizedError();
}
const identityAccessToken = await identityAwsAuthDAL.transaction(async (tx) => {

@ -17,7 +17,6 @@ export const validateAzureIdentity = async ({
const jwksUri = `https://login.microsoftonline.com/${tenantId}/discovery/keys`;
const decodedJwt = jwt.decode(azureJwt, { complete: true }) as TDecodedAzureAuthJwt;
const { kid } = decodedJwt.header;
const { data }: { data: TAzureJwksUriResponse } = await axios.get(jwksUri);
@ -28,13 +27,6 @@ export const validateAzureIdentity = async ({
const publicKey = `-----BEGIN CERTIFICATE-----\n${signingKey.x5c[0]}\n-----END CERTIFICATE-----`;
// Case: This can happen when the user uses a custom resource (such as https://management.azure.com&client_id=value).
// In this case, the audience in the decoded JWT will not have a trailing slash, but the resource will.
if (!decodedJwt.payload.aud.endsWith("/") && resource.endsWith("/")) {
// eslint-disable-next-line no-param-reassign
resource = resource.slice(0, -1);
}
return jwt.verify(azureJwt, publicKey, {
audience: resource,
issuer: `https://sts.windows.net/${tenantId}/`

@ -81,10 +81,7 @@ export const identityGcpAuthServiceFactory = ({
.map((serviceAccount) => serviceAccount.trim())
.some((serviceAccount) => serviceAccount === gcpIdentityDetails.email);
if (!isServiceAccountAllowed)
throw new ForbiddenRequestError({
message: "Access denied: GCP service account not allowed."
});
if (!isServiceAccountAllowed) throw new UnauthorizedError();
}
if (identityGcpAuth.type === "gce" && identityGcpAuth.allowedProjects && gcpIdentityDetails.computeEngineDetails) {
@ -95,10 +92,7 @@ export const identityGcpAuthServiceFactory = ({
.map((project) => project.trim())
.some((project) => project === gcpIdentityDetails.computeEngineDetails?.project_id);
if (!isProjectAllowed)
throw new ForbiddenRequestError({
message: "Access denied: GCP project not allowed."
});
if (!isProjectAllowed) throw new UnauthorizedError();
}
if (identityGcpAuth.type === "gce" && identityGcpAuth.allowedZones && gcpIdentityDetails.computeEngineDetails) {
@ -107,10 +101,7 @@ export const identityGcpAuthServiceFactory = ({
.map((zone) => zone.trim())
.some((zone) => zone === gcpIdentityDetails.computeEngineDetails?.zone);
if (!isZoneAllowed)
throw new ForbiddenRequestError({
message: "Access denied: GCP zone not allowed."
});
if (!isZoneAllowed) throw new UnauthorizedError();
}
const identityAccessToken = await identityGcpAuthDAL.transaction(async (tx) => {

@ -139,10 +139,7 @@ export const identityKubernetesAuthServiceFactory = ({
.map((namespace) => namespace.trim())
.some((namespace) => namespace === targetNamespace);
if (!isNamespaceAllowed)
throw new ForbiddenRequestError({
message: "Access denied: K8s namespace not allowed."
});
if (!isNamespaceAllowed) throw new UnauthorizedError();
}
if (identityKubernetesAuth.allowedNames) {
@ -153,10 +150,7 @@ export const identityKubernetesAuthServiceFactory = ({
.map((name) => name.trim())
.some((name) => name === targetName);
if (!isNameAllowed)
throw new ForbiddenRequestError({
message: "Access denied: K8s name not allowed."
});
if (!isNameAllowed) throw new UnauthorizedError();
}
if (identityKubernetesAuth.allowedAudience) {
@ -165,10 +159,7 @@ export const identityKubernetesAuthServiceFactory = ({
(audience) => audience === identityKubernetesAuth.allowedAudience
);
if (!isAudienceAllowed)
throw new ForbiddenRequestError({
message: "Access denied: K8s audience not allowed."
});
if (!isAudienceAllowed) throw new UnauthorizedError();
}
const identityAccessToken = await identityKubernetesAuthDAL.transaction(async (tx) => {

@ -124,17 +124,13 @@ export const identityOidcAuthServiceFactory = ({
if (identityOidcAuth.boundSubject) {
if (tokenData.sub !== identityOidcAuth.boundSubject) {
throw new ForbiddenRequestError({
message: "Access denied: OIDC subject not allowed."
});
throw new UnauthorizedError();
}
}
if (identityOidcAuth.boundAudiences) {
if (!identityOidcAuth.boundAudiences.split(", ").includes(tokenData.aud)) {
throw new ForbiddenRequestError({
message: "Access denied: OIDC audience not allowed."
});
throw new UnauthorizedError();
}
}
@ -143,9 +139,7 @@ export const identityOidcAuthServiceFactory = ({
const claimValue = (identityOidcAuth.boundClaims as Record<string, string>)[claimKey];
// handle both single and multi-valued claims
if (!claimValue.split(", ").some((claimEntry) => tokenData[claimKey] === claimEntry)) {
throw new ForbiddenRequestError({
message: "Access denied: OIDC claim not allowed."
});
throw new UnauthorizedError();
}
});
}

@ -574,14 +574,14 @@ export const integrationAuthServiceFactory = ({
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessId, accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
const kms = new AWS.KMS({
AWS.config.update({
region,
credentials: {
accessKeyId: String(accessId),
secretAccessKey: accessToken
}
});
const kms = new AWS.KMS();
const aliases = await kms.listAliases({}).promise();
const keyAliases = aliases.Aliases!.filter((alias) => {

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

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

@ -1,64 +0,0 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { KmsKeysSchema, TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TKmsKeyDALFactory = ReturnType<typeof kmskeyDALFactory>;
export const kmskeyDALFactory = (db: TDbClient) => {
const kmsOrm = ormify(db, TableName.KmsKey);
const findByIdWithAssociatedKms = async (id: string, tx?: Knex) => {
try {
const result = await (tx || db.replicaNode())(TableName.KmsKey)
.where({ [`${TableName.KmsKey}.id` as "id"]: id })
.leftJoin(TableName.InternalKms, `${TableName.KmsKey}.id`, `${TableName.InternalKms}.kmsKeyId`)
.leftJoin(TableName.ExternalKms, `${TableName.KmsKey}.id`, `${TableName.ExternalKms}.kmsKeyId`)
.first()
.select(selectAllTableCols(TableName.KmsKey))
.select(
db.ref("id").withSchema(TableName.InternalKms).as("internalKmsId"),
db.ref("encryptedKey").withSchema(TableName.InternalKms).as("internalKmsEncryptedKey"),
db.ref("encryptionAlgorithm").withSchema(TableName.InternalKms).as("internalKmsEncryptionAlgorithm"),
db.ref("version").withSchema(TableName.InternalKms).as("internalKmsVersion"),
db.ref("id").withSchema(TableName.InternalKms).as("internalKmsId")
)
.select(
db.ref("id").withSchema(TableName.ExternalKms).as("externalKmsId"),
db.ref("provider").withSchema(TableName.ExternalKms).as("externalKmsProvider"),
db.ref("encryptedProviderInputs").withSchema(TableName.ExternalKms).as("externalKmsEncryptedProviderInput"),
db.ref("status").withSchema(TableName.ExternalKms).as("externalKmsStatus"),
db.ref("statusDetails").withSchema(TableName.ExternalKms).as("externalKmsStatusDetails")
);
const data = {
...KmsKeysSchema.parse(result),
isExternal: Boolean(result?.externalKmsId),
externalKms: result?.externalKmsId
? {
id: result.externalKmsId,
provider: result.externalKmsProvider,
encryptedProviderInput: result.externalKmsEncryptedProviderInput,
status: result.externalKmsStatus,
statusDetails: result.externalKmsStatusDetails
}
: undefined,
internalKms: result?.internalKmsId
? {
id: result.internalKmsId,
encryptedKey: result.internalKmsEncryptedKey,
encryptionAlgorithm: result.internalKmsEncryptionAlgorithm,
version: result.internalKmsVersion
}
: undefined
};
return data;
} catch (error) {
throw new DatabaseError({ error, name: "Find by id" });
}
};
return { ...kmsOrm, findByIdWithAssociatedKms };
};

@ -1,34 +1,18 @@
import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { randomSecureBytes } from "@app/lib/crypto";
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TOrgDALFactory } from "../org/org-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { TInternalKmsDALFactory } from "./internal-kms-dal";
import { TKmsKeyDALFactory } from "./kms-key-dal";
import { TKmsDALFactory } from "./kms-dal";
import { TKmsRootConfigDALFactory } from "./kms-root-config-dal";
import {
TDecryptWithKeyDTO,
TDecryptWithKmsDTO,
TEncryptionWithKeyDTO,
TEncryptWithKmsDTO,
TGenerateKMSDTO
} from "./kms-types";
import { TDecryptWithKmsDTO, TEncryptWithKmsDTO, TGenerateKMSDTO } from "./kms-types";
type TKmsServiceFactoryDep = {
kmsDAL: TKmsKeyDALFactory;
projectDAL: Pick<TProjectDALFactory, "findById" | "updateById" | "transaction">;
orgDAL: Pick<TOrgDALFactory, "findById" | "updateById" | "transaction">;
kmsDAL: TKmsDALFactory;
kmsRootConfigDAL: Pick<TKmsRootConfigDALFactory, "findById" | "create">;
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "waitTillReady" | "setItemWithExpiry">;
internalKmsDAL: Pick<TInternalKmsDALFactory, "create">;
};
export type TKmsServiceFactory = ReturnType<typeof kmsServiceFactory>;
@ -41,161 +25,54 @@ const KMS_ROOT_CREATION_WAIT_TIME = 10;
// akhilmhdh: Don't edit this value. This is measured for blob concatination in kms
const KMS_VERSION = "v01";
const KMS_VERSION_BLOB_LENGTH = 3;
export const kmsServiceFactory = ({
kmsDAL,
kmsRootConfigDAL,
keyStore,
internalKmsDAL,
orgDAL,
projectDAL
}: TKmsServiceFactoryDep) => {
export const kmsServiceFactory = ({ kmsDAL, kmsRootConfigDAL, keyStore }: TKmsServiceFactoryDep) => {
let ROOT_ENCRYPTION_KEY = Buffer.alloc(0);
// this is used symmetric encryption
const generateKmsKey = async ({ orgId, isReserved = true, tx, slug }: TGenerateKMSDTO) => {
const generateKmsKey = async ({ scopeId, scopeType, isReserved = true, tx }: TGenerateKMSDTO) => {
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const kmsKeyMaterial = randomSecureBytes(32);
const encryptedKeyMaterial = cipher.encrypt(kmsKeyMaterial, ROOT_ENCRYPTION_KEY);
const sanitizedSlug = slug ? slugify(slug) : slugify(alphaNumericNanoId(8).toLowerCase());
const dbQuery = async (db: Knex) => {
const kmsDoc = await kmsDAL.create(
{
slug: sanitizedSlug,
orgId,
isReserved
},
db
);
await internalKmsDAL.create(
{
version: 1,
encryptedKey: encryptedKeyMaterial,
encryptionAlgorithm: SymmetricEncryption.AES_GCM_256,
kmsKeyId: kmsDoc.id
},
db
);
return kmsDoc;
};
if (tx) return dbQuery(tx);
const doc = await kmsDAL.transaction(async (tx2) => dbQuery(tx2));
const { encryptedKey, ...doc } = await kmsDAL.create(
{
version: 1,
encryptedKey: encryptedKeyMaterial,
encryptionAlgorithm: SymmetricEncryption.AES_GCM_256,
isReserved,
orgId: scopeType === "org" ? scopeId : undefined,
projectId: scopeType === "project" ? scopeId : undefined
},
tx
);
return doc;
};
const encryptWithKmsKey = async ({ kmsId }: Omit<TEncryptWithKmsDTO, "plainText">) => {
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
const encrypt = async ({ kmsId, plainText }: TEncryptWithKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
return ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
const kmsKey = cipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
const encryptedPlainTextBlob = cipher.encrypt(plainText, kmsKey);
// Buffer#1 encrypted text + Buffer#2 version number
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
return { cipherTextBlob };
};
const kmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY);
const encryptedPlainTextBlob = cipher.encrypt(plainText, kmsKey);
// Buffer#1 encrypted text + Buffer#2 version number
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
return { cipherTextBlob };
};
const encryptWithInputKey = async ({ key }: Omit<TEncryptionWithKeyDTO, "plainText">) => {
const decrypt = async ({ cipherTextBlob: versionedCipherTextBlob, kmsId }: TDecryptWithKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId);
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
return ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
const encryptedPlainTextBlob = cipher.encrypt(plainText, key);
// Buffer#1 encrypted text + Buffer#2 version number
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
return { cipherTextBlob };
};
};
const kmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY);
const decryptWithKmsKey = async ({ kmsId }: Omit<TDecryptWithKmsDTO, "cipherTextBlob">) => {
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
if (!kmsDoc) throw new BadRequestError({ message: "KMS ID not found" });
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const kmsKey = cipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
return ({ cipherTextBlob: versionedCipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
const decryptedBlob = cipher.decrypt(cipherTextBlob, kmsKey);
return decryptedBlob;
};
};
const decryptWithInputKey = async ({ key }: Omit<TDecryptWithKeyDTO, "cipherTextBlob">) => {
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
return ({ cipherTextBlob: versionedCipherTextBlob }: Pick<TDecryptWithKeyDTO, "cipherTextBlob">) => {
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
const decryptedBlob = cipher.decrypt(cipherTextBlob, key);
return decryptedBlob;
};
};
const getOrgKmsKeyId = async (orgId: string) => {
const keyId = await orgDAL.transaction(async (tx) => {
const org = await orgDAL.findById(orgId, tx);
if (!org) {
throw new BadRequestError({ message: "Org not found" });
}
if (!org.kmsDefaultKeyId) {
// create default kms key for certificate service
const key = await generateKmsKey({
isReserved: true,
orgId: org.id,
tx
});
await orgDAL.updateById(
org.id,
{
kmsDefaultKeyId: key.id
},
tx
);
return key.id;
}
return org.kmsDefaultKeyId;
});
return keyId;
};
const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
const keyId = await projectDAL.transaction(async (tx) => {
const project = await projectDAL.findById(projectId, tx);
if (!project) {
throw new BadRequestError({ message: "Project not found" });
}
if (!project.kmsSecretManagerKeyId) {
// create default kms key for certificate service
const key = await generateKmsKey({
isReserved: true,
orgId: project.orgId,
tx
});
await projectDAL.updateById(
projectId,
{
kmsSecretManagerKeyId: key.id
},
tx
);
return key.id;
}
return project.kmsSecretManagerKeyId;
});
return keyId;
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
const decryptedBlob = cipher.decrypt(cipherTextBlob, kmsKey);
return decryptedBlob;
};
const startService = async () => {
@ -246,11 +123,7 @@ export const kmsServiceFactory = ({
return {
startService,
generateKmsKey,
encryptWithKmsKey,
encryptWithInputKey,
decryptWithKmsKey,
decryptWithInputKey,
getOrgKmsKeyId,
getProjectSecretManagerKmsKeyId
encrypt,
decrypt
};
};

@ -1,9 +1,9 @@
import { Knex } from "knex";
export type TGenerateKMSDTO = {
orgId: string;
scopeType: "project" | "org";
scopeId: string;
isReserved?: boolean;
slug?: string;
tx?: Knex;
};
@ -12,17 +12,7 @@ export type TEncryptWithKmsDTO = {
plainText: Buffer;
};
export type TEncryptionWithKeyDTO = {
key: Buffer;
plainText: Buffer;
};
export type TDecryptWithKmsDTO = {
kmsId: string;
cipherTextBlob: Buffer;
};
export type TDecryptWithKeyDTO = {
key: Buffer;
cipherTextBlob: Buffer;
};

@ -1,6 +1,5 @@
import { TDbClient } from "@app/db";
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TOrgMembershipDALFactory = ReturnType<typeof orgMembershipDALFactory>;
@ -8,51 +7,7 @@ export type TOrgMembershipDALFactory = ReturnType<typeof orgMembershipDALFactory
export const orgMembershipDALFactory = (db: TDbClient) => {
const orgMembershipOrm = ormify(db, TableName.OrgMembership);
const findOrgMembershipById = async (membershipId: string) => {
try {
const member = await db
.replicaNode()(TableName.OrgMembership)
.where(`${TableName.OrgMembership}.id`, membershipId)
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
.leftJoin<TUserEncryptionKeys>(
TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`,
`${TableName.Users}.id`
)
.select(
db.ref("id").withSchema(TableName.OrgMembership),
db.ref("inviteEmail").withSchema(TableName.OrgMembership),
db.ref("orgId").withSchema(TableName.OrgMembership),
db.ref("role").withSchema(TableName.OrgMembership),
db.ref("roleId").withSchema(TableName.OrgMembership),
db.ref("status").withSchema(TableName.OrgMembership),
db.ref("isActive").withSchema(TableName.OrgMembership),
db.ref("email").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users),
db.ref("lastName").withSchema(TableName.Users),
db.ref("isEmailVerified").withSchema(TableName.Users),
db.ref("id").withSchema(TableName.Users).as("userId"),
db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
)
.where({ isGhost: false }) // MAKE SURE USER IS NOT A GHOST USER
.first();
if (!member) return undefined;
const { email, isEmailVerified, username, firstName, lastName, userId, publicKey, ...data } = member;
return {
...data,
user: { email, isEmailVerified, username, firstName, lastName, id: userId, publicKey }
};
} catch (error) {
throw new DatabaseError({ error, name: "Find org membership by id" });
}
};
return {
...orgMembershipOrm,
findOrgMembershipById
...orgMembershipOrm
};
};

@ -74,9 +74,7 @@ export const orgDALFactory = (db: TDbClient) => {
db.ref("role").withSchema(TableName.OrgMembership),
db.ref("roleId").withSchema(TableName.OrgMembership),
db.ref("status").withSchema(TableName.OrgMembership),
db.ref("isActive").withSchema(TableName.OrgMembership),
db.ref("email").withSchema(TableName.Users),
db.ref("isEmailVerified").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users),
db.ref("lastName").withSchema(TableName.Users),
@ -85,9 +83,9 @@ export const orgDALFactory = (db: TDbClient) => {
)
.where({ isGhost: false }); // MAKE SURE USER IS NOT A GHOST USER
return members.map(({ email, isEmailVerified, username, firstName, lastName, userId, publicKey, ...data }) => ({
return members.map(({ email, username, firstName, lastName, userId, publicKey, ...data }) => ({
...data,
user: { email, isEmailVerified, username, firstName, lastName, id: userId, publicKey }
user: { email, username, firstName, lastName, id: userId, publicKey }
}));
} catch (error) {
throw new DatabaseError({ error, name: "Find all org members" });
@ -209,9 +207,9 @@ export const orgDALFactory = (db: TDbClient) => {
}
};
const updateById = async (orgId: string, data: Partial<TOrganizations>, tx?: Knex) => {
const updateById = async (orgId: string, data: Partial<TOrganizations>) => {
try {
const [org] = await (tx || db)(TableName.Organization)
const [org] = await db(TableName.Organization)
.where({ id: orgId })
.update({ ...data })
.returning("*");

@ -42,61 +42,6 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol
return role;
};
const getRole = async (
userId: string,
orgId: string,
roleId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
) => {
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
switch (roleId) {
case "b11b49a9-09a9-4443-916a-4246f9ff2c69": {
return {
id: roleId,
orgId,
name: "Admin",
slug: "admin",
description: "Complete administration access over the organization",
permissions: packRules(orgAdminPermissions.rules),
createdAt: new Date(),
updatedAt: new Date()
};
}
case "b11b49a9-09a9-4443-916a-4246f9ff2c70": {
return {
id: roleId,
orgId,
name: "Member",
slug: "member",
description: "Non-administrative role in an organization",
permissions: packRules(orgMemberPermissions.rules),
createdAt: new Date(),
updatedAt: new Date()
};
}
case "b10d49a9-09a9-4443-916a-4246f9ff2c72": {
return {
id: "b10d49a9-09a9-4443-916a-4246f9ff2c72", // dummy user for zod validation in response
orgId,
name: "No Access",
slug: "no-access",
description: "No access to any resources in the organization",
permissions: packRules(orgNoAccessPermissions.rules),
createdAt: new Date(),
updatedAt: new Date()
};
}
default: {
const role = await orgRoleDAL.findOne({ id: roleId, orgId });
if (!role) throw new BadRequestError({ message: "Role not found", name: "Get role" });
return role;
}
}
};
const updateRole = async (
userId: string,
orgId: string,
@ -199,5 +144,5 @@ export const orgRoleServiceFactory = ({ orgRoleDAL, permissionService }: TOrgRol
return { permissions: packRules(permission.rules), membership };
};
return { createRole, getRole, updateRole, deleteRole, listRoles, getUserPermission };
return { createRole, updateRole, deleteRole, listRoles, getUserPermission };
};

@ -15,10 +15,9 @@ import { getConfig } from "@app/lib/config/env";
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
import { generateSymmetricKey, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { isDisposableEmail } from "@app/lib/validator";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { ActorAuthMethod, ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type";
@ -39,9 +38,7 @@ import {
TFindAllWorkspacesDTO,
TFindOrgMembersByEmailDTO,
TGetOrgGroupsDTO,
TGetOrgMembershipDTO,
TInviteUserToOrgDTO,
TListProjectMembershipsByOrgMembershipIdDTO,
TUpdateOrgDTO,
TUpdateOrgMembershipDTO,
TVerifyUserToOrgDTO
@ -57,7 +54,6 @@ type TOrgServiceFactoryDep = {
projectDAL: TProjectDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findProjectMembershipsByUserId" | "delete">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOne">;
incidentContactDAL: TIncidentContactsDALFactory;
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">;
smtpService: TSmtpService;
@ -83,7 +79,6 @@ export const orgServiceFactory = ({
projectDAL,
projectMembershipDAL,
projectKeyDAL,
orgMembershipDAL,
tokenService,
orgBotDAL,
licenseService,
@ -149,7 +144,10 @@ export const orgServiceFactory = ({
return members;
};
const findAllWorkspaces = async ({ actor, actorId, orgId }: TFindAllWorkspacesDTO) => {
const findAllWorkspaces = async ({ actor, actorId, actorOrgId, actorAuthMethod, orgId }: TFindAllWorkspacesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
const organizationWorkspaceIds = new Set((await projectDAL.find({ orgId })).map((workspace) => workspace.id));
let workspaces: (TProjects & { organization: string } & {
@ -209,8 +207,7 @@ export const orgServiceFactory = ({
orgId,
userId: user.id,
role: OrgMembershipRole.Admin,
status: OrgMembershipStatus.Accepted,
isActive: true
status: OrgMembershipStatus.Accepted
};
await orgDAL.createMembership(createMembershipData, tx);
@ -314,8 +311,7 @@ export const orgServiceFactory = ({
userId,
orgId: org.id,
role: OrgMembershipRole.Admin,
status: OrgMembershipStatus.Accepted,
isActive: true
status: OrgMembershipStatus.Accepted
},
tx
);
@ -369,7 +365,6 @@ export const orgServiceFactory = ({
* */
const updateOrgMembership = async ({
role,
isActive,
orgId,
userId,
membershipId,
@ -379,16 +374,8 @@ export const orgServiceFactory = ({
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
const foundMembership = await orgMembershipDAL.findOne({
id: membershipId,
orgId
});
if (!foundMembership) throw new NotFoundError({ message: "Failed to find organization membership" });
if (foundMembership.userId === userId)
throw new BadRequestError({ message: "Cannot update own organization membership" });
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
if (role && isCustomRole) {
if (isCustomRole) {
const customRole = await orgRoleDAL.findOne({ slug: role, orgId });
if (!customRole) throw new BadRequestError({ name: "Update membership", message: "Role not found" });
@ -408,7 +395,7 @@ export const orgServiceFactory = ({
return membership;
}
const [membership] = await orgDAL.updateMembership({ id: membershipId, orgId }, { role, roleId: null, isActive });
const [membership] = await orgDAL.updateMembership({ id: membershipId, orgId }, { role, roleId: null });
return membership;
};
/*
@ -473,8 +460,7 @@ export const orgServiceFactory = ({
inviteEmail: inviteeEmail,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited,
isActive: true
status: OrgMembershipStatus.Invited
},
tx
);
@ -505,8 +491,7 @@ export const orgServiceFactory = ({
orgId,
userId: user.id,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited,
isActive: true
status: OrgMembershipStatus.Invited
},
tx
);
@ -599,24 +584,6 @@ export const orgServiceFactory = ({
return { token, user };
};
const getOrgMembership = async ({
membershipId,
orgId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TGetOrgMembershipDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const membership = await orgMembershipDAL.findOrgMembershipById(membershipId);
if (!membership) throw new NotFoundError({ message: "Failed to find organization membership" });
if (membership.orgId !== orgId) throw new NotFoundError({ message: "Failed to find organization membership" });
return membership;
};
const deleteOrgMembership = async ({
orgId,
userId,
@ -640,26 +607,6 @@ export const orgServiceFactory = ({
return deletedMembership;
};
const listProjectMembershipsByOrgMembershipId = async ({
orgMembershipId,
orgId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TListProjectMembershipsByOrgMembershipIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
const membership = await orgMembershipDAL.findOrgMembershipById(orgMembershipId);
if (!membership) throw new NotFoundError({ message: "Failed to find organization membership" });
if (membership.orgId !== orgId) throw new NotFoundError({ message: "Failed to find organization membership" });
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, membership.user.id);
return projectMemberships;
};
/*
* CRUD operations of incident contacts
* */
@ -720,7 +667,6 @@ export const orgServiceFactory = ({
findOrgMembersByUsername,
createOrganization,
deleteOrganizationById,
getOrgMembership,
deleteOrgMembership,
findAllWorkspaces,
addGhostUser,
@ -729,7 +675,6 @@ export const orgServiceFactory = ({
findIncidentContacts,
createIncidentContact,
deleteIncidentContact,
getOrgGroups,
listProjectMembershipsByOrgMembershipId
getOrgGroups
};
};

@ -6,16 +6,11 @@ export type TUpdateOrgMembershipDTO = {
userId: string;
orgId: string;
membershipId: string;
role?: string;
isActive?: boolean;
role: string;
actorOrgId: string | undefined;
actorAuthMethod: ActorAuthMethod;
};
export type TGetOrgMembershipDTO = {
membershipId: string;
} & TOrgPermission;
export type TDeleteOrgMembershipDTO = {
userId: string;
orgId: string;
@ -60,7 +55,3 @@ export type TUpdateOrgDTO = {
} & TOrgPermission;
export type TGetOrgGroupsDTO = TOrgPermission;
export type TListProjectMembershipsByOrgMembershipIdDTO = {
orgMembershipId: string;
} & TOrgPermission;

@ -16,7 +16,6 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
const docs = await db
.replicaNode()(TableName.ProjectMembership)
.where({ [`${TableName.ProjectMembership}.projectId` as "projectId"]: projectId })
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
.where((qb) => {
if (filter.usernames) {
@ -59,22 +58,17 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
db.ref("isTemporary").withSchema(TableName.ProjectUserMembershipRole),
db.ref("temporaryRange").withSchema(TableName.ProjectUserMembershipRole),
db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole),
db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole),
db.ref("name").as("projectName").withSchema(TableName.Project)
db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole)
)
.where({ isGhost: false });
const members = sqlNestRelationships({
data: docs,
parentMapper: ({ email, firstName, username, lastName, publicKey, isGhost, id, userId, projectName }) => ({
parentMapper: ({ email, firstName, username, lastName, publicKey, isGhost, id, userId }) => ({
id,
userId,
projectId,
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost },
project: {
id: projectId,
name: projectName
}
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost }
}),
key: "id",
childrenMapper: [
@ -157,95 +151,14 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
const findProjectMembershipsByUserId = async (orgId: string, userId: string) => {
try {
const docs = await db
const memberships = await db
.replicaNode()(TableName.ProjectMembership)
.where({ userId })
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.id`, userId)
.where(`${TableName.Project}.orgId`, orgId)
.join<TUserEncryptionKeys>(
TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`,
`${TableName.Users}.id`
)
.join(
TableName.ProjectUserMembershipRole,
`${TableName.ProjectUserMembershipRole}.projectMembershipId`,
`${TableName.ProjectMembership}.id`
)
.leftJoin(
TableName.ProjectRoles,
`${TableName.ProjectUserMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.select(
db.ref("id").withSchema(TableName.ProjectMembership),
db.ref("isGhost").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users),
db.ref("email").withSchema(TableName.Users),
db.ref("publicKey").withSchema(TableName.UserEncryptionKey),
db.ref("firstName").withSchema(TableName.Users),
db.ref("lastName").withSchema(TableName.Users),
db.ref("id").withSchema(TableName.Users).as("userId"),
db.ref("role").withSchema(TableName.ProjectUserMembershipRole),
db.ref("id").withSchema(TableName.ProjectUserMembershipRole).as("membershipRoleId"),
db.ref("customRoleId").withSchema(TableName.ProjectUserMembershipRole),
db.ref("name").withSchema(TableName.ProjectRoles).as("customRoleName"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
db.ref("temporaryMode").withSchema(TableName.ProjectUserMembershipRole),
db.ref("isTemporary").withSchema(TableName.ProjectUserMembershipRole),
db.ref("temporaryRange").withSchema(TableName.ProjectUserMembershipRole),
db.ref("temporaryAccessStartTime").withSchema(TableName.ProjectUserMembershipRole),
db.ref("temporaryAccessEndTime").withSchema(TableName.ProjectUserMembershipRole),
db.ref("name").as("projectName").withSchema(TableName.Project),
db.ref("id").as("projectId").withSchema(TableName.Project)
)
.where({ isGhost: false });
.where({ [`${TableName.Project}.orgId` as "orgId"]: orgId })
.select(selectAllTableCols(TableName.ProjectMembership));
const members = sqlNestRelationships({
data: docs,
parentMapper: ({ email, firstName, username, lastName, publicKey, isGhost, id, projectId, projectName }) => ({
id,
userId,
projectId,
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost },
project: {
id: projectId,
name: projectName
}
}),
key: "id",
childrenMapper: [
{
label: "roles" as const,
key: "membershipRoleId",
mapper: ({
role,
customRoleId,
customRoleName,
customRoleSlug,
membershipRoleId,
temporaryRange,
temporaryMode,
temporaryAccessEndTime,
temporaryAccessStartTime,
isTemporary
}) => ({
id: membershipRoleId,
role,
customRoleId,
customRoleName,
customRoleSlug,
temporaryRange,
temporaryMode,
temporaryAccessEndTime,
temporaryAccessStartTime,
isTemporary
})
}
]
});
return members;
return memberships;
} catch (error) {
throw new DatabaseError({ error, name: "Find project memberships by user id" });
}

@ -71,8 +71,9 @@ export const getProjectKmsCertificateKeyId = async ({
if (!project.kmsCertificateKeyId) {
// create default kms key for certificate service
const key = await kmsService.generateKmsKey({
scopeId: projectId,
scopeType: "project",
isReserved: true,
orgId: project.orgId,
tx
});

@ -322,7 +322,7 @@ export const secretFolderDALFactory = (db: TDbClient) => {
.first();
if (folder) {
const { envId, envName, envSlug, ...el } = folder;
return { ...el, environment: { envId, envName, envSlug }, envId };
return { ...el, environment: { envId, envName, envSlug } };
}
} catch (error) {
throw new DatabaseError({ error, name: "Find by id" });

@ -6,7 +6,7 @@ import { TSecretFoldersInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError } from "@app/lib/errors";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
@ -14,7 +14,6 @@ import { TSecretFolderDALFactory } from "./secret-folder-dal";
import {
TCreateFolderDTO,
TDeleteFolderDTO,
TGetFolderByIdDTO,
TGetFolderDTO,
TUpdateFolderDTO,
TUpdateManyFoldersDTO
@ -369,22 +368,11 @@ export const secretFolderServiceFactory = ({
return folders;
};
const getFolderById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TGetFolderByIdDTO) => {
const folder = await folderDAL.findById(id);
if (!folder) throw new NotFoundError({ message: "folder not found" });
// folder list is allowed to be read by anyone
// permission to check does user has access
await permissionService.getProjectPermission(actor, actorId, folder.projectId, actorAuthMethod, actorOrgId);
return folder;
};
return {
createFolder,
updateFolder,
updateManyFolders,
deleteFolder,
getFolders,
getFolderById
getFolders
};
};

@ -37,7 +37,3 @@ export type TGetFolderDTO = {
environment: string;
path: string;
} & TProjectPermission;
export type TGetFolderByIdDTO = {
id: string;
} & Omit<TProjectPermission, "projectId">;

@ -1,9 +1,9 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TSecretSharing } from "@app/db/schemas";
import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import { ormify } from "@app/lib/knex";
export type TSecretSharingDALFactory = ReturnType<typeof secretSharingDALFactory>;
@ -13,58 +13,15 @@ export const secretSharingDALFactory = (db: TDbClient) => {
const pruneExpiredSharedSecrets = async (tx?: Knex) => {
try {
const today = new Date();
const docs = await (tx || db)(TableName.SecretSharing)
.where("expiresAt", "<", today)
.andWhere("encryptedValue", "<>", "")
.update({
encryptedValue: "",
tag: "",
iv: "",
hashedHex: ""
});
const docs = await (tx || db)(TableName.SecretSharing).where("expiresAt", "<", today).del();
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "pruneExpiredSharedSecrets" });
}
};
const findActiveSharedSecrets = async (filters: Partial<TSecretSharing>, tx?: Knex) => {
try {
const now = new Date();
return await (tx || db)(TableName.SecretSharing)
.where(filters)
.andWhere("expiresAt", ">", now)
.andWhere("encryptedValue", "<>", "")
.select(selectAllTableCols(TableName.SecretSharing))
.orderBy("expiresAt", "asc");
} catch (error) {
throw new DatabaseError({
error,
name: "Find Active Shared Secrets"
});
}
};
const softDeleteById = async (id: string) => {
try {
await sharedSecretOrm.updateById(id, {
encryptedValue: "",
iv: "",
tag: "",
hashedHex: ""
});
} catch (error) {
throw new DatabaseError({
error,
name: "Soft Delete Shared Secret"
});
}
};
return {
...sharedSecretOrm,
pruneExpiredSharedSecrets,
softDeleteById,
findActiveSharedSecrets
pruneExpiredSharedSecrets
};
};

@ -101,7 +101,7 @@ export const secretSharingServiceFactory = ({
const { actor, actorId, orgId, actorAuthMethod, actorOrgId } = getSharedSecretsInput;
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
if (!permission) throw new UnauthorizedError({ name: "User not in org" });
const userSharedSecrets = await secretSharingDAL.findActiveSharedSecrets({ userId: actorId, orgId });
const userSharedSecrets = await secretSharingDAL.find({ userId: actorId, orgId }, { sort: [["expiresAt", "asc"]] });
return userSharedSecrets;
};
@ -113,7 +113,7 @@ export const secretSharingServiceFactory = ({
}
if (sharedSecret.expiresAfterViews != null && sharedSecret.expiresAfterViews >= 0) {
if (sharedSecret.expiresAfterViews === 0) {
await secretSharingDAL.softDeleteById(sharedSecretId);
await secretSharingDAL.deleteById(sharedSecretId);
return;
}
await secretSharingDAL.updateById(sharedSecretId, { $decr: { expiresAfterViews: 1 } });

@ -642,7 +642,7 @@ export const secretQueueFactory = ({
});
queueService.start(QueueName.SecretWebhook, async (job) => {
await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL, projectDAL });
await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL });
});
return {

@ -1,7 +1,6 @@
import bcrypt from "bcrypt";
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
@ -21,7 +20,6 @@ type TSuperAdminServiceFactoryDep = {
authService: Pick<TAuthLoginFactory, "generateUserTokens">;
orgService: Pick<TOrgServiceFactory, "createOrganization">;
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem">;
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures">;
};
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
@ -38,8 +36,7 @@ export const superAdminServiceFactory = ({
userDAL,
authService,
orgService,
keyStore,
licenseService
keyStore
}: TSuperAdminServiceFactoryDep) => {
const initServerCfg = async () => {
// TODO(akhilmhdh): bad pattern time less change this later to me itself
@ -222,12 +219,6 @@ export const superAdminServiceFactory = ({
};
const deleteUser = async (userId: string) => {
if (!licenseService.onPremFeatures?.instanceUserManagement) {
throw new BadRequestError({
message: "Failed to delete user due to plan restriction. Upgrade to Infisical's Pro plan."
});
}
const user = await userDAL.deleteById(userId);
return user;
};

@ -9,7 +9,6 @@ import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TWebhookDALFactory } from "./webhook-dal";
import { WebhookType } from "./webhook-types";
@ -67,16 +66,11 @@ export const triggerWebhookRequest = async (webhook: TWebhooks, data: Record<str
export const getWebhookPayload = (
eventName: string,
details: {
workspaceName: string;
workspaceId: string;
environment: string;
secretPath?: string;
type?: string | null;
}
workspaceId: string,
environment: string,
secretPath?: string,
type?: string | null
) => {
const { workspaceName, workspaceId, environment, secretPath, type } = details;
switch (type) {
case WebhookType.SLACK:
return {
@ -86,8 +80,8 @@ export const getWebhookPayload = (
color: "#E7F256",
fields: [
{
title: "Project",
value: workspaceName,
title: "Workspace ID",
value: workspaceId,
short: false
},
{
@ -123,9 +117,7 @@ export type TFnTriggerWebhookDTO = {
environment: string;
webhookDAL: Pick<TWebhookDALFactory, "findAllWebhooks" | "transaction" | "update" | "bulkUpdate">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findById">;
};
// this is reusable function
// used in secret queue to trigger webhook and update status when secrets changes
export const fnTriggerWebhook = async ({
@ -133,8 +125,7 @@ export const fnTriggerWebhook = async ({
secretPath,
projectId,
webhookDAL,
projectEnvDAL,
projectDAL
projectEnvDAL
}: TFnTriggerWebhookDTO) => {
const webhooks = await webhookDAL.findAllWebhooks(projectId, environment);
const toBeTriggeredHooks = webhooks.filter(
@ -143,19 +134,9 @@ export const fnTriggerWebhook = async ({
);
if (!toBeTriggeredHooks.length) return;
logger.info("Secret webhook job started", { environment, secretPath, projectId });
const project = await projectDAL.findById(projectId);
const webhooksTriggered = await Promise.allSettled(
toBeTriggeredHooks.map((hook) =>
triggerWebhookRequest(
hook,
getWebhookPayload("secrets.modified", {
workspaceName: project.name,
workspaceId: projectId,
environment,
secretPath,
type: hook.type
})
)
triggerWebhookRequest(hook, getWebhookPayload("secrets.modified", projectId, environment, secretPath, hook.type))
)
);

@ -6,7 +6,6 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TWebhookDALFactory } from "./webhook-dal";
import { decryptWebhookDetails, getWebhookPayload, triggerWebhookRequest } from "./webhook-fns";
@ -21,18 +20,12 @@ import {
type TWebhookServiceFactoryDep = {
webhookDAL: TWebhookDALFactory;
projectEnvDAL: TProjectEnvDALFactory;
projectDAL: Pick<TProjectDALFactory, "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
export type TWebhookServiceFactory = ReturnType<typeof webhookServiceFactory>;
export const webhookServiceFactory = ({
webhookDAL,
projectEnvDAL,
permissionService,
projectDAL
}: TWebhookServiceFactoryDep) => {
export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionService }: TWebhookServiceFactoryDep) => {
const createWebhook = async ({
actor,
actorId,
@ -131,21 +124,13 @@ export const webhookServiceFactory = ({
actorAuthMethod,
actorOrgId
);
const project = await projectDAL.findById(webhook.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
let webhookError: string | undefined;
try {
await triggerWebhookRequest(
webhook,
getWebhookPayload("test", {
workspaceName: project.name,
workspaceId: webhook.projectId,
environment: webhook.environment.slug,
secretPath: webhook.secretPath,
type: webhook.type
})
getWebhookPayload("test", webhook.projectId, webhook.environment.slug, webhook.secretPath, webhook.type)
);
} catch (err) {
webhookError = (err as Error).message;

@ -10,7 +10,7 @@ require (
github.com/fatih/semgroup v1.2.0
github.com/gitleaks/go-gitdiff v0.8.0
github.com/h2non/filetype v1.1.3
github.com/infisical/go-sdk v0.3.0
github.com/infisical/go-sdk v0.2.0
github.com/mattn/go-isatty v0.0.14
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
github.com/muesli/mango-cobra v1.2.0
@ -19,7 +19,7 @@ require (
github.com/petar-dambovaliev/aho-corasick v0.0.0-20211021192214-5ab2d9280aa9
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/posthog/posthog-go v0.0.0-20221221115252-24dfed35d71a
github.com/rs/cors v1.11.0
github.com/rs/cors v1.9.0
github.com/rs/zerolog v1.26.1
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.8.1
@ -52,7 +52,7 @@ require (
github.com/chzyer/readline v1.5.1 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-logr/logr v1.4.1 // indirect

Some files were not shown because too many files have changed in this diff Show More