feat(infisical-pg): first setup for postgres migration script

This commit is contained in:
Akhil Mohan
2024-01-15 00:05:27 +05:30
parent deb8e74749
commit 87709dc86f
155 changed files with 9016 additions and 7 deletions

View File

@@ -12,6 +12,7 @@ import {
TAuthTokenSessions,
TAuthTokenSessionsInsert,
TAuthTokenSessionsUpdate,
TAuthTokensInsert,
TAuthTokensUpdate,
TBackupPrivateKey,
TBackupPrivateKeyInsert,
@@ -272,11 +273,6 @@ declare module "knex/types/tables" {
TSecretImportsInsert,
TSecretImportsUpdate
>;
[TableName.SecretSnapshot]: Knex.CompositeTableType<
TSecretSnapshots,
TSecretSnapshotsInsert,
TSecretSnapshotsUpdate
>;
[TableName.Integration]: Knex.CompositeTableType<
TIntegrations,
TIntegrationsInsert,

View File

@@ -2,11 +2,11 @@ import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors";
import { TAuthLoginFactory } from "../auth/auth-login-service";
import { AuthMethod } from "../auth/auth-type";
import { TOrgServiceFactory } from "../org/org-service";
import { TUserDalFactory } from "../user/user-dal";
import { TSuperAdminDalFactory } from "./super-admin-dal";
import { TAdminSignUpDTO } from "./super-admin-types";
import { TOrgServiceFactory } from "../org/org-service";
import { AuthMethod } from "../auth/auth-type";
type TSuperAdminServiceFactoryDep = {
serverCfgDal: TSuperAdminDalFactory;

1365
pg-migrator/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
pg-migrator/package.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "pg-migrator",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"migration": "tsx src/index.ts"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.11.0",
"@types/prompt-sync": "^4.2.3",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
},
"dependencies": {
"dotenv": "^16.3.1",
"knex": "^3.1.0",
"level": "^8.0.0",
"mongoose": "^8.0.4",
"pg": "^8.11.3",
"prompt-sync": "^4.2.0",
"zod": "^3.22.4"
}
}

1
pg-migrator/src/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
db

443
pg-migrator/src/@types/knex.d.ts vendored Normal file
View File

@@ -0,0 +1,443 @@
import { Knex } from "knex";
import {
TableName,
TApiKeys,
TApiKeysInsert,
TApiKeysUpdate,
TAuditLogs,
TAuditLogsInsert,
TAuditLogsUpdate,
TAuthTokens,
TAuthTokenSessions,
TAuthTokenSessionsInsert,
TAuthTokenSessionsUpdate,
TAuthTokensUpdate,
TBackupPrivateKey,
TBackupPrivateKeyInsert,
TBackupPrivateKeyUpdate,
TGitAppInstallSessions,
TGitAppInstallSessionsInsert,
TGitAppInstallSessionsUpdate,
TGitAppOrg,
TGitAppOrgInsert,
TGitAppOrgUpdate,
TIdentities,
TIdentitiesInsert,
TIdentitiesUpdate,
TIdentityAccessTokens,
TIdentityAccessTokensInsert,
TIdentityAccessTokensUpdate,
TIdentityOrgMemberships,
TIdentityOrgMembershipsInsert,
TIdentityOrgMembershipsUpdate,
TIdentityProjectMemberships,
TIdentityProjectMembershipsInsert,
TIdentityProjectMembershipsUpdate,
TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert,
TIdentityUaClientSecretsUpdate,
TIdentityUniversalAuths,
TIdentityUniversalAuthsInsert,
TIdentityUniversalAuthsUpdate,
TIncidentContacts,
TIncidentContactsInsert,
TIncidentContactsUpdate,
TIntegrationAuths,
TIntegrationAuthsInsert,
TIntegrationAuthsUpdate,
TIntegrations,
TIntegrationsInsert,
TIntegrationsUpdate,
TOrganizations,
TOrganizationsInsert,
TOrganizationsUpdate,
TOrgBots,
TOrgBotsInsert,
TOrgBotsUpdate,
TOrgMemberships,
TOrgMembershipsInsert,
TOrgMembershipsUpdate,
TOrgRoles,
TOrgRolesInsert,
TOrgRolesUpdate,
TProjectBots,
TProjectBotsInsert,
TProjectBotsUpdate,
TProjectEnvironments,
TProjectEnvironmentsInsert,
TProjectEnvironmentsUpdate,
TProjectKeys,
TProjectKeysInsert,
TProjectKeysUpdate,
TProjectMemberships,
TProjectMembershipsInsert,
TProjectMembershipsUpdate,
TProjectRoles,
TProjectRolesInsert,
TProjectRolesUpdate,
TProjects,
TProjectsInsert,
TProjectsUpdate,
TSamlConfigs,
TSamlConfigsInsert,
TSamlConfigsUpdate,
TSapApprovers,
TSapApproversInsert,
TSapApproversUpdate,
TSaRequestSecrets,
TSaRequestSecretsInsert,
TSaRequestSecretsUpdate,
TSaRequestSecretTags,
TSaRequestSecretTagsInsert,
TSaRequestSecretTagsUpdate,
TSarReviewers,
TSarReviewersInsert,
TSarReviewersUpdate,
TSecretApprovalPolicies,
TSecretApprovalPoliciesInsert,
TSecretApprovalPoliciesUpdate,
TSecretApprovalRequests,
TSecretApprovalRequestsInsert,
TSecretApprovalRequestsUpdate,
TSecretBlindIndexes,
TSecretBlindIndexesInsert,
TSecretBlindIndexesUpdate,
TSecretFolders,
TSecretFoldersInsert,
TSecretFoldersUpdate,
TSecretFolderVersions,
TSecretFolderVersionsInsert,
TSecretFolderVersionsUpdate,
TSecretImports,
TSecretImportsInsert,
TSecretImportsUpdate,
TSecretRotationOutputs,
TSecretRotationOutputsInsert,
TSecretRotationOutputsUpdate,
TSecretRotations,
TSecretRotationsInsert,
TSecretRotationsUpdate,
TSecrets,
TSecretScanningGitRisks,
TSecretScanningGitRisksInsert,
TSecretScanningGitRisksUpdate,
TSecretsInsert,
TSecretSnapshotFolders,
TSecretSnapshotFoldersInsert,
TSecretSnapshotFoldersUpdate,
TSecretSnapshots,
TSecretSnapshotSecrets,
TSecretSnapshotSecretsInsert,
TSecretSnapshotSecretsUpdate,
TSecretSnapshotsInsert,
TSecretSnapshotsUpdate,
TSecretsUpdate,
TSecretTagJunction,
TSecretTagJunctionInsert,
TSecretTagJunctionUpdate,
TSecretTags,
TSecretTagsInsert,
TSecretTagsUpdate,
TSecretVersions,
TSecretVersionsInsert,
TSecretVersionsUpdate,
TServiceTokens,
TServiceTokensInsert,
TServiceTokensUpdate,
TSuperAdmin,
TSuperAdminInsert,
TSuperAdminUpdate,
TTrustedIps,
TTrustedIpsInsert,
TTrustedIpsUpdate,
TUserActions,
TUserActionsInsert,
TUserActionsUpdate,
TUserEncryptionKeys,
TUserEncryptionKeysInsert,
TUserEncryptionKeysUpdate,
TUsers,
TUsersInsert,
TUsersUpdate,
TWebhooks,
TWebhooksInsert,
TWebhooksUpdate,
TAuthTokensInsert,
} from "../schemas";
declare module "knex/types/tables" {
interface Tables {
[TableName.Users]: Knex.CompositeTableType<
TUsers,
TUsersInsert,
TUsersUpdate
>;
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
TUserEncryptionKeys,
TUserEncryptionKeysInsert,
TUserEncryptionKeysUpdate
>;
[TableName.AuthTokens]: Knex.CompositeTableType<
TAuthTokens,
TAuthTokensInsert,
TAuthTokensUpdate
>;
[TableName.AuthTokenSession]: Knex.CompositeTableType<
TAuthTokenSessions,
TAuthTokenSessionsInsert,
TAuthTokenSessionsUpdate
>;
[TableName.BackupPrivateKey]: Knex.CompositeTableType<
TBackupPrivateKey,
TBackupPrivateKeyInsert,
TBackupPrivateKeyUpdate
>;
[TableName.Organization]: Knex.CompositeTableType<
TOrganizations,
TOrganizationsInsert,
TOrganizationsUpdate
>;
[TableName.OrgMembership]: Knex.CompositeTableType<
TOrgMemberships,
TOrgMembershipsInsert,
TOrgMembershipsUpdate
>;
[TableName.OrgRoles]: Knex.CompositeTableType<
TOrgRoles,
TOrgRolesInsert,
TOrgRolesUpdate
>;
[TableName.IncidentContact]: Knex.CompositeTableType<
TIncidentContacts,
TIncidentContactsInsert,
TIncidentContactsUpdate
>;
[TableName.UserAction]: Knex.CompositeTableType<
TUserActions,
TUserActionsInsert,
TUserActionsUpdate
>;
[TableName.SuperAdmin]: Knex.CompositeTableType<
TSuperAdmin,
TSuperAdminInsert,
TSuperAdminUpdate
>;
[TableName.ApiKey]: Knex.CompositeTableType<
TApiKeys,
TApiKeysInsert,
TApiKeysUpdate
>;
[TableName.Project]: Knex.CompositeTableType<
TProjects,
TProjectsInsert,
TProjectsUpdate
>;
[TableName.ProjectMembership]: Knex.CompositeTableType<
TProjectMemberships,
TProjectMembershipsInsert,
TProjectMembershipsUpdate
>;
[TableName.Environment]: Knex.CompositeTableType<
TProjectEnvironments,
TProjectEnvironmentsInsert,
TProjectEnvironmentsUpdate
>;
[TableName.ProjectBot]: Knex.CompositeTableType<
TProjectBots,
TProjectBotsInsert,
TProjectBotsUpdate
>;
[TableName.ProjectRoles]: Knex.CompositeTableType<
TProjectRoles,
TProjectRolesInsert,
TProjectRolesUpdate
>;
[TableName.ProjectKeys]: Knex.CompositeTableType<
TProjectKeys,
TProjectKeysInsert,
TProjectKeysUpdate
>;
[TableName.Secret]: Knex.CompositeTableType<
TSecrets,
TSecretsInsert,
TSecretsUpdate
>;
[TableName.SecretBlindIndex]: Knex.CompositeTableType<
TSecretBlindIndexes,
TSecretBlindIndexesInsert,
TSecretBlindIndexesUpdate
>;
[TableName.SecretVersion]: Knex.CompositeTableType<
TSecretVersions,
TSecretVersionsInsert,
TSecretVersionsUpdate
>;
[TableName.SecretFolder]: Knex.CompositeTableType<
TSecretFolders,
TSecretFoldersInsert,
TSecretFoldersUpdate
>;
[TableName.SecretFolderVersion]: Knex.CompositeTableType<
TSecretFolderVersions,
TSecretFolderVersionsInsert,
TSecretFolderVersionsUpdate
>;
[TableName.SecretTag]: Knex.CompositeTableType<
TSecretTags,
TSecretTagsInsert,
TSecretTagsUpdate
>;
[TableName.SecretImport]: Knex.CompositeTableType<
TSecretImports,
TSecretImportsInsert,
TSecretImportsUpdate
>;
[TableName.Integration]: Knex.CompositeTableType<
TIntegrations,
TIntegrationsInsert,
TIntegrationsUpdate
>;
[TableName.Webhook]: Knex.CompositeTableType<
TWebhooks,
TWebhooksInsert,
TWebhooksUpdate
>;
[TableName.ServiceToken]: Knex.CompositeTableType<
TServiceTokens,
TServiceTokensInsert,
TServiceTokensUpdate
>;
[TableName.IntegrationAuth]: Knex.CompositeTableType<
TIntegrationAuths,
TIntegrationAuthsInsert,
TIntegrationAuthsUpdate
>;
[TableName.Identity]: Knex.CompositeTableType<
TIdentities,
TIdentitiesInsert,
TIdentitiesUpdate
>;
[TableName.IdentityUniversalAuth]: Knex.CompositeTableType<
TIdentityUniversalAuths,
TIdentityUniversalAuthsInsert,
TIdentityUniversalAuthsUpdate
>;
[TableName.IdentityUaClientSecret]: Knex.CompositeTableType<
TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert,
TIdentityUaClientSecretsUpdate
>;
[TableName.IdentityAccessToken]: Knex.CompositeTableType<
TIdentityAccessTokens,
TIdentityAccessTokensInsert,
TIdentityAccessTokensUpdate
>;
[TableName.IdentityOrgMembership]: Knex.CompositeTableType<
TIdentityOrgMemberships,
TIdentityOrgMembershipsInsert,
TIdentityOrgMembershipsUpdate
>;
[TableName.IdentityProjectMembership]: Knex.CompositeTableType<
TIdentityProjectMemberships,
TIdentityProjectMembershipsInsert,
TIdentityProjectMembershipsUpdate
>;
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
TSecretApprovalPolicies,
TSecretApprovalPoliciesInsert,
TSecretApprovalPoliciesUpdate
>;
[TableName.SapApprover]: Knex.CompositeTableType<
TSapApprovers,
TSapApproversInsert,
TSapApproversUpdate
>;
[TableName.SecretApprovalRequest]: Knex.CompositeTableType<
TSecretApprovalRequests,
TSecretApprovalRequestsInsert,
TSecretApprovalRequestsUpdate
>;
[TableName.SarReviewer]: Knex.CompositeTableType<
TSarReviewers,
TSarReviewersInsert,
TSarReviewersUpdate
>;
[TableName.SarSecret]: Knex.CompositeTableType<
TSaRequestSecrets,
TSaRequestSecretsInsert,
TSaRequestSecretsUpdate
>;
[TableName.SarSecretTag]: Knex.CompositeTableType<
TSaRequestSecretTags,
TSaRequestSecretTagsInsert,
TSaRequestSecretTagsUpdate
>;
[TableName.SecretRotation]: Knex.CompositeTableType<
TSecretRotations,
TSecretRotationsInsert,
TSecretRotationsUpdate
>;
[TableName.SecretRotationOutput]: Knex.CompositeTableType<
TSecretRotationOutputs,
TSecretRotationOutputsInsert,
TSecretRotationOutputsUpdate
>;
[TableName.Snapshot]: Knex.CompositeTableType<
TSecretSnapshots,
TSecretSnapshotsInsert,
TSecretSnapshotsUpdate
>;
[TableName.SnapshotSecret]: Knex.CompositeTableType<
TSecretSnapshotSecrets,
TSecretSnapshotSecretsInsert,
TSecretSnapshotSecretsUpdate
>;
[TableName.SnapshotFolder]: Knex.CompositeTableType<
TSecretSnapshotFolders,
TSecretSnapshotFoldersInsert,
TSecretSnapshotFoldersUpdate
>;
[TableName.SamlConfig]: Knex.CompositeTableType<
TSamlConfigs,
TSamlConfigsInsert,
TSamlConfigsUpdate
>;
[TableName.OrgBot]: Knex.CompositeTableType<
TOrgBots,
TOrgBotsInsert,
TOrgBotsUpdate
>;
[TableName.AuditLog]: Knex.CompositeTableType<
TAuditLogs,
TAuditLogsInsert,
TAuditLogsUpdate
>;
[TableName.GitAppInstallSession]: Knex.CompositeTableType<
TGitAppInstallSessions,
TGitAppInstallSessionsInsert,
TGitAppInstallSessionsUpdate
>;
[TableName.GitAppOrg]: Knex.CompositeTableType<
TGitAppOrg,
TGitAppOrgInsert,
TGitAppOrgUpdate
>;
[TableName.SecretScanningGitRisk]: Knex.CompositeTableType<
TSecretScanningGitRisks,
TSecretScanningGitRisksInsert,
TSecretScanningGitRisksUpdate
>;
[TableName.TrustedIps]: Knex.CompositeTableType<
TTrustedIps,
TTrustedIpsInsert,
TTrustedIpsUpdate
>;
// Junction tables
[TableName.JnSecretTag]: Knex.CompositeTableType<
TSecretTagJunction,
TSecretTagJunctionInsert,
TSecretTagJunctionUpdate
>;
}
}

80
pg-migrator/src/index.ts Normal file
View File

@@ -0,0 +1,80 @@
import promptSync from "prompt-sync";
import mongoose from "mongoose";
import dotenv from "dotenv";
import knex from "knex";
import path from "path";
import { Level } from "level";
import { IUser, User } from "./models";
import { TUsers, TUsersInsert, TableName } from "./schemas";
const kdb = new Level<string, any>("./db", { valueEncoding: "json" });
const main = async () => {
try {
dotenv.config();
const prompt = promptSync({ sigint: true });
let mongodb_url = process.env.MONGO_DB_URL;
if (!mongodb_url) {
mongodb_url = prompt("Type the mongodb url: ");
}
console.log("Checking mongoose connection...");
await mongoose.connect(mongodb_url);
console.log("Connected successfully to mongo");
let postgres_url = process.env.POSTGRES_DB_URL;
if (!postgres_url) {
postgres_url = prompt("Type the mongodb url: ");
}
console.log("Checking postgres connection...");
const db = knex({
client: "pg",
connection: postgres_url,
migrations: {
directory: path.join(__dirname, "./migrations"),
extension: "ts",
tableName: "infisical_migrations",
},
});
console.log("Connected successfully to postgres");
await db.raw("select 1+1 as result");
console.log("Executing migration");
await db.migrate.latest();
console.log("Starting to insert users");
const users: IUser[] = [];
const newUsers: TUsersInsert[] = [];
for await (const doc of User.find().cursor({ batchSize: 100 })) {
users.push(doc);
newUsers.push({
firstName: doc.firstName,
email: doc.email,
devices: doc.devices,
lastName: doc.lastName,
isAccepted: Boolean(doc.publicKey),
superAdmin: doc.superAdmin,
authMethods: doc.authMethods,
isMfaEnabled: doc.isMfaEnabled,
});
if (users.length >= 1000) {
const newUserIds = await db.transaction(async (tx) => {
return await tx
.batchInsert<TUsers>(TableName.Users, newUsers)
.returning("id");
});
console.log(newUserIds.length);
users.slice(0, users.length);
newUsers.slice(0, newUsers.length);
}
}
process.exit(1);
} catch (error) {
console.error(error);
process.exit(1);
}
};
main();

View File

@@ -0,0 +1,37 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import {
createOnUpdateTrigger,
createUpdateAtTriggerFunction,
dropOnUpdateTrigger,
dropUpdatedAtTriggerFunction
} from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.Users);
if (!isTablePresent) {
await knex.schema.createTable(TableName.Users, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("email").notNullable();
t.specificType("authMethods", "text[]");
t.boolean("superAdmin").defaultTo(false);
t.string("firstName");
t.string("lastName");
t.boolean("isAccepted").defaultTo(false);
t.boolean("isMfaEnabled").defaultTo(false);
t.specificType("mfaMethods", "text[]");
t.jsonb("devices");
t.timestamps(true, true, true);
});
}
// this is a one time function
await createUpdateAtTriggerFunction(knex);
await createOnUpdateTrigger(knex, TableName.Users);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Users);
await dropOnUpdateTrigger(knex, TableName.Users);
await dropUpdatedAtTriggerFunction(knex);
}

