feat(infisical-pg): completed first version of pg migrator

This commit is contained in:
Akhil Mohan
2024-01-16 15:31:50 +05:30
parent 9f813d72f2
commit 5fe8bdc00b
37 changed files with 2659 additions and 943 deletions

View File

@@ -9,21 +9,35 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@casl/ability": "^6.5.0",
"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",
"uuid": "^9.0.1",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.11.0",
"@types/prompt-sync": "^4.2.3",
"@types/uuid": "^9.0.7",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
}
},
"node_modules/@casl/ability": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.5.0.tgz",
"integrity": "sha512-3guc94ugr5ylZQIpJTLz0CDfwNi0mxKVECj1vJUPAvs+Lwunh/dcuUjwzc4MHM9D8JOYX0XUZMEPedpB3vIbOw==",
"dependencies": {
"@ucast/mongo2js": "^1.3.0"
},
"funding": {
"url": "https://github.com/stalniy/casl/blob/master/BACKERS.md"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.19.11",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz",
@@ -414,6 +428,12 @@
"integrity": "sha512-Ox77gCSx0YyeakGt/qfOZUSFNSSi+sh3ABoGOiCwiO2KODx492BJnUm9oIXS+AHJtqp12iM4RduY6viTJ9bYwA==",
"dev": true
},
"node_modules/@types/uuid": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz",
"integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==",
"dev": true
},
"node_modules/@types/webidl-conversions": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
@@ -428,6 +448,37 @@
"@types/webidl-conversions": "*"
}
},
"node_modules/@ucast/core": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.10.2.tgz",
"integrity": "sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g=="
},
"node_modules/@ucast/js": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.3.tgz",
"integrity": "sha512-jBBqt57T5WagkAjqfCIIE5UYVdaXYgGkOFYv2+kjq2AVpZ2RIbwCo/TujJpDlwTVluUI+WpnRpoGU2tSGlEvFQ==",
"dependencies": {
"@ucast/core": "^1.0.0"
}
},
"node_modules/@ucast/mongo": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.3.tgz",
"integrity": "sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==",
"dependencies": {
"@ucast/core": "^1.4.1"
}
},
"node_modules/@ucast/mongo2js": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.4.tgz",
"integrity": "sha512-ahazOr1HtelA5AC1KZ9x0UwPMqqimvfmtSm/PRRSeKKeE5G2SCqTgwiNzO7i9jS8zA3dzXpKVPpXMkcYLnyItA==",
"dependencies": {
"@ucast/core": "^1.6.1",
"@ucast/js": "^3.0.0",
"@ucast/mongo": "^2.4.0"
}
},
"node_modules/abstract-level": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz",
@@ -1325,6 +1376,18 @@
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",

View File

@@ -4,23 +4,27 @@
"description": "",
"main": "index.js",
"scripts": {
"migration": "tsx src/index.ts"
"migration": "tsx src/index.ts",
"rollback": "tsx src/rollback.ts"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.11.0",
"@types/prompt-sync": "^4.2.3",
"@types/uuid": "^9.0.7",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@casl/ability": "^6.5.0",
"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",
"uuid": "^9.0.1",
"zod": "^3.22.4"
}
}

33
pg-migrator/src/folder.ts Normal file
View File

