Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Project Type - User Secrets #1

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions backend/src/@types/fastify.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import { TTotpServiceFactory } from "@app/services/totp/totp-service";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { TUserServiceFactory } from "@app/services/user/user-service";
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
import { TUserSecretsServiceFactory } from "@app/services/user-secrets/userSecrets-service";
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";

Expand Down Expand Up @@ -147,6 +148,7 @@ declare module "fastify" {
projectKey: TProjectKeyServiceFactory;
projectRole: TProjectRoleServiceFactory;
secret: TSecretServiceFactory;
userSecrets: TUserSecretsServiceFactory;
secretReplication: TSecretReplicationServiceFactory;
secretTag: TSecretTagServiceFactory;
secretImport: TSecretImportServiceFactory;
Expand Down
4 changes: 4 additions & 0 deletions backend/src/@types/knex.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ import {
TUserGroupMembershipInsert,
TUserGroupMembershipUpdate,
TUsers,
TUserSecrets,
TUserSecretsInsert,
TUserSecretsUpdate,
TUsersInsert,
TUsersUpdate,
TWebhooks,
Expand Down Expand Up @@ -547,6 +550,7 @@ declare module "knex/types/tables" {
>;
[TableName.ProjectKeys]: KnexOriginal.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>;
[TableName.Secret]: KnexOriginal.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>;
[TableName.UserSecrets]: KnexOriginal.CompositeTableType<TUserSecrets, TUserSecretsInsert, TUserSecretsUpdate>;
[TableName.SecretReference]: KnexOriginal.CompositeTableType<
TSecretReferences,
TSecretReferencesInsert,
Expand Down
28 changes: 28 additions & 0 deletions backend/src/db/migrations/20250106210021_user-secrets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Knex } from "knex";

import { TableName, UserSecretType } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";

export async function up(knex: Knex): Promise<void> {
const doesUserSecretTableExist = await knex.schema.hasTable(TableName.UserSecrets);
if (!doesUserSecretTableExist) {
await knex.schema.createTable(TableName.UserSecrets, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("itemName").notNullable();
t.string("type").notNullable().defaultTo(UserSecretType.Login);
t.binary("encryptedJSONData").notNullable();
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.timestamps(true, true, true);
t.index(["userId", "projectId"]);
});
}
await createOnUpdateTrigger(knex, TableName.UserSecrets);
}

export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.UserSecrets);
await dropOnUpdateTrigger(knex, TableName.UserSecrets);
}
1 change: 1 addition & 0 deletions backend/src/db/schemas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export * from "./user-actions";
export * from "./user-aliases";
export * from "./user-encryption-keys";
export * from "./user-group-membership";
export * from "./user-secrets";
export * from "./users";
export * from "./webhooks";
export * from "./workflow-integrations";
10 changes: 9 additions & 1 deletion backend/src/db/schemas/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export enum TableName {
ProjectKeys = "project_keys",
ProjectTemplates = "project_templates",
Secret = "secrets",
UserSecrets = "user_secrets",
SecretReference = "secret_references",
SecretSharing = "secret_sharing",
SecretBlindIndex = "secret_blind_indexes",
Expand Down Expand Up @@ -185,6 +186,12 @@ export enum SecretType {
Personal = "personal"
}

export enum UserSecretType {
Login = "login",
CreditCard = "credit-card",
SecureNote = "secure-note"
}

export enum ProjectVersion {
V1 = 1,
V2 = 2,
Expand Down Expand Up @@ -212,5 +219,6 @@ export enum ProjectType {
SecretManager = "secret-manager",
CertificateManager = "cert-manager",
KMS = "kms",
SSH = "ssh"
SSH = "ssh",
UserSecrets = "user-secrets"
}
25 changes: 25 additions & 0 deletions backend/src/db/schemas/user-secrets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// 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 UserSecretsSchema = z.object({
id: z.string().uuid(),
itemName: z.string(),
type: z.string().default("login"),
encryptedJSONData: zodBuffer,
userId: z.string().uuid(),
projectId: z.string(),
createdAt: z.date(),
updatedAt: z.date()
});

export type TUserSecrets = z.infer<typeof UserSecretsSchema>;
export type TUserSecretsInsert = Omit<z.input<typeof UserSecretsSchema>, TImmutableDBKeys>;
export type TUserSecretsUpdate = Partial<Omit<z.input<typeof UserSecretsSchema>, TImmutableDBKeys>>;
9 changes: 9 additions & 0 deletions backend/src/ee/services/audit-log/audit-log-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export enum EventType {
GET_SECRET = "get-secret",
REVEAL_SECRET = "reveal-secret",
CREATE_SECRET = "create-secret",
CREATE_USER_SECRET = "create-user-secret",
CREATE_SECRETS = "create-secrets",
UPDATE_SECRET = "update-secret",
UPDATE_SECRETS = "update-secrets",
Expand Down Expand Up @@ -310,6 +311,13 @@ interface CreateSecretEvent {
};
}

interface CreateUserSecretEvent {
type: EventType.CREATE_USER_SECRET;
metadata: {
secretId: string;
};
}