View File

@@ -0,0 +1,31 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.UserEncryptionKey);
if (!isTablePresent) {
await knex.schema.createTable(TableName.UserEncryptionKey, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.text("clientPublicKey");
t.text("serverPrivateKey");
t.integer("encryptionVersion").defaultTo(2);
t.text("protectedKey").notNullable();
t.text("protectedKeyIV").notNullable();
t.text("protectedKeyTag").notNullable();
t.text("publicKey").notNullable();
t.text("encryptedPrivateKey").notNullable();
t.text("iv").notNullable();
t.text("tag").notNullable();
t.text("salt").notNullable();
t.text("verifier").notNullable();
// one to one relationship
t.uuid("userId").notNullable().unique();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.UserEncryptionKey);
}

View File

@@ -0,0 +1,25 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.AuthTokens);
if (!isTablePresent) {
await knex.schema.createTable(TableName.AuthTokens, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("type").notNullable();
t.string("phoneNumber");
t.string("tokenHash").notNullable();
t.integer("triesLeft");
t.datetime("expiresAt").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("userId");
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.AuthTokens);
}

View File

@@ -0,0 +1,29 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.AuthTokenSession);
if (!isTablePresent) {
await knex.schema.createTable(TableName.AuthTokenSession, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("ip").notNullable();
t.string("userAgent");
t.integer("refreshVersion").notNullable().defaultTo(1);
t.integer("accessVersion").notNullable().defaultTo(1);
t.datetime("lastUsed").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
// this is a one time function
await createOnUpdateTrigger(knex, TableName.AuthTokenSession);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.AuthTokenSession);
await dropOnUpdateTrigger(knex, TableName.AuthTokenSession);
}

View File

@@ -0,0 +1,26 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesTableExist = await knex.schema.hasTable(TableName.BackupPrivateKey);
if (!doesTableExist) {
await knex.schema.createTable(TableName.BackupPrivateKey, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.text("encryptedPrivateKey").notNullable();
t.text("iv").notNullable();
t.text("tag").notNullable();
t.string("algorithm").notNullable();
t.string("keyEncoding").notNullable();
t.text("salt").notNullable();
t.text("verifier").notNullable();
t.timestamps(true, true, true);
t.uuid("userId").notNullable().unique();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.BackupPrivateKey);
}

View File

@@ -0,0 +1,33 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.Organization);
if (!isTablePresent) {
await knex.schema.createTable(TableName.Organization, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("customerId");
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
});
await knex.schema.alterTable(TableName.AuthTokens, (t) => {
t.uuid("orgId");
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
});
}
// this is a one time function
await createOnUpdateTrigger(knex, TableName.Organization);
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.AuthTokens, "orgId")) {
await knex.schema.alterTable(TableName.AuthTokens, (t) => {
t.dropColumn("orgId");
});
}
await knex.schema.dropTableIfExists(TableName.Organization);
await dropOnUpdateTrigger(knex, TableName.Organization);
}

View File

@@ -0,0 +1,48 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { OrgMembershipStatus } from "../schemas/models";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isOrgRolePresent = await knex.schema.hasTable(TableName.OrgRoles);
if (!isOrgRolePresent) {
await knex.schema.createTable(TableName.OrgRoles, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("description");
t.string("slug").notNullable();
t.jsonb("permissions").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
});
}
const isOrgTablePresent = await knex.schema.hasTable(TableName.OrgMembership);
if (!isOrgTablePresent) {
await knex.schema.createTable(TableName.OrgMembership, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("role").notNullable();
t.string("status").notNullable().defaultTo(OrgMembershipStatus.Invited);
t.string("inviteEmail");
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("userId");
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
});
}
// this is a one time function
await createOnUpdateTrigger(knex, TableName.OrgMembership);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.OrgMembership);
await knex.schema.dropTableIfExists(TableName.OrgRoles);
await dropOnUpdateTrigger(knex, TableName.OrgMembership);
}

View File

@@ -0,0 +1,25 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.IncidentContact);
if (!isTablePresent) {
await knex.schema.createTable(TableName.IncidentContact, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("email").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
});
}
// this is a one time function
await createOnUpdateTrigger(knex, TableName.IncidentContact);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IncidentContact);
await dropOnUpdateTrigger(knex, TableName.IncidentContact);
}

View File

@@ -0,0 +1,20 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.UserAction);
if (!isTablePresent) {
await knex.schema.createTable(TableName.UserAction, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("action").notNullable();
t.timestamps(true, true, true);
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.UserAction);
}

View File

@@ -0,0 +1,23 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.SuperAdmin);
if (!isTablePresent) {
await knex.schema.createTable(TableName.SuperAdmin, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.boolean("initialized").defaultTo(false);
t.boolean("allowSignUp").defaultTo(true);
t.timestamps(true, true, true);
});
}
// this is a one time function
await createOnUpdateTrigger(knex, TableName.SuperAdmin);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SuperAdmin);
await dropOnUpdateTrigger(knex, TableName.SuperAdmin);
}

View File

@@ -0,0 +1,26 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.ApiKey);
if (!isTablePresent) {
await knex.schema.createTable(TableName.ApiKey, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.datetime("lastUsed");
t.datetime("expiresAt");
t.string("secretHash").notNullable();
t.timestamps(true, true, true);
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.ApiKey);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ApiKey);
await dropOnUpdateTrigger(knex, TableName.ApiKey);
}

View File

@@ -0,0 +1,60 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Project))) {
await knex.schema.createTable(TableName.Project, (t) => {
t.string("id", 36).primary().defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.boolean("autoCapitalization").defaultTo(true);
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.Project);
// environments
if (!(await knex.schema.hasTable(TableName.Environment))) {
await knex.schema.createTable(TableName.Environment, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("slug").notNullable();
t.integer("position").notNullable();
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
// this will ensure ever env has its position
t.unique(["projectId", "position"], {
indexName: "env_pos_composite_uniqe",
deferrable: "deferred"
});
t.timestamps(true, true, true);
});
}
// project key
if (!(await knex.schema.hasTable(TableName.ProjectKeys))) {
await knex.schema.createTable(TableName.ProjectKeys, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.text("encryptedKey").notNullable();
t.text("nonce").notNullable();
t.uuid("receiverId").notNullable();
t.foreign("receiverId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("senderId");
// if sender is deleted just don't do anything to this record
t.foreign("senderId").references("id").inTable(TableName.Users).onDelete("SET NULL");
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.ProjectKeys);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Environment);
await knex.schema.dropTableIfExists(TableName.ProjectKeys);
await knex.schema.dropTableIfExists(TableName.Project);
await dropOnUpdateTrigger(knex, TableName.ProjectKeys);
await dropOnUpdateTrigger(knex, TableName.Project);
}

View File

@@ -0,0 +1,43 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ProjectRoles))) {
await knex.schema.createTable(TableName.ProjectRoles, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("description");
t.string("slug").notNullable();
t.jsonb("permissions").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
if (!(await knex.schema.hasTable(TableName.ProjectMembership))) {
await knex.schema.createTable(TableName.ProjectMembership, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("role").notNullable();
// does not need update trigger we will do it manually
t.timestamps(true, true, true);
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");
// until role is changed/removed the role should not deleted
t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.ProjectRoles);
});
}
await createOnUpdateTrigger(knex, TableName.ProjectMembership);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ProjectMembership);
await knex.schema.dropTableIfExists(TableName.ProjectRoles);
await dropOnUpdateTrigger(knex, TableName.ProjectMembership);
}

View File

@@ -0,0 +1,42 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretFolder))) {
await knex.schema.createTable(TableName.SecretFolder, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.integer("version").defaultTo(1);
t.timestamps(true, true, true);
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.uuid("parentId");
t.foreign("parentId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.SecretFolder);
if (!(await knex.schema.hasTable(TableName.SecretFolderVersion))) {
await knex.schema.createTable(TableName.SecretFolderVersion, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.integer("version").defaultTo(1);
t.timestamps(true, true, true);
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.uuid("folderId").notNullable();
// t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("SET NULL");
});
}
await createOnUpdateTrigger(knex, TableName.SecretFolderVersion);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretFolderVersion);
await knex.schema.dropTableIfExists(TableName.SecretFolder);
await dropOnUpdateTrigger(knex, TableName.SecretFolder);
await dropOnUpdateTrigger(knex, TableName.SecretFolderVersion);
}

View File

@@ -0,0 +1,30 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretImport))) {
await knex.schema.createTable(TableName.SecretImport, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("version").defaultTo(1);
t.string("importPath").notNullable();
t.uuid("importEnv").notNullable();
t.foreign("importEnv").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.integer("position").notNullable();
t.timestamps(true, true, true);
t.uuid("folderId").notNullable();
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
t.unique(["folderId", "position"], {
indexName: "import_pos_composite_uniqe",
deferrable: "deferred"
});
});
}
await createOnUpdateTrigger(knex, TableName.SecretImport);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretImport);
await dropOnUpdateTrigger(knex, TableName.SecretImport);
}

View File

@@ -0,0 +1,26 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretTag))) {
await knex.schema.createTable(TableName.SecretTag, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("slug").notNullable();
t.string("color");
t.timestamps(true, true, true);
t.uuid("createdBy");
t.foreign("createdBy").references("id").inTable(TableName.Users).onDelete("SET NULL");
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.SecretTag);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretTag);
await dropOnUpdateTrigger(knex, TableName.SecretTag);
}

View File

@@ -0,0 +1,65 @@
import { Knex } from "knex";
import { SecretEncryptionAlgo, SecretKeyEncoding, SecretType, TableName } from "../schemas";
import { createJunctionTable, createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretBlindIndex))) {
await knex.schema.createTable(TableName.SecretBlindIndex, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.text("encryptedSaltCipherText").notNullable();
t.text("saltIV").notNullable();
t.text("saltTag").notNullable();
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
t.string("projectId").notNullable().unique();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretBlindIndex);
if (!(await knex.schema.hasTable(TableName.Secret))) {
await knex.schema.createTable(TableName.Secret, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("version").defaultTo(1).notNullable();
t.string("type").notNullable().defaultTo(SecretType.Shared);
// t.text("secretKeyHash").notNullable();
// t.text("secretValueHash");
// t.text("secretCommentHash");
t.text("secretBlindIndex").notNullable();
t.text("secretKeyCiphertext").notNullable();
t.text("secretKeyIV").notNullable();
t.text("secretKeyTag").notNullable();
t.text("secretValueCiphertext").notNullable();
t.text("secretValueIV").notNullable(); // symmetric encryption
t.text("secretValueTag").notNullable();
t.text("secretCommentCiphertext");
t.text("secretCommentIV");
t.text("secretCommentTag");
t.string("secretReminderNote");
t.integer("secretReminderRepeatDays");
t.boolean("skipMultilineEncoding").defaultTo(false);
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
t.jsonb("metadata");
t.uuid("userId");
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("folderId").notNullable();
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.Secret);
// many to many relation between tags
await createJunctionTable(knex, TableName.JnSecretTag, TableName.Secret, TableName.SecretTag);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretBlindIndex);
await dropOnUpdateTrigger(knex, TableName.SecretBlindIndex);
await knex.schema.dropTableIfExists(TableName.JnSecretTag);
await knex.schema.dropTableIfExists(TableName.Secret);
await dropOnUpdateTrigger(knex, TableName.Secret);
}

View File

@@ -0,0 +1,53 @@
import { Knex } from "knex";
import { SecretEncryptionAlgo, SecretKeyEncoding, SecretType, TableName } from "../schemas";
import { createJunctionTable, createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretVersion))) {
await knex.schema.createTable(TableName.SecretVersion, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("version").defaultTo(1).notNullable();
t.string("type").notNullable().defaultTo(SecretType.Shared);
t.text("secretBlindIndex").notNullable();
t.text("secretKeyCiphertext").notNullable();
t.text("secretKeyIV").notNullable();
t.text("secretKeyTag").notNullable();
t.text("secretValueCiphertext").notNullable();
t.text("secretValueIV").notNullable(); // symmetric encryption
t.text("secretValueTag").notNullable();
t.text("secretCommentCiphertext");
t.text("secretCommentIV");
t.text("secretCommentTag");
t.string("secretReminderNote");
t.integer("secretReminderRepeatDays");
t.boolean("skipMultilineEncoding").defaultTo(false);
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
t.jsonb("metadata");
// to avoid orphan rows
t.uuid("envId");
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.uuid("secretId").notNullable();
t.uuid("folderId").notNullable();
// t.foreign("secretId").references("id").inTable(TableName.Secret).onDelete("SET NULL");
t.uuid("userId");
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretVersion);
// many to many relation between tags
await createJunctionTable(
knex,
TableName.JnSecretVersionTag,
TableName.SecretVersion,
TableName.SecretTag
);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.JnSecretVersionTag);
await knex.schema.dropTableIfExists(TableName.SecretVersion);
await dropOnUpdateTrigger(knex, TableName.SecretVersion);
}