@@ -0,0 +1,33 @@
import { TFolderSchema } from "./models";
export const folderBfsTraversal = async (
root: TFolderSchema,
callback: (
data: TFolderSchema & { parentId: string | null },
) => void | Promise<void>,
) => {
const queue = [root];
while (queue.length) {
const folder = queue.pop() as TFolderSchema & { parentId: null };
callback(folder);
queue.push(
...folder.children.map((el) => ({
...el,
parentId: folder.id,
})),
);
}
};
export const flattenFolders = (folders: TFolderSchema) => {
const flattened: {
id: string;
parentId: string | null;
name: string;
version: number;
}[] = [];
folderBfsTraversal(folders, ({ name, version, parentId, id }) => {
flattened.push({ name, version, parentId, id });
});
return flattened;
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,16 @@
import { Knex } from "knex";
import { SecretEncryptionAlgo, SecretKeyEncoding, SecretType, TableName } from "../schemas";
import { createJunctionTable, createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
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))) {
@@ -10,10 +19,15 @@ export async function up(knex: Knex): Promise<void> {
t.text("encryptedSaltCipherText").notNullable();
t.text("saltIV").notNullable();
t.text("saltTag").notNullable();
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
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.foreign("projectId")
.references("id")
.inTable(TableName.Project)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
@@ -27,7 +41,8 @@ export async function up(knex: Knex): Promise<void> {
// t.text("secretKeyHash").notNullable();
// t.text("secretValueHash");
// t.text("secretCommentHash");
t.text("secretBlindIndex").notNullable();
// this is required but for backward compatiability we are making it nullable
t.text("secretBlindIndex");
t.text("secretKeyCiphertext").notNullable();
t.text("secretKeyIV").notNullable();
t.text("secretKeyTag").notNullable();
@@ -40,19 +55,32 @@ export async function up(knex: Knex): Promise<void> {
t.string("secretReminderNote");
t.integer("secretReminderRepeatDays");
t.boolean("skipMultilineEncoding").defaultTo(false);
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
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.foreign("userId")
.references("id")
.inTable(TableName.Users)
.onDelete("CASCADE");
t.uuid("folderId").notNullable();
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
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);
await createJunctionTable(
knex,
TableName.JnSecretTag,
TableName.Secret,
TableName.SecretTag,
);
}
export async function down(knex: Knex): Promise<void> {

View File

@@ -1,7 +1,16 @@
import { Knex } from "knex";
import { SecretEncryptionAlgo, SecretKeyEncoding, SecretType, TableName } from "../schemas";
import { createJunctionTable, createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
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))) {
@@ -9,7 +18,7 @@ export async function up(knex: Knex): Promise<void> {
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("secretBlindIndex");
t.text("secretKeyCiphertext").notNullable();
t.text("secretKeyIV").notNullable();
t.text("secretKeyTag").notNullable();
@@ -22,17 +31,25 @@ export async function up(knex: Knex): Promise<void> {
t.string("secretReminderNote");
t.integer("secretReminderRepeatDays");
t.boolean("skipMultilineEncoding").defaultTo(false);
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
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.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.foreign("userId")
.references("id")
.inTable(TableName.Users)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
@@ -42,7 +59,7 @@ export async function up(knex: Knex): Promise<void> {
knex,
TableName.JnSecretVersionTag,
TableName.SecretVersion,
TableName.SecretTag
TableName.SecretTag,
);
}

View File

@@ -7,7 +7,6 @@ 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();
@@ -21,7 +20,10 @@ export async function up(knex: Knex): Promise<void> {
.inTable(TableName.IdentityUaClientSecret)
.onDelete("CASCADE");
t.uuid("identityId").notNullable();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.foreign("identityId")
.references("id")
.inTable(TableName.Identity)
.onDelete("CASCADE");
t.timestamps(true, true, true);
});
}

View File

@@ -28,7 +28,7 @@ const apiKeyDataSchema = new Schema<IAPIKeyData>(
secretHash: {
type: String,
required: true,
select: false,
},
},
{

View File

@@ -1,74 +1,67 @@
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;
_id: Types.ObjectId;
user: Types.ObjectId;
encryptedPrivateKey: string;
iv: string;
tag: string;
salt: string;
algorithm: string;
keyEncoding: "base64" | "utf8";
verifier: string;
createdAt: string;
updatedAt: 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,
}
{
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,
},
encryptedPrivateKey: {
type: String,
required: true,
},
iv: {
type: String,
required: true,
},
tag: {
type: String,
required: true,
},
algorithm: {
// the encryption algorithm used
type: String,
required: true,
},
keyEncoding: {
type: String,
required: true,
},
salt: {
type: String,
required: true,
},
verifier: {
type: String,
required: true,
},
},
{
timestamps: true,
},
);
export const BackupPrivateKey = model<IBackupPrivateKey>(
"BackupPrivateKey",
backupPrivateKeySchema
"BackupPrivateKey",
backupPrivateKeySchema,
);

View File

@@ -1,77 +1,68 @@
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";
_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,
}
{
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,
},
iv: {
type: String,
required: true,
},
tag: {
type: String,
required: true,
},
algorithm: {
// the encryption algorithm used
type: String,
required: true,
},
keyEncoding: {
type: String,
required: true,
},
},
{
timestamps: true,
},
);
export const Bot = model<IBot>("Bot", botSchema);
export const Bot = model<IBot>("Bot", botSchema);

View File

@@ -1,96 +1,81 @@
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";
_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,
}
{
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,
required: true,
},
symmetricKeyKeyEncoding: {
type: String,
required: true,
},
encryptedPrivateKey: {
type: String,
required: true,
},
privateKeyIV: {
type: String,
required: true,
},
privateKeyTag: {
type: String,
required: true,
},
privateKeyAlgorithm: {
type: String,
required: true,
},
privateKeyKeyEncoding: {
type: String,
required: true,
},
},
{
timestamps: true,
},
);
export const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema);
export const BotOrg = model<IBotOrg>("BotOrg", botOrgSchema);

