mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 13:26:16 +00:00
feat(infisical-pg): completed test for secret folder and import
This commit is contained in:
@@ -0,0 +1,151 @@
|
|||||||
|
import { seedData1 } from "@app/db/seed-data";
|
||||||
|
import { DEFAULT_PROJECT_ENVS } from "@app/db/seeds/3-project";
|
||||||
|
|
||||||
|
describe("Project Environment Router", async () => {
|
||||||
|
test("Get default environments", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v1/workspace/${seedData1.project.id}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("workspace");
|
||||||
|
// check for default environments
|
||||||
|
expect(payload).toEqual({
|
||||||
|
workspace: expect.objectContaining({
|
||||||
|
name: seedData1.project.name,
|
||||||
|
id: seedData1.project.id,
|
||||||
|
slug: seedData1.project.slug,
|
||||||
|
environments: expect.arrayContaining([
|
||||||
|
expect.objectContaining(DEFAULT_PROJECT_ENVS[0]),
|
||||||
|
expect.objectContaining(DEFAULT_PROJECT_ENVS[1]),
|
||||||
|
expect.objectContaining(DEFAULT_PROJECT_ENVS[2])
|
||||||
|
])
|
||||||
|
})
|
||||||
|
});
|
||||||
|
// ensure only two default environments exist
|
||||||
|
expect(payload.workspace.environments.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockProjectEnv = { name: "temp", slug: "temp", id: "" }; // id will be filled in create op
|
||||||
|
test("Create environment", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v1/workspace/${seedData1.project.id}/environments`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
name: mockProjectEnv.name,
|
||||||
|
slug: mockProjectEnv.slug
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("environment");
|
||||||
|
expect(payload.environment).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: mockProjectEnv.name,
|
||||||
|
slug: mockProjectEnv.slug,
|
||||||
|
projectId: seedData1.project.id,
|
||||||
|
position: DEFAULT_PROJECT_ENVS.length + 1,
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
mockProjectEnv.id = payload.environment.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Update environment", async () => {
|
||||||
|
const updatedName = { name: "temp#2", slug: "temp2" };
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `/api/v1/workspace/${seedData1.project.id}/environments/${mockProjectEnv.id}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
name: updatedName.name,
|
||||||
|
slug: updatedName.slug,
|
||||||
|
position: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("environment");
|
||||||
|
expect(payload.environment).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: updatedName.name,
|
||||||
|
slug: updatedName.slug,
|
||||||
|
projectId: seedData1.project.id,
|
||||||
|
position: 1,
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
mockProjectEnv.name = updatedName.name;
|
||||||
|
mockProjectEnv.slug = updatedName.slug;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Delete environment", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/v1/workspace/${seedData1.project.id}/environments/${mockProjectEnv.id}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("environment");
|
||||||
|
expect(payload.environment).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: mockProjectEnv.name,
|
||||||
|
slug: mockProjectEnv.slug,
|
||||||
|
position: 1,
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
updatedAt: expect.any(String)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// after all these opreations the list of environment should be still same
|
||||||
|
test("Default list of environment", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v1/workspace/${seedData1.project.id}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("workspace");
|
||||||
|
// check for default environments
|
||||||
|
expect(payload).toEqual({
|
||||||
|
workspace: expect.objectContaining({
|
||||||
|
name: seedData1.project.name,
|
||||||
|
id: seedData1.project.id,
|
||||||
|
slug: seedData1.project.slug,
|
||||||
|
environments: expect.arrayContaining([
|
||||||
|
expect.objectContaining(DEFAULT_PROJECT_ENVS[0]),
|
||||||
|
expect.objectContaining(DEFAULT_PROJECT_ENVS[1]),
|
||||||
|
expect.objectContaining(DEFAULT_PROJECT_ENVS[2])
|
||||||
|
])
|
||||||
|
})
|
||||||
|
});
|
||||||
|
// ensure only two default environments exist
|
||||||
|
expect(payload.workspace.environments.length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { seedData1 } from "@app/db/seed-data";
|
||||||
|
|
||||||
|
describe("Secret Folder Router", async () => {
|
||||||
|
test.each([
|
||||||
|
{ name: "folder1", path: "/" }, // one in root
|
||||||
|
{ name: "folder1", path: "/level1/level2" }, // then create a deep one creating intermediate ones
|
||||||
|
{ name: "folder2", path: "/" },
|
||||||
|
{ name: "folder1", path: "/level1/level2" } // this should not create folder return same thing
|
||||||
|
])("Create folder $name in $path", async ({ name, path }) => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v1/folders`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
name,
|
||||||
|
path
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("folder");
|
||||||
|
// check for default environments
|
||||||
|
expect(payload).toEqual({
|
||||||
|
folder: expect.objectContaining({
|
||||||
|
name,
|
||||||
|
id: expect.any(String)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
expected: {
|
||||||
|
folders: [{ name: "folder1" }, { name: "level1" }, { name: "folder2" }],
|
||||||
|
length: 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ path: "/level1/level2", expected: { folders: [{ name: "folder1" }], length: 1 } }
|
||||||
|
])("Get folders $path", async ({ path, expected }) => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v1/folders`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("folders");
|
||||||
|
expect(payload.folders.length).toBe(expected.length);
|
||||||
|
expect(payload).toEqual({ folders: expected.folders.map((el) => expect.objectContaining(el)) });
|
||||||
|
});
|
||||||
|
|
||||||
|
let toBeDeleteFolderId = "";
|
||||||
|
test("Update a deep folder", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `/api/v1/folders/folder1`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
name: "folder-updated",
|
||||||
|
path: "/level1/level2"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("folder");
|
||||||
|
expect(payload.folder).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "folder-updated"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
toBeDeleteFolderId = payload.folder.id;
|
||||||
|
|
||||||
|
const resUpdatedFolders = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v1/folders`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path: "/level1/level2"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(resUpdatedFolders.statusCode).toBe(200);
|
||||||
|
const updatedFolderList = JSON.parse(resUpdatedFolders.payload);
|
||||||
|
expect(updatedFolderList).toHaveProperty("folders");
|
||||||
|
expect(updatedFolderList.folders.length).toEqual(1);
|
||||||
|
expect(updatedFolderList.folders[0].name).toEqual("folder-updated");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Delete a deep folder", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/v1/folders/${toBeDeleteFolderId}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path: "/level1/level2"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("folder");
|
||||||
|
expect(payload.folder).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
name: "folder-updated"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const resUpdatedFolders = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v1/folders`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path: "/level1/level2"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(resUpdatedFolders.statusCode).toBe(200);
|
||||||
|
const updatedFolderList = JSON.parse(resUpdatedFolders.payload);
|
||||||
|
expect(updatedFolderList).toHaveProperty("folders");
|
||||||
|
expect(updatedFolderList.folders.length).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import { seedData1 } from "@app/db/seed-data";
|
||||||
|
|
||||||
|
describe("Secret Folder Router", async () => {
|
||||||
|
test.each([
|
||||||
|
{ importEnv: "dev", importPath: "/" }, // one in root
|
||||||
|
{ importEnv: "staging", importPath: "/" } // then create a deep one creating intermediate ones
|
||||||
|
])("Create secret import $importEnv with path $importPath", async ({ importPath, importEnv }) => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v1/secret-imports`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path: "/",
|
||||||
|
import: {
|
||||||
|
environment: importEnv,
|
||||||
|
path: importPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("secretImport");
|
||||||
|
// check for default environments
|
||||||
|
expect(payload.secretImport).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
importPath: expect.any(String),
|
||||||
|
importEnv: expect.objectContaining({
|
||||||
|
name: expect.any(String),
|
||||||
|
slug: expect.any(String),
|
||||||
|
id: expect.any(String)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let testSecretImportId = "";
|
||||||
|
test("Get secret imports", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v1/secret-imports`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path: "/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("secretImports");
|
||||||
|
expect(payload.secretImports.length).toBe(2);
|
||||||
|
testSecretImportId = payload.secretImports[0].id;
|
||||||
|
expect(payload.secretImports).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
importPath: expect.any(String),
|
||||||
|
importEnv: expect.objectContaining({
|
||||||
|
name: expect.any(String),
|
||||||
|
slug: expect.any(String),
|
||||||
|
id: expect.any(String)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Update secret import position", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `/api/v1/secret-imports/${testSecretImportId}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path: "/",
|
||||||
|
import: {
|
||||||
|
position: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("secretImport");
|
||||||
|
// check for default environments
|
||||||
|
expect(payload.secretImport).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
importPath: expect.any(String),
|
||||||
|
position: 2,
|
||||||
|
importEnv: expect.objectContaining({
|
||||||
|
name: expect.any(String),
|
||||||
|
slug: expect.any(String),
|
||||||
|
id: expect.any(String)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const secretImportsListRes = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v1/secret-imports`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path: "/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(secretImportsListRes.statusCode).toBe(200);
|
||||||
|
const secretImportList = JSON.parse(secretImportsListRes.payload);
|
||||||
|
expect(secretImportList).toHaveProperty("secretImports");
|
||||||
|
expect(secretImportList.secretImports[1].id).toEqual(testSecretImportId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Delete secret import position", async () => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/v1/secret-imports/${testSecretImportId}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path: "/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const payload = JSON.parse(res.payload);
|
||||||
|
expect(payload).toHaveProperty("secretImport");
|
||||||
|
// check for default environments
|
||||||
|
expect(payload.secretImport).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(String),
|
||||||
|
importPath: expect.any(String),
|
||||||
|
importEnv: expect.objectContaining({
|
||||||
|
name: expect.any(String),
|
||||||
|
slug: expect.any(String),
|
||||||
|
id: expect.any(String)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const secretImportsListRes = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v1/secret-imports`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
workspaceId: seedData1.project.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path: "/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(secretImportsListRes.statusCode).toBe(200);
|
||||||
|
const secretImportList = JSON.parse(secretImportsListRes.payload);
|
||||||
|
expect(secretImportList).toHaveProperty("secretImports");
|
||||||
|
expect(secretImportList.secretImports.length).toEqual(1);
|
||||||
|
expect(secretImportList.secretImports[0].position).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -22,7 +22,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.foreign("statusChangeBy")
|
t.foreign("statusChangeBy")
|
||||||
.references("id")
|
.references("id")
|
||||||
.inTable(TableName.ProjectMembership)
|
.inTable(TableName.ProjectMembership)
|
||||||
.onDelete("CASCADE");
|
.onDelete("SET NULL");
|
||||||
t.uuid("committerId").notNullable();
|
t.uuid("committerId").notNullable();
|
||||||
t.foreign("committerId")
|
t.foreign("committerId")
|
||||||
.references("id")
|
.references("id")
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
import argon2, { argon2id } from "argon2";
|
||||||
|
import jsrp from "jsrp";
|
||||||
|
import nacl from "tweetnacl";
|
||||||
|
import { encodeBase64 } from "tweetnacl-util";
|
||||||
|
|
||||||
|
import {
|
||||||
|
decryptAsymmetric,
|
||||||
|
decryptSymmetric,
|
||||||
|
encryptAsymmetric,
|
||||||
|
encryptSymmetric
|
||||||
|
} from "@app/lib/crypto";
|
||||||
|
|
||||||
|
import { TUserEncryptionKeys } from "./schemas";
|
||||||
|
|
||||||
export const seedData1 = {
|
export const seedData1 = {
|
||||||
id: "3dafd81d-4388-432b-a4c5-f735616868c1",
|
id: "3dafd81d-4388-432b-a4c5-f735616868c1",
|
||||||
email: "[email protected]",
|
email: "[email protected]",
|
||||||
@@ -8,9 +24,117 @@ export const seedData1 = {
|
|||||||
},
|
},
|
||||||
project: {
|
project: {
|
||||||
id: "77fa7aed-9288-401e-a4c9-3a9430be62a0",
|
id: "77fa7aed-9288-401e-a4c9-3a9430be62a0",
|
||||||
name: "first project"
|
name: "first project",
|
||||||
|
slug: "first-project"
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
name: "Development",
|
||||||
|
slug: "dev"
|
||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
id: "a9dfafba-a3b7-42e3-8618-91abb702fd36"
|
id: "a9dfafba-a3b7-42e3-8618-91abb702fd36"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateUserSrpKeys = async (password: string) => {
|
||||||
|
const pair = nacl.box.keyPair();
|
||||||
|
const secretKeyUint8Array = pair.secretKey;
|
||||||
|
const publicKeyUint8Array = pair.publicKey;
|
||||||
|
const privateKey = encodeBase64(secretKeyUint8Array);
|
||||||
|
const publicKey = encodeBase64(publicKeyUint8Array);
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const client = new jsrp.client();
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
client.init({ username: seedData1.email, password: seedData1.password }, () => resolve(null));
|
||||||
|
});
|
||||||
|
const { salt, verifier } = await new Promise<{ salt: string; verifier: string }>(
|
||||||
|
(resolve, reject) => {
|
||||||
|
client.createVerifier((err, res) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
return resolve(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const derivedKey = await argon2.hash(password, {
|
||||||
|
salt: Buffer.from(salt),
|
||||||
|
memoryCost: 65536,
|
||||||
|
timeCost: 3,
|
||||||
|
parallelism: 1,
|
||||||
|
hashLength: 32,
|
||||||
|
type: argon2id,
|
||||||
|
raw: true
|
||||||
|
});
|
||||||
|
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||||
|
|
||||||
|
const key = crypto.randomBytes(32);
|
||||||
|
|
||||||
|
// create encrypted private key by encrypting the private
|
||||||
|
// key with the symmetric key [key]
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedPrivateKey,
|
||||||
|
iv: encryptedPrivateKeyIV,
|
||||||
|
tag: encryptedPrivateKeyTag
|
||||||
|
} = encryptSymmetric(privateKey, key.toString("base64"));
|
||||||
|
|
||||||
|
// create the protected key by encrypting the symmetric key
|
||||||
|
// [key] with the derived key
|
||||||
|
const {
|
||||||
|
ciphertext: protectedKey,
|
||||||
|
iv: protectedKeyIV,
|
||||||
|
tag: protectedKeyTag
|
||||||
|
} = encryptSymmetric(key.toString("hex"), derivedKey.toString("base64"));
|
||||||
|
|
||||||
|
return {
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
publicKey,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
encryptedPrivateKeyIV,
|
||||||
|
encryptedPrivateKeyTag,
|
||||||
|
salt,
|
||||||
|
verifier
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserPrivateKey = async (password: string, user: TUserEncryptionKeys) => {
|
||||||
|
const derivedKey = await argon2.hash(password, {
|
||||||
|
salt: Buffer.from(user.salt),
|
||||||
|
memoryCost: 65536,
|
||||||
|
timeCost: 3,
|
||||||
|
parallelism: 1,
|
||||||
|
hashLength: 32,
|
||||||
|
type: argon2id,
|
||||||
|
raw: true
|
||||||
|
});
|
||||||
|
if (!derivedKey) throw new Error("Failed to derive key from password");
|
||||||
|
const key = decryptSymmetric({
|
||||||
|
ciphertext: user.protectedKey,
|
||||||
|
iv: user.protectedKeyIV,
|
||||||
|
tag: user.protectedKeyTag,
|
||||||
|
key: derivedKey.toString("base64")
|
||||||
|
});
|
||||||
|
const privateKey = decryptSymmetric({
|
||||||
|
ciphertext: user.encryptedPrivateKey,
|
||||||
|
iv: user.iv,
|
||||||
|
tag: user.tag,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
return privateKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildUserProjectKey = async (privateKey: string, publickey: string) => {
|
||||||
|
const randomBytes = crypto.randomBytes(16).toString("hex");
|
||||||
|
const { nonce, ciphertext } = encryptAsymmetric(randomBytes, publickey, privateKey);
|
||||||
|
return { nonce, ciphertext };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserProjectKey = async (privateKey: string) => {
|
||||||
|
const key = decryptAsymmetric({
|
||||||
|
ciphertext: decryptFileKey.encryptedKey,
|
||||||
|
nonce: decryptFileKey.nonce,
|
||||||
|
publicKey: decryptFileKey.sender.publicKey,
|
||||||
|
privateKey: PRIVATE_KEY
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,78 +1,8 @@
|
|||||||
import crypto from "node:crypto";
|
|
||||||
|
|
||||||
import argon2, { argon2id } from "argon2";
|
|
||||||
import jsrp from "jsrp";
|
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import nacl from "tweetnacl";
|
|
||||||
import { encodeBase64 } from "tweetnacl-util";
|
|
||||||
|
|
||||||
import { encryptSymmetric } from "@app/lib/crypto";
|
|
||||||
|
|
||||||
import { AuthMethod } from "../../services/auth/auth-type";
|
import { AuthMethod } from "../../services/auth/auth-type";
|
||||||
import { TableName } from "../schemas";
|
import { TableName } from "../schemas";
|
||||||
import { seedData1 } from "../seed-data";
|
import { generateUserSrpKeys, seedData1 } from "../seed-data";
|
||||||
|
|
||||||
export const generateUserSrpKeys = async (password: string) => {
|
|
||||||
const pair = nacl.box.keyPair();
|
|
||||||
const secretKeyUint8Array = pair.secretKey;
|
|
||||||
const publicKeyUint8Array = pair.publicKey;
|
|
||||||
const privateKey = encodeBase64(secretKeyUint8Array);
|
|
||||||
const publicKey = encodeBase64(publicKeyUint8Array);
|
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const client = new jsrp.client();
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
client.init({ username: seedData1.email, password: seedData1.password }, () => resolve(null));
|
|
||||||
});
|
|
||||||
const { salt, verifier } = await new Promise<{ salt: string; verifier: string }>(
|
|
||||||
(resolve, reject) => {
|
|
||||||
client.createVerifier((err, res) => {
|
|
||||||
if (err) return reject(err);
|
|
||||||
return resolve(res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const derivedKey = await argon2.hash(password, {
|
|
||||||
salt: Buffer.from(salt),
|
|
||||||
memoryCost: 65536,
|
|
||||||
timeCost: 3,
|
|
||||||
parallelism: 1,
|
|
||||||
hashLength: 32,
|
|
||||||
type: argon2id,
|
|
||||||
raw: true
|
|
||||||
});
|
|
||||||
if (!derivedKey) throw new Error("Failed to derive key from password");
|
|
||||||
|
|
||||||
const key = crypto.randomBytes(32);
|
|
||||||
|
|
||||||
// create encrypted private key by encrypting the private
|
|
||||||
// key with the symmetric key [key]
|
|
||||||
const {
|
|
||||||
ciphertext: encryptedPrivateKey,
|
|
||||||
iv: encryptedPrivateKeyIV,
|
|
||||||
tag: encryptedPrivateKeyTag
|
|
||||||
} = encryptSymmetric(privateKey, key.toString("base64"));
|
|
||||||
|
|
||||||
// create the protected key by encrypting the symmetric key
|
|
||||||
// [key] with the derived key
|
|
||||||
const {
|
|
||||||
ciphertext: protectedKey,
|
|
||||||
iv: protectedKeyIV,
|
|
||||||
tag: protectedKeyTag
|
|
||||||
} = encryptSymmetric(key.toString("hex"), derivedKey.toString("base64"));
|
|
||||||
|
|
||||||
return {
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
publicKey,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function seed(knex: Knex): Promise<void> {
|
export async function seed(knex: Knex): Promise<void> {
|
||||||
// Deletes ALL existing entries
|
// Deletes ALL existing entries
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
// @ts-ignore because we need that id for api calls
|
// @ts-ignore because we need that id for api calls
|
||||||
id: seedData1.organization.id,
|
id: seedData1.organization.id,
|
||||||
name: "infisical",
|
name: "infisical",
|
||||||
|
slug: "infisical",
|
||||||
customerId: null
|
customerId: null
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
import { OrgMembershipRole, TableName } from "../schemas";
|
||||||
import { seedData1 } from "../seed-data";
|
import { seedData1 } from "../seed-data";
|
||||||
|
|
||||||
export const DEFAULT_PROJECT_ENVS = [
|
export const DEFAULT_PROJECT_ENVS = [
|
||||||
@@ -19,10 +19,22 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
.insert({
|
.insert({
|
||||||
name: seedData1.project.name,
|
name: seedData1.project.name,
|
||||||
orgId: seedData1.organization.id,
|
orgId: seedData1.organization.id,
|
||||||
|
slug: "first-project",
|
||||||
// @ts-ignore pre calc id
|
// @ts-ignore pre calc id
|
||||||
id: seedData1.project.id
|
id: seedData1.project.id
|
||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
|
await knex(TableName.ProjectKeys).insert({
|
||||||
|
projectId: project.id,
|
||||||
|
senderId: seedData1.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex(TableName.ProjectMembership).insert({
|
||||||
|
projectId: project.id,
|
||||||
|
role: OrgMembershipRole.Admin,
|
||||||
|
userId: seedData1.id
|
||||||
|
});
|
||||||
const envs = await knex(TableName.Environment)
|
const envs = await knex(TableName.Environment)
|
||||||
.insert(
|
.insert(
|
||||||
DEFAULT_PROJECT_ENVS.map(({ name, slug }, index) => ({
|
DEFAULT_PROJECT_ENVS.map(({ name, slug }, index) => ({
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export const permissionServiceFactory = ({
|
|||||||
// user permission for a project in an organization
|
// user permission for a project in an organization
|
||||||
const getUserProjectPermission = async (userId: string, projectId: string) => {
|
const getUserProjectPermission = async (userId: string, projectId: string) => {
|
||||||
const membership = await permissionDAL.getProjectPermission(userId, projectId);
|
const membership = await permissionDAL.getProjectPermission(userId, projectId);
|
||||||
if (!membership) throw new UnauthorizedError({ name: "User not in org" });
|
if (!membership) throw new UnauthorizedError({ name: "User not in project" });
|
||||||
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
|
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
|
||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new BadRequestError({ name: "Custom permission not found" });
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ export const permissionServiceFactory = ({
|
|||||||
|
|
||||||
const getIdentityProjectPermission = async (identityId: string, projectId: string) => {
|
const getIdentityProjectPermission = async (identityId: string, projectId: string) => {
|
||||||
const membership = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
const membership = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||||
if (!membership) throw new UnauthorizedError({ name: "Identity not in org" });
|
if (!membership) throw new UnauthorizedError({ name: "Identity not in project" });
|
||||||
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
|
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
|
||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new BadRequestError({ name: "Custom permission not found" });
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ export const permissionServiceFactory = ({
|
|||||||
default:
|
default:
|
||||||
throw new UnauthorizedError({
|
throw new UnauthorizedError({
|
||||||
message: "Permission not defined",
|
message: "Permission not defined",
|
||||||
name: "Get org permission"
|
name: "Get project permission"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -84,22 +84,17 @@ export const secretFolderServiceFactory = ({
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
if (missingSegment.length) {
|
if (missingSegment.length) {
|
||||||
const newFolders: Array<TSecretFoldersInsert & { id: string }> = missingSegment.map(
|
const newFolders: Array<TSecretFoldersInsert & { id: string }> = missingSegment.map(
|
||||||
(segment, i) =>
|
(segment) => {
|
||||||
i === 0
|
const newFolder = {
|
||||||
? {
|
name: segment,
|
||||||
name: segment,
|
parentId: parentFolderId,
|
||||||
parentId: parentFolder.id,
|
id: uuidv4(),
|
||||||
id: uuidv4(),
|
envId: env.id,
|
||||||
envId: env.id,
|
version: 1
|
||||||
version: 1
|
};
|
||||||
}
|
parentFolderId = newFolder.id;
|
||||||
: {
|
return newFolder;
|
||||||
name: segment,
|
}
|
||||||
parentId: newFolders[i - 1].id,
|
|
||||||
id: uuidv4(),
|
|
||||||
envId: env.id,
|
|
||||||
version: 1
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
parentFolderId = newFolders.at(-1)?.id as string;
|
parentFolderId = newFolders.at(-1)?.id as string;
|
||||||
const docs = await folderDAL.insertMany(newFolders, tx);
|
const docs = await folderDAL.insertMany(newFolders, tx);
|
||||||
@@ -156,12 +151,12 @@ export const secretFolderServiceFactory = ({
|
|||||||
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
|
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
|
||||||
if (!env)
|
if (!env)
|
||||||
throw new BadRequestError({ message: "Environment not found", name: "Update folder" });
|
throw new BadRequestError({ message: "Environment not found", name: "Update folder" });
|
||||||
let folder = await folderDAL.findOne({ envId: env.id, id, parentId: parentFolder.id });
|
const folder = await folderDAL
|
||||||
// now folder api accepts id based change
|
.findOne({ envId: env.id, id, parentId: parentFolder.id })
|
||||||
// this is for cli and when cli removes this will remove this logic
|
// now folder api accepts id based change
|
||||||
if (!folder) {
|
// this is for cli backward compatiability and when cli removes this, we will remove this logic
|
||||||
folder = await folderDAL.findOne({ envId: env.id, name: id, parentId: parentFolder.id });
|
.catch(() => folderDAL.findOne({ envId: env.id, name: id, parentId: parentFolder.id }));
|
||||||
}
|
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
const newFolder = await folderDAL.transaction(async (tx) => {
|
const newFolder = await folderDAL.transaction(async (tx) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user