View File

@@ -0,0 +1,35 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ProjectBot))) {
await knex.schema.createTable(TableName.ProjectBot, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.boolean("isActive").defaultTo(false).notNullable();
t.text("encryptedPrivateKey").notNullable();
t.text("publicKey").notNullable();
t.text("iv").notNullable();
t.text("tag").notNullable();
t.string("algorithm").notNullable();
t.string("keyEncoding").notNullable();
t.text("encryptedProjectKey");
t.text("encryptedProjectKeyNonce");
// one to one relationship
t.string("projectId").notNullable().unique();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.uuid("senderId");
t.foreign("senderId").references("id").inTable(TableName.Users).onDelete("SET NULL");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.ProjectBot);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ProjectBot);
await dropOnUpdateTrigger(knex, TableName.ProjectBot);
}

View File

@@ -0,0 +1,71 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IntegrationAuth))) {
await knex.schema.createTable(TableName.IntegrationAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("integration").notNullable();
t.string("teamId"); // vercel-specific
t.string("url"); // for self hosted
t.string("namespace"); // hashicorp specific
t.string("accountId"); // netlify
t.string("refreshCiphertext");
t.string("refreshIV");
t.string("refreshTag");
t.string("accessIdCiphertext");
t.string("accessIdIV");
t.string("accessIdTag");
t.string("accessCiphertext");
t.string("accessIV");
t.string("accessTag");
t.datetime("accessExpiresAt");
t.jsonb("metadata");
t.string("algorithm").notNullable();
t.string("keyEncoding").notNullable();
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.IntegrationAuth);
if (!(await knex.schema.hasTable(TableName.Integration))) {
await knex.schema.createTable(TableName.Integration, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.boolean("isActive").notNullable();
t.string("url"); // self hosted
t.string("app"); // name of app in provider
t.string("appId");
t.string("targetEnvironment");
t.string("targetEnvironmentId");
t.string("targetService"); // railway - qovery specific
t.string("targetServiceId");
t.string("owner"); // github specific
t.string("path"); // aws parameter store / vercel preview branch
t.string("region"); // aws
t.string("scope"); // qovery specific scope
t.string("integration").notNullable();
t.jsonb("metadata");
t.uuid("integrationAuthId").notNullable();
t.foreign("integrationAuthId")
.references("id")
.inTable(TableName.IntegrationAuth)
.onDelete("CASCADE");
t.uuid("envId").notNullable();
t.string("secretPath").defaultTo("/").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.Integration);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Integration);
await knex.schema.dropTableIfExists(TableName.IntegrationAuth);
await dropOnUpdateTrigger(knex, TableName.IntegrationAuth);
await dropOnUpdateTrigger(knex, TableName.Integration);
}

View File

@@ -0,0 +1,32 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ServiceToken))) {
await knex.schema.createTable(TableName.ServiceToken, (t) => {
t.string("id", 36).primary().defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.jsonb("scopes").notNullable();
t.specificType("permissions", "text[]").notNullable();
t.datetime("lastUsed");
t.datetime("expiresAt");
t.text("secretHash").notNullable();
t.text("encryptedKey");
t.text("iv");
t.text("tag");
t.timestamps(true, true, true);
// user is old one
t.string("createdBy").notNullable();
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.ServiceToken);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ServiceToken);
await dropOnUpdateTrigger(knex, TableName.ServiceToken);
}

View File

@@ -0,0 +1,32 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Webhook))) {
await knex.schema.createTable(TableName.Webhook, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("secretPath").notNullable().defaultTo("/");
t.string("url").notNullable();
t.string("lastStatus");
t.text("lastRunErrorMessage");
t.boolean("isDisabled").defaultTo(false).notNullable();
// webhook signature
t.text("encryptedSecretKey");
t.text("iv");
t.text("tag");
t.string("algorithm");
t.string("keyEncoding");
t.timestamps(true, true, true);
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.Webhook);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Webhook);
await dropOnUpdateTrigger(knex, TableName.Webhook);
}

View File

@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Identity))) {
await knex.schema.createTable(TableName.Identity, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("authMethod");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.Identity);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.Identity);
await dropOnUpdateTrigger(knex, TableName.Identity);
}

View File

@@ -0,0 +1,49 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityUniversalAuth))) {
await knex.schema.createTable(TableName.IdentityUniversalAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("clientId").notNullable();
t.integer("accessTokenTTL").defaultTo(7200).notNullable();
t.integer("accessTokenMaxTTL").defaultTo(7200).notNullable();
t.integer("accessTokenNumUsesLimit").defaultTo(0).notNullable();
t.jsonb("clientSecretTrustedIps").notNullable();
t.jsonb("accessTokenTrustedIps").notNullable();
t.timestamps(true, true, true);
t.uuid("identityId").notNullable().unique();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
});
}
if (!(await knex.schema.hasTable(TableName.IdentityUaClientSecret))) {
await knex.schema.createTable(TableName.IdentityUaClientSecret, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("description").notNullable();
t.string("clientSecretPrefix").notNullable();
t.string("clientSecretHash").notNullable();
t.datetime("clientSecretLastUsedAt");
t.integer("clientSecretNumUses").defaultTo(0).notNullable();
t.integer("clientSecretNumUsesLimit").defaultTo(0).notNullable();
t.integer("clientSecretTTL").defaultTo(0).notNullable();
t.boolean("isClientSecretRevoked").defaultTo(false).notNullable();
t.timestamps(true, true, true);
t.uuid("identityUAId").notNullable();
t.foreign("identityUAId")
.references("id")
.inTable(TableName.IdentityUniversalAuth)
.onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.IdentityUniversalAuth);
await createOnUpdateTrigger(knex, TableName.IdentityUaClientSecret);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityUaClientSecret);
await knex.schema.dropTableIfExists(TableName.IdentityUniversalAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityUaClientSecret);
await dropOnUpdateTrigger(knex, TableName.IdentityUniversalAuth);
}

View File

@@ -0,0 +1,35 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityAccessToken))) {
await knex.schema.createTable(TableName.IdentityAccessToken, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("authType").notNullable();
t.integer("accessTokenTTL").defaultTo(2592000).notNullable(); // 30 days second
t.integer("accessTokenMaxTTL").defaultTo(2592000).notNullable();
t.integer("accessTokenNumUses").defaultTo(0).notNullable();
t.integer("accessTokenNumUsesLimit").defaultTo(0).notNullable();
t.datetime("accessTokenLastUsedAt");
t.datetime("accessTokenLastRenewedAt");
t.boolean("isAccessTokenRevoked").defaultTo(false).notNullable();
t.uuid("identityUAClientSecretId");
t.foreign("identityUAClientSecretId")
.references("id")
.inTable(TableName.IdentityUaClientSecret)
.onDelete("CASCADE");
t.uuid("identityId").notNullable();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.IdentityAccessToken);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityAccessToken);
await dropOnUpdateTrigger(knex, TableName.IdentityAccessToken);
}

View File

@@ -0,0 +1,44 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityOrgMembership))) {
await knex.schema.createTable(TableName.IdentityOrgMembership, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("role").notNullable();
t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
t.uuid("identityId").notNullable();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
});
}
await createOnUpdateTrigger(knex, TableName.IdentityOrgMembership);
if (!(await knex.schema.hasTable(TableName.IdentityProjectMembership))) {
await knex.schema.createTable(TableName.IdentityProjectMembership, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("role").notNullable();
t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.ProjectRoles);
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.uuid("identityId").notNullable();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.IdentityProjectMembership);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityOrgMembership);
await knex.schema.dropTableIfExists(TableName.IdentityProjectMembership);
await dropOnUpdateTrigger(knex, TableName.IdentityProjectMembership);
await dropOnUpdateTrigger(knex, TableName.IdentityOrgMembership);
}

View File

@@ -0,0 +1,45 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicy))) {
await knex.schema.createTable(TableName.SecretApprovalPolicy, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.string("secretPath");
t.integer("approvals").defaultTo(1).notNullable();
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicy);
if (!(await knex.schema.hasTable(TableName.SapApprover))) {
await knex.schema.createTable(TableName.SapApprover, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("approverId").notNullable();
t.foreign("approverId")
.references("id")
.inTable(TableName.ProjectMembership)
.onDelete("CASCADE");
t.uuid("policyId").notNullable();
t.foreign("policyId")
.references("id")
.inTable(TableName.SecretApprovalPolicy)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SapApprover);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SapApprover);
await knex.schema.dropTableIfExists(TableName.SecretApprovalPolicy);
await dropOnUpdateTrigger(knex, TableName.SapApprover);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalPolicy);
}

View File

@@ -0,0 +1,115 @@
import { Knex } from "knex";
import { SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretApprovalRequest))) {
await knex.schema.createTable(TableName.SecretApprovalRequest, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("policyId").notNullable();
t.boolean("hasMerged").defaultTo(false).notNullable();
t.string("status").defaultTo("open").notNullable();
t.jsonb("conflicts");
t.foreign("policyId")
.references("id")
.inTable(TableName.SecretApprovalPolicy)
.onDelete("CASCADE");
t.string("slug").notNullable();
t.uuid("folderId").notNullable();
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
t.uuid("statusChangeBy");
t.foreign("statusChangeBy")
.references("id")
.inTable(TableName.ProjectMembership)
.onDelete("CASCADE");
t.uuid("committerId").notNullable();
t.foreign("committerId")
.references("id")
.inTable(TableName.ProjectMembership)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretApprovalRequest);
if (!(await knex.schema.hasTable(TableName.SarReviewer))) {
await knex.schema.createTable(TableName.SarReviewer, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("member").notNullable();
t.foreign("member").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
t.string("status").notNullable();
t.uuid("requestId").notNullable();
t.foreign("requestId")
.references("id")
.inTable(TableName.SecretApprovalRequest)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SarReviewer);
if (!(await knex.schema.hasTable(TableName.SarSecret))) {
await knex.schema.createTable(TableName.SarSecret, (t) => {
// everything related to secret
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.integer("version").defaultTo(1);
t.text("secretBlindIndex").notNullable();
t.text("secretKeyCiphertext").notNullable();
t.text("secretKeyIV").notNullable();
t.text("secretKeyTag").notNullable();
t.text("secretValueCiphertext").notNullable();
t.text("secretValueIV").notNullable(); // symmetric encryption
t.text("secretValueTag").notNullable();
t.text("secretCommentCiphertext");
t.text("secretCommentIV");
t.text("secretCommentTag");
t.string("secretReminderNote");
t.integer("secretReminderRepeatDays");
t.boolean("skipMultilineEncoding").defaultTo(false);
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
t.jsonb("metadata");
t.timestamps(true, true, true);
// commit details
t.uuid("requestId").notNullable();
t.foreign("requestId")
.references("id")
.inTable(TableName.SecretApprovalRequest)
.onDelete("CASCADE");
t.string("op").notNullable();
t.uuid("secretId");
t.foreign("secretId").references("id").inTable(TableName.Secret).onDelete("SET NULL");
t.uuid("secretVersion");
t.foreign("secretVersion")
.references("id")
.inTable(TableName.SecretVersion)
.onDelete("SET NULL");
});
}
await createOnUpdateTrigger(knex, TableName.SarSecret);
if (!(await knex.schema.hasTable(TableName.SarSecretTag))) {
await knex.schema.createTable(TableName.SarSecretTag, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("secretId").notNullable();
t.foreign("secretId").references("id").inTable(TableName.SarSecret).onDelete("CASCADE");
t.uuid("tagId").notNullable();
t.foreign("tagId").references("id").inTable(TableName.SecretTag).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SarSecretTag);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SarSecretTag);
await knex.schema.dropTableIfExists(TableName.SarSecret);
await knex.schema.dropTableIfExists(TableName.SarReviewer);
await knex.schema.dropTableIfExists(TableName.SecretApprovalRequest);
await dropOnUpdateTrigger(knex, TableName.SarSecretTag);
await dropOnUpdateTrigger(knex, TableName.SarSecret);
await dropOnUpdateTrigger(knex, TableName.SarReviewer);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalRequest);
}

View File

@@ -0,0 +1,47 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SecretRotation))) {
await knex.schema.createTable(TableName.SecretRotation, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("provider").notNullable();
t.string("secretPath").notNullable();
t.integer("interval").notNullable();
t.datetime("lastRotatedAt");
t.string("status");
t.text("statusMessage");
t.text("encryptedData");
t.text("encryptedDataIV");
t.text("encryptedDataTag");
t.string("algorithm");
t.string("keyEncoding");
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.SecretRotation);
if (!(await knex.schema.hasTable(TableName.SecretRotationOutput))) {
await knex.schema.createTable(TableName.SecretRotationOutput, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("key").notNullable();
t.uuid("secretId").notNullable();
t.foreign("secretId").references("id").inTable(TableName.Secret).onDelete("CASCADE");
t.uuid("rotationId").notNullable();
t.foreign("rotationId")
.references("id")
.inTable(TableName.SecretRotation)
.onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretRotationOutput);
await knex.schema.dropTableIfExists(TableName.SecretRotation);
await dropOnUpdateTrigger(knex, TableName.SecretRotation);
}

View File

@@ -0,0 +1,61 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.Snapshot))) {
await knex.schema.createTable(TableName.Snapshot, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
// this is not a relation kept like that
// this ensure snapshot are not lost when folder gets deleted and rolled back
t.uuid("folderId").notNullable();
t.uuid("parentFolderId");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.Snapshot);
if (!(await knex.schema.hasTable(TableName.SnapshotSecret))) {
await knex.schema.createTable(TableName.SnapshotSecret, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
// not a relation kept like that to keep it when rolled back
t.uuid("secretVersionId").notNullable();
t.foreign("secretVersionId")
.references("id")
.inTable(TableName.SecretVersion)
.onDelete("CASCADE");
t.uuid("snapshotId").notNullable();
t.foreign("snapshotId").references("id").inTable(TableName.Snapshot).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
if (!(await knex.schema.hasTable(TableName.SnapshotFolder))) {
await knex.schema.createTable(TableName.SnapshotFolder, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
// not a relation kept like that to keep it when rolled back
t.uuid("folderVersionId").notNullable();
t.foreign("folderVersionId")
.references("id")
.inTable(TableName.SecretFolderVersion)
.onDelete("CASCADE");
t.uuid("snapshotId").notNullable();
t.foreign("snapshotId").references("id").inTable(TableName.Snapshot).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SnapshotSecret);
await knex.schema.dropTableIfExists(TableName.SnapshotFolder);
await knex.schema.dropTableIfExists(TableName.Snapshot);
await dropOnUpdateTrigger(knex, TableName.Snapshot);
}

View File

@@ -0,0 +1,33 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.SamlConfig))) {
await knex.schema.createTable(TableName.SamlConfig, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("authProvider").notNullable();
t.boolean("isActive").notNullable();
t.string("encryptedEntryPoint");
t.string("entryPointIV");
t.string("entryPointTag");
t.string("encryptedIssuer");
t.string("issuerTag");
t.string("issuerIV");
t.text("encryptedCert");
t.string("certIV");
t.string("certTag");
t.timestamps(true, true, true);
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization);
});
}
await createOnUpdateTrigger(knex, TableName.SamlConfig);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SamlConfig);
await dropOnUpdateTrigger(knex, TableName.SamlConfig);
}

View File

@@ -0,0 +1,35 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.OrgBot))) {
await knex.schema.createTable(TableName.OrgBot, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.text("publicKey").notNullable();
t.text("encryptedSymmetricKey").notNullable();
t.text("symmetricKeyIV").notNullable();
t.text("symmetricKeyTag").notNullable();
t.string("symmetricKeyAlgorithm").notNullable();
t.string("symmetricKeyKeyEncoding").notNullable();
t.text("encryptedPrivateKey").notNullable();
t.text("privateKeyIV").notNullable();
t.text("privateKeyTag").notNullable();
t.string("privateKeyAlgorithm").notNullable();
t.string("privateKeyKeyEncoding").notNullable();
// one to one relationship
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.OrgBot);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.OrgBot);
await dropOnUpdateTrigger(knex, TableName.OrgBot);
}