View File

@@ -1,104 +1,100 @@
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;
_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
},
{
identity: {
type: Schema.Types.ObjectId,
ref: "Identity",
required: false,
},
{
timestamps: true
}
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,
required: true,
},
prefix: {
type: Number,
required: false,
},
},
],
default: [],
required: true,
},
isAccessTokenRevoked: {
type: Boolean,
default: false,
required: true,
},
},
{
timestamps: true,
},
);
export const IdentityAccessToken = model<IIdentityAccessToken>("IdentityAccessToken", identityAccessTokenSchema);
export const IdentityAccessToken = model<IIdentityAccessToken>(
"IdentityAccessToken",
identityAccessTokenSchema,
);

View File

@@ -1,5 +1,4 @@
import { Schema, Types, model } from "mongoose";
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../variables";
export interface IIdentityMembership {
_id: Types.ObjectId;
@@ -13,7 +12,7 @@ const identityMembershipSchema = new Schema<IIdentityMembership>(
{
identity: {
type: Schema.Types.ObjectId,
ref: "Identity"
ref: "Identity",
},
workspace: {
type: Schema.Types.ObjectId,
@@ -23,17 +22,19 @@ const identityMembershipSchema = new Schema<IIdentityMembership>(
},
role: {
type: String,
enum: [ADMIN, MEMBER, VIEWER, CUSTOM, NO_ACCESS],
required: true
required: true,
},
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
}
ref: "Role",
},
},
{
timestamps: true
}
timestamps: true,
},
);
export const IdentityMembership = model<IIdentityMembership>("IdentityMembership", identityMembershipSchema);
export const IdentityMembership = model<IIdentityMembership>(
"IdentityMembership",
identityMembershipSchema,
);

View File

@@ -1,5 +1,4 @@
import { Schema, Types, model } from "mongoose";
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS} from "../variables";
export interface IIdentityMembershipOrg {
_id: Types.ObjectId;
@@ -13,25 +12,27 @@ const identityMembershipOrgSchema = new Schema<IIdentityMembershipOrg>(
{
identity: {
type: Schema.Types.ObjectId,
ref: "Identity"
ref: "Identity",
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization"
ref: "Organization",
},
role: {
type: String,
enum: [ADMIN, MEMBER, NO_ACCESS, CUSTOM],
required: true
required: true,
},
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
}
ref: "Role",
},
},
{
timestamps: true
}
timestamps: true,
},
);
export const IdentityMembershipOrg = model<IIdentityMembershipOrg>("IdentityMembershipOrg", identityMembershipOrgSchema);
export const IdentityMembershipOrg = model<IIdentityMembershipOrg>(
"IdentityMembershipOrg",
identityMembershipOrgSchema,
);

View File

@@ -1,107 +1,99 @@
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>;
_id: Types.ObjectId;
identity: Types.ObjectId;
clientId: string;
clientSecretTrustedIps: Array<{}>;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<{}>;
}
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
}
{
identity: {
type: Schema.Types.ObjectId,
ref: "Identity",
required: true,
},
{
timestamps: true
}
clientId: {
type: String,
required: true,
},
clientSecretTrustedIps: {
type: [
{
ipAddress: {
type: String,
required: true,
},
type: {
type: String,
required: true,
},
prefix: {
type: Number,
required: false,
},
},
],
default: [
{
ipAddress: "0.0.0.0",
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,
required: true,
},
prefix: {
type: Number,
required: false,
},
},
],
default: [],
required: true,
},
},
{
timestamps: true,
},
);
export const IdentityUniversalAuth = model<IIdentityUniversalAuth>("IdentityUniversalAuth", identityUniversalAuthSchema);
export const IdentityUniversalAuth = model<IIdentityUniversalAuth>(
"IdentityUniversalAuth",
identityUniversalAuthSchema,
);

View File