interface CreateSecretBatchEvent {
type: EventType.CREATE_SECRETS;
metadata: {
Expand Down Expand Up @@ -1911,6 +1919,7 @@ export type Event =
| GetSecretsEvent
| GetSecretEvent
| CreateSecretEvent
| CreateUserSecretEvent
| CreateSecretBatchEvent
| UpdateSecretEvent
| UpdateSecretBatchEvent
Expand Down
26 changes: 26 additions & 0 deletions backend/src/lib/api-docs/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,21 @@ export const RAW_SECRETS = {
}
} as const;

export const RAW_USER_SECRETS = {
CREATE: {
type: "The type of the secret to create.",
workspaceId: "The ID of the project to create the secret in."
},
DELETE: {
workspaceId: "The ID of the project where the secret(s) is located."
},
UPDATE: {
type: "The type of the secret to create.",
workspaceId: "The ID of the project to create the secret in.",
secretId: "The secrets's id"
}
} as const;

export const SECRET_IMPORTS = {
LIST: {
workspaceId: "The ID of the project to list secret imports from.",
Expand Down Expand Up @@ -815,6 +830,17 @@ export const DASHBOARD = {
}
} as const;

export const USER_SECRETS_DASHBOARD = {
SECRET_OVERVIEW_LIST: {
projectId: "The ID of the project to list secrets/folders from.",
offset: "The offset to start from. If you enter 10, it will start from the 10th secret/folder.",
limit: "The number of secrets/folders to return.",
orderBy: "The column to order secrets/folders by.",
orderDirection: "The direction to order secrets/folders in.",
includeSecrets: "Whether to include project secrets in the response."
}
} as const;

export const AUDIT_LOGS = {
EXPORT: {
projectId:
Expand Down
12 changes: 12 additions & 0 deletions backend/src/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ import { userDALFactory } from "@app/services/user/user-dal";
import { userServiceFactory } from "@app/services/user/user-service";
import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
import { userSecretsDALFactory } from "@app/services/user-secrets/userSecrets-dal";
import { userSecretsServiceFactory } from "@app/services/user-secrets/userSecrets-service";
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
import { workflowIntegrationDALFactory } from "@app/services/workflow-integration/workflow-integration-dal";
Expand Down Expand Up @@ -277,6 +279,7 @@ export const registerRoutes = async (
const projectBotDAL = projectBotDALFactory(db);

const secretDAL = secretDALFactory(db);
const userSecretsDAL = userSecretsDALFactory(db);
const secretTagDAL = secretTagDALFactory(db);
const folderDAL = secretFolderDALFactory(db);
const folderVersionDAL = secretFolderVersionDALFactory(db);
Expand Down Expand Up @@ -1031,6 +1034,14 @@ export const registerRoutes = async (
secretApprovalRequestService
});

const userSecretService = userSecretsServiceFactory({
permissionService,
projectBotService,
projectDAL,
userSecretsDAL,
kmsService
});

const secretSharingService = secretSharingServiceFactory({
permissionService,
secretSharingDAL,
Expand Down Expand Up @@ -1396,6 +1407,7 @@ export const registerRoutes = async (
projectEnv: projectEnvService,
projectRole: projectRoleService,
secret: secretService,
userSecrets: userSecretService,
secretReplication: secretReplicationService,
secretTag: secretTagService,
rateLimit: rateLimitService,
Expand Down
24 changes: 24 additions & 0 deletions backend/src/server/routes/sanitizedSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ProjectRolesSchema,
ProjectsSchema,
SecretApprovalPoliciesSchema,
UserSecretType,
UsersSchema
} from "@app/db/schemas";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
Expand Down Expand Up @@ -115,6 +116,29 @@ export const secretRawSchema = z.object({
updatedAt: z.date()
});

const ZLoginSecretType = z.object({
userName: z.string(),
password: z.string()
});
const ZCardSecretType = z.object({
cardNumber: z.string(),
expiryDate: z.string(),
CVV: z.string()
});
const ZSecureNoteSecretType = z.object({
content: z.string()
});
export const ZDecryptedJSONData = z.union([ZLoginSecretType, ZCardSecretType, ZSecureNoteSecretType]);

export const userSecretRawSchema = z.object({
id: z.string(),
decryptedJSONData: ZDecryptedJSONData,
itemName: z.string(),
type: z.nativeEnum(UserSecretType),
createdAt: z.date(),
updatedAt: z.date()
});

export const ProjectPermissionSchema = z.object({
action: z
.nativeEnum(ProjectPermissionActions)
Expand Down
9 changes: 8 additions & 1 deletion backend/src/server/routes/v1/project-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
.default("false")
.transform((value) => value === "true"),
type: z
.enum([ProjectType.SecretManager, ProjectType.KMS, ProjectType.CertificateManager, ProjectType.SSH, "all"])
.enum([
ProjectType.SecretManager,
ProjectType.KMS,
ProjectType.CertificateManager,
ProjectType.SSH,
ProjectType.UserSecrets,
"all"
])
.optional()
}),
response: {
Expand Down
4 changes: 4 additions & 0 deletions backend/src/server/routes/v3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { registerSecretBlindIndexRouter } from "./secret-blind-index-router";
import { registerSecretRouter } from "./secret-router";
import { registerSignupRouter } from "./signup-router";
import { registerUserRouter } from "./user-router";
import { registerUserSecretsDashboardRouter } from "./user-secrets-dashboard-router";
import { registerUserSecretsRouter } from "./user-secrets-router";

export const registerV3Routes = async (server: FastifyZodProvider) => {
await server.register(registerSignupRouter, { prefix: "/signup" });
await server.register(registerLoginRouter, { prefix: "/auth" });
await server.register(registerUserRouter, { prefix: "/users" });
await server.register(registerSecretRouter, { prefix: "/secrets" });
await server.register(registerUserSecretsRouter, { prefix: "/user-secrets" });
await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" });
await server.register(registerExternalMigrationRouter, { prefix: "/migrate" });
await server.register(registerUserSecretsDashboardRouter, { prefix: "/user-secrets-dashboard" });
};
Loading