View File

@@ -0,0 +1,29 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.AuditLog))) {
await knex.schema.createTable(TableName.AuditLog, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("actor").notNullable();
t.jsonb("actorMetadata").notNullable();
t.string("ipAddress");
t.string("eventType").notNullable();
t.jsonb("eventMetadata");
t.string("userAgent");
t.string("userAgentType");
t.datetime("expiresAt");
t.timestamps(true, true, true);
// no trigger needed as this collection is append only
t.uuid("orgId");
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.string("projectId");
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.AuditLog);
}

View File

@@ -0,0 +1,79 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.GitAppInstallSession))) {
await knex.schema.createTable(TableName.GitAppInstallSession, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("sessionId").notNullable().unique();
t.uuid("userId");
// one to one relationship
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
createOnUpdateTrigger(knex, TableName.GitAppInstallSession);
if (!(await knex.schema.hasTable(TableName.GitAppOrg))) {
await knex.schema.createTable(TableName.GitAppOrg, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("installationId").notNullable().unique();
t.uuid("userId").notNullable();
// one to one relationship
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
createOnUpdateTrigger(knex, TableName.GitAppOrg);
if (!(await knex.schema.hasTable(TableName.SecretScanningGitRisk))) {
await knex.schema.createTable(TableName.SecretScanningGitRisk, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("description");
t.string("startLine");
t.string("endLine");
t.string("startColumn");
t.string("endColumn");
t.string("file");
t.string("symlinkFile");
t.string("commit");
t.string("entropy");
t.string("author");
t.string("email");
t.string("date");
t.text("message");
t.specificType("tags", "text[]");
t.string("ruleID");
t.string("fingerprint").unique();
t.string("fingerPrintWithoutCommitId");
t.boolean("isFalsePositive").defaultTo(false);
t.boolean("isResolved").defaultTo(false);
t.string("riskOwner");
t.string("installationId").notNullable();
t.string("repositoryId");
t.string("repositoryLink");
t.string("repositoryFullName");
t.string("pusherName");
t.string("pusherEmail");
t.string("status");
// one to one relationship
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
createOnUpdateTrigger(knex, TableName.SecretScanningGitRisk);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretScanningGitRisk);
await knex.schema.dropTableIfExists(TableName.GitAppOrg);
await knex.schema.dropTableIfExists(TableName.GitAppInstallSession);
await dropOnUpdateTrigger(knex, TableName.SecretScanningGitRisk);
await dropOnUpdateTrigger(knex, TableName.GitAppOrg);
await dropOnUpdateTrigger(knex, TableName.GitAppInstallSession);
}

View File

@@ -0,0 +1,26 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.TrustedIps))) {
await knex.schema.createTable(TableName.TrustedIps, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("ipAddress").notNullable();
t.string("type").notNullable();
t.integer("prefix");
t.boolean("isActive").defaultTo(true);
t.string("comment");
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project);
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.TrustedIps);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.TrustedIps);
await dropOnUpdateTrigger(knex, TableName.TrustedIps);
}

View File