@@ -1,35 +1,3 @@
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";
@@ -90,127 +58,98 @@ const integrationSchema = new Schema<IIntegration>(
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
required: true,
},
environment: {
type: String,
required: true
required: true,
},
isActive: {
type: Boolean,
required: true
required: true,
},
url: {
// for custom self-hosted integrations (e.g. self-hosted GitHub enterprise)
type: String,
default: null
default: null,
},
app: {
// name of app in provider
type: String,
default: null
default: null,
},
appId: {
// id of app in provider
type: String,
default: null
default: null,
},
targetEnvironment: {
// target environment
type: String,
default: null
default: null,
},
targetEnvironmentId: {
type: String,
default: null
default: null,
},
targetService: {
// railway-specific service
// qovery-specific project
type: String,
default: null
default: null,
},
targetServiceId: {
// railway-specific service
// qovery specific project
type: String,
default: null
default: null,
},
owner: {
// github-specific repo owner-login
type: String,
default: null
default: null,
},
path: {
// aws-parameter-store-specific path
// (also) vercel preview-branch
type: String,
default: null
default: null,
},
region: {
// aws-parameter-store-specific path
type: String,
default: null
default: null,
},
scope: {
// qovery-specific scope
type: String,
default: null
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
required: true,
},
integrationAuth: {
type: Schema.Types.ObjectId,
ref: "IntegrationAuth",
required: true
required: true,
},
secretPath: {
type: String,
required: true,
default: "/"
default: "/",
},
metadata: {
type: Schema.Types.Mixed,
default: {}
}
default: {},
},
},
{
timestamps: true
}
timestamps: true,
},
);
export const Integration = model<IIntegration>("Integration", integrationSchema);
export const Integration = model<IIntegration>(
"Integration",
integrationSchema,
);

View File

@@ -1,36 +1,3 @@
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";
@@ -91,116 +58,87 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
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
required: true,
},
teamId: {
// vercel-specific integration param
type: String
type: String,
},
url: {
// for any self-hosted integrations (e.g. self-hosted hashicorp-vault)
type: String
type: String,
},
namespace: {
// hashicorp-vault-specific integration param
type: String
type: String,
},
accountId: {
// netlify-specific integration param
type: String
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
required: true,
},
keyEncoding: {
type: String,
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
required: true
required: true,
},
metadata: {
type: Schema.Types.Mixed
}
type: Schema.Types.Mixed,
},
},
{
timestamps: true
}
timestamps: true,
},
);
export const IntegrationAuth = model<IIntegrationAuth>("IntegrationAuth", integrationAuthSchema);
export const IntegrationAuth = model<IIntegrationAuth>(
"IntegrationAuth",
integrationAuthSchema,
);

View File

@@ -1,5 +1,4 @@
import { Schema, Types, model } from "mongoose";
import { ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../variables";
export interface IMembershipPermission {
environmentSlug: string;
@@ -20,15 +19,15 @@ const membershipSchema = new Schema<IMembership>(
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
ref: "User",
},
inviteEmail: {
type: String
type: String,
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
required: true,
},
deniedPermissions: {
type: [
@@ -36,25 +35,24 @@ const membershipSchema = new Schema<IMembership>(
environmentSlug: String,
ability: {
type: String,
enum: ["read", "write"]
}
}
enum: ["read", "write"],
},
},
],
default: []
default: [],
},
role: {
type: String,
enum: [ADMIN, MEMBER, VIEWER, NO_ACCESS, CUSTOM],
required: true
required: true,
},
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
}
ref: "Role",
},
},
{
timestamps: true
}
timestamps: true,
},
);
export const Membership = model<IMembership>("Membership", membershipSchema);
export const Membership = model<IMembership>("Membership", membershipSchema);

View File

@@ -1,5 +1,4 @@
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;
@@ -9,39 +8,42 @@ export interface IMembershipOrg extends Document {
role: "admin" | "member" | "no-access" | "custom";
customRole: Types.ObjectId;
status: "invited" | "accepted";
createdAt: string;
updatedAt: string;
}
const membershipOrgSchema = new Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
ref: "User",
},
inviteEmail: {
type: String
type: String,
},
organization: {
type: Schema.Types.ObjectId,
ref: "Organization"
ref: "Organization",
},
role: {
type: String,
enum: [ADMIN, MEMBER, NO_ACCESS, CUSTOM],
required: true
required: true,
},
status: {
type: String,
enum: [INVITED, ACCEPTED],
required: true
required: true,
},
customRole: {
type: Schema.Types.ObjectId,
ref: "Role"
}
ref: "Role",
},
},
{
timestamps: true
}
timestamps: true,
},
);
export const MembershipOrg = model<IMembershipOrg>("MembershipOrg", membershipOrgSchema);
export const MembershipOrg = model<IMembershipOrg>(
"MembershipOrg",
membershipOrgSchema,
);

View File

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

View File

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

View File

@@ -1,11 +1,4 @@
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;
@@ -48,123 +41,118 @@ const secretSchema = new Schema<ISecret>(
version: {
type: Number,
required: true,
default: 1
default: 1,
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
required: true,
},
type: {
type: String,
enum: [SECRET_SHARED, SECRET_PERSONAL],
required: true
required: true,
},
user: {
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: "User"
ref: "User",
},
tags: {
ref: "Tag",
type: [Schema.Types.ObjectId],
default: []
default: [],
},
environment: {
type: String,
required: true
required: true,
},
secretBlindIndex: {
type: String,
select: false
},
secretKeyCiphertext: {
type: String,
required: true
required: true,
},
secretKeyIV: {
type: String, // symmetric
required: true
required: true,
},
secretKeyTag: {
type: String, // symmetric
required: true
required: true,
},
secretKeyHash: {
type: String
type: String,
},
secretValueCiphertext: {
type: String,
required: true
required: true,
},
secretValueIV: {
type: String, // symmetric
required: true
required: true,
},
secretValueTag: {
type: String, // symmetric
required: true
required: true,
},
secretValueHash: {
type: String
type: String,
},
secretCommentCiphertext: {
type: String,
required: false
required: false,
},
secretCommentIV: {
type: String, // symmetric
required: false
required: false,
},
secretCommentTag: {
type: String, // symmetric
required: false
required: false,
},
secretCommentHash: {
type: String,
required: false
required: false,
},
secretReminderRepeatDays: {
type: Number,
required: false,
default: null
default: null,
},
secretReminderNote: {
type: String,
required: false,
default: null
default: null,
},
skipMultilineEncoding: {
type: Boolean,
required: false
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"
default: "root",
},
metadata: {
type: Schema.Types.Mixed
}
type: Schema.Types.Mixed,
},
},
{
timestamps: true
}
timestamps: true,
},
);
secretSchema.index({ tags: 1 }, { background: true });

View File

@@ -1,26 +1,17 @@
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"
REJECTED = "rejected",
}
export enum CommitType {
DELETE = "delete",
UPDATE = "update",
CREATE = "create"
CREATE = "create",
}
const SLUG_ALPHABETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const nanoId = customAlphabet(SLUG_ALPHABETS, 10);
export interface ISecretApprovalSecChange {
_id: Types.ObjectId;
version: number;
@@ -50,7 +41,9 @@ export type ISecretCommits<T = Types.ObjectId, J = Types.ObjectId> = Array<
// on merge
secretVersion: J;
secret: T;
newVersion: Partial<Omit<ISecretApprovalSecChange, "_id">> & { _id: Types.ObjectId };
newVersion: Partial<Omit<ISecretApprovalSecChange, "_id">> & {
_id: Types.ObjectId;
};
op: CommitType.UPDATE;
}
| {
@@ -82,58 +75,54 @@ const secretApprovalSecretChangeSchema = new Schema<ISecretApprovalSecChange>({
version: {
type: Number,
default: 1,
required: true
required: true,
},
secretBlindIndex: {
type: String,
select: false
},
secretKeyCiphertext: {
type: String,
required: true
required: true,
},
secretKeyIV: {
type: String, // symmetric
required: true
required: true,
},
secretKeyTag: {
type: String, // symmetric
required: true
required: true,
},
secretValueCiphertext: {
type: String,
required: true
required: true,
},
secretValueIV: {
type: String, // symmetric
required: true
required: true,
},
secretValueTag: {
type: String, // symmetric
required: true
required: true,
},
skipMultilineEncoding: {
type: Boolean,
required: false
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: []
}
default: [],
},
});
const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
@@ -141,20 +130,19 @@ const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
required: true,
},
environment: {
type: String,
required: true
required: true,
},
folderId: {
type: String,
required: true,
default: "root"
default: "root",
},
slug: {
type: String,
default: () => nanoId()
},
reviewers: {
type: [
@@ -162,12 +150,16 @@ const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
member: {
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: "Membership"
ref: "Membership",
},
status: { type: String, enum: ApprovalStatus, default: ApprovalStatus.PENDING }
}
status: {
type: String,
enum: ApprovalStatus,
default: ApprovalStatus.PENDING,
},
},
],
default: []
default: [],
},
policy: { type: Schema.Types.ObjectId, ref: "SecretApprovalPolicy" },
hasMerged: { type: Boolean, default: false },
@@ -179,25 +171,25 @@ const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
secret: { type: Types.ObjectId, ref: "Secret" },
newVersion: secretApprovalSecretChangeSchema,
secretVersion: { type: Types.ObjectId, ref: "SecretVersion" },
op: { type: String, enum: [CommitType], required: true }
}
op: { type: String, enum: [CommitType], required: true },
},
],
conflicts: {
type: [
{
secretId: { type: String, required: true },
op: { type: String, enum: [CommitType], required: true }
}
op: { type: String, enum: [CommitType], required: true },
},
],
default: []
}
default: [],
},
},
{
timestamps: true
}
timestamps: true,
},
);
export const SecretApprovalRequest = model<ISecretApprovalRequest>(
"SecretApprovalRequest",
secretApprovalRequestSchema
secretApprovalRequestSchema,
);