@@ -0,0 +1,39 @@
import { Schema, Types, model } from "mongoose";
export interface IAPIKeyData {
name: string;
user: Types.ObjectId;
lastUsed: Date;
expiresAt: Date;
secretHash: string;
}
const apiKeyDataSchema = new Schema<IAPIKeyData>(
{
name: {
type: String,
required: true,
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
lastUsed: {
type: Date,
},
expiresAt: {
type: Date,
},
secretHash: {
type: String,
required: true,
select: false,
},
},
{
timestamps: true,
}
);
export const APIKeyData = model<IAPIKeyData>("APIKeyData", apiKeyDataSchema);

View File

@@ -0,0 +1,38 @@
import { Document, Schema, Types, model } from "mongoose";
export interface IAPIKeyDataV2 extends Document {
_id: Types.ObjectId;
name: string;
user: Types.ObjectId;
lastUsed?: Date
usageCount: number;
expiresAt?: Date;
}
const apiKeyDataV2Schema = new Schema(
{
name: {
type: String,
required: true
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true
},
lastUsed: {
type: Date,
required: false
},
usageCount: {
type: Number,
default: 0,
required: true
}
},
{
timestamps: true
}
);
export const APIKeyDataV2 = model<IAPIKeyDataV2>("APIKeyDataV2", apiKeyDataV2Schema);

View File

@@ -0,0 +1,70 @@
import { Schema, Types, model } from "mongoose";
import { ActorType, EventType, UserAgentType } from "./enums";
import { Actor, Event } from "./types";
export interface IAuditLog {
actor: Actor;
organization: Types.ObjectId;
workspace: Types.ObjectId;
ipAddress: string;
event: Event;
userAgent: string;
userAgentType: UserAgentType;
expiresAt?: Date;
}
const auditLogSchema = new Schema<IAuditLog>(
{
actor: {
type: {
type: String,
enum: ActorType,
required: true
},
metadata: {
type: Schema.Types.Mixed
}
},
organization: {
type: Schema.Types.ObjectId,
required: false
},
workspace: {
type: Schema.Types.ObjectId,
required: false,
index: true
},
ipAddress: {
type: String,
required: true
},
event: {
type: {
type: String,
enum: EventType,
required: true
},
metadata: {
type: Schema.Types.Mixed
}
},
userAgent: {
type: String,
required: true
},
userAgentType: {
type: String,
enum: UserAgentType,
required: true
},
expiresAt: {
type: Date,
expires: 0
}
},
{
timestamps: true
}
);
export const AuditLog = model<IAuditLog>("AuditLog", auditLogSchema);

View File

@@ -0,0 +1,69 @@
export enum ActorType { // would extend to AWS, Azure, ...
USER = "user", // userIdentity
SERVICE = "service",
IDENTITY = "identity"
}
export enum UserAgentType {
WEB = "web",
CLI = "cli",
K8_OPERATOR = "k8-operator",
TERRAFORM = "terraform",
OTHER = "other",
PYTHON_SDK = "InfisicalPythonSDK",
NODE_SDK = "InfisicalNodeSDK"
}
export enum EventType {
GET_SECRETS = "get-secrets",
GET_SECRET = "get-secret",
REVEAL_SECRET = "reveal-secret",
CREATE_SECRET = "create-secret",
CREATE_SECRETS = "create-secrets",
UPDATE_SECRET = "update-secret",
UPDATE_SECRETS = "update-secrets",
DELETE_SECRET = "delete-secret",
DELETE_SECRETS = "delete-secrets",
GET_WORKSPACE_KEY = "get-workspace-key",
AUTHORIZE_INTEGRATION = "authorize-integration",
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
CREATE_INTEGRATION = "create-integration",
DELETE_INTEGRATION = "delete-integration",
ADD_TRUSTED_IP = "add-trusted-ip",
UPDATE_TRUSTED_IP = "update-trusted-ip",
DELETE_TRUSTED_IP = "delete-trusted-ip",
CREATE_SERVICE_TOKEN = "create-service-token", // v2
DELETE_SERVICE_TOKEN = "delete-service-token", // v2
CREATE_IDENTITY = "create-identity",
UPDATE_IDENTITY = "update-identity",
DELETE_IDENTITY = "delete-identity",
LOGIN_IDENTITY_UNIVERSAL_AUTH = "login-identity-universal-auth",
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth",
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",
ADD_WORKSPACE_MEMBER = "add-workspace-member",
ADD_BATCH_WORKSPACE_MEMBER = "add-workspace-members",
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
CREATE_FOLDER = "create-folder",
UPDATE_FOLDER = "update-folder",
DELETE_FOLDER = "delete-folder",
CREATE_WEBHOOK = "create-webhook",
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
DELETE_WEBHOOK = "delete-webhook",
GET_SECRET_IMPORTS = "get-secret-imports",
CREATE_SECRET_IMPORT = "create-secret-import",
UPDATE_SECRET_IMPORT = "update-secret-import",
DELETE_SECRET_IMPORT = "delete-secret-import",
UPDATE_USER_WORKSPACE_ROLE = "update-user-workspace-role",
UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS = "update-user-workspace-denied-permissions",
SECRET_APPROVAL_MERGED = "secret-approval-merged",
SECRET_APPROVAL_REQUEST = "secret-approval-request",
SECRET_APPROVAL_CLOSED = "secret-approval-closed",
SECRET_APPROVAL_REOPENED = "secret-approval-reopened"
}

View File

@@ -0,0 +1,3 @@
export * from "./auditLog";
export * from "./enums";
export * from "./types";

View File

@@ -0,0 +1,585 @@
import { ActorType, EventType } from "./enums";
import { IIdentityTrustedIp } from "../../../models";
interface UserActorMetadata {
userId: string;
email: string;
}
interface ServiceActorMetadata {
serviceId: string;
name: string;
}
interface IdentityActorMetadata {
identityId: string;
name: string;
}
export interface UserActor {
type: ActorType.USER;
metadata: UserActorMetadata;
}
export interface ServiceActor {
type: ActorType.SERVICE;
metadata: ServiceActorMetadata;
}
export interface IdentityActor {
type: ActorType.IDENTITY;
metadata: IdentityActorMetadata;
}
export type Actor = UserActor | ServiceActor | IdentityActor;
interface GetSecretsEvent {
type: EventType.GET_SECRETS;
metadata: {
environment: string;
secretPath: string;
numberOfSecrets: number;
};
}
interface GetSecretEvent {
type: EventType.GET_SECRET;
metadata: {
environment: string;
secretPath: string;
secretId: string;
secretKey: string;
secretVersion: number;
};
}
interface CreateSecretEvent {
type: EventType.CREATE_SECRET;
metadata: {
environment: string;
secretPath: string;
secretId: string;
secretKey: string;
secretVersion: number;
};
}
interface CreateSecretBatchEvent {
type: EventType.CREATE_SECRETS;
metadata: {
environment: string;
secretPath: string;
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
};
}
interface UpdateSecretEvent {
type: EventType.UPDATE_SECRET;
metadata: {
environment: string;
secretPath: string;
secretId: string;
secretKey: string;
secretVersion: number;
};
}
interface UpdateSecretBatchEvent {
type: EventType.UPDATE_SECRETS;
metadata: {
environment: string;
secretPath: string;
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
};
}
interface DeleteSecretEvent {
type: EventType.DELETE_SECRET;
metadata: {
environment: string;
secretPath: string;
secretId: string;
secretKey: string;
secretVersion: number;
};
}
interface DeleteSecretBatchEvent {
type: EventType.DELETE_SECRETS;
metadata: {
environment: string;
secretPath: string;
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
};
}
interface GetWorkspaceKeyEvent {
type: EventType.GET_WORKSPACE_KEY;
metadata: {
keyId: string;
};
}
interface AuthorizeIntegrationEvent {
type: EventType.AUTHORIZE_INTEGRATION;
metadata: {
integration: string;
};
}
interface UnauthorizeIntegrationEvent {
type: EventType.UNAUTHORIZE_INTEGRATION;
metadata: {
integration: string;
};
}
interface CreateIntegrationEvent {
type: EventType.CREATE_INTEGRATION;
metadata: {
integrationId: string;
integration: string; // TODO: fix type
environment: string;
secretPath: string;
url?: string;
app?: string;
appId?: string;
targetEnvironment?: string;
targetEnvironmentId?: string;
targetService?: string;
targetServiceId?: string;
path?: string;
region?: string;
};
}
interface DeleteIntegrationEvent {
type: EventType.DELETE_INTEGRATION;
metadata: {
integrationId: string;
integration: string; // TODO: fix type
environment: string;
secretPath: string;
url?: string;
app?: string;
appId?: string;
targetEnvironment?: string;
targetEnvironmentId?: string;
targetService?: string;
targetServiceId?: string;
path?: string;
region?: string;
};
}
interface AddTrustedIPEvent {
type: EventType.ADD_TRUSTED_IP;
metadata: {
trustedIpId: string;
ipAddress: string;
prefix?: number;
};
}
interface UpdateTrustedIPEvent {
type: EventType.UPDATE_TRUSTED_IP;
metadata: {
trustedIpId: string;
ipAddress: string;
prefix?: number;
};
}
interface DeleteTrustedIPEvent {
type: EventType.DELETE_TRUSTED_IP;
metadata: {
trustedIpId: string;
ipAddress: string;
prefix?: number;
};
}
interface CreateServiceTokenEvent {
type: EventType.CREATE_SERVICE_TOKEN;
metadata: {
name: string;
scopes: Array<{
environment: string;
secretPath: string;
}>;
};
}
interface DeleteServiceTokenEvent {
type: EventType.DELETE_SERVICE_TOKEN;
metadata: {
name: string;
scopes: Array<{
environment: string;
secretPath: string;
}>;
};
}
interface CreateIdentityEvent { // note: currently not logging org-role
type: EventType.CREATE_IDENTITY;
metadata: {
identityId: string;
name: string;
};
}
interface UpdateIdentityEvent {
type: EventType.UPDATE_IDENTITY;
metadata: {
identityId: string;
name?: string;
};
}
interface DeleteIdentityEvent {
type: EventType.DELETE_IDENTITY;
metadata: {
identityId: string;
};
}
interface LoginIdentityUniversalAuthEvent {
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH ;
metadata: {
identityId: string;
identityUniversalAuthId: string;
clientSecretId: string;
identityAccessTokenId: string;
};
}
interface AddIdentityUniversalAuthEvent {
type: EventType.ADD_IDENTITY_UNIVERSAL_AUTH;
metadata: {
identityId: string;
clientSecretTrustedIps: Array<IIdentityTrustedIp>;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<IIdentityTrustedIp>;
};
}
interface UpdateIdentityUniversalAuthEvent {
type: EventType.UPDATE_IDENTITY_UNIVERSAL_AUTH;
metadata: {
identityId: string;
clientSecretTrustedIps?: Array<IIdentityTrustedIp>;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<IIdentityTrustedIp>;
};
}
interface GetIdentityUniversalAuthEvent {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH;
metadata: {
identityId: string;
};
}
interface CreateIdentityUniversalAuthClientSecretEvent {
type: EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET ;
metadata: {
identityId: string;
clientSecretId: string;
};
}
interface GetIdentityUniversalAuthClientSecretsEvent {
type: EventType.GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS;
metadata: {
identityId: string;
};
}
interface RevokeIdentityUniversalAuthClientSecretEvent {
type: EventType.REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET ;
metadata: {
identityId: string;
clientSecretId: string;
};
}
interface CreateEnvironmentEvent {
type: EventType.CREATE_ENVIRONMENT;
metadata: {
name: string;
slug: string;
};
}
interface UpdateEnvironmentEvent {
type: EventType.UPDATE_ENVIRONMENT;
metadata: {
oldName: string;
newName: string;
oldSlug: string;
newSlug: string;
};
}
interface DeleteEnvironmentEvent {
type: EventType.DELETE_ENVIRONMENT;
metadata: {
name: string;
slug: string;
};
}
interface AddWorkspaceMemberEvent {
type: EventType.ADD_WORKSPACE_MEMBER;
metadata: {
userId: string;
email: string;
};
}
interface AddBatchWorkspaceMemberEvent {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER;
metadata: Array<{
userId: string;
email: string;
}>;
}
interface RemoveWorkspaceMemberEvent {
type: EventType.REMOVE_WORKSPACE_MEMBER;
metadata: {
userId: string;
email: string;
};
}
interface CreateFolderEvent {
type: EventType.CREATE_FOLDER;
metadata: {
environment: string;
folderId: string;
folderName: string;
folderPath: string;
};
}
interface UpdateFolderEvent {
type: EventType.UPDATE_FOLDER;
metadata: {
environment: string;
folderId: string;
oldFolderName: string;
newFolderName: string;
folderPath: string;
};
}
interface DeleteFolderEvent {
type: EventType.DELETE_FOLDER;
metadata: {
environment: string;
folderId: string;
folderName: string;
folderPath: string;
};
}
interface CreateWebhookEvent {
type: EventType.CREATE_WEBHOOK;
metadata: {
webhookId: string;
environment: string;
secretPath: string;
webhookUrl: string;
isDisabled: boolean;
};
}
interface UpdateWebhookStatusEvent {
type: EventType.UPDATE_WEBHOOK_STATUS;
metadata: {
webhookId: string;
environment: string;
secretPath: string;
webhookUrl: string;
isDisabled: boolean;
};
}
interface DeleteWebhookEvent {
type: EventType.DELETE_WEBHOOK;
metadata: {
webhookId: string;
environment: string;
secretPath: string;
webhookUrl: string;
isDisabled: boolean;
};
}
interface GetSecretImportsEvent {
type: EventType.GET_SECRET_IMPORTS;
metadata: {
environment: string;
secretImportId: string;
folderId: string;
numberOfImports: number;
};
}
interface CreateSecretImportEvent {
type: EventType.CREATE_SECRET_IMPORT;
metadata: {
secretImportId: string;
folderId: string;
importFromEnvironment: string;
importFromSecretPath: string;
importToEnvironment: string;
importToSecretPath: string;
};
}
interface UpdateSecretImportEvent {
type: EventType.UPDATE_SECRET_IMPORT;
metadata: {
secretImportId: string;
folderId: string;
importToEnvironment: string;
importToSecretPath: string;
orderBefore: {
environment: string;
secretPath: string;
}[];
orderAfter: {
environment: string;
secretPath: string;
}[];
};
}
interface DeleteSecretImportEvent {
type: EventType.DELETE_SECRET_IMPORT;
metadata: {
secretImportId: string;
folderId: string;
importFromEnvironment: string;
importFromSecretPath: string;
importToEnvironment: string;
importToSecretPath: string;
};
}
interface UpdateUserRole {
type: EventType.UPDATE_USER_WORKSPACE_ROLE;
metadata: {
userId: string;
email: string;
oldRole: string;
newRole: string;
};
}
interface UpdateUserDeniedPermissions {
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS;
metadata: {
userId: string;
email: string;
deniedPermissions: {
environmentSlug: string;
ability: string;
}[];
};
}
interface SecretApprovalMerge {
type: EventType.SECRET_APPROVAL_MERGED;
metadata: {
mergedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
interface SecretApprovalClosed {
type: EventType.SECRET_APPROVAL_CLOSED;
metadata: {
closedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
interface SecretApprovalReopened {
type: EventType.SECRET_APPROVAL_REOPENED;
metadata: {
reopenedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
interface SecretApprovalRequest {
type: EventType.SECRET_APPROVAL_REQUEST;
metadata: {
committedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
export type Event =
| GetSecretsEvent
| GetSecretEvent
| CreateSecretEvent
| CreateSecretBatchEvent
| UpdateSecretEvent
| UpdateSecretBatchEvent
| DeleteSecretEvent
| DeleteSecretBatchEvent
| GetWorkspaceKeyEvent
| AuthorizeIntegrationEvent
| UnauthorizeIntegrationEvent
| CreateIntegrationEvent
| DeleteIntegrationEvent
| AddTrustedIPEvent
| UpdateTrustedIPEvent
| DeleteTrustedIPEvent
| CreateServiceTokenEvent
| DeleteServiceTokenEvent
| CreateIdentityEvent
| UpdateIdentityEvent
| DeleteIdentityEvent
| LoginIdentityUniversalAuthEvent
| AddIdentityUniversalAuthEvent
| UpdateIdentityUniversalAuthEvent
| GetIdentityUniversalAuthEvent
| CreateIdentityUniversalAuthClientSecretEvent
| GetIdentityUniversalAuthClientSecretsEvent
| RevokeIdentityUniversalAuthClientSecretEvent
| CreateEnvironmentEvent
| UpdateEnvironmentEvent
| DeleteEnvironmentEvent
| AddWorkspaceMemberEvent
| AddBatchWorkspaceMemberEvent
| RemoveWorkspaceMemberEvent
| CreateFolderEvent
| UpdateFolderEvent
| DeleteFolderEvent
| CreateWebhookEvent
| UpdateWebhookStatusEvent
| DeleteWebhookEvent
| GetSecretImportsEvent
| CreateSecretImportEvent
| UpdateSecretImportEvent
| DeleteSecretImportEvent
| UpdateUserRole
| UpdateUserDeniedPermissions
| SecretApprovalMerge
| SecretApprovalClosed
| SecretApprovalRequest
| SecretApprovalReopened;

View File

@@ -0,0 +1,74 @@
import { Schema, Types, model } from "mongoose";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
} from "../variables";
export interface IBackupPrivateKey {
_id: Types.ObjectId;
user: Types.ObjectId;
encryptedPrivateKey: string;
iv: string;
tag: string;
salt: string;
algorithm: string;
keyEncoding: "base64" | "utf8";
verifier: string;
}
const backupPrivateKeySchema = new Schema<IBackupPrivateKey>(
{
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
encryptedPrivateKey: {
type: String,
select: false,
required: true,
},
iv: {
type: String,
select: false,
required: true,
},
tag: {
type: String,
select: false,
required: true,
},
algorithm: { // the encryption algorithm used
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true,
},
keyEncoding: {
type: String,
enum: [
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64,
],
required: true,
},
salt: {
type: String,
select: false,
required: true,
},
verifier: {
type: String,
select: false,
required: true,
},
},
{
timestamps: true,
}
);
export const BackupPrivateKey = model<IBackupPrivateKey>(
"BackupPrivateKey",
backupPrivateKeySchema
);

View File

@@ -0,0 +1,77 @@
import { Schema, Types, model } from "mongoose";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
} from "../variables";
export interface IBot {
_id: Types.ObjectId;
name: string;
workspace: Types.ObjectId;
isActive: boolean;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
algorithm: "aes-256-gcm";
keyEncoding: "base64" | "utf8";
}
const botSchema = new Schema<IBot>(
{
name: {
type: String,
required: true,
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
isActive: {
type: Boolean,
required: true,
default: false,
},
publicKey: {
type: String,
required: true,
},
encryptedPrivateKey: {
type: String,
required: true,
select: false,
},
iv: {
type: String,
required: true,
select: false,
},
tag: {
type: String,
required: true,
select: false,
},
algorithm: { // the encryption algorithm used
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true,
select: false,
},
keyEncoding: {
type: String,
enum: [
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64,
],
required: true,
select: false,
},
},
{
timestamps: true,
}
);
export const Bot = model<IBot>("Bot", botSchema);

View File

@@ -0,0 +1,43 @@
import { Schema, Types, model } from "mongoose";
export interface IBotKey {
_id: Types.ObjectId;
encryptedKey: string;
nonce: string;
sender: Types.ObjectId;
bot: Types.ObjectId;
workspace: Types.ObjectId;
}
const botKeySchema = new Schema<IBotKey>(
{
encryptedKey: {
type: String,
required: true,
},
nonce: {
type: String,
required: true,
},
sender: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
bot: {
type: Schema.Types.ObjectId,
ref: "Bot",
required: true,
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
},
{
timestamps: true,
}
);
export const BotKey = model<IBotKey>("BotKey", botKeySchema);

View File

@@ -0,0 +1,96 @@
import { Schema, Types, model } from "mongoose";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
} from "../variables";
export interface IBotOrg {
_id: Types.ObjectId;
name: string;
organization: Types.ObjectId;
publicKey: string;
encryptedSymmetricKey: string;
symmetricKeyIV: string;
symmetricKeyTag: string;
symmetricKeyAlgorithm: "aes-256-gcm";
symmetricKeyKeyEncoding: "base64" | "utf8";
encryptedPrivateKey: string;
privateKeyIV: string;
privateKeyTag: string;
privateKeyAlgorithm: "aes-256-gcm";
privateKeyKeyEncoding: "base64" | "utf8";
}
const botOrgSchema = new Schema<IBotOrg>(
{
name: {
type: String,
required: true,
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization",
required: true,
},
publicKey: {
type: String,
required: true,
},
encryptedSymmetricKey: {
type: String,
required: true
},
symmetricKeyIV: {
type: String,
required: true
},
symmetricKeyTag: {
type: String,
required: true
},
symmetricKeyAlgorithm: {
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true
},
symmetricKeyKeyEncoding: {
type: String,
enum: [
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64,
],
required: true
},
encryptedPrivateKey: {
type: String,
required: true
},
privateKeyIV: {
type: String,
required: true
},
privateKeyTag: {
type: String,
required: true
},
privateKeyAlgorithm: {
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true
},
privateKeyKeyEncoding: {
type: String,
enum: [
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64,
],
required: true
},
},
{
timestamps: true,
}
);
export const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema);

View File

@@ -0,0 +1,54 @@
import { Schema, Types, model } from "mongoose";
export type TFolderRootSchema = {
_id: Types.ObjectId;
workspace: Types.ObjectId;
environment: string;
nodes: TFolderSchema;
};
export type TFolderSchema = {
id: string;
name: string;
version: number;
children: TFolderSchema[];
};
const folderSchema = new Schema<TFolderSchema>({
id: {
required: true,
type: String,
},
version: {
required: true,
type: Number,
default: 1,
},
name: {
required: true,
type: String,
default: "root",
},
});
folderSchema.add({ children: [folderSchema] });
const folderRootSchema = new Schema<TFolderRootSchema>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
environment: {
type: String,
required: true,
},
nodes: folderSchema,
},
{
timestamps: true,
}
);
export const Folder = model<TFolderRootSchema>("Folder", folderRootSchema);

View File

@@ -0,0 +1,58 @@
import { Schema, Types, model } from "mongoose";
export type TFolderRootVersionSchema = {
_id: Types.ObjectId;
workspace: Types.ObjectId;
environment: string;
nodes: TFolderVersionSchema;
};
export type TFolderVersionSchema = {
id: string;
name: string;
version: number;
children: TFolderVersionSchema[];
};
const folderVersionSchema = new Schema<TFolderVersionSchema>({
id: {
required: true,
type: String,
default: "root",
},
name: {
required: true,
type: String,
default: "root",
},
version: {
required: true,
type: Number,
default: 1,
},
});
folderVersionSchema.add({ children: [folderVersionSchema] });
const folderRootVersionSchema = new Schema<TFolderRootVersionSchema>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
environment: {
type: String,
required: true,
},
nodes: folderVersionSchema,
},
{
timestamps: true,
}
);
export const FolderVersion = model<TFolderRootVersionSchema>(
"FolderVersion",
folderRootVersionSchema
);

View File

@@ -0,0 +1,32 @@
import { Schema, Types, model } from "mongoose";
type GitAppInstallationSession = {
id: string;
sessionId: string;
organization: Types.ObjectId;
user: Types.ObjectId;
}
const gitAppInstallationSession = new Schema<GitAppInstallationSession>({
id: {
required: true,
type: String,
},
sessionId: {
type: String,
required: true,
unique: true
},
organization: {
type: Schema.Types.ObjectId,
required: true,
unique: true
},
user: {
type: Schema.Types.ObjectId,
ref: "User"
}
});
export const GitAppInstallationSession = model<GitAppInstallationSession>("git_app_installation_session", gitAppInstallationSession);

View File

@@ -0,0 +1,29 @@
import { Schema, model } from "mongoose";
type Installation = {
installationId: string
organizationId: string
user: Schema.Types.ObjectId
};
const gitAppOrganizationInstallation = new Schema<Installation>({
installationId: {
type: String,
required: true,
unique: true
},
organizationId: {
type: String,
required: true,
unique: true
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
}
});
export const GitAppOrganizationInstallation = model<Installation>("git_app_organization_installation", gitAppOrganizationInstallation);

View File

@@ -0,0 +1,150 @@
import { Schema, model } from "mongoose";
export const STATUS_RESOLVED_FALSE_POSITIVE = "RESOLVED_FALSE_POSITIVE";
export const STATUS_RESOLVED_REVOKED = "RESOLVED_REVOKED";
export const STATUS_RESOLVED_NOT_REVOKED = "RESOLVED_NOT_REVOKED";
export const STATUS_UNRESOLVED = "UNRESOLVED";
export type IGitRisks = {
id: string;
description: string;
startLine: string;
endLine: string;
startColumn: string;
endColumn: string;
match: string;
secret: string;
file: string;
symlinkFile: string;
commit: string;
entropy: string;
author: string;
email: string;
date: string;
message: string;
tags: string[];
ruleID: string;
fingerprint: string;
fingerPrintWithoutCommitId: string
isFalsePositive: boolean; // New field for marking risks as false positives
isResolved: boolean; // New field for marking risks as resolved
riskOwner: string | null; // New field for setting a risk owner (nullable string)
installationId: string,
repositoryId: string,
repositoryLink: string
repositoryFullName: string
status: string
pusher: {
name: string,
email: string
},
organization: Schema.Types.ObjectId,
}
const gitRisks = new Schema<IGitRisks>({
id: {
type: String,
},
description: {
type: String,
},
startLine: {
type: String,
},
endLine: {
type: String,
},
startColumn: {
type: String,
},
endColumn: {
type: String,
},
file: {
type: String,
},
symlinkFile: {
type: String,
},
commit: {
type: String,
},
entropy: {
type: String,
},
author: {
type: String,
},
email: {
type: String,
},
date: {
type: String,
},
message: {
type: String,
},
tags: {
type: [String],
},
ruleID: {
type: String,
},
fingerprint: {
type: String,
unique: true
},
fingerPrintWithoutCommitId: {
type: String,
},
isFalsePositive: {
type: Boolean,
default: false
},
isResolved: {
type: Boolean,
default: false
},
riskOwner: {
type: String,
default: null
},
installationId: {
type: String,
require: true
},
repositoryId: {
type: String
},
repositoryLink: {
type: String
},
repositoryFullName: {
type: String
},
pusher: {
name: {
type: String
},
email: {
type: String
},
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization",
},
status: {
type: String,
enum: [
STATUS_RESOLVED_FALSE_POSITIVE,
STATUS_RESOLVED_REVOKED,
STATUS_RESOLVED_NOT_REVOKED,
STATUS_UNRESOLVED
],
default: STATUS_UNRESOLVED
}
}, { timestamps: true });
export const GitRisks = model<IGitRisks>("GitRisks", gitRisks);

View File

@@ -0,0 +1,38 @@
import { Document, Schema, Types, model } from "mongoose";
import { IPType } from "../ee/models";
export interface IIdentityTrustedIp {
ipAddress: string;
type: IPType;
prefix: number;
}
export enum IdentityAuthMethod {
UNIVERSAL_AUTH = "universal-auth"
}
export interface IIdentity extends Document {
_id: Types.ObjectId;
name: string;
authMethod?: IdentityAuthMethod;
}
const identitySchema = new Schema(
{
name: {
type: String,
required: true
},
authMethod: {
type: String,
enum: IdentityAuthMethod,
required: false,
},
},
{
timestamps: true
}
);
export const Identity = model<IIdentity>("Identity", identitySchema);

View File

@@ -0,0 +1,104 @@
import { Document, Schema, Types, model } from "mongoose";
import { IIdentityTrustedIp } from "./identity";
import { IPType } from "../ee/models/trustedIp";
export interface IIdentityAccessToken extends Document {
_id: Types.ObjectId;
identity: Types.ObjectId;
identityUniversalAuthClientSecret?: Types.ObjectId;
accessTokenLastUsedAt?: Date;
accessTokenLastRenewedAt?: Date;
accessTokenNumUses: number;
accessTokenNumUsesLimit: number;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenTrustedIps: Array<IIdentityTrustedIp>;
isAccessTokenRevoked: boolean;
updatedAt: Date;
createdAt: Date;
}
const identityAccessTokenSchema = new Schema(
{
identity: {
type: Schema.Types.ObjectId,
ref: "Identity",
required: false
},
identityUniversalAuthClientSecret: {
type: Schema.Types.ObjectId,
ref: "IdentityUniversalAuthClientSecret",
required: false
},
accessTokenLastUsedAt: {
type: Date,
required: false
},
accessTokenLastRenewedAt: {
type: Date,
required: false
},
accessTokenNumUses: {
// number of times access token has been used
type: Number,
default: 0,
required: true
},
accessTokenNumUsesLimit: {
// number of times access token can be used for
type: Number,
default: 0, // default: used as many times as needed
required: true
},
accessTokenTTL: { // seconds
// incremental lifetime
type: Number,
default: 2592000, // 30 days
required: true
},
accessTokenMaxTTL: { // seconds
// max lifetime
type: Number,
default: 2592000, // 30 days
required: true
},
accessTokenTrustedIps: {
type: [
{
ipAddress: {
type: String,
required: true
},
type: {
type: String,
enum: [
IPType.IPV4,
IPType.IPV6
],
required: true
},
prefix: {
type: Number,
required: false
}
}
],
default: [{
ipAddress: "0.0.0.0",
type: IPType.IPV4.toString(),
prefix: 0
}],
required: true
},
isAccessTokenRevoked: {
type: Boolean,
default: false,
required: true
},
},
{
timestamps: true
}
);
export const IdentityAccessToken = model<IIdentityAccessToken>("IdentityAccessToken", identityAccessTokenSchema);

View File

@@ -0,0 +1,39 @@
import { Schema, Types, model } from "mongoose";
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../variables";
export interface IIdentityMembership {
_id: Types.ObjectId;
identity: Types.ObjectId;
workspace: Types.ObjectId;
role: "admin" | "member" | "viewer" | "no-access" | "custom";
customRole: Types.ObjectId;
}
const identityMembershipSchema = new Schema<IIdentityMembership>(
{
identity: {
type: Schema.Types.ObjectId,
ref: "Identity"
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
index: true,
},
role: {
type: String,
enum: [ADMIN, MEMBER, VIEWER, CUSTOM, NO_ACCESS],
required: true
},
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
}
},
{
timestamps: true
}
);
export const IdentityMembership = model<IIdentityMembership>("IdentityMembership", identityMembershipSchema);

View File

@@ -0,0 +1,37 @@
import { Schema, Types, model } from "mongoose";
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS} from "../variables";
export interface IIdentityMembershipOrg {
_id: Types.ObjectId;
identity: Types.ObjectId;
organization: Types.ObjectId;
role: "admin" | "member" | "no-access" | "custom";
customRole: Types.ObjectId;
}
const identityMembershipOrgSchema = new Schema<IIdentityMembershipOrg>(
{
identity: {
type: Schema.Types.ObjectId,
ref: "Identity"
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization"
},
role: {
type: String,
enum: [ADMIN, MEMBER, NO_ACCESS, CUSTOM],
required: true
},
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
}
},
{
timestamps: true
}
);
export const IdentityMembershipOrg = model<IIdentityMembershipOrg>("IdentityMembershipOrg", identityMembershipOrgSchema);