View File

@@ -1,58 +1,49 @@
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"
_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,
},
}
);
const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>({
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
encryptedSaltCiphertext: {
// TODO: make these
type: String,
required: true,
},
saltIV: {
type: String,
required: true,
},
saltTag: {
type: String,
required: true,
},
algorithm: {
type: String,
required: true,
},
keyEncoding: {
type: String,
required: true,
},
});
secretBlindIndexDataSchema.index({ workspace: 1 });
export const SecretBlindIndexData = model<ISecretBlindIndexData>("SecretBlindIndexData", secretBlindIndexDataSchema);
export const SecretBlindIndexData = model<ISecretBlindIndexData>(
"SecretBlindIndexData",
secretBlindIndexDataSchema,
);

View File

@@ -0,0 +1,106 @@
import { Document, Schema, model, Types } from "mongoose";
export interface ISecretRotation extends Document {
_id: Types.ObjectId;
name: string;
interval: number;
provider: string;
customProvider: Types.ObjectId;
workspace: Types.ObjectId;
environment: string;
secretPath: string;
outputs: Array<{
key: string;
secret: Types.ObjectId;
}>;
status?: "success" | "failed";
lastRotatedAt?: string;
statusMessage?: string;
encryptedData: string;
encryptedDataIV: string;
encryptedDataTag: string;
algorithm: string;
keyEncoding: string;
}
const secretRotationSchema = new Schema(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
},
provider: {
type: String,
required: true,
},
customProvider: {
type: Schema.Types.ObjectId,
ref: "SecretRotationProvider",
},
environment: {
type: String,
required: true,
},
secretPath: {
type: String,
required: true,
},
interval: {
type: Number,
required: true,
},
lastRotatedAt: {
type: String,
},
status: {
type: String,
enum: ["success", "failed"],
},
statusMessage: {
type: String,
},
// encrypted data on input keys and secrets got
encryptedData: {
type: String,
select: false,
},
encryptedDataIV: {
type: String,
select: false,
},
encryptedDataTag: {
type: String,
select: false,
},
algorithm: {
// the encryption algorithm used
type: String,
required: true,
select: false,
},
keyEncoding: {
type: String,
required: true,
select: false,
},
outputs: [
{
key: {
type: String,
required: true,
},
secret: {
type: Schema.Types.ObjectId,
ref: "Secret",
},
},
],
},
{
timestamps: true,
},
);
export const SecretRotation = model<ISecretRotation>(
"SecretRotation",
secretRotationSchema,
);

View File

@@ -1,6 +1,8 @@
import { Schema, Types, model } from "mongoose";
export interface ISecretSnapshot {
id: string;
_id: string;
workspace: Types.ObjectId;
environment: string;
folderId: string | "root";
@@ -43,10 +45,10 @@ const secretSnapshotSchema = new Schema<ISecretSnapshot>(
},
{
timestamps: true,
}
},
);
export const SecretSnapshot = model<ISecretSnapshot>(
"SecretSnapshot",
secretSnapshotSchema
);
secretSnapshotSchema,
);

View File

@@ -1,11 +1,4 @@
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;
@@ -37,96 +30,94 @@ const secretVersionSchema = new Schema<ISecretVersion>(
// could be deleted
type: Schema.Types.ObjectId,
ref: "Secret",
required: true
required: true,
},
version: {
type: Number,
default: 1,
required: true
required: true,
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
required: true,
},
type: {
type: String,
enum: [SECRET_SHARED, SECRET_PERSONAL],
required: true
required: true,
},
user: {
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: "User"
ref: "User",
},
environment: {
type: String,
required: true
required: true,
},
isDeleted: {
// consider removing field
type: Boolean,
default: false,
required: true
required: true,
},
secretBlindIndex: {
type: String,
select: false
},
secretKeyCiphertext: {
type: String,
required: true
required: true,
},
secretKeyIV: {
type: String, // symmetric
required: true
required: true,
},
secretKeyTag: {
type: String, // symmetric
required: true
required: true,
},
secretValueCiphertext: {
type: String,
required: true
required: true,
},
secretValueIV: {
type: String, // symmetric
required: true
required: true,
},
secretValueTag: {
type: String, // symmetric
required: true
required: true,
},
skipMultilineEncoding: {
type: Boolean,
required: false
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
required: true,
},
tags: {
ref: "Tag",
type: [Schema.Types.ObjectId],
default: []
}
default: [],
},
},
{
timestamps: true
}
timestamps: true,
},
);
export const SecretVersion = model<ISecretVersion>("SecretVersion", secretVersionSchema);
export const SecretVersion = model<ISecretVersion>(
"SecretVersion",
secretVersionSchema,
);

View File

@@ -65,19 +65,19 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
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],

View File

@@ -35,7 +35,7 @@ const tokenDataSchema = new Schema<ITokenData>({
},
tokenHash: {
type: String,
select: false,
required: true,
},
triesLeft: {

View File

@@ -7,7 +7,7 @@ export enum AuthMethod {
GITLAB = "gitlab",
OKTA_SAML = "okta-saml",
AZURE_SAML = "azure-saml",
JUMPCLOUD_SAML = "jumpcloud-saml"
JUMPCLOUD_SAML = "jumpcloud-saml",
}
export interface IUser extends Document {
@@ -34,6 +34,8 @@ export interface IUser extends Document {
ip: string;
userAgent: string;
}[];
createdAt: string;
updatedAt: string;
}
const userSchema = new Schema<IUser>(
@@ -41,101 +43,101 @@ const userSchema = new Schema<IUser>(
authProvider: {
// TODO field: deprecate
type: String,
enum: AuthMethod
enum: AuthMethod,
},
authMethods: {
type: [
{
type: String,
enum: AuthMethod
}
enum: AuthMethod,
},
],
default: [AuthMethod.EMAIL],
required: true
required: true,
},
email: {
type: String,
required: true,
unique: true
unique: true,
},
firstName: {
type: String
type: String,
},
lastName: {
type: String
type: String,
},
encryptionVersion: {
type: Number,
select: false,
default: 1 // to resolve backward-compatibility issues
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
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
default: false,
},
mfaMethods: [
{
type: String
}
type: String,
},
],
devices: {
type: [
{
ip: String,
userAgent: String
}
userAgent: String,
},
],
default: [],
select: false
}
},
},
{
timestamps: true
}
timestamps: true,
},
);
export const User = model<IUser>("User", userSchema);

View File

@@ -1,5 +1,4 @@
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;
@@ -22,60 +21,58 @@ const WebhookSchema = new Schema<IWebhook>(
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true
required: true,
},
environment: {
type: String,
required: true
required: true,
},
secretPath: {
type: String,
required: true,
default: "/"
default: "/",
},
url: {
type: String,
required: true
required: true,
},
lastStatus: {
type: String,
enum: ["success", "failed"]
enum: ["success", "failed"],
},
lastRunErrorMessage: {
type: String
type: String,
},
isDisabled: {
type: Boolean,
default: false
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
}
timestamps: true,
},
);
export const Webhook = model<IWebhook>("Webhook", WebhookSchema);

View File

@@ -0,0 +1,29 @@
import dotenv from "dotenv";
import knex from "knex";
import { Level } from "level";
import path from "path";
const main = async () => {
dotenv.config();
let postgres_url = process.env.POSTGRES_DB_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("Good to go with postgres");
const kdb = new Level<string, any>("./db", { valueEncoding: "json" });
console.log("Starting rolling back to latest");
await db.migrate.rollback({}, true);
console.log("Rolling back completed");
kdb.clear();
process.exit(0);
};
main();

View File