View File

@@ -0,0 +1,107 @@
import { Document, Schema, Types, model } from "mongoose";
import { IPType } from "../ee/models";
import { IIdentityTrustedIp } from "./identity";
export interface IIdentityUniversalAuth extends Document {
_id: Types.ObjectId;
identity: Types.ObjectId;
clientId: string;
clientSecretTrustedIps: Array<IIdentityTrustedIp>;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<IIdentityTrustedIp>;
}
const identityUniversalAuthSchema = new Schema(
{
identity: {
type: Schema.Types.ObjectId,
ref: "Identity",
required: true
},
clientId: {
type: String,
required: true
},
clientSecretTrustedIps: {
type: [
{
ipAddress: {
type: String,
required: true
},
type: {
type: String,
enum: [
IPType.IPV4,
IPType.IPV6
],
required: true
},
prefix: {
type: Number,
required: false
}
}
],
default: [{
ipAddress: "0.0.0.0",
type: IPType.IPV4.toString(),
prefix: 0
}],
required: true
},
accessTokenTTL: { // seconds
// incremental lifetime
type: Number,
default: 7200,
required: true
},
accessTokenMaxTTL: { // seconds
// max lifetime
type: Number,
default: 7200,
required: true
},
accessTokenNumUsesLimit: {
// number of times access token can be used for
type: Number,
default: 0, // default: used as many times as needed
required: true
},
accessTokenTrustedIps: {
type: [
{
ipAddress: {
type: String,
required: true
},
type: {
type: String,
enum: [
IPType.IPV4,
IPType.IPV6
],
required: true
},
prefix: {
type: Number,
required: false
}
}
],
default: [{
ipAddress: "0.0.0.0",
type: IPType.IPV4.toString(),
prefix: 0
}],
required: true
}
},
{
timestamps: true
}
);
export const IdentityUniversalAuth = model<IIdentityUniversalAuth>("IdentityUniversalAuth", identityUniversalAuthSchema);

View File

@@ -0,0 +1,81 @@
import { Document, Schema, Types, model } from "mongoose";
export interface IIdentityUniversalAuthClientSecret extends Document {
_id: Types.ObjectId;
identity: Types.ObjectId;
identityUniversalAuth : Types.ObjectId;
description: string;
clientSecretPrefix: string;
clientSecretHash: string;
clientSecretLastUsedAt?: Date;
clientSecretNumUses: number;
clientSecretNumUsesLimit: number;
clientSecretTTL: number;
updatedAt: Date;
createdAt: Date;
isClientSecretRevoked: boolean;
}
const identityUniversalAuthClientSecretSchema = new Schema(
{
identity: {
type: Schema.Types.ObjectId,
ref: "Identity",
required: true
},
identityUniversalAuth: {
type: Schema.Types.ObjectId,
ref: "IdentityUniversalAuth",
required: true
},
description: {
type: String,
required: true
},
clientSecretPrefix: {
type: String,
required: true
},
clientSecretHash: {
type: String,
required: true
},
clientSecretLastUsedAt: {
type: Date,
required: false
},
clientSecretNumUses: {
// number of times client secret has been used
// in login operation
type: Number,
default: 0,
required: true
},
clientSecretNumUsesLimit: {
// number of times client secret can be used for
// a login operation
type: Number,
default: 0, // default: used as many times as needed
required: true
},
clientSecretTTL: {
type: Number,
default: 0, // default: does not expire
required: true
},
isClientSecretRevoked: {
type: Boolean,
default: false,
required: true
}
},
{
timestamps: true
}
);
identityUniversalAuthClientSecretSchema.index(
{ identityUniversalAuth: 1, isClientSecretRevoked: 1 }
);
export const IdentityUniversalAuthClientSecret = model<IIdentityUniversalAuthClientSecret>("IdentityUniversalAuthClientSecret", identityUniversalAuthClientSecretSchema);

View File

@@ -0,0 +1,29 @@
import { Schema, Types, model } from "mongoose";
export interface IIncidentContactOrg {
_id: Types.ObjectId;
email: string;
organization: Types.ObjectId;
}
const incidentContactOrgSchema = new Schema<IIncidentContactOrg>(
{
email: {
type: String,
required: true,
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization",
required: true,
},
},
{
timestamps: true,
}
);
export const IncidentContactOrg = model<IIncidentContactOrg>(
"IncidentContactOrg",
incidentContactOrgSchema
);

View File

@@ -0,0 +1,49 @@
export * from "./backupPrivateKey";
export * from "./bot";
export * from "./botOrg";
export * from "./botKey";
export * from "./incidentContactOrg";
export * from "./integration/integration";
export * from "./integrationAuth";
export * from "./key";
export * from "./membership";
export * from "./membershipOrg";
export * from "./organization";
export * from "./secret";
export * from "./tag";
export * from "./folder";
export * from "./secretImports";
export * from "./secretBlindIndexData";
export * from "./serviceToken"; // TODO: deprecate
export * from "./tokenData";
export * from "./user";
export * from "./userAction";
export * from "./workspace";
export * from "./serviceTokenData"; // TODO: deprecate
// new
export * from "./identity";
export * from "./identityMembership";
export * from "./identityMembershipOrg";
export * from "./identityUniversalAuth";
export * from "./identityUniversalAuthClientSecret";
export * from "./identityAccessToken";
export * from "./apiKeyData"; // TODO: deprecate
export * from "./apiKeyDataV2";
export * from "./loginSRPDetail";
export * from "./tokenVersion";
export * from "./webhooks";
export * from "./secretSnapshot";
export * from "./secretVersion";
export * from "./folderVersion";
export * from "./role";
export * from "./ssoConfig";
export * from "./trustedIp";
export * from "./auditLog";
export * from "./gitRisks";
export * from "./gitAppOrganizationInstallation";
export * from "./gitAppInstallationSession";
export * from "./secretApprovalPolicy";
export * from "./secretApprovalRequest";

View File

@@ -0,0 +1 @@
export * from "./integration";

View File

@@ -0,0 +1,216 @@
import {
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_BITBUCKET,
INTEGRATION_CHECKLY,
INTEGRATION_CIRCLECI,
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CLOUD_66,
INTEGRATION_CODEFRESH,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_HASHICORP_VAULT,
INTEGRATION_HASURA_CLOUD,
INTEGRATION_HEROKU,
INTEGRATION_LARAVELFORGE,
INTEGRATION_NETLIFY,
INTEGRATION_NORTHFLANK,
INTEGRATION_QOVERY,
INTEGRATION_RAILWAY,
INTEGRATION_RENDER,
INTEGRATION_SUPABASE,
INTEGRATION_TEAMCITY,
INTEGRATION_TERRAFORM_CLOUD,
INTEGRATION_TRAVISCI,
INTEGRATION_VERCEL,
INTEGRATION_WINDMILL
} from "../../variables";
import { Schema, Types, model } from "mongoose";
import { Metadata } from "./types";
export interface IIntegration {
_id: Types.ObjectId;
workspace: Types.ObjectId;
environment: string;
isActive: boolean;
url: string;
app: string;
appId: string;
owner: string;
targetEnvironment: string;
targetEnvironmentId: string;
targetService: string;
targetServiceId: string;
path: string;
region: string;
scope: string;
secretPath: string;
integration:
| "azure-key-vault"
| "aws-parameter-store"
| "aws-secret-manager"
| "heroku"
| "vercel"
| "netlify"
| "github"
| "gitlab"
| "render"
| "railway"
| "flyio"
| "circleci"
| "laravel-forge"
| "travisci"
| "supabase"
| "checkly"
| "qovery"
| "terraform-cloud"
| "teamcity"
| "hashicorp-vault"
| "cloudflare-pages"
| "cloudflare-workers"
| "bitbucket"
| "codefresh"
| "digital-ocean-app-platform"
| "cloud-66"
| "northflank"
| "windmill"
| "gcp-secret-manager"
| "hasura-cloud";
integrationAuth: Types.ObjectId;
metadata: Metadata;
}
const integrationSchema = new Schema<IIntegration>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
environment: {
type: String,
required: true
},
isActive: {
type: Boolean,
required: true
},
url: {
// for custom self-hosted integrations (e.g. self-hosted GitHub enterprise)
type: String,
default: null
},
app: {
// name of app in provider
type: String,
default: null
},
appId: {
// id of app in provider
type: String,
default: null
},
targetEnvironment: {
// target environment
type: String,
default: null
},
targetEnvironmentId: {
type: String,
default: null
},
targetService: {
// railway-specific service
// qovery-specific project
type: String,
default: null
},
targetServiceId: {
// railway-specific service
// qovery specific project
type: String,
default: null
},
owner: {
// github-specific repo owner-login
type: String,
default: null
},
path: {
// aws-parameter-store-specific path
// (also) vercel preview-branch
type: String,
default: null
},
region: {
// aws-parameter-store-specific path
type: String,
default: null
},
scope: {
// qovery-specific scope
type: String,
default: null
},
integration: {
type: String,
enum: [
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_RAILWAY,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_LARAVELFORGE,
INTEGRATION_TRAVISCI,
INTEGRATION_SUPABASE,
INTEGRATION_CHECKLY,
INTEGRATION_QOVERY,
INTEGRATION_TERRAFORM_CLOUD,
INTEGRATION_TEAMCITY,
INTEGRATION_HASHICORP_VAULT,
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CODEFRESH,
INTEGRATION_WINDMILL,
INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_HASURA_CLOUD
],
required: true
},
integrationAuth: {
type: Schema.Types.ObjectId,
ref: "IntegrationAuth",
required: true
},
secretPath: {
type: String,
required: true,
default: "/"
},
metadata: {
type: Schema.Types.Mixed,
default: {}
}
},
{
timestamps: true
}
);
export const Integration = model<IIntegration>("Integration", integrationSchema);

View File

@@ -0,0 +1,8 @@
export type Metadata = {
secretPrefix?: string;
secretSuffix?: string;
secretGCPLabel?: {
labelName: string;
labelValue: string;
}
}

View File

@@ -0,0 +1 @@
export * from "./integrationAuth";

View File

@@ -0,0 +1,206 @@
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_BITBUCKET,
INTEGRATION_CIRCLECI,
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CLOUD_66,
INTEGRATION_CODEFRESH,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_FLYIO,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_HASHICORP_VAULT,
INTEGRATION_HASURA_CLOUD,
INTEGRATION_HEROKU,
INTEGRATION_LARAVELFORGE,
INTEGRATION_NETLIFY,
INTEGRATION_NORTHFLANK,
INTEGRATION_RAILWAY,
INTEGRATION_RENDER,
INTEGRATION_SUPABASE,
INTEGRATION_TEAMCITY,
INTEGRATION_TERRAFORM_CLOUD,
INTEGRATION_TRAVISCI,
INTEGRATION_VERCEL,
INTEGRATION_WINDMILL
} from "../../variables";
import { Document, Schema, Types, model } from "mongoose";
import { IntegrationAuthMetadata } from "./types";
export interface IIntegrationAuth extends Document {
_id: Types.ObjectId;
workspace: Types.ObjectId;
integration:
| "heroku"
| "vercel"
| "netlify"
| "github"
| "gitlab"
| "render"
| "railway"
| "flyio"
| "azure-key-vault"
| "laravel-forge"
| "circleci"
| "travisci"
| "supabase"
| "aws-parameter-store"
| "aws-secret-manager"
| "checkly"
| "qovery"
| "cloudflare-pages"
| "cloudflare-workers"
| "codefresh"
| "digital-ocean-app-platform"
| "bitbucket"
| "cloud-66"
| "terraform-cloud"
| "teamcity"
| "northflank"
| "windmill"
| "gcp-secret-manager"
| "hasura-cloud";
teamId: string;
accountId: string;
url: string;
namespace: string;
refreshCiphertext?: string;
refreshIV?: string;
refreshTag?: string;
accessIdCiphertext?: string;
accessIdIV?: string;
accessIdTag?: string;
accessCiphertext?: string;
accessIV?: string;
accessTag?: string;
algorithm?: "aes-256-gcm";
keyEncoding?: "utf8" | "base64";
accessExpiresAt?: Date;
metadata?: IntegrationAuthMetadata;
}
const integrationAuthSchema = new Schema<IIntegrationAuth>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
integration: {
type: String,
enum: [
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_RAILWAY,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_LARAVELFORGE,
INTEGRATION_TRAVISCI,
INTEGRATION_TEAMCITY,
INTEGRATION_SUPABASE,
INTEGRATION_TERRAFORM_CLOUD,
INTEGRATION_HASHICORP_VAULT,
INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CODEFRESH,
INTEGRATION_WINDMILL,
INTEGRATION_BITBUCKET,
INTEGRATION_DIGITAL_OCEAN_APP_PLATFORM,
INTEGRATION_CLOUD_66,
INTEGRATION_NORTHFLANK,
INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_HASURA_CLOUD
],
required: true
},
teamId: {
// vercel-specific integration param
type: String
},
url: {
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
type: String
},
namespace: {
// hashicorp-vault-specific integration param
type: String
},
accountId: {
// netlify-specific integration param
type: String
},
refreshCiphertext: {
type: String,
select: false
},
refreshIV: {
type: String,
select: false
},
refreshTag: {
type: String,
select: false
},
accessIdCiphertext: {
type: String,
select: false
},
accessIdIV: {
type: String,
select: false
},
accessIdTag: {
type: String,
select: false
},
accessCiphertext: {
type: String,
select: false
},
accessIV: {
type: String,
select: false
},
accessTag: {
type: String,
select: false
},
accessExpiresAt: {
type: Date,
select: false
},
algorithm: {
// the encryption algorithm used
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true
},
keyEncoding: {
type: String,
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
required: true
},
metadata: {
type: Schema.Types.Mixed
}
},
{
timestamps: true
}
);
export const IntegrationAuth = model<IIntegrationAuth>("IntegrationAuth", integrationAuthSchema);