@@ -9,7 +9,6 @@ import { TImmutableDBKeys } from "./models";
export const IdentityAccessTokensSchema = z.object({
id: z.string().uuid(),
authType: z.string(),
accessTokenTTL: z.number().default(2592000),
accessTokenMaxTTL: z.number().default(2592000),
accessTokenNumUses: z.number().default(0),
@@ -24,5 +23,10 @@ export const IdentityAccessTokensSchema = z.object({
});
export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>;
export type TIdentityAccessTokensInsert = Omit<TIdentityAccessTokens, TImmutableDBKeys>;
export type TIdentityAccessTokensUpdate = Partial<Omit<TIdentityAccessTokens, TImmutableDBKeys>>;
export type TIdentityAccessTokensInsert = Omit<
TIdentityAccessTokens,
TImmutableDBKeys
>;
export type TIdentityAccessTokensUpdate = Partial<
Omit<TIdentityAccessTokens, TImmutableDBKeys>
>;

View File

@@ -10,8 +10,8 @@ import { TImmutableDBKeys } from "./models";
export const SecretVersionsSchema = z.object({
id: z.string().uuid(),
version: z.number().default(1),
type: z.string().default('shared'),
secretBlindIndex: z.string(),
type: z.string().default("shared"),
secretBlindIndex: z.string().nullable().optional(),
secretKeyCiphertext: z.string(),
secretKeyIV: z.string(),
secretKeyTag: z.string(),
@@ -24,8 +24,8 @@ export const SecretVersionsSchema = z.object({
secretReminderNote: z.string().nullable().optional(),
secretReminderRepeatDays: z.number().nullable().optional(),
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
algorithm: z.string().default('aes-256-gcm'),
keyEncoding: z.string().default('utf8'),
algorithm: z.string().default("aes-256-gcm"),
keyEncoding: z.string().default("utf8"),
metadata: z.unknown().nullable().optional(),
envId: z.string().uuid().nullable().optional(),
secretId: z.string().uuid(),
@@ -37,4 +37,6 @@ export const SecretVersionsSchema = z.object({
export type TSecretVersions = z.infer<typeof SecretVersionsSchema>;
export type TSecretVersionsInsert = Omit<TSecretVersions, TImmutableDBKeys>;
export type TSecretVersionsUpdate = Partial<Omit<TSecretVersions, TImmutableDBKeys>>;
export type TSecretVersionsUpdate = Partial<
Omit<TSecretVersions, TImmutableDBKeys>
>;

View File

@@ -10,8 +10,8 @@ import { TImmutableDBKeys } from "./models";
export const SecretsSchema = z.object({
id: z.string().uuid(),
version: z.number().default(1),
type: z.string().default('shared'),
secretBlindIndex: z.string(),
type: z.string().default("shared"),
secretBlindIndex: z.string().nullable().optional(),
secretKeyCiphertext: z.string(),
secretKeyIV: z.string(),
secretKeyTag: z.string(),
@@ -24,8 +24,8 @@ export const SecretsSchema = z.object({
secretReminderNote: z.string().nullable().optional(),
secretReminderRepeatDays: z.number().nullable().optional(),
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
algorithm: z.string().default('aes-256-gcm'),
keyEncoding: z.string().default('utf8'),
algorithm: z.string().default("aes-256-gcm"),
keyEncoding: z.string().default("utf8"),
metadata: z.unknown().nullable().optional(),
userId: z.string().uuid().nullable().optional(),
folderId: z.string().uuid(),

45
pg-migrator/src/utils.ts Normal file
View File

@@ -0,0 +1,45 @@
import { Knex } from "knex";
import { TableName } from "./schemas";
export const createJunctionTable = (
knex: Knex,
tableName: TableName,
table1Name: TableName,
table2Name: TableName
) =>
knex.schema.createTable(tableName, (table) => {
table.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
table.uuid(`${table1Name}Id`).unsigned().notNullable(); // Foreign key for table1
table.uuid(`${table2Name}Id`).unsigned().notNullable(); // Foreign key for table2
table.foreign(`${table1Name}Id`).references("id").inTable(table1Name);
table.foreign(`${table2Name}Id`).references("id").inTable(table2Name);
});
// one time logic
// this is a postgres function log to set updateAt to present time whenever row gets updated
export const createUpdateAtTriggerFunction = (knex: Knex) =>
knex.raw(`
CREATE OR REPLACE FUNCTION on_update_timestamp() RETURNS TRIGGER AS $$ BEGIN NEW."updatedAt" = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`);
export const dropUpdatedAtTriggerFunction = (knex: Knex) =>
knex.raw(`
DROP FUNCTION IF EXISTS on_update_timestamp() CASCADE;
`);
// we would be using this to apply updatedAt where ever we wanta
// remember to set `timestamps(true,true,true)` before this on schema
export const createOnUpdateTrigger = (knex: Knex, tableName: string) =>
knex.raw(`
CREATE TRIGGER "${tableName}_updatedAt"
BEFORE UPDATE ON ${tableName}
FOR EACH ROW
EXECUTE PROCEDURE on_update_timestamp();
`);
export const dropOnUpdateTrigger = (knex: Knex, tableName: string) =>
knex.raw(`DROP TRIGGER IF EXISTS "${tableName}_updatedAt" ON ${tableName}`);