View File

@@ -0,0 +1,5 @@
interface GCPIntegrationAuthMetadata {
authMethod: "oauth2" | "serviceAccount"
}
export type IntegrationAuthMetadata = GCPIntegrationAuthMetadata;

View File

@@ -0,0 +1,43 @@
import { Schema, Types, model } from "mongoose";
export interface IKey {
_id: Types.ObjectId;
encryptedKey: string;
nonce: string;
sender: Types.ObjectId;
receiver: Types.ObjectId;
workspace: Types.ObjectId;
}
const keySchema = new Schema<IKey>(
{
encryptedKey: {
type: String,
required: true,
},
nonce: {
type: String,
required: true,
},
sender: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
receiver: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
},
{
timestamps: true,
},
);
export const Key = model<IKey>("Key", keySchema);

View File

@@ -0,0 +1,27 @@
import mongoose, { Schema, Types, model } from "mongoose";
export interface ILoginSRPDetail {
_id: Types.ObjectId;
clientPublicKey: string;
email: string;
serverBInt: mongoose.Schema.Types.Buffer;
userId: string;
expireAt: Date;
}
const loginSRPDetailSchema = new Schema<ILoginSRPDetail>(
{
clientPublicKey: {
type: String,
required: true,
},
email: {
type: String,
unique: true,
},
serverBInt: { type: mongoose.Schema.Types.Buffer },
expireAt: { type: Date },
}
);
export const LoginSRPDetail = model("LoginSRPDetail", loginSRPDetailSchema);

View File

@@ -0,0 +1,60 @@
import { Schema, Types, model } from "mongoose";
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../variables";
export interface IMembershipPermission {
environmentSlug: string;
ability: string;
}
export interface IMembership {
_id: Types.ObjectId;
user: Types.ObjectId;
inviteEmail?: string;
workspace: Types.ObjectId;
role: "admin" | "member" | "viewer" | "no-access" | "custom";
customRole: Types.ObjectId;
deniedPermissions: IMembershipPermission[];
}
const membershipSchema = new Schema<IMembership>(
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
inviteEmail: {
type: String
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
deniedPermissions: {
type: [
{
environmentSlug: String,
ability: {
type: String,
enum: ["read", "write"]
}
}
],
default: []
},
role: {
type: String,
enum: [ADMIN, MEMBER, VIEWER, NO_ACCESS, CUSTOM],
required: true
},
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
}
},
{
timestamps: true
}
);
export const Membership = model<IMembership>("Membership", membershipSchema);

View File

@@ -0,0 +1,47 @@
import { Document, Schema, Types, model } from "mongoose";
import { ACCEPTED, ADMIN, CUSTOM, INVITED, MEMBER, NO_ACCESS } from "../variables";
export interface IMembershipOrg extends Document {
_id: Types.ObjectId;
user: Types.ObjectId;
inviteEmail: string;
organization: Types.ObjectId;
role: "admin" | "member" | "no-access" | "custom";
customRole: Types.ObjectId;
status: "invited" | "accepted";
}
const membershipOrgSchema = new Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
inviteEmail: {
type: String
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization"
},
role: {
type: String,
enum: [ADMIN, MEMBER, NO_ACCESS, CUSTOM],
required: true
},
status: {
type: String,
enum: [INVITED, ACCEPTED],
required: true
},
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
}
},
{
timestamps: true
}
);
export const MembershipOrg = model<IMembershipOrg>("MembershipOrg", membershipOrgSchema);

View File

@@ -0,0 +1,24 @@
import { Schema, Types, model } from "mongoose";
export interface IOrganization {
_id: Types.ObjectId;
name: string;
customerId?: string;
}
const organizationSchema = new Schema<IOrganization>(
{
name: {
type: String,
required: true,
},
customerId: {
type: String,
},
},
{
timestamps: true,
}
);
export const Organization = model<IOrganization>("Organization", organizationSchema);

View File

@@ -0,0 +1,53 @@
import { Schema, Types, model } from "mongoose";
export interface IRole {
_id: Types.ObjectId;
name: string;
description: string;
slug: string;
permissions: Array<unknown>;
workspace: Types.ObjectId;
organization: Types.ObjectId;
isOrgRole: boolean;
}
const roleSchema = new Schema<IRole>(
{
name: {
type: String,
required: true
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization",
required: true
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace"
},
isOrgRole: {
type: Boolean,
required: true,
select: false
},
description: {
type: String
},
slug: {
type: String,
required: true
},
permissions: {
type: Array,
required: true
}
},
{
timestamps: true
}
);
roleSchema.index({ organization: 1, workspace: 1 });
export const Role = model<IRole>("Role", roleSchema);

View File

@@ -0,0 +1,172 @@
import { Schema, Types, model } from "mongoose";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
SECRET_PERSONAL,
SECRET_SHARED
} from "../variables";
export interface ISecret {
_id: Types.ObjectId;
version: number;
workspace: Types.ObjectId;
type: string;
user?: Types.ObjectId;
environment: string;
secretBlindIndex?: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretKeyHash: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
secretValueHash: string;
secretCommentCiphertext?: string;
secretCommentIV?: string;
secretCommentTag?: string;
secretCommentHash?: string;
// ? NOTE: This works great for workspace-level reminders.
// ? If we want to do it on a user-basis, we should ideally have a seperate model for reminders.
secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null;
skipMultilineEncoding?: boolean;
algorithm: "aes-256-gcm";
keyEncoding: "utf8" | "base64";
tags?: string[];
folder?: string;
metadata?: {
[key: string]: string;
};
}
const secretSchema = new Schema<ISecret>(
{
version: {
type: Number,
required: true,
default: 1
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
type: {
type: String,
enum: [SECRET_SHARED, SECRET_PERSONAL],
required: true
},
user: {
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: "User"
},
tags: {
ref: "Tag",
type: [Schema.Types.ObjectId],
default: []
},
environment: {
type: String,
required: true
},
secretBlindIndex: {
type: String,
select: false
},
secretKeyCiphertext: {
type: String,
required: true
},
secretKeyIV: {
type: String, // symmetric
required: true
},
secretKeyTag: {
type: String, // symmetric
required: true
},
secretKeyHash: {
type: String
},
secretValueCiphertext: {
type: String,
required: true
},
secretValueIV: {
type: String, // symmetric
required: true
},
secretValueTag: {
type: String, // symmetric
required: true
},
secretValueHash: {
type: String
},
secretCommentCiphertext: {
type: String,
required: false
},
secretCommentIV: {
type: String, // symmetric
required: false
},
secretCommentTag: {
type: String, // symmetric
required: false
},
secretCommentHash: {
type: String,
required: false
},
secretReminderRepeatDays: {
type: Number,
required: false,
default: null
},
secretReminderNote: {
type: String,
required: false,
default: null
},
skipMultilineEncoding: {
type: Boolean,
required: false
},
algorithm: {
// the encryption algorithm used
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true,
default: ALGORITHM_AES_256_GCM
},
keyEncoding: {
type: String,
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
required: true,
default: ENCODING_SCHEME_UTF8
},
folder: {
type: String,
default: "root"
},
metadata: {
type: Schema.Types.Mixed
}
},
{
timestamps: true
}
);
secretSchema.index({ tags: 1 }, { background: true });
export const Secret = model<ISecret>("Secret", secretSchema);

View File

@@ -0,0 +1,51 @@
import { Schema, Types, model } from "mongoose";
export interface ISecretApprovalPolicy {
_id: Types.ObjectId;
workspace: Types.ObjectId;
name: string;
environment: string;
secretPath?: string;
approvers: Types.ObjectId[];
approvals: number;
}
const secretApprovalPolicySchema = new Schema<ISecretApprovalPolicy>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
approvers: [
{
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: "Membership"
}
],
name: {
type: String
},
environment: {
type: String,
required: true
},
secretPath: {
type: String,
required: false
},
approvals: {
type: Number,
default: 1
}
},
{
timestamps: true
}
);
export const SecretApprovalPolicy = model<ISecretApprovalPolicy>(
"SecretApprovalPolicy",
secretApprovalPolicySchema
);

View File

@@ -0,0 +1,203 @@
import { Schema, Types, model } from "mongoose";
import { customAlphabet } from "nanoid";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8
} from "../../variables";
export enum ApprovalStatus {
PENDING = "pending",
APPROVED = "approved",
REJECTED = "rejected"
}
export enum CommitType {
DELETE = "delete",
UPDATE = "update",
CREATE = "create"
}
const SLUG_ALPHABETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const nanoId = customAlphabet(SLUG_ALPHABETS, 10);
export interface ISecretApprovalSecChange {
_id: Types.ObjectId;
version: number;
secretBlindIndex?: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
secretCommentIV?: string;
secretCommentTag?: string;
secretCommentCiphertext?: string;
skipMultilineEncoding?: boolean;
algorithm?: "aes-256-gcm";
keyEncoding?: "utf8" | "base64";
tags?: string[];
}
export type ISecretCommits<T = Types.ObjectId, J = Types.ObjectId> = Array<
| {
newVersion: ISecretApprovalSecChange;
op: CommitType.CREATE;
}
| {
// secret is recorded to get the latest version, we can keep ref to secret for pulling change as it will also get changed
// on merge
secretVersion: J;
secret: T;
newVersion: Partial<Omit<ISecretApprovalSecChange, "_id">> & { _id: Types.ObjectId };
op: CommitType.UPDATE;
}
| {
secret: T;
secretVersion: J;
op: CommitType.DELETE;
}
>;
export interface ISecretApprovalRequest {
_id: Types.ObjectId;
committer: Types.ObjectId;
slug: string;
statusChangeBy: Types.ObjectId;
reviewers: {
member: Types.ObjectId;
status: ApprovalStatus;
}[];
workspace: Types.ObjectId;
environment: string;
folderId: string;
hasMerged: boolean;
status: "open" | "close";
policy: Types.ObjectId;
commits: ISecretCommits;
conflicts: Array<{ secretId: string; op: CommitType }>;
}
const secretApprovalSecretChangeSchema = new Schema<ISecretApprovalSecChange>({
version: {
type: Number,
default: 1,
required: true
},
secretBlindIndex: {
type: String,
select: false
},
secretKeyCiphertext: {
type: String,
required: true
},
secretKeyIV: {
type: String, // symmetric
required: true
},
secretKeyTag: {
type: String, // symmetric
required: true
},
secretValueCiphertext: {
type: String,
required: true
},
secretValueIV: {
type: String, // symmetric
required: true
},
secretValueTag: {
type: String, // symmetric
required: true
},
skipMultilineEncoding: {
type: Boolean,
required: false
},
algorithm: {
// the encryption algorithm used
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true,
default: ALGORITHM_AES_256_GCM
},
keyEncoding: {
type: String,
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
required: true,
default: ENCODING_SCHEME_UTF8
},
tags: {
ref: "Tag",
type: [Schema.Types.ObjectId],
default: []
}
});
const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
environment: {
type: String,
required: true
},
folderId: {
type: String,
required: true,
default: "root"
},
slug: {
type: String,
default: () => nanoId()
},
reviewers: {
type: [
{
member: {
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: "Membership"
},
status: { type: String, enum: ApprovalStatus, default: ApprovalStatus.PENDING }
}
],
default: []
},
policy: { type: Schema.Types.ObjectId, ref: "SecretApprovalPolicy" },
hasMerged: { type: Boolean, default: false },
status: { type: String, enum: ["close", "open"], default: "open" },
committer: { type: Schema.Types.ObjectId, ref: "Membership" },
statusChangeBy: { type: Schema.Types.ObjectId, ref: "Membership" },
commits: [
{
secret: { type: Types.ObjectId, ref: "Secret" },
newVersion: secretApprovalSecretChangeSchema,
secretVersion: { type: Types.ObjectId, ref: "SecretVersion" },
op: { type: String, enum: [CommitType], required: true }
}
],
conflicts: {
type: [
{
secretId: { type: String, required: true },
op: { type: String, enum: [CommitType], required: true }
}
],
default: []
}
},
{
timestamps: true
}
);
export const SecretApprovalRequest = model<ISecretApprovalRequest>(
"SecretApprovalRequest",
secretApprovalRequestSchema
);

View File

@@ -0,0 +1,58 @@
import { Document, Schema, Types, model } from "mongoose";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
} from "../variables";
export interface ISecretBlindIndexData extends Document {
_id: Types.ObjectId;
workspace: Types.ObjectId;
encryptedSaltCiphertext: string;
saltIV: string;
saltTag: string;
algorithm: "aes-256-gcm";
keyEncoding: "base64" | "utf8"
}
const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
encryptedSaltCiphertext: { // TODO: make these select: false
type: String,
required: true,
},
saltIV: {
type: String,
required: true,
},
saltTag: {
type: String,
required: true,
},
algorithm: {
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true,
select: false,
},
keyEncoding: {
type: String,
enum: [
ENCODING_SCHEME_UTF8,
ENCODING_SCHEME_BASE64,
],
required: true,
select: false,
},
}
);
secretBlindIndexDataSchema.index({ workspace: 1 });
export const SecretBlindIndexData = model<ISecretBlindIndexData>("SecretBlindIndexData", secretBlindIndexDataSchema);

View File

@@ -0,0 +1,51 @@
import { Schema, Types, model } from "mongoose";
export interface ISecretImports {
_id: Types.ObjectId;
workspace: Types.ObjectId;
environment: string;
folderId: string;
imports: Array<{
environment: string;
secretPath: string;
}>;
}
const secretImportSchema = new Schema<ISecretImports>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
environment: {
type: String,
required: true
},
folderId: {
type: String,
required: true,
default: "root"
},
imports: {
type: [
{
environment: {
type: String,
required: true
},
secretPath: {
type: String,
required: true
}
}
],
default: []
}
},
{
timestamps: true
}
);
export const SecretImport = model<ISecretImports>("SecretImports", secretImportSchema);

View File

@@ -0,0 +1,52 @@
import { Schema, Types, model } from "mongoose";
export interface ISecretSnapshot {
workspace: Types.ObjectId;
environment: string;
folderId: string | "root";
version: number;
secretVersions: Types.ObjectId[];
folderVersion: Types.ObjectId;
}
const secretSnapshotSchema = new Schema<ISecretSnapshot>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
environment: {
type: String,
required: true,
},
folderId: {
type: String,
default: "root",
},
version: {
type: Number,
default: 1,
required: true,
},
secretVersions: [
{
type: Schema.Types.ObjectId,
ref: "SecretVersion",
required: true,
},
],
folderVersion: {
type: Schema.Types.ObjectId,
ref: "FolderVersion",
},
},
{
timestamps: true,
}
);
export const SecretSnapshot = model<ISecretSnapshot>(
"SecretSnapshot",
secretSnapshotSchema
);

View File

@@ -0,0 +1,132 @@
import { Schema, Types, model } from "mongoose";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
SECRET_PERSONAL,
SECRET_SHARED
} from "../../variables";
export interface ISecretVersion {
_id: Types.ObjectId;
secret: Types.ObjectId;
version: number;
workspace: Types.ObjectId; // new
type: string; // new
user?: Types.ObjectId; // new
environment: string; // new
isDeleted: boolean;
secretBlindIndex?: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
skipMultilineEncoding?: boolean;
algorithm: "aes-256-gcm";
keyEncoding: "utf8" | "base64";
createdAt: string;
folder?: string;
tags?: string[];
}
const secretVersionSchema = new Schema<ISecretVersion>(
{
secret: {
// could be deleted
type: Schema.Types.ObjectId,
ref: "Secret",
required: true
},
version: {
type: Number,
default: 1,
required: true
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
type: {
type: String,
enum: [SECRET_SHARED, SECRET_PERSONAL],
required: true
},
user: {
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: "User"
},
environment: {
type: String,
required: true
},
isDeleted: {
// consider removing field
type: Boolean,
default: false,
required: true
},
secretBlindIndex: {
type: String,
select: false
},
secretKeyCiphertext: {
type: String,
required: true
},
secretKeyIV: {
type: String, // symmetric
required: true
},
secretKeyTag: {
type: String, // symmetric
required: true
},
secretValueCiphertext: {
type: String,
required: true
},
secretValueIV: {
type: String, // symmetric
required: true
},
secretValueTag: {
type: String, // symmetric
required: true
},
skipMultilineEncoding: {
type: Boolean,
required: false
},
algorithm: {
// the encryption algorithm used
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true,
default: ALGORITHM_AES_256_GCM
},
keyEncoding: {
type: String,
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
required: true,
default: ENCODING_SCHEME_UTF8
},
folder: {
type: String,
required: true
},
tags: {
ref: "Tag",
type: [Schema.Types.ObjectId],
default: []
}
},
{
timestamps: true
}
);
export const SecretVersion = model<ISecretVersion>("SecretVersion", secretVersionSchema);

View File

@@ -0,0 +1,25 @@
import { Schema, Types, model } from "mongoose";
export interface IServerConfig {
_id: Types.ObjectId;
initialized: boolean;
allowSignUp: boolean;
}
const serverConfigSchema = new Schema<IServerConfig>(
{
initialized: {
type: Boolean,
default: false
},
allowSignUp: {
type: Boolean,
default: true
}
},
{
timestamps: true
}
);
export const ServerConfig = model<IServerConfig>("ServerConfig", serverConfigSchema);

View File

@@ -0,0 +1,60 @@
// TODO: deprecate
import { Schema, Types, model } from "mongoose";
export interface IServiceToken {
_id: Types.ObjectId;
name: string;
user: Types.ObjectId;
workspace: Types.ObjectId;
environment: string;
expiresAt: Date;
publicKey: string;
encryptedKey: string;
nonce: string;
}
const serviceTokenSchema = new Schema<IServiceToken>(
{
name: {
type: String,
required: true,
},
user: {
// token issuer
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
environment: {
type: String,
required: true,
},
expiresAt: {
type: Date,
},
publicKey: {
type: String,
required: true,
select: true,
},
encryptedKey: {
type: String,
required: true,
select: true,
},
nonce: {
type: String,
required: true,
select: true,
},
},
{
timestamps: true,
}
);
export const ServiceToken = model<IServiceToken>("ServiceToken", serviceTokenSchema);

View File

@@ -0,0 +1,93 @@
// TODO: deprecate
import { Document, Schema, Types, model } from "mongoose";
export interface IServiceTokenData extends Document {
_id: Types.ObjectId;
name: string;
workspace: Types.ObjectId;
scopes: Array<{
environment: string;
secretPath: string;
}>;
user: Types.ObjectId;
serviceAccount: Types.ObjectId;
lastUsed: Date;
expiresAt: Date;
secretHash: string;
encryptedKey: string;
iv: string;
tag: string;
permissions: string[];
}
const serviceTokenDataSchema = new Schema<IServiceTokenData>(
{
name: {
type: String,
required: true
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
scopes: {
type: [
{
environment: {
type: String,
required: true
},
secretPath: {
type: String,
default: "/",
required: true
}
}
],
required: true
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount"
},
lastUsed: {
type: Date
},
expiresAt: {
type: Date
},
secretHash: {
type: String,
required: true,
select: false
},
encryptedKey: {
type: String,
select: false
},
iv: {
type: String,
select: false
},
tag: {
type: String,
select: false
},
permissions: {
type: [String],
enum: ["read", "write"],
default: ["read"]
}
},
{
timestamps: true
}
);
export const ServiceTokenData = model<IServiceTokenData>("ServiceTokenData", serviceTokenDataSchema);

View File

@@ -0,0 +1,72 @@
import { Schema, Types, model } from "mongoose";
export enum AuthProvider {
OKTA_SAML = "okta-saml",
AZURE_SAML = "azure-saml",
JUMPCLOUD_SAML = "jumpcloud-saml"
}
export interface ISSOConfig {
organization: Types.ObjectId;
authProvider: AuthProvider;
isActive: boolean;
encryptedEntryPoint: string;
entryPointIV: string;
entryPointTag: string;
encryptedIssuer: string;
issuerIV: string;
issuerTag: string;
encryptedCert: string;
certIV: string;
certTag: string;
}
const ssoConfigSchema = new Schema<ISSOConfig>(
{
organization: {
type: Schema.Types.ObjectId,
ref: "Organization"
},
authProvider: {
type: String,
enum: AuthProvider,
required: true
},
isActive: {
type: Boolean,
required: true
},
encryptedEntryPoint: {
type: String
},
entryPointIV: {
type: String
},
entryPointTag: {
type: String
},
encryptedIssuer: {
type: String
},
issuerIV: {
type: String
},
issuerTag: {
type: String
},
encryptedCert: {
type: String
},
certIV: {
type: String
},
certTag: {
type: String
}
},
{
timestamps: true
}
);
export const SSOConfig = model<ISSOConfig>("SSOConfig", ssoConfigSchema);

View File

@@ -0,0 +1,53 @@
import { Schema, Types, model } from "mongoose";
export interface ITag {
_id: Types.ObjectId;
name: string;
tagColor: string;
slug: string;
user: Types.ObjectId;
workspace: Types.ObjectId;
}
const tagSchema = new Schema<ITag>(
{
name: {
type: String,
required: true,
trim: true,
},
tagColor: {
type: String,
required: false,
trim: true,
},
slug: {
type: String,
required: true,
trim: true,
lowercase: true,
validate: [
function (value: any) {
return value.indexOf(" ") === -1;
},
"slug cannot contain spaces",
],
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
},
},
{
timestamps: true,
}
);
tagSchema.index({ slug: 1, workspace: 1 }, { unique: true })
tagSchema.index({ workspace: 1 })
export const Tag = model<ITag>("Tag", tagSchema);

View File

@@ -0,0 +1,30 @@
import { Schema, model } from "mongoose";
export interface IToken {
email: string;
token: string;
createdAt: Date;
ttl: number;
}
const tokenSchema = new Schema<IToken>({
email: {
type: String,
required: true,
},
token: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
ttl: {
type: Number,
},
});
tokenSchema.index({ email: 1 });
export const Token = model<IToken>("Token", tokenSchema);

View File

@@ -0,0 +1,53 @@
import { Schema, Types, model } from "mongoose";
export interface ITokenData {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
tokenHash: string;
triesLeft?: number;
expiresAt: Date;
createdAt: Date;
updatedAt: Date;
}
const tokenDataSchema = new Schema<ITokenData>({
type: {
type: String,
enum: [
"emailConfirmation",
"emailMfa",
"organizationInvitation",
"passwordReset",
],
required: true,
},
email: {
type: String,
},
phoneNumber: {
type: String,
},
organization: { // organizationInvitation-specific field
type: Schema.Types.ObjectId,
ref: "Organization",
},
tokenHash: {
type: String,
select: false,
required: true,
},
triesLeft: {
type: Number,
},
expiresAt: {
type: Date,
expires: 0,
required: true,
},
}, {
timestamps: true,
});
export const TokenData = model<ITokenData>("TokenData", tokenDataSchema);

View File

@@ -0,0 +1,45 @@
import { Document, Schema, Types, model } from "mongoose";
export interface ITokenVersion extends Document {
user: Types.ObjectId;
ip: string;
userAgent: string;
refreshVersion: number;
accessVersion: number;
lastUsed: Date;
}
const tokenVersionSchema = new Schema<ITokenVersion>(
{
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
ip: {
type: String,
required: true,
},
userAgent: {
type: String,
required: true,
},
refreshVersion: {
type: Number,
required: true,
},
accessVersion: {
type: Number,
required: true,
},
lastUsed: {
type: Date,
required: true,
},
},
{
timestamps: true,
}
);
export const TokenVersion = model<ITokenVersion>("TokenVersion", tokenVersionSchema);

View File

@@ -0,0 +1,54 @@
import { Schema, Types, model } from "mongoose";
export enum IPType {
IPV4 = "ipv4",
IPV6 = "ipv6"
}
export interface ITrustedIP {
_id: Types.ObjectId;
workspace: Types.ObjectId;
ipAddress: string;
type: "ipv4" | "ipv6", // either IPv4/IPv6 address or network IPv4/IPv6 address
isActive: boolean;
comment: string;
prefix?: number; // CIDR
}
const trustedIpSchema = new Schema<ITrustedIP>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
ipAddress: {
type: String,
required: true
},
type: {
type: String,
enum: [
IPType.IPV4,
IPType.IPV6
],
required: true
},
prefix: {
type: Number,
required: false
},
isActive: {
type: Boolean,
required: true
},
comment: {
type: String
}
},
{
timestamps: true
}
);
export const TrustedIP = model<ITrustedIP>("TrustedIP", trustedIpSchema);

View File

@@ -0,0 +1,141 @@
import { Document, Schema, Types, model } from "mongoose";
export enum AuthMethod {
EMAIL = "email",
GOOGLE = "google",
GITHUB = "github",
GITLAB = "gitlab",
OKTA_SAML = "okta-saml",
AZURE_SAML = "azure-saml",
JUMPCLOUD_SAML = "jumpcloud-saml"
}
export interface IUser extends Document {
_id: Types.ObjectId;
authProvider?: AuthMethod;
authMethods: AuthMethod[];
email: string;
superAdmin?: boolean;
firstName?: string;
lastName?: string;
encryptionVersion: number;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
publicKey?: string;
encryptedPrivateKey?: string;
iv?: string;
tag?: string;
salt?: string;
verifier?: string;
isMfaEnabled: boolean;
mfaMethods: boolean;
devices: {
ip: string;
userAgent: string;
}[];
}
const userSchema = new Schema<IUser>(
{
authProvider: {
// TODO field: deprecate
type: String,
enum: AuthMethod
},
authMethods: {
type: [
{
type: String,
enum: AuthMethod
}
],
default: [AuthMethod.EMAIL],
required: true
},
email: {
type: String,
required: true,
unique: true
},
firstName: {
type: String
},
lastName: {
type: String
},
encryptionVersion: {
type: Number,
select: false,
default: 1 // to resolve backward-compatibility issues
},
protectedKey: {
// introduced as part of encryption version 2
type: String,
select: false
},
protectedKeyIV: {
// introduced as part of encryption version 2
type: String,
select: false
},
protectedKeyTag: {
// introduced as part of encryption version 2
type: String,
select: false
},
publicKey: {
type: String,
select: false
},
encryptedPrivateKey: {
type: String,
select: false
},
superAdmin: {
type: Boolean
},
iv: {
// iv of [encryptedPrivateKey]
type: String,
select: false
},
tag: {
// tag of [encryptedPrivateKey]
type: String,
select: false
},
salt: {
type: String,
select: false
},
verifier: {
type: String,
select: false
},
isMfaEnabled: {
type: Boolean,
default: false
},
mfaMethods: [
{
type: String
}
],
devices: {
type: [
{
ip: String,
userAgent: String
}
],
default: [],
select: false
}
},
{
timestamps: true
}
);
export const User = model<IUser>("User", userSchema);

View File

@@ -0,0 +1,26 @@
import { Schema, Types, model } from "mongoose";
export interface IUserAction {
_id: Types.ObjectId;
user: Types.ObjectId;
action: string;
}
const userActionSchema = new Schema<IUserAction>(
{
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
action: {
type: String,
required: true,
},
},
{
timestamps: true,
}
);
export const UserAction = model<IUserAction>("UserAction", userActionSchema);

View File

@@ -0,0 +1,81 @@
import { Document, Schema, Types, model } from "mongoose";
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8 } from "../variables";
export interface IWebhook extends Document {
_id: Types.ObjectId;
workspace: Types.ObjectId;
environment: string;
secretPath: string;
url: string;
lastStatus: "success" | "failed";
lastRunErrorMessage?: string;
isDisabled: boolean;
encryptedSecretKey: string;
iv: string;
tag: string;
algorithm: "aes-256-gcm";
keyEncoding: "base64" | "utf8";
}
const WebhookSchema = new Schema<IWebhook>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
},
environment: {
type: String,
required: true
},
secretPath: {
type: String,
required: true,
default: "/"
},
url: {
type: String,
required: true
},
lastStatus: {
type: String,
enum: ["success", "failed"]
},
lastRunErrorMessage: {
type: String
},
isDisabled: {
type: Boolean,
default: false
},
// used for webhook signature
encryptedSecretKey: {
type: String,
select: false
},
iv: {
type: String,
select: false
},
tag: {
type: String,
select: false
},
algorithm: {
// the encryption algorithm used
type: String,
enum: [ALGORITHM_AES_256_GCM],
select: false
},
keyEncoding: {
type: String,
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
select: false
}
},
{
timestamps: true
}
);
export const Webhook = model<IWebhook>("Webhook", WebhookSchema);

View File

@@ -0,0 +1,52 @@
import { Schema, Types, model } from "mongoose";
export interface IWorkspace {
_id: Types.ObjectId;
name: string;
organization: Types.ObjectId;
environments: Array<{
name: string;
slug: string;
}>;
autoCapitalization: boolean;
}
const workspaceSchema = new Schema<IWorkspace>({
name: {
type: String,
required: true,
},
autoCapitalization: {
type: Boolean,
default: true,
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization",
required: true,
},
environments: {
type: [
{
name: String,
slug: String,
},
],
default: [
{
name: "Development",
slug: "dev",
},
{
name: "Staging",
slug: "staging",
},
{
name: "Production",
slug: "prod",
},
],
},
});
export const Workspace = model<IWorkspace>("Workspace", workspaceSchema);

View File

@@ -0,0 +1,23 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const ApiKeysSchema = z.object({
id: z.string().uuid(),
name: z.string(),
lastUsed: z.date().nullable().optional(),
expiresAt: z.date().nullable().optional(),
secretHash: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
userId: z.string().uuid(),
});
export type TApiKeys = z.infer<typeof ApiKeysSchema>;
export type TApiKeysInsert = Omit<TApiKeys, TImmutableDBKeys>;
export type TApiKeysUpdate = Partial<Omit<TApiKeys, TImmutableDBKeys>>;

View File

@@ -0,0 +1,28 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const AuditLogsSchema = z.object({
id: z.string().uuid(),
actor: z.string(),
actorMetadata: z.unknown(),
ipAddress: z.string().nullable().optional(),
eventType: z.string(),
eventMetadata: z.unknown().nullable().optional(),
userAgent: z.string().nullable().optional(),
userAgentType: z.string().nullable().optional(),
expiresAt: z.date().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
orgId: z.string().uuid().nullable().optional(),
projectId: z.string().nullable().optional(),
});
export type TAuditLogs = z.infer<typeof AuditLogsSchema>;
export type TAuditLogsInsert = Omit<TAuditLogs, TImmutableDBKeys>;
export type TAuditLogsUpdate = Partial<Omit<TAuditLogs, TImmutableDBKeys>>;

View File

@@ -0,0 +1,24 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const AuthTokenSessionsSchema = z.object({
id: z.string().uuid(),
ip: z.string(),
userAgent: z.string().nullable().optional(),
refreshVersion: z.number().default(1),
accessVersion: z.number().default(1),
lastUsed: z.date(),
createdAt: z.date(),
updatedAt: z.date(),
userId: z.string().uuid(),
});
export type TAuthTokenSessions = z.infer<typeof AuthTokenSessionsSchema>;
export type TAuthTokenSessionsInsert = Omit<TAuthTokenSessions, TImmutableDBKeys>;
export type TAuthTokenSessionsUpdate = Partial<Omit<TAuthTokenSessions, TImmutableDBKeys>>;

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