Compare commits

..

1 Commits

Author SHA1 Message Date
5246a1cab3 patch check for version 2024-02-07 12:44:03 -05:00
364 changed files with 10605 additions and 13429 deletions

View File

@ -3,18 +3,16 @@
# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION # THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
# Required
DB_CONNECTION_URI=postgres://infisical:infisical@db:5432/infisical
# JWT # JWT
# Required secrets to sign JWT tokens # Required secrets to sign JWT tokens
# THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION # THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION
AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE= AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE=
# Postgres creds # MongoDB
POSTGRES_PASSWORD=infisical # Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref
POSTGRES_USER=infisical # to the MongoDB container instance or Mongo Cloud
POSTGRES_DB=infisical # Required
MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
# Redis # Redis
REDIS_URL=redis://redis:6379 REDIS_URL=redis://redis:6379

View File

@ -1 +0,0 @@
DB_CONNECTION_URI=

View File

@ -1,4 +0,0 @@
REDIS_URL=redis://localhost:6379
DB_CONNECTION_URI=postgres://infisical:infisical@localhost/infisical?sslmode=disable
AUTH_SECRET=4bnfe4e407b8921c104518903515b218
ENCRYPTION_KEY=4bnfe4e407b8921c104518903515b218

View File

@ -1,75 +0,0 @@
name: "Check API For Breaking Changes"
on:
pull_request:
types: [opened, synchronize]
paths:
- "backend/src/server/routes/**"
jobs:
check-be-api-changes:
name: Check API Changes
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout source
uses: actions/checkout@v3
# - name: Setup Node 20
# uses: actions/setup-node@v3
# with:
# node-version: "20"
# uncomment this when testing locally using nektos/act
- uses: KengoTODA/actions-setup-docker-compose@v1
if: ${{ env.ACT }}
name: Install `docker-compose` for local simulations
with:
version: "2.14.2"
- name: 📦Build the latest image
run: docker build --tag infisical-api .
working-directory: backend
- name: Start postgres and redis
run: touch .env && docker-compose -f docker-compose.dev.yml up -d db redis
- name: Start the server
run: |
echo "SECRET_SCANNING_GIT_APP_ID=793712" >> .env
echo "SECRET_SCANNING_PRIVATE_KEY=some-random" >> .env
echo "SECRET_SCANNING_WEBHOOK_SECRET=some-random" >> .env
docker run --name infisical-api -d -p 4000:4000 -e DB_CONNECTION_URI=$DB_CONNECTION_URI -e REDIS_URL=$REDIS_URL -e JWT_AUTH_SECRET=$JWT_AUTH_SECRET --env-file .env --entrypoint '/bin/sh' infisical-api -c "npm run migration:latest && ls && node dist/main.mjs"
env:
REDIS_URL: redis://172.17.0.1:6379
DB_CONNECTION_URI: postgres://infisical:infisical@172.17.0.1:5432/infisical?sslmode=disable
JWT_AUTH_SECRET: something-random
- uses: actions/setup-go@v5
with:
go-version: '1.21.5'
- name: Wait for container to be stable and check logs
run: |
SECONDS=0
HEALTHY=0
while [ $SECONDS -lt 60 ]; do
if docker ps | grep infisical-api | grep -q healthy; then
echo "Container is healthy."
HEALTHY=1
break
fi
echo "Waiting for container to be healthy... ($SECONDS seconds elapsed)"
docker logs infisical-api
sleep 2
SECONDS=$((SECONDS+2))
done
if [ $HEALTHY -ne 1 ]; then
echo "Container did not become healthy in time"
exit 1
fi
- name: Install openapi-diff
run: go install github.com/tufin/oasdiff@latest
- name: Running OpenAPI Spec diff action
run: oasdiff breaking https://app.infisical.com/api/docs/json http://localhost:4000/api/docs/json --fail-on ERR
- name: cleanup
run: |
docker-compose -f "docker-compose.dev.yml" down
docker stop infisical-api
docker remove infisical-api

View File

@ -0,0 +1,43 @@
name: "Check Backend Pull Request"
on:
pull_request:
types: [opened, synchronize]
paths:
- "backend/**"
- "!backend/README.md"
- "!backend/.*"
- "backend/.eslintrc.js"
jobs:
check-be-pr:
name: Check
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: 🔧 Setup Node 16
uses: actions/setup-node@v3
with:
node-version: "16"
cache: "npm"
cache-dependency-path: backend/package-lock.json
- name: 📦 Install dependencies
run: npm ci --only-production
working-directory: backend
# - name: 🧪 Run tests
# run: npm run test:ci
# working-directory: backend
# - name: 📁 Upload test results
# uses: actions/upload-artifact@v3
# if: always()
# with:
# name: be-test-results
# path: |
# ./backend/reports
# ./backend/coverage
- name: 🏗️ Run build
run: npm run build
working-directory: backend

View File

@ -1,35 +0,0 @@
name: "Check Backend PR types and lint"
on:
pull_request:
types: [opened, synchronize]
paths:
- "backend/**"
- "!backend/README.md"
- "!backend/.*"
- "backend/.eslintrc.js"
jobs:
check-be-pr:
name: Check TS and Lint
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: 🔧 Setup Node 20
uses: actions/setup-node@v3
with:
node-version: "20"
cache: "npm"
cache-dependency-path: backend/package-lock.json
- name: Install dependencies
run: npm install
working-directory: backend
- name: Run type check
run: npm run type:check
working-directory: backend
- name: Run lint check
run: npm run lint
working-directory: backend

View File

@ -1,4 +1,4 @@
name: Check Frontend Type and Lint check name: Check Frontend Pull Request
on: on:
pull_request: pull_request:
@ -10,8 +10,8 @@ on:
- "frontend/.eslintrc.js" - "frontend/.eslintrc.js"
jobs: jobs:
check-fe-ts-lint: check-fe-pr:
name: Check Frontend Type and Lint check name: Check
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 15 timeout-minutes: 15
@ -25,11 +25,12 @@ jobs:
cache: "npm" cache: "npm"
cache-dependency-path: frontend/package-lock.json cache-dependency-path: frontend/package-lock.json
- name: 📦 Install dependencies - name: 📦 Install dependencies
run: npm install run: npm ci --only-production --ignore-scripts
working-directory: frontend working-directory: frontend
- name: 🏗️ Run Type check # -
run: npm run type:check # name: 🧪 Run tests
working-directory: frontend # run: npm run test:ci
- name: 🏗️ Run Link check # working-directory: frontend
run: npm run lint:fix - name: 🏗️ Run build
run: npm run build
working-directory: frontend working-directory: frontend

View File

@ -5,14 +5,9 @@ on:
- "infisical/v*.*.*-postgres" - "infisical/v*.*.*-postgres"
jobs: jobs:
infisical-tests:
name: Run tests before deployment
# https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview
uses: ./.github/workflows/run-backend-tests.yml
infisical-standalone: infisical-standalone:
name: Build infisical standalone image postgres name: Build infisical standalone image postgres
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [infisical-tests]
steps: steps:
- name: Extract version from tag - name: Extract version from tag
id: extract_version id: extract_version

View File

@ -1,47 +0,0 @@
name: "Run backend tests"
on:
pull_request:
types: [opened, synchronize]
paths:
- "backend/**"
- "!backend/README.md"
- "!backend/.*"
- "backend/.eslintrc.js"
workflow_call:
jobs:
check-be-pr:
name: Run integration test
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- uses: KengoTODA/actions-setup-docker-compose@v1
if: ${{ env.ACT }}
name: Install `docker-compose` for local simulations
with:
version: "2.14.2"
- name: 🔧 Setup Node 20
uses: actions/setup-node@v3
with:
node-version: "20"
cache: "npm"
cache-dependency-path: backend/package-lock.json
- name: Install dependencies
run: npm install
working-directory: backend
- name: Start postgres and redis
run: touch .env && docker-compose -f docker-compose.dev.yml up -d db redis
- name: Start integration test
run: npm run test:e2e
working-directory: backend
env:
REDIS_URL: redis://172.17.0.1:6379
DB_CONNECTION_URI: postgres://infisical:infisical@172.17.0.1:5432/infisical?sslmode=disable
AUTH_SECRET: something-random
ENCRYPTION_KEY: 4bnfe4e407b8921c104518903515b218
- name: cleanup
run: |
docker-compose -f "docker-compose.dev.yml" down

4
.gitignore vendored
View File

@ -6,7 +6,7 @@ node_modules
.env.gamma .env.gamma
.env.prod .env.prod
.env.infisical .env.infisical
.env.migration
*~ *~
*.swp *.swp
*.swo *.swo
@ -63,5 +63,3 @@ yarn-error.log*
.vscode/* .vscode/*
frontend-build frontend-build
*.tgz

View File

@ -5,10 +5,16 @@ push:
docker-compose -f docker-compose.yml push docker-compose -f docker-compose.yml push
up-dev: up-dev:
docker compose -f docker-compose.dev.yml up --build docker-compose -f docker-compose.dev.yml up --build
up-pg-dev:
docker compose -f docker-compose.pg.yml up --build
i-dev:
infisical run -- docker-compose -f docker-compose.dev.yml up --build
up-prod: up-prod:
docker-compose -f docker-compose.prod.yml up --build docker-compose -f docker-compose.yml up --build
down: down:
docker-compose down docker-compose down

View File

@ -84,13 +84,13 @@ To set up and run Infisical locally, make sure you have Git and Docker installed
Linux/macOS: Linux/macOS:
```console ```console
git clone https://github.com/Infisical/infisical && cd "$(basename $_ .git)" && cp .env.example .env && docker-compose -f docker-compose.prod.yml up git clone https://github.com/Infisical/infisical && cd "$(basename $_ .git)" && cp .env.example .env && docker-compose -f docker-compose.yml up
``` ```
Windows Command Prompt: Windows Command Prompt:
```console ```console
git clone https://github.com/Infisical/infisical && cd infisical && copy .env.example .env && docker-compose -f docker-compose.prod.yml up git clone https://github.com/Infisical/infisical && cd infisical && copy .env.example .env && docker-compose -f docker-compose.yml up
``` ```
Create an account at `http://localhost:80` Create an account at `http://localhost:80`

View File

@ -1,3 +1,2 @@
vitest-environment-infisical.ts vitest-environment-infisical.ts
vitest.config.ts vitest.config.ts
vitest.e2e.config.ts

View File

@ -21,18 +21,6 @@ module.exports = {
tsconfigRootDir: __dirname tsconfigRootDir: __dirname
}, },
root: true, root: true,
overrides: [
{
files: ["./e2e-test/**/*"],
rules: {
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-call": "off",
}
}
],
rules: { rules: {
"@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-unsafe-enum-comparison": "off", "@typescript-eslint/no-unsafe-enum-comparison": "off",

View File

@ -1,71 +0,0 @@
import { OrgMembershipRole } from "@app/db/schemas";
import { seedData1 } from "@app/db/seed-data";
export const createIdentity = async (name: string, role: string) => {
const createIdentityRes = await testServer.inject({
method: "POST",
url: "/api/v1/identities",
body: {
name,
role,
organizationId: seedData1.organization.id
},
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
expect(createIdentityRes.statusCode).toBe(200);
return createIdentityRes.json().identity;
};
export const deleteIdentity = async (id: string) => {
const deleteIdentityRes = await testServer.inject({
method: "DELETE",
url: `/api/v1/identities/${id}`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
expect(deleteIdentityRes.statusCode).toBe(200);
return deleteIdentityRes.json().identity;
};
describe("Identity v1", async () => {
test("Create identity", async () => {
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
expect(newIdentity.name).toBe("mac1");
expect(newIdentity.authMethod).toBeNull();
await deleteIdentity(newIdentity.id);
});
test("Update identity", async () => {
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
expect(newIdentity.name).toBe("mac1");
expect(newIdentity.authMethod).toBeNull();
const updatedIdentity = await testServer.inject({
method: "PATCH",
url: `/api/v1/identities/${newIdentity.id}`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
name: "updated-mac-1",
role: OrgMembershipRole.Member
}
});
expect(updatedIdentity.statusCode).toBe(200);
expect(updatedIdentity.json().identity.name).toBe("updated-mac-1");
await deleteIdentity(newIdentity.id);
});
test("Delete Identity", async () => {
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
const deletedIdentity = await deleteIdentity(newIdentity.id);
expect(deletedIdentity.name).toBe("mac1");
});
});

View File

@ -1,6 +1,5 @@
import jsrp from "jsrp";
import { seedData1 } from "@app/db/seed-data"; import { seedData1 } from "@app/db/seed-data";
import jsrp from "jsrp";
describe("Login V1 Router", async () => { describe("Login V1 Router", async () => {
// eslint-disable-next-line // eslint-disable-next-line

View File

@ -1,40 +1,6 @@
import { seedData1 } from "@app/db/seed-data"; import { seedData1 } from "@app/db/seed-data";
import { DEFAULT_PROJECT_ENVS } from "@app/db/seeds/3-project"; import { DEFAULT_PROJECT_ENVS } from "@app/db/seeds/3-project";
const createProjectEnvironment = async (name: string, slug: string) => {
const res = await testServer.inject({
method: "POST",
url: `/api/v1/workspace/${seedData1.project.id}/environments`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
name,
slug
}
});
expect(res.statusCode).toBe(200);
const payload = JSON.parse(res.payload);
expect(payload).toHaveProperty("environment");
return payload.environment;
};
const deleteProjectEnvironment = async (envId: string) => {
const res = await testServer.inject({
method: "DELETE",
url: `/api/v1/workspace/${seedData1.project.id}/environments/${envId}`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
expect(res.statusCode).toBe(200);
const payload = JSON.parse(res.payload);
expect(payload).toHaveProperty("environment");
return payload.environment;
};
describe("Project Environment Router", async () => { describe("Project Environment Router", async () => {
test("Get default environments", async () => { test("Get default environments", async () => {
const res = await testServer.inject({ const res = await testServer.inject({
@ -65,10 +31,24 @@ describe("Project Environment Router", async () => {
expect(payload.workspace.environments.length).toBe(3); expect(payload.workspace.environments.length).toBe(3);
}); });
const mockProjectEnv = { name: "temp", slug: "temp" }; // id will be filled in create op const mockProjectEnv = { name: "temp", slug: "temp", id: "" }; // id will be filled in create op
test("Create environment", async () => { test("Create environment", async () => {
const newEnvironment = await createProjectEnvironment(mockProjectEnv.name, mockProjectEnv.slug); const res = await testServer.inject({
expect(newEnvironment).toEqual( 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({ expect.objectContaining({
id: expect.any(String), id: expect.any(String),
name: mockProjectEnv.name, name: mockProjectEnv.name,
@ -79,15 +59,14 @@ describe("Project Environment Router", async () => {
updatedAt: expect.any(String) updatedAt: expect.any(String)
}) })
); );
await deleteProjectEnvironment(newEnvironment.id); mockProjectEnv.id = payload.environment.id;
}); });
test("Update environment", async () => { test("Update environment", async () => {
const newEnvironment = await createProjectEnvironment(mockProjectEnv.name, mockProjectEnv.slug);
const updatedName = { name: "temp#2", slug: "temp2" }; const updatedName = { name: "temp#2", slug: "temp2" };
const res = await testServer.inject({ const res = await testServer.inject({
method: "PATCH", method: "PATCH",
url: `/api/v1/workspace/${seedData1.project.id}/environments/${newEnvironment.id}`, url: `/api/v1/workspace/${seedData1.project.id}/environments/${mockProjectEnv.id}`,
headers: { headers: {
authorization: `Bearer ${jwtAuthToken}` authorization: `Bearer ${jwtAuthToken}`
}, },
@ -103,7 +82,7 @@ describe("Project Environment Router", async () => {
expect(payload).toHaveProperty("environment"); expect(payload).toHaveProperty("environment");
expect(payload.environment).toEqual( expect(payload.environment).toEqual(
expect.objectContaining({ expect.objectContaining({
id: newEnvironment.id, id: expect.any(String),
name: updatedName.name, name: updatedName.name,
slug: updatedName.slug, slug: updatedName.slug,
projectId: seedData1.project.id, projectId: seedData1.project.id,
@ -112,21 +91,61 @@ describe("Project Environment Router", async () => {
updatedAt: expect.any(String) updatedAt: expect.any(String)
}) })
); );
await deleteProjectEnvironment(newEnvironment.id); mockProjectEnv.name = updatedName.name;
mockProjectEnv.slug = updatedName.slug;
}); });
test("Delete environment", async () => { test("Delete environment", async () => {
const newEnvironment = await createProjectEnvironment(mockProjectEnv.name, mockProjectEnv.slug); const res = await testServer.inject({
const deletedProjectEnvironment = await deleteProjectEnvironment(newEnvironment.id); method: "DELETE",
expect(deletedProjectEnvironment).toEqual( 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({ expect.objectContaining({
id: deletedProjectEnvironment.id, id: expect.any(String),
name: mockProjectEnv.name, name: mockProjectEnv.name,
slug: mockProjectEnv.slug, slug: mockProjectEnv.slug,
position: 4, position: 1,
createdAt: expect.any(String), createdAt: expect.any(String),
updatedAt: 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);
});
}); });

View File

@ -1,6 +1,12 @@
import { seedData1 } from "@app/db/seed-data"; import { seedData1 } from "@app/db/seed-data";
const createFolder = async (dto: { path: string; name: string }) => { 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({ const res = await testServer.inject({
method: "POST", method: "POST",
url: `/api/v1/folders`, url: `/api/v1/folders`,
@ -10,47 +16,21 @@ const createFolder = async (dto: { path: string; name: string }) => {
body: { body: {
workspaceId: seedData1.project.id, workspaceId: seedData1.project.id,
environment: seedData1.environment.slug, environment: seedData1.environment.slug,
name: dto.name, name,
path: dto.path path
} }
}); });
expect(res.statusCode).toBe(200);
return res.json().folder;
};
const deleteFolder = async (dto: { path: string; id: string }) => {
const res = await testServer.inject({
method: "DELETE",
url: `/api/v1/folders/${dto.id}`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
path: dto.path
}
});
expect(res.statusCode).toBe(200); expect(res.statusCode).toBe(200);
return res.json().folder; const payload = JSON.parse(res.payload);
}; expect(payload).toHaveProperty("folder");
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 createdFolder = await createFolder({ path, name });
// check for default environments // check for default environments
expect(createdFolder).toEqual( expect(payload).toEqual({
expect.objectContaining({ folder: expect.objectContaining({
name, name,
id: expect.any(String) id: expect.any(String)
}) })
); });
await deleteFolder({ path, id: createdFolder.id });
}); });
test.each([ test.each([
@ -63,8 +43,6 @@ describe("Secret Folder Router", async () => {
}, },
{ path: "/level1/level2", expected: { folders: [{ name: "folder1" }], length: 1 } } { path: "/level1/level2", expected: { folders: [{ name: "folder1" }], length: 1 } }
])("Get folders $path", async ({ path, expected }) => { ])("Get folders $path", async ({ path, expected }) => {
const newFolders = await Promise.all(expected.folders.map(({ name }) => createFolder({ name, path })));
const res = await testServer.inject({ const res = await testServer.inject({
method: "GET", method: "GET",
url: `/api/v1/folders`, url: `/api/v1/folders`,
@ -81,22 +59,36 @@ describe("Secret Folder Router", async () => {
expect(res.statusCode).toBe(200); expect(res.statusCode).toBe(200);
const payload = JSON.parse(res.payload); const payload = JSON.parse(res.payload);
expect(payload).toHaveProperty("folders"); expect(payload).toHaveProperty("folders");
expect(payload.folders.length >= expected.folders.length).toBeTruthy(); expect(payload.folders.length).toBe(expected.length);
expect(payload).toEqual({ expect(payload).toEqual({ folders: expected.folders.map((el) => expect.objectContaining(el)) });
folders: expect.arrayContaining(expected.folders.map((el) => expect.objectContaining(el)))
});
await Promise.all(newFolders.map(({ id }) => deleteFolder({ path, id })));
}); });
let toBeDeleteFolderId = "";
test("Update a deep folder", async () => { test("Update a deep folder", async () => {
const newFolder = await createFolder({ name: "folder-updated", path: "/level1/level2" }); const res = await testServer.inject({
expect(newFolder).toEqual( 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({ expect.objectContaining({
id: expect.any(String), id: expect.any(String),
name: "folder-updated" name: "folder-updated"
}) })
); );
toBeDeleteFolderId = payload.folder.id;
const resUpdatedFolders = await testServer.inject({ const resUpdatedFolders = await testServer.inject({
method: "GET", method: "GET",
@ -114,16 +106,14 @@ describe("Secret Folder Router", async () => {
expect(resUpdatedFolders.statusCode).toBe(200); expect(resUpdatedFolders.statusCode).toBe(200);
const updatedFolderList = JSON.parse(resUpdatedFolders.payload); const updatedFolderList = JSON.parse(resUpdatedFolders.payload);
expect(updatedFolderList).toHaveProperty("folders"); expect(updatedFolderList).toHaveProperty("folders");
expect(updatedFolderList.folders.length).toEqual(1);
expect(updatedFolderList.folders[0].name).toEqual("folder-updated"); expect(updatedFolderList.folders[0].name).toEqual("folder-updated");
await deleteFolder({ path: "/level1/level2", id: newFolder.id });
}); });
test("Delete a deep folder", async () => { test("Delete a deep folder", async () => {
const newFolder = await createFolder({ name: "folder-updated", path: "/level1/level2" });
const res = await testServer.inject({ const res = await testServer.inject({
method: "DELETE", method: "DELETE",
url: `/api/v1/folders/${newFolder.id}`, url: `/api/v1/folders/${toBeDeleteFolderId}`,
headers: { headers: {
authorization: `Bearer ${jwtAuthToken}` authorization: `Bearer ${jwtAuthToken}`
}, },

View File

@ -1,6 +1,10 @@
import { seedData1 } from "@app/db/seed-data"; import { seedData1 } from "@app/db/seed-data";
const createSecretImport = async (importPath: string, importEnv: string) => { 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({ const res = await testServer.inject({
method: "POST", method: "POST",
url: `/api/v1/secret-imports`, url: `/api/v1/secret-imports`,
@ -21,37 +25,8 @@ const createSecretImport = async (importPath: string, importEnv: string) => {
expect(res.statusCode).toBe(200); expect(res.statusCode).toBe(200);
const payload = JSON.parse(res.payload); const payload = JSON.parse(res.payload);
expect(payload).toHaveProperty("secretImport"); expect(payload).toHaveProperty("secretImport");
return payload.secretImport;
};
const deleteSecretImport = async (id: string) => {
const res = await testServer.inject({
method: "DELETE",
url: `/api/v1/secret-imports/${id}`,
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");
return payload.secretImport;
};
describe("Secret Import 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 }) => {
// check for default environments // check for default environments
const payload = await createSecretImport(importPath, importEnv); expect(payload.secretImport).toEqual(
expect(payload).toEqual(
expect.objectContaining({ expect.objectContaining({
id: expect.any(String), id: expect.any(String),
importPath: expect.any(String), importPath: expect.any(String),
@ -62,12 +37,10 @@ describe("Secret Import Router", async () => {
}) })
}) })
); );
await deleteSecretImport(payload.id);
}); });
let testSecretImportId = "";
test("Get secret imports", async () => { test("Get secret imports", async () => {
const createdImport1 = await createSecretImport("/", "dev");
const createdImport2 = await createSecretImport("/", "staging");
const res = await testServer.inject({ const res = await testServer.inject({
method: "GET", method: "GET",
url: `/api/v1/secret-imports`, url: `/api/v1/secret-imports`,
@ -85,6 +58,7 @@ describe("Secret Import Router", async () => {
const payload = JSON.parse(res.payload); const payload = JSON.parse(res.payload);
expect(payload).toHaveProperty("secretImports"); expect(payload).toHaveProperty("secretImports");
expect(payload.secretImports.length).toBe(2); expect(payload.secretImports.length).toBe(2);
testSecretImportId = payload.secretImports[0].id;
expect(payload.secretImports).toEqual( expect(payload.secretImports).toEqual(
expect.arrayContaining([ expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
@ -98,20 +72,12 @@ describe("Secret Import Router", async () => {
}) })
]) ])
); );
await deleteSecretImport(createdImport1.id);
await deleteSecretImport(createdImport2.id);
}); });
test("Update secret import position", async () => { test("Update secret import position", async () => {
const devImportDetails = { path: "/", envSlug: "dev" }; const res = await testServer.inject({
const stagingImportDetails = { path: "/", envSlug: "staging" };
const createdImport1 = await createSecretImport(devImportDetails.path, devImportDetails.envSlug);
const createdImport2 = await createSecretImport(stagingImportDetails.path, stagingImportDetails.envSlug);
const updateImportRes = await testServer.inject({
method: "PATCH", method: "PATCH",
url: `/api/v1/secret-imports/${createdImport1.id}`, url: `/api/v1/secret-imports/${testSecretImportId}`,
headers: { headers: {
authorization: `Bearer ${jwtAuthToken}` authorization: `Bearer ${jwtAuthToken}`
}, },
@ -125,8 +91,8 @@ describe("Secret Import Router", async () => {
} }
}); });
expect(updateImportRes.statusCode).toBe(200); expect(res.statusCode).toBe(200);
const payload = JSON.parse(updateImportRes.payload); const payload = JSON.parse(res.payload);
expect(payload).toHaveProperty("secretImport"); expect(payload).toHaveProperty("secretImport");
// check for default environments // check for default environments
expect(payload.secretImport).toEqual( expect(payload.secretImport).toEqual(
@ -136,7 +102,7 @@ describe("Secret Import Router", async () => {
position: 2, position: 2,
importEnv: expect.objectContaining({ importEnv: expect.objectContaining({
name: expect.any(String), name: expect.any(String),
slug: expect.stringMatching(devImportDetails.envSlug), slug: expect.any(String),
id: expect.any(String) id: expect.any(String)
}) })
}) })
@ -158,19 +124,28 @@ describe("Secret Import Router", async () => {
expect(secretImportsListRes.statusCode).toBe(200); expect(secretImportsListRes.statusCode).toBe(200);
const secretImportList = JSON.parse(secretImportsListRes.payload); const secretImportList = JSON.parse(secretImportsListRes.payload);
expect(secretImportList).toHaveProperty("secretImports"); expect(secretImportList).toHaveProperty("secretImports");
expect(secretImportList.secretImports[1].id).toEqual(createdImport1.id); expect(secretImportList.secretImports[1].id).toEqual(testSecretImportId);
expect(secretImportList.secretImports[0].id).toEqual(createdImport2.id);
await deleteSecretImport(createdImport1.id);
await deleteSecretImport(createdImport2.id);
}); });
test("Delete secret import position", async () => { test("Delete secret import position", async () => {
const createdImport1 = await createSecretImport("/", "dev"); const res = await testServer.inject({
const createdImport2 = await createSecretImport("/", "staging"); method: "DELETE",
const deletedImport = await deleteSecretImport(createdImport1.id); 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 // check for default environments
expect(deletedImport).toEqual( expect(payload.secretImport).toEqual(
expect.objectContaining({ expect.objectContaining({
id: expect.any(String), id: expect.any(String),
importPath: expect.any(String), importPath: expect.any(String),
@ -200,7 +175,5 @@ describe("Secret Import Router", async () => {
expect(secretImportList).toHaveProperty("secretImports"); expect(secretImportList).toHaveProperty("secretImports");
expect(secretImportList.secretImports.length).toEqual(1); expect(secretImportList.secretImports.length).toEqual(1);
expect(secretImportList.secretImports[0].position).toEqual(1); expect(secretImportList.secretImports[0].position).toEqual(1);
await deleteSecretImport(createdImport2.id);
}); });
}); });

View File

@ -1,579 +0,0 @@
import crypto from "node:crypto";
import { SecretType, TSecrets } from "@app/db/schemas";
import { decryptSecret, encryptSecret, getUserPrivateKey, seedData1 } from "@app/db/seed-data";
import { decryptAsymmetric, decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
const createServiceToken = async (
scopes: { environment: string; secretPath: string }[],
permissions: ("read" | "write")[]
) => {
const projectKeyRes = await testServer.inject({
method: "GET",
url: `/api/v2/workspace/${seedData1.project.id}/encrypted-key`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
const projectKeyEnc = JSON.parse(projectKeyRes.payload);
const userInfoRes = await testServer.inject({
method: "GET",
url: "/api/v2/users/me",
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
const { user: userInfo } = JSON.parse(userInfoRes.payload);
const privateKey = await getUserPrivateKey(seedData1.password, userInfo);
const projectKey = decryptAsymmetric({
ciphertext: projectKeyEnc.encryptedKey,
nonce: projectKeyEnc.nonce,
publicKey: projectKeyEnc.sender.publicKey,
privateKey
});
const randomBytes = crypto.randomBytes(16).toString("hex");
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8(projectKey, randomBytes);
const serviceTokenRes = await testServer.inject({
method: "POST",
url: "/api/v2/service-token",
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
name: "test-token",
workspaceId: seedData1.project.id,
scopes,
encryptedKey: ciphertext,
iv,
tag,
permissions,
expiresIn: null
}
});
expect(serviceTokenRes.statusCode).toBe(200);
const serviceTokenInfo = serviceTokenRes.json();
expect(serviceTokenInfo).toHaveProperty("serviceToken");
expect(serviceTokenInfo).toHaveProperty("serviceTokenData");
return `${serviceTokenInfo.serviceToken}.${randomBytes}`;
};
const deleteServiceToken = async () => {
const serviceTokenListRes = await testServer.inject({
method: "GET",
url: `/api/v1/workspace/${seedData1.project.id}/service-token-data`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
expect(serviceTokenListRes.statusCode).toBe(200);
const serviceTokens = JSON.parse(serviceTokenListRes.payload).serviceTokenData as { name: string; id: string }[];
expect(serviceTokens.length).toBeGreaterThan(0);
const serviceTokenInfo = serviceTokens.find(({ name }) => name === "test-token");
expect(serviceTokenInfo).toBeDefined();
const deleteTokenRes = await testServer.inject({
method: "DELETE",
url: `/api/v2/service-token/${serviceTokenInfo?.id}`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
}
});
expect(deleteTokenRes.statusCode).toBe(200);
};
const createSecret = async (dto: {
projectKey: string;
path: string;
key: string;
value: string;
comment: string;
type?: SecretType;
token: string;
}) => {
const createSecretReqBody = {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
type: dto.type || SecretType.Shared,
secretPath: dto.path,
...encryptSecret(dto.projectKey, dto.key, dto.value, dto.comment)
};
const createSecRes = await testServer.inject({
method: "POST",
url: `/api/v3/secrets/${dto.key}`,
headers: {
authorization: `Bearer ${dto.token}`
},
body: createSecretReqBody
});
expect(createSecRes.statusCode).toBe(200);
const createdSecretPayload = JSON.parse(createSecRes.payload);
expect(createdSecretPayload).toHaveProperty("secret");
return createdSecretPayload.secret;
};
const deleteSecret = async (dto: { path: string; key: string; token: string }) => {
const deleteSecRes = await testServer.inject({
method: "DELETE",
url: `/api/v3/secrets/${dto.key}`,
headers: {
authorization: `Bearer ${dto.token}`
},
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
secretPath: dto.path
}
});
expect(deleteSecRes.statusCode).toBe(200);
const updatedSecretPayload = JSON.parse(deleteSecRes.payload);
expect(updatedSecretPayload).toHaveProperty("secret");
return updatedSecretPayload.secret;
};
describe("Service token secret ops", async () => {
let serviceToken = "";
let projectKey = "";
let folderId = "";
beforeAll(async () => {
serviceToken = await createServiceToken(
[{ secretPath: "/**", environment: seedData1.environment.slug }],
["read", "write"]
);
// this is ensure cli service token decryptiong working fine
const serviceTokenInfoRes = await testServer.inject({
method: "GET",
url: "/api/v2/service-token",
headers: {
authorization: `Bearer ${serviceToken}`
}
});
expect(serviceTokenInfoRes.statusCode).toBe(200);
const serviceTokenInfo = serviceTokenInfoRes.json();
const serviceTokenParts = serviceToken.split(".");
projectKey = decryptSymmetric128BitHexKeyUTF8({
key: serviceTokenParts[3],
tag: serviceTokenInfo.tag,
ciphertext: serviceTokenInfo.encryptedKey,
iv: serviceTokenInfo.iv
});
// create a deep folder
const folderCreate = await testServer.inject({
method: "POST",
url: `/api/v1/folders`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
name: "folder",
path: "/nested1/nested2"
}
});
expect(folderCreate.statusCode).toBe(200);
folderId = folderCreate.json().folder.id;
});
afterAll(async () => {
await deleteServiceToken();
// create a deep folder
const deleteFolder = await testServer.inject({
method: "DELETE",
url: `/api/v1/folders/${folderId}`,
headers: {
authorization: `Bearer ${jwtAuthToken}`
},
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
path: "/nested1/nested2"
}
});
expect(deleteFolder.statusCode).toBe(200);
});
const testSecrets = [
{
path: "/",
secret: {
key: "ST-SEC",
value: "something-secret",
comment: "some comment"
}
},
{
path: "/nested1/nested2/folder",
secret: {
key: "NESTED-ST-SEC",
value: "something-secret",
comment: "some comment"
}
}
];
const getSecrets = async (environment: string, secretPath = "/") => {
const res = await testServer.inject({
method: "GET",
url: `/api/v3/secrets`,
headers: {
authorization: `Bearer ${serviceToken}`
},
query: {
secretPath,
environment,
workspaceId: seedData1.project.id
}
});
const secrets: TSecrets[] = JSON.parse(res.payload).secrets || [];
return secrets.map((el) => ({ ...decryptSecret(projectKey, el), type: el.type }));
};
test.each(testSecrets)("Create secret in path $path", async ({ secret, path }) => {
const createdSecret = await createSecret({ projectKey, path, ...secret, token: serviceToken });
const decryptedSecret = decryptSecret(projectKey, createdSecret);
expect(decryptedSecret.key).toEqual(secret.key);
expect(decryptedSecret.value).toEqual(secret.value);
expect(decryptedSecret.comment).toEqual(secret.comment);
expect(decryptedSecret.version).toEqual(1);
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: secret.key,
value: secret.value,
type: SecretType.Shared
})
])
);
await deleteSecret({ path, key: secret.key, token: serviceToken });
});
test.each(testSecrets)("Get secret by name in path $path", async ({ secret, path }) => {
await createSecret({ projectKey, path, ...secret, token: serviceToken });
const getSecByNameRes = await testServer.inject({
method: "GET",
url: `/api/v3/secrets/${secret.key}`,
headers: {
authorization: `Bearer ${serviceToken}`
},
query: {
secretPath: path,
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug
}
});
expect(getSecByNameRes.statusCode).toBe(200);
const getSecretByNamePayload = JSON.parse(getSecByNameRes.payload);
expect(getSecretByNamePayload).toHaveProperty("secret");
const decryptedSecret = decryptSecret(projectKey, getSecretByNamePayload.secret);
expect(decryptedSecret.key).toEqual(secret.key);
expect(decryptedSecret.value).toEqual(secret.value);
expect(decryptedSecret.comment).toEqual(secret.comment);
await deleteSecret({ path, key: secret.key, token: serviceToken });
});
test.each(testSecrets)("Update secret in path $path", async ({ path, secret }) => {
await createSecret({ projectKey, path, ...secret, token: serviceToken });
const updateSecretReqBody = {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
type: SecretType.Shared,
secretPath: path,
...encryptSecret(projectKey, secret.key, "new-value", secret.comment)
};
const updateSecRes = await testServer.inject({
method: "PATCH",
url: `/api/v3/secrets/${secret.key}`,
headers: {
authorization: `Bearer ${serviceToken}`
},
body: updateSecretReqBody
});
expect(updateSecRes.statusCode).toBe(200);
const updatedSecretPayload = JSON.parse(updateSecRes.payload);
expect(updatedSecretPayload).toHaveProperty("secret");
const decryptedSecret = decryptSecret(projectKey, updatedSecretPayload.secret);
expect(decryptedSecret.key).toEqual(secret.key);
expect(decryptedSecret.value).toEqual("new-value");
expect(decryptedSecret.comment).toEqual(secret.comment);
// list secret should have updated value
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.arrayContaining([
expect.objectContaining({
key: secret.key,
value: "new-value",
type: SecretType.Shared
})
])
);
await deleteSecret({ path, key: secret.key, token: serviceToken });
});
test.each(testSecrets)("Delete secret in path $path", async ({ secret, path }) => {
await createSecret({ projectKey, path, ...secret, token: serviceToken });
const deletedSecret = await deleteSecret({ path, key: secret.key, token: serviceToken });
const decryptedSecret = decryptSecret(projectKey, deletedSecret);
expect(decryptedSecret.key).toEqual(secret.key);
// shared secret deletion should delete personal ones also
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.not.arrayContaining([
expect.objectContaining({
key: secret.key,
type: SecretType.Shared
})
])
);
});
test.each(testSecrets)("Bulk create secrets in path $path", async ({ secret, path }) => {
const createSharedSecRes = await testServer.inject({
method: "POST",
url: `/api/v3/secrets/batch`,
headers: {
authorization: `Bearer ${serviceToken}`
},
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
secretPath: path,
secrets: Array.from(Array(5)).map((_e, i) => ({
secretName: `BULK-${secret.key}-${i + 1}`,
...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, secret.value, secret.comment)
}))
}
});
expect(createSharedSecRes.statusCode).toBe(200);
const createSharedSecPayload = JSON.parse(createSharedSecRes.payload);
expect(createSharedSecPayload).toHaveProperty("secrets");
// bulk ones should exist
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.arrayContaining(
Array.from(Array(5)).map((_e, i) =>
expect.objectContaining({
key: `BULK-${secret.key}-${i + 1}`,
type: SecretType.Shared
})
)
)
);
await Promise.all(
Array.from(Array(5)).map((_e, i) =>
deleteSecret({ path, token: serviceToken, key: `BULK-${secret.key}-${i + 1}` })
)
);
});
test.each(testSecrets)("Bulk create fail on existing secret in path $path", async ({ secret, path }) => {
await createSecret({ projectKey, ...secret, key: `BULK-${secret.key}-1`, path, token: serviceToken });
const createSharedSecRes = await testServer.inject({
method: "POST",
url: `/api/v3/secrets/batch`,
headers: {
authorization: `Bearer ${serviceToken}`
},
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
secretPath: path,
secrets: Array.from(Array(5)).map((_e, i) => ({
secretName: `BULK-${secret.key}-${i + 1}`,
...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, secret.value, secret.comment)
}))
}
});
expect(createSharedSecRes.statusCode).toBe(400);
await deleteSecret({ path, key: `BULK-${secret.key}-1`, token: serviceToken });
});
test.each(testSecrets)("Bulk update secrets in path $path", async ({ secret, path }) => {
await Promise.all(
Array.from(Array(5)).map((_e, i) =>
createSecret({ projectKey, token: serviceToken, ...secret, key: `BULK-${secret.key}-${i + 1}`, path })
)
);
const updateSharedSecRes = await testServer.inject({
method: "PATCH",
url: `/api/v3/secrets/batch`,
headers: {
authorization: `Bearer ${serviceToken}`
},
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
secretPath: path,
secrets: Array.from(Array(5)).map((_e, i) => ({
secretName: `BULK-${secret.key}-${i + 1}`,
...encryptSecret(projectKey, `BULK-${secret.key}-${i + 1}`, "update-value", secret.comment)
}))
}
});
expect(updateSharedSecRes.statusCode).toBe(200);
const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload);
expect(updateSharedSecPayload).toHaveProperty("secrets");
// bulk ones should exist
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.arrayContaining(
Array.from(Array(5)).map((_e, i) =>
expect.objectContaining({
key: `BULK-${secret.key}-${i + 1}`,
value: "update-value",
type: SecretType.Shared
})
)
)
);
await Promise.all(
Array.from(Array(5)).map((_e, i) =>
deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}`, token: serviceToken })
)
);
});
test.each(testSecrets)("Bulk delete secrets in path $path", async ({ secret, path }) => {
await Promise.all(
Array.from(Array(5)).map((_e, i) =>
createSecret({ projectKey, token: serviceToken, ...secret, key: `BULK-${secret.key}-${i + 1}`, path })
)
);
const deletedSharedSecRes = await testServer.inject({
method: "DELETE",
url: `/api/v3/secrets/batch`,
headers: {
authorization: `Bearer ${serviceToken}`
},
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
secretPath: path,
secrets: Array.from(Array(5)).map((_e, i) => ({
secretName: `BULK-${secret.key}-${i + 1}`
}))
}
});
expect(deletedSharedSecRes.statusCode).toBe(200);
const deletedSecretPayload = JSON.parse(deletedSharedSecRes.payload);
expect(deletedSecretPayload).toHaveProperty("secrets");
// bulk ones should exist
const secrets = await getSecrets(seedData1.environment.slug, path);
expect(secrets).toEqual(
expect.not.arrayContaining(
Array.from(Array(5)).map((_e, i) =>
expect.objectContaining({
key: `BULK-${secret.value}-${i + 1}`,
type: SecretType.Shared
})
)
)
);
});
});
describe("Service token fail cases", async () => {
test("Unauthorized secret path access", async () => {
const serviceToken = await createServiceToken(
[{ secretPath: "/", environment: seedData1.environment.slug }],
["read", "write"]
);
const fetchSecrets = await testServer.inject({
method: "GET",
url: "/api/v3/secrets",
query: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
secretPath: "/nested/deep"
},
headers: {
authorization: `Bearer ${serviceToken}`
}
});
expect(fetchSecrets.statusCode).toBe(401);
expect(fetchSecrets.json().error).toBe("PermissionDenied");
await deleteServiceToken();
});
test("Unauthorized secret environment access", async () => {
const serviceToken = await createServiceToken(
[{ secretPath: "/", environment: seedData1.environment.slug }],
["read", "write"]
);
const fetchSecrets = await testServer.inject({
method: "GET",
url: "/api/v3/secrets",
query: {
workspaceId: seedData1.project.id,
environment: "prod",
secretPath: "/"
},
headers: {
authorization: `Bearer ${serviceToken}`
}
});
expect(fetchSecrets.statusCode).toBe(401);
expect(fetchSecrets.json().error).toBe("PermissionDenied");
await deleteServiceToken();
});
test("Unauthorized write operation", async () => {
const serviceToken = await createServiceToken(
[{ secretPath: "/", environment: seedData1.environment.slug }],
["read"]
);
const writeSecrets = await testServer.inject({
method: "POST",
url: `/api/v3/secrets/NEW`,
body: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
type: SecretType.Shared,
secretPath: "/",
// doesn't matter project key because this will fail before that due to read only access
...encryptSecret(crypto.randomBytes(16).toString("hex"), "NEW", "value", "")
},
headers: {
authorization: `Bearer ${serviceToken}`
}
});
expect(writeSecrets.statusCode).toBe(401);
expect(writeSecrets.json().error).toBe("PermissionDenied");
// but read access should still work fine
const fetchSecrets = await testServer.inject({
method: "GET",
url: "/api/v3/secrets",
query: {
workspaceId: seedData1.project.id,
environment: seedData1.environment.slug,
secretPath: "/"
},
headers: {
authorization: `Bearer ${serviceToken}`
}
});
expect(fetchSecrets.statusCode).toBe(200);
await deleteServiceToken();
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,26 @@
// eslint-disable-next-line // import { main } from "@app/server/app";
import "ts-node/register"; import { initEnvConfig } from "@app/lib/config/env";
import dotenv from "dotenv"; import dotenv from "dotenv";
import jwt from "jsonwebtoken";
import knex from "knex"; import knex from "knex";
import path from "path"; import path from "path";
import { seedData1 } from "@app/db/seed-data";
import { initEnvConfig } from "@app/lib/config/env";
import { initLogger } from "@app/lib/logger";
import { main } from "@app/server/app";
import { AuthTokenType } from "@app/services/auth/auth-type";
import { mockQueue } from "./mocks/queue";
import { mockSmtpServer } from "./mocks/smtp"; import { mockSmtpServer } from "./mocks/smtp";
import { initLogger } from "@app/lib/logger";
import jwt from "jsonwebtoken";
dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true }); import "ts-node/register";
import { main } from "@app/server/app";
import { mockQueue } from "./mocks/queue";
import { AuthTokenType } from "@app/services/auth/auth-type";
import { seedData1 } from "@app/db/seed-data";
dotenv.config({ path: path.join(__dirname, "../.env.test") });
export default { export default {
name: "knex-env", name: "knex-env",
transformMode: "ssr", transformMode: "ssr",
async setup() { async setup() {
const logger = await initLogger();
const cfg = initEnvConfig(logger);
const db = knex({ const db = knex({
client: "pg", client: "pg",
connection: cfg.DB_CONNECTION_URI, connection: process.env.DB_CONNECTION_URI,
migrations: { migrations: {
directory: path.join(__dirname, "../src/db/migrations"), directory: path.join(__dirname, "../src/db/migrations"),
extension: "ts", extension: "ts",
@ -41,6 +37,8 @@ export default {
await db.seed.run(); await db.seed.run();
const smtp = mockSmtpServer(); const smtp = mockSmtpServer();
const queue = mockQueue(); const queue = mockQueue();
const logger = await initLogger();
const cfg = initEnvConfig(logger);
const server = await main({ db, smtp, logger, queue }); const server = await main({ db, smtp, logger, queue });
// @ts-expect-error type // @ts-expect-error type
globalThis.testServer = server; globalThis.testServer = server;
@ -56,7 +54,6 @@ export default {
{ expiresIn: cfg.JWT_AUTH_LIFETIME } { expiresIn: cfg.JWT_AUTH_LIFETIME }
); );
} catch (error) { } catch (error) {
console.log("[TEST] Error setting up environment", error);
await db.destroy(); await db.destroy();
throw error; throw error;
} }

2286
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -24,8 +24,8 @@
"migration:latest": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest", "migration:latest": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest",
"migration:rollback": "knex --knexfile ./src/db/knexfile.ts migrate:rollback", "migration:rollback": "knex --knexfile ./src/db/knexfile.ts migrate:rollback",
"seed:new": "tsx ./scripts/create-seed-file.ts", "seed:new": "tsx ./scripts/create-seed-file.ts",
"seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run", "seed:run": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest" "db:reset": "npm run migration:rollback -- --all && npm run migration:latest && npm run seed:run"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -67,21 +67,21 @@
"tsx": "^4.4.0", "tsx": "^4.4.0",
"typescript": "^5.3.2", "typescript": "^5.3.2",
"vite-tsconfig-paths": "^4.2.2", "vite-tsconfig-paths": "^4.2.2",
"vitest": "^1.2.2" "vitest": "^1.0.4"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.504.0", "@aws-sdk/client-secrets-manager": "^3.485.0",
"@casl/ability": "^6.5.0", "@casl/ability": "^6.5.0",
"@fastify/cookie": "^9.2.0", "@fastify/cookie": "^9.2.0",
"@fastify/cors": "^8.5.0", "@fastify/cors": "^8.4.1",
"@fastify/etag": "^5.1.0", "@fastify/etag": "^5.1.0",
"@fastify/formbody": "^7.4.0", "@fastify/formbody": "^7.4.0",
"@fastify/helmet": "^11.1.1", "@fastify/helmet": "^11.1.1",
"@fastify/passport": "^2.4.0", "@fastify/passport": "^2.4.0",
"@fastify/rate-limit": "^9.0.0", "@fastify/rate-limit": "^9.0.0",
"@fastify/session": "^10.7.0", "@fastify/session": "^10.7.0",
"@fastify/swagger": "^8.14.0", "@fastify/swagger": "^8.12.0",
"@fastify/swagger-ui": "^2.1.0", "@fastify/swagger-ui": "^1.10.1",
"@node-saml/passport-saml": "^4.0.4", "@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^20.0.2", "@octokit/rest": "^20.0.2",
"@octokit/webhooks-types": "^7.3.1", "@octokit/webhooks-types": "^7.3.1",
@ -90,13 +90,13 @@
"@ucast/mongo2js": "^1.3.4", "@ucast/mongo2js": "^1.3.4",
"ajv": "^8.12.0", "ajv": "^8.12.0",
"argon2": "^0.31.2", "argon2": "^0.31.2",
"aws-sdk": "^2.1549.0", "aws-sdk": "^2.1532.0",
"axios": "^1.6.7", "axios": "^1.6.2",
"axios-retry": "^4.0.0", "axios-retry": "^4.0.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"bullmq": "^5.1.6", "bullmq": "^5.1.1",
"dotenv": "^16.4.1", "dotenv": "^16.3.1",
"fastify": "^4.26.0", "fastify": "^4.24.3",
"fastify-plugin": "^4.5.1", "fastify-plugin": "^4.5.1",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
@ -106,11 +106,10 @@
"knex": "^3.0.1", "knex": "^3.0.1",
"libsodium-wrappers": "^0.7.13", "libsodium-wrappers": "^0.7.13",
"lodash.isequal": "^4.5.0", "lodash.isequal": "^4.5.0",
"mysql2": "^3.9.1", "mysql2": "^3.6.5",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"node-cache": "^5.1.2", "node-cache": "^5.1.2",
"nodemailer": "^6.9.9", "nodemailer": "^6.9.7",
"ora": "^7.0.1",
"passport-github": "^1.1.0", "passport-github": "^1.1.0",
"passport-gitlab2": "^5.0.0", "passport-gitlab2": "^5.0.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
@ -118,12 +117,12 @@
"picomatch": "^3.0.1", "picomatch": "^3.0.1",
"pino": "^8.16.2", "pino": "^8.16.2",
"posthog-node": "^3.6.0", "posthog-node": "^3.6.0",
"probot": "^13.0.0", "probot": "^12.3.3",
"smee-client": "^2.0.0", "smee-client": "^2.0.0",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1", "tweetnacl-util": "^0.15.1",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"zod": "^3.22.4", "zod": "^3.22.4",
"zod-to-json-schema": "^3.22.4" "zod-to-json-schema": "^3.22.0"
} }
} }

View File

@ -7,10 +7,11 @@ import promptSync from "prompt-sync";
const prompt = promptSync({ sigint: true }); const prompt = promptSync({ sigint: true });
const migrationName = prompt("Enter name for seedfile: "); const migrationName = prompt("Enter name for seedfile: ");
const fileCounter = readdirSync(path.join(__dirname, "../src/db/seeds")).length || 1; const fileCounter = readdirSync(path.join(__dirname, "../src/db/seed")).length || 1;
execSync( execSync(
`npx knex seed:make --knexfile ${path.join(__dirname, "../src/db/knexfile.ts")} -x ts ${ `npx knex seed:make --knexfile ${path.join(
fileCounter + 1 __dirname,
}-${migrationName}`, "../src/db/knexfile.ts"
)} -x ts ${fileCounter}-${migrationName}`,
{ stdio: "inherit" } { stdio: "inherit" }
); );

View File

@ -3,9 +3,13 @@ import dotenv from "dotenv";
import path from "path"; import path from "path";
import knex from "knex"; import knex from "knex";
import { writeFileSync } from "fs"; import { writeFileSync } from "fs";
import promptSync from "prompt-sync";
const prompt = promptSync({ sigint: true });
dotenv.config({ dotenv.config({
path: path.join(__dirname, "../../.env.migration") path: path.join(__dirname, "../.env"),
debug: true
}); });
const db = knex({ const db = knex({
@ -90,7 +94,17 @@ const main = async () => {
.orderBy("table_name") .orderBy("table_name")
).filter((el) => !el.tableName.includes("_migrations")); ).filter((el) => !el.tableName.includes("_migrations"));
console.log("Select a table to generate schema");
console.table(tables);
console.log("all: all tables");
const selectedTables = prompt("Type table numbers comma seperated: ");
const tableNumbers =
selectedTables !== "all" ? selectedTables.split(",").map((el) => Number(el)) : [];
for (let i = 0; i < tables.length; i += 1) { for (let i = 0; i < tables.length; i += 1) {
// skip if not desired table
if (selectedTables !== "all" && !tableNumbers.includes(i)) continue;
const { tableName } = tables[i]; const { tableName } = tables[i];
const columns = await db(tableName).columnInfo(); const columns = await db(tableName).columnInfo();
const columnNames = Object.keys(columns); const columnNames = Object.keys(columns);
@ -110,16 +124,16 @@ const main = async () => {
if (colInfo.nullable) { if (colInfo.nullable) {
ztype = ztype.concat(".nullable().optional()"); ztype = ztype.concat(".nullable().optional()");
} }
schema = schema.concat( schema = schema.concat(`${!schema ? "\n" : ""} ${columnName}: ${ztype},\n`);
`${!schema ? "\n" : ""} ${columnName}: ${ztype}${colNum === columnNames.length - 1 ? "" : ","}\n`
);
} }
const dashcase = tableName.split("_").join("-"); const dashcase = tableName.split("_").join("-");
const pascalCase = tableName const pascalCase = tableName
.split("_") .split("_")
.reduce((prev, curr) => prev + `${curr.at(0)?.toUpperCase()}${curr.slice(1).toLowerCase()}`, ""); .reduce(
(prev, curr) => prev + `${curr.at(0)?.toUpperCase()}${curr.slice(1).toLowerCase()}`,
""
);
writeFileSync( writeFileSync(
path.join(__dirname, "../src/db/schemas", `${dashcase}.ts`), path.join(__dirname, "../src/db/schemas", `${dashcase}.ts`),
`// Code generated by automation script, DO NOT EDIT. `// Code generated by automation script, DO NOT EDIT.
@ -138,6 +152,15 @@ export type T${pascalCase}Insert = Omit<T${pascalCase}, TImmutableDBKeys>;
export type T${pascalCase}Update = Partial<Omit<T${pascalCase}, TImmutableDBKeys>>; export type T${pascalCase}Update = Partial<Omit<T${pascalCase}, TImmutableDBKeys>>;
` `
); );
// const file = readFileSync(path.join(__dirname, "../src/db/schemas/index.ts"), "utf8");
// if (!file.includes(`export * from "./${dashcase};"`)) {
// appendFileSync(
// path.join(__dirname, "../src/db/schemas/index.ts"),
// `\nexport * from "./${dashcase}";`,
// "utf8"
// );
// }
} }
process.exit(0); process.exit(0);

View File

@ -6,7 +6,6 @@ import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service"; import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service"; import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service"; import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service";
import { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service"; import { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
@ -52,7 +51,6 @@ declare module "fastify" {
// used for mfa session authentication // used for mfa session authentication
mfa: { mfa: {
userId: string; userId: string;
orgId?: string;
user: TUsers; user: TUsers;
}; };
// identity injection. depending on which kinda of token the information is filled in auth // identity injection. depending on which kinda of token the information is filled in auth
@ -60,7 +58,6 @@ declare module "fastify" {
permission: { permission: {
type: ActorType; type: ActorType;
id: string; id: string;
orgId?: string;
}; };
// passport data // passport data
passportUser: { passportUser: {
@ -106,7 +103,6 @@ declare module "fastify" {
secretRotation: TSecretRotationServiceFactory; secretRotation: TSecretRotationServiceFactory;
snapshot: TSecretSnapshotServiceFactory; snapshot: TSecretSnapshotServiceFactory;
saml: TSamlConfigServiceFactory; saml: TSamlConfigServiceFactory;
scim: TScimServiceFactory;
auditLog: TAuditLogServiceFactory; auditLog: TAuditLogServiceFactory;
secretScanning: TSecretScanningServiceFactory; secretScanning: TSecretScanningServiceFactory;
license: TLicenseServiceFactory; license: TLicenseServiceFactory;

View File

@ -83,9 +83,6 @@ import {
TSamlConfigs, TSamlConfigs,
TSamlConfigsInsert, TSamlConfigsInsert,
TSamlConfigsUpdate, TSamlConfigsUpdate,
TScimTokens,
TScimTokensInsert,
TScimTokensUpdate,
TSecretApprovalPolicies, TSecretApprovalPolicies,
TSecretApprovalPoliciesApprovers, TSecretApprovalPoliciesApprovers,
TSecretApprovalPoliciesApproversInsert, TSecretApprovalPoliciesApproversInsert,
@ -265,7 +262,6 @@ declare module "knex/types/tables" {
TIdentityProjectMembershipsInsert, TIdentityProjectMembershipsInsert,
TIdentityProjectMembershipsUpdate TIdentityProjectMembershipsUpdate
>; >;
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType< [TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
TSecretApprovalPolicies, TSecretApprovalPolicies,
TSecretApprovalPoliciesInsert, TSecretApprovalPoliciesInsert,

View File

@ -5,13 +5,9 @@ import dotenv from "dotenv";
import type { Knex } from "knex"; import type { Knex } from "knex";
import path from "path"; import path from "path";
// Update with your config settings. . // Update with your config settings.
dotenv.config({ dotenv.config({
path: path.join(__dirname, "../../../.env.migration"), path: path.join(__dirname, "../../.env"),
debug: true
});
dotenv.config({
path: path.join(__dirname, "../../../.env"),
debug: true debug: true
}); });
export default { export default {

View File

@ -1,25 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.boolean("authEnforced").defaultTo(false);
t.index("slug");
});
await knex.schema.alterTable(TableName.SamlConfig, (t) => {
t.datetime("lastUsed");
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.Organization, (t) => {
t.dropColumn("authEnforced");
t.dropIndex("slug");
});
await knex.schema.alterTable(TableName.SamlConfig, (t) => {
t.dropColumn("lastUsed");
});
}

View File

@ -1,31 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ScimToken))) {
await knex.schema.createTable(TableName.ScimToken, (t) => {
t.string("id", 36).primary().defaultTo(knex.fn.uuid());
t.bigInteger("ttlDays").defaultTo(365).notNullable();
t.string("description").notNullable();
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await knex.schema.alterTable(TableName.Organization, (t) => {
t.boolean("scimEnabled").defaultTo(false);
});
await createOnUpdateTrigger(knex, TableName.ScimToken);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ScimToken);
await dropOnUpdateTrigger(knex, TableName.ScimToken);
await knex.schema.alterTable(TableName.Organization, (t) => {
t.dropColumn("scimEnabled");
});
}

View File

@ -1,39 +0,0 @@
import { Knex } from "knex";
import { ProjectVersion, TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasGhostUserColumn = await knex.schema.hasColumn(TableName.Users, "isGhost");
const hasProjectVersionColumn = await knex.schema.hasColumn(TableName.Project, "version");
if (!hasGhostUserColumn) {
await knex.schema.alterTable(TableName.Users, (t) => {
t.boolean("isGhost").defaultTo(false).notNullable();
});
}
if (!hasProjectVersionColumn) {
await knex.schema.alterTable(TableName.Project, (t) => {
t.integer("version").defaultTo(ProjectVersion.V1).notNullable();
t.string("upgradeStatus").nullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasGhostUserColumn = await knex.schema.hasColumn(TableName.Users, "isGhost");
const hasProjectVersionColumn = await knex.schema.hasColumn(TableName.Project, "version");
if (hasGhostUserColumn) {
await knex.schema.alterTable(TableName.Users, (t) => {
t.dropColumn("isGhost");
});
}
if (hasProjectVersionColumn) {
await knex.schema.alterTable(TableName.Project, (t) => {
t.dropColumn("version");
t.dropColumn("upgradeStatus");
});
}
}

View File

@ -1,20 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const isTablePresent = await knex.schema.hasTable(TableName.SuperAdmin);
if (isTablePresent) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.string("allowedSignUpDomain");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.SuperAdmin, "allowedSignUpDomain")) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.dropColumn("allowedSignUpDomain");
});
}
}

View File

@ -26,7 +26,6 @@ export * from "./project-memberships";
export * from "./project-roles"; export * from "./project-roles";
export * from "./projects"; export * from "./projects";
export * from "./saml-configs"; export * from "./saml-configs";
export * from "./scim-tokens";
export * from "./secret-approval-policies"; export * from "./secret-approval-policies";
export * from "./secret-approval-policies-approvers"; export * from "./secret-approval-policies-approvers";
export * from "./secret-approval-request-secret-tags"; export * from "./secret-approval-request-secret-tags";

View File

@ -40,7 +40,6 @@ export enum TableName {
IdentityUaClientSecret = "identity_ua_client_secrets", IdentityUaClientSecret = "identity_ua_client_secrets",
IdentityOrgMembership = "identity_org_memberships", IdentityOrgMembership = "identity_org_memberships",
IdentityProjectMembership = "identity_project_memberships", IdentityProjectMembership = "identity_project_memberships",
ScimToken = "scim_tokens",
SecretApprovalPolicy = "secret_approval_policies", SecretApprovalPolicy = "secret_approval_policies",
SecretApprovalPolicyApprover = "secret_approval_policies_approvers", SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
SecretApprovalRequest = "secret_approval_requests", SecretApprovalRequest = "secret_approval_requests",
@ -112,17 +111,6 @@ export enum SecretType {
Personal = "personal" Personal = "personal"
} }
export enum ProjectVersion {
V1 = 1,
V2 = 2
}
export enum ProjectUpgradeStatus {
InProgress = "IN_PROGRESS",
// Completed -> Will be null if completed. So a completed status is not needed
Failed = "FAILED"
}
export enum IdentityAuthMethod { export enum IdentityAuthMethod {
Univeral = "universal-auth" Univeral = "universal-auth"
} }

View File

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

View File

@ -14,9 +14,7 @@ export const ProjectsSchema = z.object({
autoCapitalization: z.boolean().default(true).nullable().optional(), autoCapitalization: z.boolean().default(true).nullable().optional(),
orgId: z.string().uuid(), orgId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date()
version: z.number().default(1),
upgradeStatus: z.string().nullable().optional()
}); });
export type TProjects = z.infer<typeof ProjectsSchema>; export type TProjects = z.infer<typeof ProjectsSchema>;

View File

@ -22,8 +22,7 @@ export const SamlConfigsSchema = z.object({
certTag: z.string().nullable().optional(), certTag: z.string().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
orgId: z.string().uuid(), orgId: z.string().uuid()
lastUsed: z.date().nullable().optional()
}); });
export type TSamlConfigs = z.infer<typeof SamlConfigsSchema>; export type TSamlConfigs = z.infer<typeof SamlConfigsSchema>;

View File

@ -1,21 +0,0 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const ScimTokensSchema = z.object({
id: z.string(),
ttlDays: z.coerce.number().default(365),
description: z.string(),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TScimTokens = z.infer<typeof ScimTokensSchema>;
export type TScimTokensInsert = Omit<TScimTokens, TImmutableDBKeys>;
export type TScimTokensUpdate = Partial<Omit<TScimTokens, TImmutableDBKeys>>;

View File

@ -12,8 +12,7 @@ export const SuperAdminSchema = z.object({
initialized: z.boolean().default(false).nullable().optional(), initialized: z.boolean().default(false).nullable().optional(),
allowSignUp: z.boolean().default(true).nullable().optional(), allowSignUp: z.boolean().default(true).nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date()
allowedSignUpDomain: z.string().nullable().optional()
}); });
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>; export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;

View File

@ -19,8 +19,7 @@ export const UsersSchema = z.object({
mfaMethods: z.string().array().nullable().optional(), mfaMethods: z.string().array().nullable().optional(),
devices: z.unknown().nullable().optional(), devices: z.unknown().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date()
isGhost: z.boolean().default(false)
}); });
export type TUsers = z.infer<typeof UsersSchema>; export type TUsers = z.infer<typeof UsersSchema>;

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-mutable-exports */
import crypto from "node:crypto"; import crypto from "node:crypto";
import argon2, { argon2id } from "argon2"; import argon2, { argon2id } from "argon2";
@ -7,21 +6,17 @@ import nacl from "tweetnacl";
import { encodeBase64 } from "tweetnacl-util"; import { encodeBase64 } from "tweetnacl-util";
import { import {
decryptAsymmetric,
// decryptAsymmetric, // decryptAsymmetric,
decryptSymmetric128BitHexKeyUTF8, decryptSymmetric,
encryptAsymmetric, encryptAsymmetric,
encryptSymmetric128BitHexKeyUTF8 encryptSymmetric
} from "@app/lib/crypto"; } from "@app/lib/crypto";
import { TSecrets, TUserEncryptionKeys } from "./schemas"; import { TUserEncryptionKeys } from "./schemas";
export let userPrivateKey: string | undefined;
export let userPublicKey: string | undefined;
export const seedData1 = { export const seedData1 = {
id: "3dafd81d-4388-432b-a4c5-f735616868c1", id: "3dafd81d-4388-432b-a4c5-f735616868c1",
email: process.env.TEST_USER_EMAIL || "test@localhost.local", email: "test@localhost.local",
password: process.env.TEST_USER_PASSWORD || "testInfisical@1", password: process.env.TEST_USER_PASSWORD || "testInfisical@1",
organization: { organization: {
id: "180870b7-f464-4740-8ffe-9d11c9245ea7", id: "180870b7-f464-4740-8ffe-9d11c9245ea7",
@ -36,22 +31,8 @@ export const seedData1 = {
name: "Development", name: "Development",
slug: "dev" slug: "dev"
}, },
machineIdentity: {
id: "88fa7aed-9288-401e-a4c9-fa9430be62a0",
name: "mac1",
clientCredentials: {
id: "3f6135db-f237-421d-af66-a8f4e80d443b",
secret: "da35a5a5a7b57f977a9a73394506e878a7175d06606df43dc93e1472b10cf339"
}
},
token: { token: {
id: "a9dfafba-a3b7-42e3-8618-91abb702fd36" id: "a9dfafba-a3b7-42e3-8618-91abb702fd36"
},
// We set these values during user creation, and later re-use them during project seeding.
encryptionKeys: {
publicKey: "",
privateKey: ""
} }
}; };
@ -92,7 +73,7 @@ export const generateUserSrpKeys = async (password: string) => {
ciphertext: encryptedPrivateKey, ciphertext: encryptedPrivateKey,
iv: encryptedPrivateKeyIV, iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag tag: encryptedPrivateKeyTag
} = encryptSymmetric128BitHexKeyUTF8(privateKey, key); } = encryptSymmetric(privateKey, key.toString("base64"));
// create the protected key by encrypting the symmetric key // create the protected key by encrypting the symmetric key
// [key] with the derived key // [key] with the derived key
@ -100,7 +81,7 @@ export const generateUserSrpKeys = async (password: string) => {
ciphertext: protectedKey, ciphertext: protectedKey,
iv: protectedKeyIV, iv: protectedKeyIV,
tag: protectedKeyTag tag: protectedKeyTag
} = encryptSymmetric128BitHexKeyUTF8(key.toString("hex"), derivedKey); } = encryptSymmetric(key.toString("hex"), derivedKey.toString("base64"));
return { return {
protectedKey, protectedKey,
@ -126,102 +107,32 @@ export const getUserPrivateKey = async (password: string, user: TUserEncryptionK
raw: true raw: true
}); });
if (!derivedKey) throw new Error("Failed to derive key from password"); if (!derivedKey) throw new Error("Failed to derive key from password");
const key = decryptSymmetric({
const key = decryptSymmetric128BitHexKeyUTF8({
ciphertext: user.protectedKey as string, ciphertext: user.protectedKey as string,
iv: user.protectedKeyIV as string, iv: user.protectedKeyIV as string,
tag: user.protectedKeyTag as string, tag: user.protectedKeyTag as string,
key: derivedKey key: derivedKey.toString("base64")
}); });
const privateKey = decryptSymmetric({
const privateKey = decryptSymmetric128BitHexKeyUTF8({
ciphertext: user.encryptedPrivateKey, ciphertext: user.encryptedPrivateKey,
iv: user.iv, iv: user.iv,
tag: user.tag, tag: user.tag,
key: Buffer.from(key, "hex") key
}); });
return privateKey; return privateKey;
}; };
export const buildUserProjectKey = (privateKey: string, publickey: string) => { export const buildUserProjectKey = async (privateKey: string, publickey: string) => {
const randomBytes = crypto.randomBytes(16).toString("hex"); const randomBytes = crypto.randomBytes(16).toString("hex");
const { nonce, ciphertext } = encryptAsymmetric(randomBytes, publickey, privateKey); const { nonce, ciphertext } = encryptAsymmetric(randomBytes, publickey, privateKey);
return { nonce, ciphertext }; return { nonce, ciphertext };
}; };
export const getUserProjectKey = async (privateKey: string, ciphertext: string, nonce: string, publicKey: string) => { // export const getUserProjectKey = async (privateKey: string) => {
return decryptAsymmetric({ // const key = decryptAsymmetric({
ciphertext, // ciphertext: decryptFileKey.encryptedKey,
nonce, // nonce: decryptFileKey.nonce,
publicKey, // publicKey: decryptFileKey.sender.publicKey,
privateKey // privateKey: PRIVATE_KEY
}); // });
}; // };
export const encryptSecret = (encKey: string, key: string, value?: string, comment?: string) => {
// encrypt key
const {
ciphertext: secretKeyCiphertext,
iv: secretKeyIV,
tag: secretKeyTag
} = encryptSymmetric128BitHexKeyUTF8(key, encKey);
// encrypt value
const {
ciphertext: secretValueCiphertext,
iv: secretValueIV,
tag: secretValueTag
} = encryptSymmetric128BitHexKeyUTF8(value ?? "", encKey);
// encrypt comment
const {
ciphertext: secretCommentCiphertext,
iv: secretCommentIV,
tag: secretCommentTag
} = encryptSymmetric128BitHexKeyUTF8(comment ?? "", encKey);
return {
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
};
};
export const decryptSecret = (decryptKey: string, encSecret: TSecrets) => {
const secretKey = decryptSymmetric128BitHexKeyUTF8({
key: decryptKey,
ciphertext: encSecret.secretKeyCiphertext,
tag: encSecret.secretKeyTag,
iv: encSecret.secretKeyIV
});
const secretValue = decryptSymmetric128BitHexKeyUTF8({
key: decryptKey,
ciphertext: encSecret.secretValueCiphertext,
tag: encSecret.secretValueTag,
iv: encSecret.secretValueIV
});
const secretComment =
encSecret.secretCommentIV && encSecret.secretCommentTag && encSecret.secretCommentCiphertext
? decryptSymmetric128BitHexKeyUTF8({
key: decryptKey,
ciphertext: encSecret.secretCommentCiphertext,
tag: encSecret.secretCommentTag,
iv: encSecret.secretCommentIV
})
: "";
return {
key: secretKey,
value: secretValue,
comment: secretComment,
version: encSecret.version
};
};

View File

@ -14,8 +14,7 @@ export async function seed(knex: Knex): Promise<void> {
const [user] = await knex(TableName.Users) const [user] = await knex(TableName.Users)
.insert([ .insert([
{ {
// eslint-disable-next-line // @ts-expect-error exluded type id needs to be inserted here to keep it testable
// @ts-ignore
id: seedData1.id, id: seedData1.id,
email: seedData1.email, email: seedData1.email,
superAdmin: true, superAdmin: true,
@ -49,8 +48,7 @@ export async function seed(knex: Knex): Promise<void> {
]); ]);
await knex(TableName.AuthTokenSession).insert({ await knex(TableName.AuthTokenSession).insert({
// eslint-disable-next-line // @ts-expect-error exluded type id needs to be inserted here to keep it testable
// @ts-ignore
id: seedData1.token.id, id: seedData1.token.id,
userId: seedData1.id, userId: seedData1.id,
ip: "151.196.220.213", ip: "151.196.220.213",

View File

@ -14,8 +14,7 @@ export async function seed(knex: Knex): Promise<void> {
const [org] = await knex(TableName.Organization) const [org] = await knex(TableName.Organization)
.insert([ .insert([
{ {
// eslint-disable-next-line // @ts-expect-error exluded type id needs to be inserted here to keep it testable
// @ts-ignore
id: seedData1.organization.id, id: seedData1.organization.id,
name: "infisical", name: "infisical",
slug: "infisical", slug: "infisical",

View File

@ -1,11 +1,7 @@
import crypto from "node:crypto";
import { Knex } from "knex"; import { Knex } from "knex";
import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto"; import { OrgMembershipRole, TableName } from "../schemas";
import { seedData1 } from "../seed-data";
import { OrgMembershipRole, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
import { buildUserProjectKey, getUserPrivateKey, seedData1 } from "../seed-data";
export const DEFAULT_PROJECT_ENVS = [ export const DEFAULT_PROJECT_ENVS = [
{ name: "Development", slug: "dev" }, { name: "Development", slug: "dev" },
@ -24,32 +20,21 @@ export async function seed(knex: Knex): Promise<void> {
name: seedData1.project.name, name: seedData1.project.name,
orgId: seedData1.organization.id, orgId: seedData1.organization.id,
slug: "first-project", slug: "first-project",
// eslint-disable-next-line // @ts-expect-error exluded type id needs to be inserted here to keep it testable
// @ts-ignore
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({ await knex(TableName.ProjectMembership).insert({
projectId: project.id, projectId: project.id,
role: OrgMembershipRole.Admin, role: OrgMembershipRole.Admin,
userId: seedData1.id userId: seedData1.id
}); });
const user = await knex(TableName.UserEncryptionKey).where({ userId: seedData1.id }).first();
if (!user) throw new Error("User not found");
const userPrivateKey = await getUserPrivateKey(seedData1.password, user);
const projectKey = buildUserProjectKey(userPrivateKey, user.publicKey);
await knex(TableName.ProjectKeys).insert({
projectId: project.id,
nonce: projectKey.nonce,
encryptedKey: projectKey.ciphertext,
receiverId: seedData1.id,
senderId: seedData1.id
});
// create default environments and default folders
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) => ({
@ -61,19 +46,4 @@ export async function seed(knex: Knex): Promise<void> {
) )
.returning("*"); .returning("*");
await knex(TableName.SecretFolder).insert(envs.map(({ id }) => ({ name: "root", envId: id, parentId: null }))); await knex(TableName.SecretFolder).insert(envs.map(({ id }) => ({ name: "root", envId: id, parentId: null })));
// save secret secret blind index
const encKey = process.env.ENCRYPTION_KEY;
if (!encKey) throw new Error("Missing ENCRYPTION_KEY");
const salt = crypto.randomBytes(16).toString("base64");
const secretBlindIndex = encryptSymmetric128BitHexKeyUTF8(salt, encKey);
// insert secret blind index for project
await knex(TableName.SecretBlindIndex).insert({
projectId: project.id,
encryptedSaltCipherText: secretBlindIndex.ciphertext,
saltIV: secretBlindIndex.iv,
saltTag: secretBlindIndex.tag,
algorithm: SecretEncryptionAlgo.AES_256_GCM,
keyEncoding: SecretKeyEncoding.UTF8
});
} }

View File

@ -1,83 +0,0 @@
import bcrypt from "bcrypt";
import { Knex } from "knex";
import { IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas";
import { seedData1 } from "../seed-data";
export async function seed(knex: Knex): Promise<void> {
// Deletes ALL existing entries
await knex(TableName.Identity).del();
await knex(TableName.IdentityOrgMembership).del();
// Inserts seed entries
await knex(TableName.Identity).insert([
{
// eslint-disable-next-line
// @ts-ignore
id: seedData1.machineIdentity.id,
name: seedData1.machineIdentity.name,
authMethod: IdentityAuthMethod.Univeral
}
]);
const identityUa = await knex(TableName.IdentityUniversalAuth)
.insert([
{
identityId: seedData1.machineIdentity.id,
clientId: seedData1.machineIdentity.clientCredentials.id,
clientSecretTrustedIps: JSON.stringify([
{
type: "ipv4",
prefix: 0,
ipAddress: "0.0.0.0"
},
{
type: "ipv6",
prefix: 0,
ipAddress: "::"
}
]),
accessTokenTrustedIps: JSON.stringify([
{
type: "ipv4",
prefix: 0,
ipAddress: "0.0.0.0"
},
{
type: "ipv6",
prefix: 0,
ipAddress: "::"
}
]),
accessTokenTTL: 2592000,
accessTokenMaxTTL: 2592000,
accessTokenNumUsesLimit: 0
}
])
.returning("*");
const clientSecretHash = await bcrypt.hash(seedData1.machineIdentity.clientCredentials.secret, 10);
await knex(TableName.IdentityUaClientSecret).insert([
{
identityUAId: identityUa[0].id,
description: "",
clientSecretTTL: 0,
clientSecretNumUses: 0,
clientSecretNumUsesLimit: 0,
clientSecretPrefix: seedData1.machineIdentity.clientCredentials.secret.slice(0, 4),
clientSecretHash,
isClientSecretRevoked: false
}
]);
await knex(TableName.IdentityOrgMembership).insert([
{
identityId: seedData1.machineIdentity.id,
orgId: seedData1.organization.id,
role: OrgMembershipRole.Admin
}
]);
await knex(TableName.IdentityProjectMembership).insert({
identityId: seedData1.machineIdentity.id,
role: ProjectMembershipRole.Admin,
projectId: seedData1.project.id
});
}

View File

@ -3,7 +3,6 @@ import { registerOrgRoleRouter } from "./org-role-router";
import { registerProjectRoleRouter } from "./project-role-router"; import { registerProjectRoleRouter } from "./project-role-router";
import { registerProjectRouter } from "./project-router"; import { registerProjectRouter } from "./project-router";
import { registerSamlRouter } from "./saml-router"; import { registerSamlRouter } from "./saml-router";
import { registerScimRouter } from "./scim-router";
import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router"; import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-router";
import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router"; import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router";
import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router"; import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router";
@ -34,7 +33,6 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
prefix: "/secret-rotation-providers" prefix: "/secret-rotation-providers"
}); });
await server.register(registerSamlRouter, { prefix: "/sso" }); await server.register(registerSamlRouter, { prefix: "/sso" });
await server.register(registerScimRouter, { prefix: "/scim" });
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" }); await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" }); await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
await server.register(registerSecretVersionRouter, { prefix: "/secret" }); await server.register(registerSecretVersionRouter, { prefix: "/secret" });

View File

@ -22,7 +22,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.getOrgPlansTableByBillCycle({ const data = await server.services.license.getOrgPlansTableByBillCycle({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId, orgId: req.params.organizationId,
billingCycle: req.query.billingCycle billingCycle: req.query.billingCycle
}); });
@ -44,7 +43,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const plan = await server.services.license.getOrgPlan({ const plan = await server.services.license.getOrgPlan({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return { plan }; return { plan };
@ -87,7 +85,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.startOrgTrial({ const data = await server.services.license.startOrgTrial({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId, orgId: req.params.organizationId,
success_url: req.body.success_url success_url: req.body.success_url
}); });
@ -109,7 +106,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.createOrganizationPortalSession({ const data = await server.services.license.createOrganizationPortalSession({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return data; return data;
@ -130,7 +126,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.getOrgBillingInfo({ const data = await server.services.license.getOrgBillingInfo({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return data; return data;
@ -151,7 +146,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.getOrgPlanTable({ const data = await server.services.license.getOrgPlanTable({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return data; return data;
@ -172,7 +166,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.getOrgBillingDetails({ const data = await server.services.license.getOrgBillingDetails({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return data; return data;
@ -197,7 +190,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.updateOrgBillingDetails({ const data = await server.services.license.updateOrgBillingDetails({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId, orgId: req.params.organizationId,
name: req.body.name, name: req.body.name,
email: req.body.email email: req.body.email
@ -220,7 +212,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.getOrgPmtMethods({ const data = await server.services.license.getOrgPmtMethods({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return data; return data;
@ -245,7 +236,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.addOrgPmtMethods({ const data = await server.services.license.addOrgPmtMethods({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId, orgId: req.params.organizationId,
success_url: req.body.success_url, success_url: req.body.success_url,
cancel_url: req.body.cancel_url cancel_url: req.body.cancel_url
@ -271,7 +261,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.delOrgPmtMethods({ const data = await server.services.license.delOrgPmtMethods({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId, orgId: req.params.organizationId,
pmtMethodId: req.params.pmtMethodId pmtMethodId: req.params.pmtMethodId
}); });
@ -295,7 +284,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.getOrgTaxIds({ const data = await server.services.license.getOrgTaxIds({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return data; return data;
@ -322,7 +310,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.addOrgTaxId({ const data = await server.services.license.addOrgTaxId({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId, orgId: req.params.organizationId,
type: req.body.type, type: req.body.type,
value: req.body.value value: req.body.value
@ -348,7 +335,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.delOrgTaxId({ const data = await server.services.license.delOrgTaxId({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId, orgId: req.params.organizationId,
taxId: req.params.taxId taxId: req.params.taxId
}); });
@ -372,7 +358,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.getOrgTaxInvoices({ const data = await server.services.license.getOrgTaxInvoices({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return data; return data;
@ -395,7 +380,6 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.getOrgLicenses({ const data = await server.services.license.getOrgLicenses({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return data; return data;

View File

@ -26,12 +26,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const role = await server.services.orgRole.createRole( const role = await server.services.orgRole.createRole(req.permission.id, req.params.organizationId, req.body);
req.permission.id,
req.params.organizationId,
req.body,
req.permission.orgId
);
return { role }; return { role };
} }
}); });
@ -62,8 +57,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
req.permission.id, req.permission.id,
req.params.organizationId, req.params.organizationId,
req.params.roleId, req.params.roleId,
req.body, req.body
req.permission.orgId
); );
return { role }; return { role };
} }
@ -88,8 +82,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
const role = await server.services.orgRole.deleteRole( const role = await server.services.orgRole.deleteRole(
req.permission.id, req.permission.id,
req.params.organizationId, req.params.organizationId,
req.params.roleId, req.params.roleId
req.permission.orgId
); );
return { role }; return { role };
} }
@ -114,11 +107,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const roles = await server.services.orgRole.listRoles( const roles = await server.services.orgRole.listRoles(req.permission.id, req.params.organizationId);
req.permission.id,
req.params.organizationId,
req.permission.orgId
);
return { data: { roles } }; return { data: { roles } };
} }
}); });
@ -141,8 +130,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
handler: async (req) => { handler: async (req) => {
const { permissions, membership } = await server.services.orgRole.getUserPermission( const { permissions, membership } = await server.services.orgRole.getUserPermission(
req.permission.id, req.permission.id,
req.params.organizationId, req.params.organizationId
req.permission.orgId
); );
return { permissions, membership }; return { permissions, membership };
} }

View File

@ -30,8 +30,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
req.permission.type, req.permission.type,
req.permission.id, req.permission.id,
req.params.projectId, req.params.projectId,
req.body, req.body
req.permission.orgId
); );
return { role }; return { role };
} }
@ -64,8 +63,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
req.permission.id, req.permission.id,
req.params.projectId, req.params.projectId,
req.params.roleId, req.params.roleId,
req.body, req.body
req.permission.orgId
); );
return { role }; return { role };
} }
@ -91,8 +89,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
req.permission.type, req.permission.type,
req.permission.id, req.permission.id,
req.params.projectId, req.params.projectId,
req.params.roleId, req.params.roleId
req.permission.orgId
); );
return { role }; return { role };
} }
@ -120,8 +117,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
const roles = await server.services.projectRole.listRoles( const roles = await server.services.projectRole.listRoles(
req.permission.type, req.permission.type,
req.permission.id, req.permission.id,
req.params.projectId, req.params.projectId
req.permission.orgId
); );
return { data: { roles } }; return { data: { roles } };
} }
@ -147,8 +143,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
handler: async (req) => { handler: async (req) => {
const { permissions, membership } = await server.services.projectRole.getUserPermission( const { permissions, membership } = await server.services.projectRole.getUserPermission(
req.permission.id, req.permission.id,
req.params.projectId, req.params.projectId
req.permission.orgId
); );
return { data: { permissions, membership } }; return { data: { permissions, membership } };
} }

View File

@ -11,13 +11,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
method: "GET", method: "GET",
url: "/:workspaceId/secret-snapshots", url: "/:workspaceId/secret-snapshots",
schema: { schema: {
description: "Return project secret snapshots ids",
security: [
{
apiKeyAuth: [],
bearerAuth: []
}
],
params: z.object({ params: z.object({
workspaceId: z.string().trim() workspaceId: z.string().trim()
}), }),
@ -38,7 +31,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
const secretSnapshots = await server.services.snapshot.listSnapshots({ const secretSnapshots = await server.services.snapshot.listSnapshots({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId, projectId: req.params.workspaceId,
...req.query ...req.query
}); });
@ -68,7 +60,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
const count = await server.services.snapshot.projectSecretSnapshotCount({ const count = await server.services.snapshot.projectSecretSnapshotCount({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId, projectId: req.params.workspaceId,
environment: req.query.environment, environment: req.query.environment,
path: req.query.path path: req.query.path
@ -81,13 +72,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
method: "GET", method: "GET",
url: "/:workspaceId/audit-logs", url: "/:workspaceId/audit-logs",
schema: { schema: {
description: "Return audit logs",
security: [
{
bearerAuth: [],
apiKeyAuth: []
}
],
params: z.object({ params: z.object({
workspaceId: z.string().trim() workspaceId: z.string().trim()
}), }),
@ -128,7 +112,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
handler: async (req) => { handler: async (req) => {
const auditLogs = await server.services.auditLog.listProjectAuditLogs({ const auditLogs = await server.services.auditLog.listProjectAuditLogs({
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId, projectId: req.params.workspaceId,
...req.query, ...req.query,
auditLogActor: req.query.actor, auditLogActor: req.query.actor,

View File

@ -13,12 +13,13 @@ import { FastifyRequest } from "fastify";
import { z } from "zod"; import { z } from "zod";
import { SamlConfigsSchema } from "@app/db/schemas"; import { SamlConfigsSchema } from "@app/db/schemas";
import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types"; import { SamlProviders } from "@app/ee/services/saml-config/saml-config-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
type TSAMLConfig = { type TSAMLConfig = {
callbackUrl: string; callbackUrl: string;
@ -27,7 +28,6 @@ type TSAMLConfig = {
cert: string; cert: string;
audience: string; audience: string;
wantAuthnResponseSigned?: boolean; wantAuthnResponseSigned?: boolean;
disableRequestedAuthnContext?: boolean;
}; };
export const registerSamlRouter = async (server: FastifyZodProvider) => { export const registerSamlRouter = async (server: FastifyZodProvider) => {
@ -44,30 +44,17 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
// eslint-disable-next-line // eslint-disable-next-line
getSamlOptions: async (req, done) => { getSamlOptions: async (req, done) => {
try { try {
const { samlConfigId, orgSlug } = req.params; const { ssoIdentifier } = req.params;
if (!ssoIdentifier) throw new BadRequestError({ message: "Missing sso identitier" });
let ssoLookupDetails: TGetSamlCfgDTO; const ssoConfig = await server.services.saml.getSaml({
if (orgSlug) {
ssoLookupDetails = {
type: "orgSlug",
orgSlug
};
} else if (samlConfigId) {
ssoLookupDetails = {
type: "ssoId", type: "ssoId",
id: samlConfigId id: ssoIdentifier
}; });
} else { if (!ssoConfig) throw new BadRequestError({ message: "SSO config not found" });
throw new BadRequestError({ message: "Missing sso identitier or org slug" });
}
const ssoConfig = await server.services.saml.getSaml(ssoLookupDetails);
if (!ssoConfig || !ssoConfig.isActive)
throw new BadRequestError({ message: "Failed to authenticate with SAML SSO" });
const samlConfig: TSAMLConfig = { const samlConfig: TSAMLConfig = {
callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${ssoConfig.id}`, callbackUrl: `${appCfg.SITE_URL}/api/v1/sso/saml2/${ssoIdentifier}`,
entryPoint: ssoConfig.entryPoint, entryPoint: ssoConfig.entryPoint,
issuer: ssoConfig.issuer, issuer: ssoConfig.issuer,
cert: ssoConfig.cert, cert: ssoConfig.cert,
@ -77,8 +64,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
samlConfig.wantAuthnResponseSigned = false; samlConfig.wantAuthnResponseSigned = false;
} }
if (ssoConfig.authProvider === SamlProviders.AZURE_SAML) { if (ssoConfig.authProvider === SamlProviders.AZURE_SAML) {
samlConfig.disableRequestedAuthnContext = true; if (req.body.RelayState && JSON.parse(req.body.RelayState).spIntiaited) {
if (req.body?.RelayState && JSON.parse(req.body.RelayState).spInitiated) {
samlConfig.audience = `spn:${ssoConfig.issuer}`; samlConfig.audience = `spn:${ssoConfig.issuer}`;
} }
} }
@ -93,6 +79,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
// eslint-disable-next-line // eslint-disable-next-line
async (req, profile, cb) => { async (req, profile, cb) => {
try { try {
const serverCfg = await getServerCfg();
if (!profile) throw new BadRequestError({ message: "Missing profile" }); if (!profile) throw new BadRequestError({ message: "Missing profile" });
const { firstName } = profile; const { firstName } = profile;
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
@ -105,6 +92,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
email, email,
firstName: profile.firstName as string, firstName: profile.firstName as string,
lastName: profile.lastName as string, lastName: profile.lastName as string,
isSignupAllowed: Boolean(serverCfg.allowSignUp),
relayState: (req.body as { RelayState?: string }).RelayState, relayState: (req.body as { RelayState?: string }).RelayState,
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string, authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string
@ -120,11 +108,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
); );
server.route({ server.route({
url: "/redirect/saml2/organizations/:orgSlug", url: "/redirect/saml2/:ssoIdentifier",
method: "GET", method: "GET",
schema: { schema: {
params: z.object({ params: z.object({
orgSlug: z.string().trim() ssoIdentifier: z.string().trim()
}), }),
querystring: z.object({ querystring: z.object({
callback_port: z.string().optional() callback_port: z.string().optional()
@ -146,37 +134,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
}); });
server.route({ server.route({
url: "/redirect/saml2/:samlConfigId", url: "/saml2/:ssoIdentifier",
method: "GET",
schema: {
params: z.object({
samlConfigId: z.string().trim()
}),
querystring: z.object({
callback_port: z.string().optional()
})
},
preValidation: (req, res) =>
(
passport.authenticate("saml", {
failureRedirect: "/",
additionalParams: {
RelayState: JSON.stringify({
spInitiated: true,
callbackPort: req.query.callback_port ?? ""
})
}
} as any) as any
)(req, res),
handler: () => {}
});
server.route({
url: "/saml2/:samlConfigId",
method: "POST", method: "POST",
schema: { schema: {
params: z.object({ params: z.object({
samlConfigId: z.string().trim() ssoIdentifier: z.string().trim()
}) })
}, },
preValidation: passport.authenticate("saml", { preValidation: passport.authenticate("saml", {
@ -215,8 +177,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
isActive: z.boolean(), isActive: z.boolean(),
entryPoint: z.string(), entryPoint: z.string(),
issuer: z.string(), issuer: z.string(),
cert: z.string(), cert: z.string()
lastUsed: z.date().nullable().optional()
}) })
.optional() .optional()
} }
@ -225,7 +186,6 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
const saml = await server.services.saml.getSaml({ const saml = await server.services.saml.getSaml({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
orgId: req.query.organizationId, orgId: req.query.organizationId,
type: "org" type: "org"
}); });
@ -254,7 +214,6 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
const saml = await server.services.saml.createSamlCfg({ const saml = await server.services.saml.createSamlCfg({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId, orgId: req.body.organizationId,
...req.body ...req.body
}); });
@ -285,7 +244,6 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
const saml = await server.services.saml.updateSamlCfg({ const saml = await server.services.saml.updateSamlCfg({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId, orgId: req.body.organizationId,
...req.body ...req.body
}); });

View File

@ -1,331 +0,0 @@
import { z } from "zod";
import { ScimTokensSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerScimRouter = async (server: FastifyZodProvider) => {
server.addContentTypeParser("application/scim+json", { parseAs: "string" }, (_, body, done) => {
try {
const strBody = body instanceof Buffer ? body.toString() : body;
const json: unknown = JSON.parse(strBody);
done(null, json);
} catch (err) {
const error = err as Error;
done(error, undefined);
}
});
server.route({
url: "/scim-tokens",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
organizationId: z.string().trim(),
description: z.string().trim().default(""),
ttlDays: z.number().min(0).default(0)
}),
response: {
200: z.object({
scimToken: z.string().trim()
})
}
},
handler: async (req) => {
const { scimToken } = await server.services.scim.createScimToken({
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId,
description: req.body.description,
ttlDays: req.body.ttlDays
});
return { scimToken };
}
});
server.route({
url: "/scim-tokens",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({
organizationId: z.string().trim()
}),
response: {
200: z.object({
scimTokens: z.array(ScimTokensSchema)
})
}
},
handler: async (req) => {
const scimTokens = await server.services.scim.listScimTokens({
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
orgId: req.query.organizationId
});
return { scimTokens };
}
});
server.route({
url: "/scim-tokens/:scimTokenId",
method: "DELETE",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
scimTokenId: z.string().trim()
}),
response: {
200: z.object({
scimToken: ScimTokensSchema
})
}
},
handler: async (req) => {
const scimToken = await server.services.scim.deleteScimToken({
scimTokenId: req.params.scimTokenId,
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId
});
return { scimToken };
}
});
// SCIM server endpoints
server.route({
url: "/Users",
method: "GET",
schema: {
querystring: z.object({
startIndex: z.coerce.number().default(1),
count: z.coerce.number().default(20),
filter: z.string().trim().optional()
}),
response: {
200: z.object({
Resources: z.array(
z.object({
id: z.string().trim(),
userName: z.string().trim(),
name: z.object({
familyName: z.string().trim(),
givenName: z.string().trim()
}),
emails: z.array(
z.object({
primary: z.boolean(),
value: z.string().email(),
type: z.string().trim()
})
),
displayName: z.string().trim(),
active: z.boolean()
})
),
itemsPerPage: z.number(),
schemas: z.array(z.string()),
startIndex: z.number(),
totalResults: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const users = await req.server.services.scim.listScimUsers({
offset: req.query.startIndex,
limit: req.query.count,
filter: req.query.filter,
orgId: req.permission.orgId as string
});
return users;
}
});
server.route({
url: "/Users/:userId",
method: "GET",
schema: {
params: z.object({
userId: z.string().trim()
}),
response: {
201: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
userName: z.string().trim(),
name: z.object({
familyName: z.string().trim(),
givenName: z.string().trim()
}),
emails: z.array(
z.object({
primary: z.boolean(),
value: z.string().email(),
type: z.string().trim()
})
),
displayName: z.string().trim(),
active: z.boolean()
})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const user = await req.server.services.scim.getScimUser({
userId: req.params.userId,
orgId: req.permission.orgId as string
});
return user;
}
});
server.route({
url: "/Users",
method: "POST",
schema: {
body: z.object({
schemas: z.array(z.string()),
userName: z.string().trim().email(),
name: z.object({
familyName: z.string().trim(),
givenName: z.string().trim()
}),
// emails: z.array( // optional?
// z.object({
// primary: z.boolean(),
// value: z.string().email(),
// type: z.string().trim()
// })
// ),
// displayName: z.string().trim(),
active: z.boolean()
}),
response: {
200: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
userName: z.string().trim().email(),
name: z.object({
familyName: z.string().trim(),
givenName: z.string().trim()
}),
emails: z.array(
z.object({
primary: z.boolean(),
value: z.string().email(),
type: z.string().trim()
})
),
displayName: z.string().trim(),
active: z.boolean()
})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const user = await req.server.services.scim.createScimUser({
email: req.body.userName,
firstName: req.body.name.givenName,
lastName: req.body.name.familyName,
orgId: req.permission.orgId as string
});
return user;
}
});
server.route({
url: "/Users/:userId",
method: "PATCH",
schema: {
params: z.object({
userId: z.string().trim()
}),
body: z.object({
schemas: z.array(z.string()),
Operations: z.array(
z.object({
op: z.string().trim(),
path: z.string().trim().optional(),
value: z.union([
z.object({
active: z.boolean()
}),
z.string().trim()
])
})
)
}),
response: {
200: z.object({})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const user = await req.server.services.scim.updateScimUser({
userId: req.params.userId,
orgId: req.permission.orgId as string,
operations: req.body.Operations
});
return user;
}
});
server.route({
url: "/Users/:userId",
method: "PUT",
schema: {
params: z.object({
userId: z.string().trim()
}),
body: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
userName: z.string().trim(),
name: z.object({
familyName: z.string().trim(),
givenName: z.string().trim()
}),
displayName: z.string().trim(),
active: z.boolean()
}),
response: {
200: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
userName: z.string().trim(),
name: z.object({
familyName: z.string().trim(),
givenName: z.string().trim()
}),
emails: z.array(
z.object({
primary: z.boolean(),
value: z.string().email(),
type: z.string().trim()
})
),
displayName: z.string().trim(),
active: z.boolean()
})
}
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const user = await req.server.services.scim.replaceScimUser({
userId: req.params.userId,
orgId: req.permission.orgId as string,
active: req.body.active
});
return user;
}
});
};

View File

@ -34,7 +34,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({ const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.body.workspaceId, projectId: req.body.workspaceId,
...req.body, ...req.body,
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}` name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`
@ -72,7 +71,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({ const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
...req.body, ...req.body,
secretPolicyId: req.params.sapId secretPolicyId: req.params.sapId
}); });
@ -98,7 +96,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({ const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
secretPolicyId: req.params.sapId secretPolicyId: req.params.sapId
}); });
return { approval }; return { approval };
@ -123,7 +120,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
const approvals = await server.services.secretApprovalPolicy.getSecretApprovalPolicyByProjectId({ const approvals = await server.services.secretApprovalPolicy.getSecretApprovalPolicyByProjectId({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId projectId: req.query.workspaceId
}); });
return { approvals }; return { approvals };
@ -150,7 +146,6 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({ const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId, projectId: req.query.workspaceId,
...req.query ...req.query
}); });

View File

@ -52,7 +52,6 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const approvals = await server.services.secretApprovalRequest.getSecretApprovals({ const approvals = await server.services.secretApprovalRequest.getSecretApprovals({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
...req.query, ...req.query,
projectId: req.query.workspaceId projectId: req.query.workspaceId
}); });
@ -81,7 +80,6 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const approvals = await server.services.secretApprovalRequest.requestCount({ const approvals = await server.services.secretApprovalRequest.requestCount({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId projectId: req.query.workspaceId
}); });
return { approvals }; return { approvals };
@ -106,7 +104,6 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const { approval } = await server.services.secretApprovalRequest.mergeSecretApprovalRequest({ const { approval } = await server.services.secretApprovalRequest.mergeSecretApprovalRequest({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
approvalId: req.params.id approvalId: req.params.id
}); });
return { approval }; return { approval };
@ -134,7 +131,6 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const review = await server.services.secretApprovalRequest.reviewApproval({ const review = await server.services.secretApprovalRequest.reviewApproval({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
approvalId: req.params.id, approvalId: req.params.id,
status: req.body.status status: req.body.status
}); });
@ -163,7 +159,6 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const approval = await server.services.secretApprovalRequest.updateApprovalStatus({ const approval = await server.services.secretApprovalRequest.updateApprovalStatus({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId,
approvalId: req.params.id, approvalId: req.params.id,
status: req.body.status status: req.body.status
}); });
@ -271,7 +266,6 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const approval = await server.services.secretApprovalRequest.getSecretApprovalDetails({ const approval = await server.services.secretApprovalRequest.getSecretApprovalDetails({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
id: req.params.id id: req.params.id
}); });
return { approval }; return { approval };

View File

@ -30,7 +30,6 @@ export const registerSecretRotationProviderRouter = async (server: FastifyZodPro
const providers = await server.services.secretRotation.getProviderTemplates({ const providers = await server.services.secretRotation.getProviderTemplates({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId projectId: req.params.workspaceId
}); });
return providers; return providers;

View File

@ -40,7 +40,6 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
const secretRotation = await server.services.secretRotation.createRotation({ const secretRotation = await server.services.secretRotation.createRotation({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
...req.body, ...req.body,
projectId: req.body.workspaceId projectId: req.body.workspaceId
}); });
@ -74,7 +73,6 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
const secretRotation = await server.services.secretRotation.restartById({ const secretRotation = await server.services.secretRotation.restartById({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
rotationId: req.body.id rotationId: req.body.id
}); });
return { secretRotation }; return { secretRotation };
@ -125,7 +123,6 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
const secretRotations = await server.services.secretRotation.getByProjectId({ const secretRotations = await server.services.secretRotation.getByProjectId({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId projectId: req.query.workspaceId
}); });
return { secretRotations }; return { secretRotations };
@ -158,7 +155,6 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
const secretRotation = await server.services.secretRotation.deleteById({ const secretRotation = await server.services.secretRotation.deleteById({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
rotationId: req.params.id rotationId: req.params.id
}); });
return { secretRotation }; return { secretRotation };

View File

@ -22,7 +22,6 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
const session = await server.services.secretScanning.createInstallationSession({ const session = await server.services.secretScanning.createInstallationSession({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId orgId: req.body.organizationId
}); });
return session; return session;
@ -46,7 +45,6 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
const { installatedApp } = await server.services.secretScanning.linkInstallationToOrg({ const { installatedApp } = await server.services.secretScanning.linkInstallationToOrg({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
...req.body ...req.body
}); });
return installatedApp; return installatedApp;
@ -67,7 +65,6 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
const appInstallationCompleted = await server.services.secretScanning.getOrgInstallationStatus({ const appInstallationCompleted = await server.services.secretScanning.getOrgInstallationStatus({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return { appInstallationCompleted }; return { appInstallationCompleted };
@ -88,7 +85,6 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
const { risks } = await server.services.secretScanning.getRisksByOrg({ const { risks } = await server.services.secretScanning.getRisksByOrg({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId orgId: req.params.organizationId
}); });
return { risks }; return { risks };
@ -110,7 +106,6 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
const { risk } = await server.services.secretScanning.updateRiskStatus({ const { risk } = await server.services.secretScanning.updateRiskStatus({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId, orgId: req.params.organizationId,
riskId: req.params.riskId, riskId: req.params.riskId,
...req.body ...req.body

View File

@ -27,7 +27,6 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
const secretVersions = await server.services.secret.getSecretVersions({ const secretVersions = await server.services.secret.getSecretVersions({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
limit: req.query.limit, limit: req.query.limit,
offset: req.query.offset, offset: req.query.offset,
secretId: req.params.secretId secretId: req.params.secretId

View File

@ -46,7 +46,6 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
const secretSnapshot = await server.services.snapshot.getSnapshotData({ const secretSnapshot = await server.services.snapshot.getSnapshotData({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
id: req.params.secretSnapshotId id: req.params.secretSnapshotId
}); });
return { secretSnapshot }; return { secretSnapshot };
@ -57,13 +56,6 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
method: "POST", method: "POST",
url: "/:secretSnapshotId/rollback", url: "/:secretSnapshotId/rollback",
schema: { schema: {
description: "Roll back project secrets to those captured in a secret snapshot version.",
security: [
{
apiKeyAuth: [],
bearerAuth: []
}
],
params: z.object({ params: z.object({
secretSnapshotId: z.string().trim() secretSnapshotId: z.string().trim()
}), }),
@ -78,7 +70,6 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
const secretSnapshot = await server.services.snapshot.rollbackSnapshot({ const secretSnapshot = await server.services.snapshot.rollbackSnapshot({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
id: req.params.secretSnapshotId id: req.params.secretSnapshotId
}); });
return { secretSnapshot }; return { secretSnapshot };

View File

@ -24,8 +24,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
const trustedIps = await server.services.trustedIp.listIpsByProjectId({ const trustedIps = await server.services.trustedIp.listIpsByProjectId({
projectId: req.params.workspaceId, projectId: req.params.workspaceId,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id
actorOrgId: req.permission.orgId
}); });
return { trustedIps }; return { trustedIps };
} }
@ -55,7 +54,6 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId, projectId: req.params.workspaceId,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
...req.body ...req.body
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
@ -99,7 +97,6 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId, projectId: req.params.workspaceId,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
trustedIpId: req.params.trustedIpId, trustedIpId: req.params.trustedIpId,
...req.body ...req.body
}); });
@ -140,7 +137,6 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId, projectId: req.params.workspaceId,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorOrgId: req.permission.orgId,
trustedIpId: req.params.trustedIpId trustedIpId: req.params.trustedIpId
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({

View File

@ -30,11 +30,10 @@ export const auditLogServiceFactory = ({
startDate, startDate,
actor, actor,
actorId, actorId,
actorOrgId,
projectId, projectId,
auditLogActor auditLogActor
}: TListProjectAuditLogDTO) => { }: TListProjectAuditLogDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
const auditLogs = await auditLogDAL.find({ const auditLogs = await auditLogDAL.find({
startDate, startDate,
@ -58,7 +57,6 @@ export const auditLogServiceFactory = ({
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) { if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
if (!data.projectId && !data.orgId) throw new BadRequestError({ message: "Must either project id or org id" }); if (!data.projectId && !data.orgId) throw new BadRequestError({ message: "Must either project id or org id" });
} }
return auditLogQueue.pushToLog(data); return auditLogQueue.pushToLog(data);
}; };

View File

@ -15,7 +15,7 @@ export type TListProjectAuditLogDTO = {
export type TCreateAuditLogDTO = { export type TCreateAuditLogDTO = {
event: Event; event: Event;
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor; actor: UserActor | IdentityActor | ServiceActor;
orgId?: string; orgId?: string;
projectId?: string; projectId?: string;
} & BaseAuthData; } & BaseAuthData;
@ -105,8 +105,6 @@ interface IdentityActorMetadata {
name: string; name: string;
} }
interface ScimClientActorMetadata {}
export interface UserActor { export interface UserActor {
type: ActorType.USER; type: ActorType.USER;
metadata: UserActorMetadata; metadata: UserActorMetadata;
@ -122,12 +120,7 @@ export interface IdentityActor {
metadata: IdentityActorMetadata; metadata: IdentityActorMetadata;
} }
export interface ScimClientActor { export type Actor = UserActor | ServiceActor | IdentityActor;
type: ActorType.SCIM_CLIENT;
metadata: ScimClientActorMetadata;
}
export type Actor = UserActor | ServiceActor | IdentityActor | ScimClientActor;
interface GetSecretsEvent { interface GetSecretsEvent {
type: EventType.GET_SECRETS; type: EventType.GET_SECRETS;

View File

@ -1,27 +0,0 @@
export const getDefaultOnPremFeatures = () => {
return {
_id: null,
slug: null,
tier: -1,
workspaceLimit: null,
workspacesUsed: 0,
memberLimit: null,
membersUsed: 0,
environmentLimit: null,
environmentsUsed: 0,
secretVersioning: true,
pitRecovery: false,
ipAllowlisting: true,
rbac: false,
customRateLimits: false,
customAlerts: false,
auditLogs: false,
auditLogsRetentionDays: 0,
samlSSO: false,
status: null,
trial_end: null,
has_used_trial: true,
secretApproval: false,
secretRotation: true
};
};

View File

@ -24,7 +24,6 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
auditLogs: false, auditLogs: false,
auditLogsRetentionDays: 0, auditLogsRetentionDays: 0,
samlSSO: false, samlSSO: false,
scim: false,
status: null, status: null,
trial_end: null, trial_end: null,
has_used_trial: true, has_used_trial: true,

View File

@ -44,7 +44,7 @@ type TLicenseServiceFactoryDep = {
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>; export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
const LICENSE_SERVER_CLOUD_LOGIN = "/api/auth/v1/license-server-login"; const LICENSE_SERVER_CLOUD_LOGIN = "/api/auth/v1/license-server-login";
const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/license-login"; const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/licence-login";
const FEATURE_CACHE_KEY = (orgId: string, projectId?: string) => `${orgId}-${projectId || ""}`; const FEATURE_CACHE_KEY = (orgId: string, projectId?: string) => `${orgId}-${projectId || ""}`;
export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: TLicenseServiceFactoryDep) => { export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: TLicenseServiceFactoryDep) => {
@ -92,7 +92,7 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
// else it would reach catch statement // else it would reach catch statement
isValidLicense = true; isValidLicense = true;
} catch (error) { } catch (error) {
logger.error(error, `init-license: encountered an error when init license`); logger.error(`init-license: encountered an error when init license [error]`, error);
} }
}; };
@ -175,14 +175,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
}; };
// below all are api calls // below all are api calls
const getOrgPlansTableByBillCycle = async ({ const getOrgPlansTableByBillCycle = async ({ orgId, actor, actorId, billingCycle }: TOrgPlansTableDTO) => {
orgId, const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
actor,
actorId,
actorOrgId,
billingCycle
}: TOrgPlansTableDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const { data } = await licenseServerCloudApi.request.get( const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` `/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
@ -190,15 +184,15 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return data; return data;
}; };
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, projectId }: TOrgPlanDTO) => { const getOrgPlan = async ({ orgId, actor, actorId, projectId }: TOrgPlanDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const plan = await getPlan(orgId, projectId); const plan = await getPlan(orgId, projectId);
return plan; return plan;
}; };
const startOrgTrial = async ({ orgId, actorId, actor, actorOrgId, success_url }: TStartOrgTrialDTO) => { const startOrgTrial = async ({ orgId, actorId, actor, success_url }: TStartOrgTrialDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
@ -219,8 +213,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return { url }; return { url };
}; };
const createOrganizationPortalSession = async ({ orgId, actorId, actor, actorOrgId }: TCreateOrgPortalSession) => { const createOrganizationPortalSession = async ({ orgId, actorId, actor }: TCreateOrgPortalSession) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
@ -266,8 +260,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return { url }; return { url };
}; };
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgBillingInfo = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -283,8 +277,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
}; };
// returns org current plan feature table // returns org current plan feature table
const getOrgPlanTable = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgPlanTable = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -299,8 +293,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return data; return data;
}; };
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgBillingDetails = async ({ orgId, actor, actorId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -316,15 +310,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return data; return data;
}; };
const updateOrgBillingDetails = async ({ const updateOrgBillingDetails = async ({ actorId, actor, orgId, name, email }: TUpdateOrgBillingDetailsDTO) => {
actorId, const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
actor,
actorOrgId,
orgId,
name,
email
}: TUpdateOrgBillingDetailsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -343,8 +330,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return data; return data;
}; };
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorOrgId }: TOrgPmtMethodsDTO) => { const getOrgPmtMethods = async ({ orgId, actor, actorId }: TOrgPmtMethodsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -362,15 +349,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return pmtMethods; return pmtMethods;
}; };
const addOrgPmtMethods = async ({ const addOrgPmtMethods = async ({ orgId, actor, actorId, success_url, cancel_url }: TAddOrgPmtMethodDTO) => {
orgId, const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
actor,
actorId,
actorOrgId,
success_url,
cancel_url
}: TAddOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -391,8 +371,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return { url }; return { url };
}; };
const delOrgPmtMethods = async ({ actorId, actor, actorOrgId, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => { const delOrgPmtMethods = async ({ actorId, actor, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -408,8 +388,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return data; return data;
}; };
const getOrgTaxIds = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgTaxIdDTO) => { const getOrgTaxIds = async ({ orgId, actor, actorId }: TGetOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -426,8 +406,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return taxIds; return taxIds;
}; };
const addOrgTaxId = async ({ actorId, actor, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => { const addOrgTaxId = async ({ actorId, actor, orgId, type, value }: TAddOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -447,8 +427,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return data; return data;
}; };
const delOrgTaxId = async ({ orgId, actor, actorId, actorOrgId, taxId }: TDelOrgTaxIdDTO) => { const delOrgTaxId = async ({ orgId, actor, actorId, taxId }: TDelOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -464,8 +444,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return data; return data;
}; };
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, orgId }: TOrgInvoiceDTO) => { const getOrgTaxInvoices = async ({ actorId, actor, orgId }: TOrgInvoiceDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -481,8 +461,8 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
return invoices; return invoices;
}; };
const getOrgLicenses = async ({ orgId, actor, actorId, actorOrgId }: TOrgLicensesDTO) => { const getOrgLicenses = async ({ orgId, actor, actorId }: TOrgLicensesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);

View File

@ -25,7 +25,6 @@ export type TFeatureSet = {
auditLogs: false; auditLogs: false;
auditLogsRetentionDays: 0; auditLogsRetentionDays: 0;
samlSSO: false; samlSSO: false;
scim: false;
status: null; status: null;
trial_end: null; trial_end: null;
has_used_trial: true; has_used_trial: true;

View File

@ -16,7 +16,6 @@ export enum OrgPermissionSubjects {
Settings = "settings", Settings = "settings",
IncidentAccount = "incident-contact", IncidentAccount = "incident-contact",
Sso = "sso", Sso = "sso",
Scim = "scim",
Billing = "billing", Billing = "billing",
SecretScanning = "secret-scanning", SecretScanning = "secret-scanning",
Identity = "identity" Identity = "identity"
@ -30,7 +29,6 @@ export type OrgPermissionSet =
| [OrgPermissionActions, OrgPermissionSubjects.Settings] | [OrgPermissionActions, OrgPermissionSubjects.Settings]
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount] | [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
| [OrgPermissionActions, OrgPermissionSubjects.Sso] | [OrgPermissionActions, OrgPermissionSubjects.Sso]
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning] | [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
| [OrgPermissionActions, OrgPermissionSubjects.Billing] | [OrgPermissionActions, OrgPermissionSubjects.Billing]
| [OrgPermissionActions, OrgPermissionSubjects.Identity]; | [OrgPermissionActions, OrgPermissionSubjects.Identity];
@ -71,11 +69,6 @@ const buildAdminPermission = () => {
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); can(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Sso); can(OrgPermissionActions.Delete, OrgPermissionSubjects.Sso);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Scim);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing); can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing); can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing); can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);

View File

@ -10,10 +10,8 @@ export const permissionDALFactory = (db: TDbClient) => {
try { try {
const membership = await db(TableName.OrgMembership) const membership = await db(TableName.OrgMembership)
.leftJoin(TableName.OrgRoles, `${TableName.OrgMembership}.roleId`, `${TableName.OrgRoles}.id`) .leftJoin(TableName.OrgRoles, `${TableName.OrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
.join(TableName.Organization, `${TableName.OrgMembership}.orgId`, `${TableName.Organization}.id`)
.where("userId", userId) .where("userId", userId)
.where(`${TableName.OrgMembership}.orgId`, orgId) .where(`${TableName.OrgMembership}.orgId`, orgId)
.select(db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"))
.select("permissions") .select("permissions")
.select(selectAllTableCols(TableName.OrgMembership)) .select(selectAllTableCols(TableName.OrgMembership))
.first(); .first();
@ -28,11 +26,9 @@ export const permissionDALFactory = (db: TDbClient) => {
try { try {
const membership = await db(TableName.IdentityOrgMembership) const membership = await db(TableName.IdentityOrgMembership)
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`) .leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
.join(TableName.Organization, `${TableName.IdentityOrgMembership}.orgId`, `${TableName.Organization}.id`)
.where("identityId", identityId) .where("identityId", identityId)
.where(`${TableName.IdentityOrgMembership}.orgId`, orgId) .where(`${TableName.IdentityOrgMembership}.orgId`, orgId)
.select(selectAllTableCols(TableName.IdentityOrgMembership)) .select(selectAllTableCols(TableName.IdentityOrgMembership))
.select(db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"))
.select("permissions") .select("permissions")
.first(); .first();
return membership; return membership;
@ -45,15 +41,9 @@ export const permissionDALFactory = (db: TDbClient) => {
try { try {
const membership = await db(TableName.ProjectMembership) const membership = await db(TableName.ProjectMembership)
.leftJoin(TableName.ProjectRoles, `${TableName.ProjectMembership}.roleId`, `${TableName.ProjectRoles}.id`) .leftJoin(TableName.ProjectRoles, `${TableName.ProjectMembership}.roleId`, `${TableName.ProjectRoles}.id`)
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
.where("userId", userId) .where("userId", userId)
.where(`${TableName.ProjectMembership}.projectId`, projectId) .where(`${TableName.ProjectMembership}.projectId`, projectId)
.select(selectAllTableCols(TableName.ProjectMembership)) .select(selectAllTableCols(TableName.ProjectMembership))
.select(
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("orgId").withSchema(TableName.Project)
)
.select("permissions") .select("permissions")
.first(); .first();

View File

@ -94,15 +94,12 @@ export const permissionServiceFactory = ({
/* /*
* Get user permission in an organization * Get user permission in an organization
* */ * */
const getUserOrgPermission = async (userId: string, orgId: string, userOrgId?: string) => { const getUserOrgPermission = async (userId: string, orgId: string) => {
const membership = await permissionDAL.getOrgPermission(userId, orgId); const membership = await permissionDAL.getOrgPermission(userId, orgId);
if (!membership) throw new UnauthorizedError({ name: "User not in org" }); if (!membership) throw new UnauthorizedError({ name: "User not in org" });
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) { if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {
throw new BadRequestError({ name: "Custom permission not found" }); throw new BadRequestError({ name: "Custom permission not found" });
} }
if (membership.orgAuthEnforced && membership.orgId !== userOrgId) {
throw new BadRequestError({ name: "Cannot access org-scoped resource" });
}
return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; return { permission: buildOrgPermission(membership.role, membership.permissions), membership };
}; };
@ -115,10 +112,10 @@ export const permissionServiceFactory = ({
return { permission: buildOrgPermission(membership.role, membership.permissions), membership }; return { permission: buildOrgPermission(membership.role, membership.permissions), membership };
}; };
const getOrgPermission = async (type: ActorType, id: string, orgId: string, actorOrgId?: string) => { const getOrgPermission = async (type: ActorType, id: string, orgId: string) => {
switch (type) { switch (type) {
case ActorType.USER: case ActorType.USER:
return getUserOrgPermission(id, orgId, actorOrgId); return getUserOrgPermission(id, orgId);
case ActorType.IDENTITY: case ActorType.IDENTITY:
return getIdentityOrgPermission(id, orgId); return getIdentityOrgPermission(id, orgId);
default: default:
@ -145,17 +142,12 @@ 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, userOrgId?: 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 project" }); 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" });
} }
if (membership.orgAuthEnforced && membership.orgId !== userOrgId) {
throw new BadRequestError({ name: "Cannot access org-scoped resource" });
}
return { return {
permission: buildProjectPermission(membership.role, membership.permissions), permission: buildProjectPermission(membership.role, membership.permissions),
membership membership
@ -168,7 +160,6 @@ export const permissionServiceFactory = ({
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" });
} }
return { return {
permission: buildProjectPermission(membership.role, membership.permissions), permission: buildProjectPermission(membership.role, membership.permissions),
membership membership
@ -177,8 +168,6 @@ export const permissionServiceFactory = ({
const getServiceTokenProjectPermission = async (serviceTokenId: string, projectId: string) => { const getServiceTokenProjectPermission = async (serviceTokenId: string, projectId: string) => {
const serviceToken = await serviceTokenDAL.findById(serviceTokenId); const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
if (!serviceToken) throw new BadRequestError({ message: "Service token not found" });
if (serviceToken.projectId !== projectId) if (serviceToken.projectId !== projectId)
throw new UnauthorizedError({ throw new UnauthorizedError({
message: "Failed to find service authorization for given project" message: "Failed to find service authorization for given project"
@ -195,8 +184,6 @@ export const permissionServiceFactory = ({
: { : {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>; permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & { membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & {
orgAuthEnforced: boolean;
orgId: string;
permissions?: unknown; permissions?: unknown;
}; };
}; };
@ -204,12 +191,11 @@ export const permissionServiceFactory = ({
const getProjectPermission = async <T extends ActorType>( const getProjectPermission = async <T extends ActorType>(
type: T, type: T,
id: string, id: string,
projectId: string, projectId: string
actorOrgId?: string
): Promise<TProjectPermissionRT<T>> => { ): Promise<TProjectPermissionRT<T>> => {
switch (type) { switch (type) {
case ActorType.USER: case ActorType.USER:
return getUserProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>; return getUserProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>;
case ActorType.SERVICE: case ActorType.SERVICE:
return getServiceTokenProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>; return getServiceTokenProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>;
case ActorType.IDENTITY: case ActorType.IDENTITY:

View File

@ -1,31 +1,10 @@
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas"; import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex"; import { ormify } from "@app/lib/knex";
export type TSamlConfigDALFactory = ReturnType<typeof samlConfigDALFactory>; export type TSamlConfigDALFactory = ReturnType<typeof samlConfigDALFactory>;
export const samlConfigDALFactory = (db: TDbClient) => { export const samlConfigDALFactory = (db: TDbClient) => {
const samlCfgOrm = ormify(db, TableName.SamlConfig); const samlCfgOrm = ormify(db, TableName.SamlConfig);
return samlCfgOrm;
const findEnforceableSamlCfg = async (orgId: string) => {
try {
const samlCfg = await db(TableName.SamlConfig)
.where({
orgId,
isActive: true
})
.whereNotNull("lastUsed")
.first();
return samlCfg;
} catch (error) {
throw new DatabaseError({ error, name: "Find org by id" });
}
};
return {
...samlCfgOrm,
findEnforceableSamlCfg
};
}; };

View File

@ -18,7 +18,7 @@ import {
infisicalSymmetricEncypt infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption"; } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { AuthTokenType } from "@app/services/auth/auth-type";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
@ -27,15 +27,18 @@ import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
import { TSamlConfigDALFactory } from "./saml-config-dal"; import { TSamlConfigDALFactory } from "./saml-config-dal";
import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } from "./saml-config-types"; import {
SamlProviders,
TCreateSamlCfgDTO,
TGetSamlCfgDTO,
TSamlLoginDTO,
TUpdateSamlCfgDTO
} from "./saml-config-types";
type TSamlConfigServiceFactoryDep = { type TSamlConfigServiceFactoryDep = {
samlConfigDAL: TSamlConfigDALFactory; samlConfigDAL: TSamlConfigDALFactory;
userDAL: Pick<TUserDALFactory, "create" | "findUserByEmail" | "transaction" | "updateById">; userDAL: Pick<TUserDALFactory, "create" | "findUserByEmail" | "transaction" | "updateById">;
orgDAL: Pick< orgDAL: Pick<TOrgDALFactory, "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById">;
TOrgDALFactory,
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
>;
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">; orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@ -54,7 +57,6 @@ export const samlConfigServiceFactory = ({
const createSamlCfg = async ({ const createSamlCfg = async ({
cert, cert,
actor, actor,
actorOrgId,
orgId, orgId,
issuer, issuer,
actorId, actorId,
@ -62,7 +64,7 @@ export const samlConfigServiceFactory = ({
entryPoint, entryPoint,
authProvider authProvider
}: TCreateSamlCfgDTO) => { }: TCreateSamlCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@ -138,14 +140,12 @@ export const samlConfigServiceFactory = ({
certIV, certIV,
certTag certTag
}); });
return samlConfig; return samlConfig;
}; };
const updateSamlCfg = async ({ const updateSamlCfg = async ({
orgId, orgId,
actor, actor,
actorOrgId,
cert, cert,
actorId, actorId,
issuer, issuer,
@ -153,7 +153,7 @@ export const samlConfigServiceFactory = ({
entryPoint, entryPoint,
authProvider authProvider
}: TUpdateSamlCfgDTO) => { }: TUpdateSamlCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
if (!plan.samlSSO) if (!plan.samlSSO)
@ -162,7 +162,7 @@ export const samlConfigServiceFactory = ({
"Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration." "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
}); });
const updateQuery: TSamlConfigsUpdate = { authProvider, isActive, lastUsed: null }; const updateQuery: TSamlConfigsUpdate = { authProvider, isActive };
const orgBot = await orgBotDAL.findOne({ orgId }); const orgBot = await orgBotDAL.findOne({ orgId });
if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" }); if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
const key = infisicalSymmetricDecrypt({ const key = infisicalSymmetricDecrypt({
@ -195,8 +195,6 @@ export const samlConfigServiceFactory = ({
updateQuery.certTag = certTag; updateQuery.certTag = certTag;
} }
const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery); const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery);
await orgDAL.updateById(orgId, { authEnforced: false, scimEnabled: false });
return ssoConfig; return ssoConfig;
}; };
@ -205,10 +203,6 @@ export const samlConfigServiceFactory = ({
if (dto.type === "org") { if (dto.type === "org") {
ssoConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); ssoConfig = await samlConfigDAL.findOne({ orgId: dto.orgId });
if (!ssoConfig) return; if (!ssoConfig) return;
} else if (dto.type === "orgSlug") {
const org = await orgDAL.findOne({ slug: dto.orgSlug });
if (!org) return;
ssoConfig = await samlConfigDAL.findOne({ orgId: org.id });
} else if (dto.type === "ssoId") { } else if (dto.type === "ssoId") {
// TODO: // TODO:
// We made this change because saml config ids were not moved over during the migration // We made this change because saml config ids were not moved over during the migration
@ -233,12 +227,7 @@ export const samlConfigServiceFactory = ({
// when dto is type id means it's internally used // when dto is type id means it's internally used
if (dto.type === "org") { if (dto.type === "org") {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission(dto.actor, dto.actorId, ssoConfig.orgId);
dto.actor,
dto.actorId,
ssoConfig.orgId,
dto.actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
} }
const { const {
@ -295,20 +284,35 @@ export const samlConfigServiceFactory = ({
isActive: ssoConfig.isActive, isActive: ssoConfig.isActive,
entryPoint, entryPoint,
issuer, issuer,
cert, cert
lastUsed: ssoConfig.lastUsed
}; };
}; };
const samlLogin = async ({ firstName, email, lastName, authProvider, orgId, relayState }: TSamlLoginDTO) => { const samlLogin = async ({
firstName,
email,
lastName,
authProvider,
orgId,
relayState,
isSignupAllowed
}: TSamlLoginDTO) => {
const appCfg = getConfig(); const appCfg = getConfig();
let user = await userDAL.findUserByEmail(email); let user = await userDAL.findUserByEmail(email);
const isSamlSignUpDisabled = !isSignupAllowed && !user;
if (isSamlSignUpDisabled) throw new BadRequestError({ message: "User signup disabled", name: "Saml SSO login" });
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) throw new BadRequestError({ message: "Org not found" }); if (!organization) throw new BadRequestError({ message: "Org not found" });
if (user) { if (user) {
const hasSamlEnabled = (user.authMethods || []).some((method) =>
Object.values(SamlProviders).includes(method as SamlProviders)
);
await userDAL.transaction(async (tx) => { await userDAL.transaction(async (tx) => {
if (!hasSamlEnabled) {
await userDAL.updateById(user.id, { authMethods: [authProvider] }, tx);
}
const [orgMembership] = await orgDAL.findMembership({ userId: user.id, orgId }, { tx }); const [orgMembership] = await orgDAL.findMembership({ userId: user.id, orgId }, { tx });
if (!orgMembership) { if (!orgMembership) {
await orgDAL.createMembership( await orgDAL.createMembership(
@ -338,8 +342,7 @@ export const samlConfigServiceFactory = ({
email, email,
firstName, firstName,
lastName, lastName,
authMethods: [AuthMethod.EMAIL], authMethods: [authProvider]
isGhost: false
}, },
tx tx
); );
@ -375,9 +378,6 @@ export const samlConfigServiceFactory = ({
expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME
} }
); );
await samlConfigDAL.update({ orgId }, { lastUsed: new Date() });
return { isUserCompleted, providerAuthToken }; return { isUserCompleted, providerAuthToken };
}; };

View File

@ -25,11 +25,7 @@ export type TUpdateSamlCfgDTO = Partial<{
TOrgPermission; TOrgPermission;
export type TGetSamlCfgDTO = export type TGetSamlCfgDTO =
| { type: "org"; orgId: string; actor: ActorType; actorId: string; actorOrgId?: string } | { type: "org"; orgId: string; actor: ActorType; actorId: string }
| {
type: "orgSlug";
orgSlug: string;
}
| { | {
type: "ssoId"; type: "ssoId";
id: string; id: string;
@ -41,6 +37,7 @@ export type TSamlLoginDTO = {
lastName?: string; lastName?: string;
authProvider: string; authProvider: string;
orgId: string; orgId: string;
isSignupAllowed: boolean;
// saml thingy // saml thingy
relayState?: string; relayState?: string;
}; };

View File

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

View File

@ -1,58 +0,0 @@
import { TListScimUsers, TScimUser } from "./scim-types";
export const buildScimUserList = ({
scimUsers,
offset,
limit
}: {
scimUsers: TScimUser[];
offset: number;
limit: number;
}): TListScimUsers => {
return {
Resources: scimUsers,
itemsPerPage: limit,
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
startIndex: offset,
totalResults: scimUsers.length
};
};
export const buildScimUser = ({
userId,
firstName,
lastName,
email,
active
}: {
userId: string;
firstName: string;
lastName: string;
email: string;
active: boolean;
}): TScimUser => {
return {
schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
id: userId,
userName: email,
displayName: `${firstName} ${lastName}`,
name: {
givenName: firstName,
middleName: null,
familyName: lastName
},
emails: [
{
primary: true,
value: email,
type: "work"
}
],
active,
groups: [],
meta: {
resourceType: "User",
location: null
}
};
};

View File

@ -1,431 +0,0 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
import { TOrgPermission } from "@app/lib/types";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { deleteOrgMembership } from "@app/services/org/org-fns";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { buildScimUser, buildScimUserList } from "./scim-fns";
import {
TCreateScimTokenDTO,
TCreateScimUserDTO,
TDeleteScimTokenDTO,
TGetScimUserDTO,
TListScimUsers,
TListScimUsersDTO,
TReplaceScimUserDTO,
TScimTokenJwtPayload,
TUpdateScimUserDTO
} from "./scim-types";
type TScimServiceFactoryDep = {
scimDAL: Pick<TScimDALFactory, "create" | "find" | "findById" | "deleteById">;
userDAL: Pick<TUserDALFactory, "findOne" | "create" | "transaction">;
orgDAL: Pick<
TOrgDALFactory,
"createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction"
>;
projectDAL: Pick<TProjectDALFactory, "find">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
smtpService: TSmtpService;
};
export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>;
export const scimServiceFactory = ({
licenseService,
scimDAL,
userDAL,
orgDAL,
projectDAL,
projectMembershipDAL,
permissionService,
smtpService
}: TScimServiceFactoryDep) => {
const createScimToken = async ({ actor, actorId, actorOrgId, orgId, description, ttlDays }: TCreateScimTokenDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(orgId);
if (!plan.scim)
throw new BadRequestError({
message: "Failed to create a SCIM token due to plan restriction. Upgrade plan to create a SCIM token."
});
const appCfg = getConfig();
const scimTokenData = await scimDAL.create({
orgId,
description,
ttlDays
});
const scimToken = jwt.sign(
{
scimTokenId: scimTokenData.id,
authTokenType: AuthTokenType.SCIM_TOKEN
},
appCfg.AUTH_SECRET
);
return { scimToken };
};
const listScimTokens = async ({ actor, actorId, actorOrgId, orgId }: TOrgPermission) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(orgId);
if (!plan.scim)
throw new BadRequestError({
message: "Failed to get SCIM tokens due to plan restriction. Upgrade plan to get SCIM tokens."
});
const scimTokens = await scimDAL.find({ orgId });
return scimTokens;
};
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorOrgId }: TDeleteScimTokenDTO) => {
let scimToken = await scimDAL.findById(scimTokenId);
if (!scimToken) throw new BadRequestError({ message: "Failed to find SCIM token to delete" });
const { permission } = await permissionService.getOrgPermission(actor, actorId, scimToken.orgId, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(scimToken.orgId);
if (!plan.scim)
throw new BadRequestError({
message: "Failed to delete the SCIM token due to plan restriction. Upgrade plan to delete the SCIM token."
});
scimToken = await scimDAL.deleteById(scimTokenId);
return scimToken;
};
// SCIM server endpoints
const listScimUsers = async ({ offset, limit, filter, orgId }: TListScimUsersDTO): Promise<TListScimUsers> => {
const org = await orgDAL.findById(orgId);
if (!org.scimEnabled)
throw new ScimRequestError({
detail: "SCIM is disabled for the organization",
status: 403
});
const parseFilter = (filterToParse: string | undefined) => {
if (!filterToParse) return {};
const [parsedName, parsedValue] = filterToParse.split("eq").map((s) => s.trim());
let attributeName = parsedName;
if (parsedName === "userName") {
attributeName = "email";
}
return { [attributeName]: parsedValue };
};
const findOpts = {
...(offset && { offset }),
...(limit && { limit })
};
const users = await orgDAL.findMembership(
{
orgId,
...parseFilter(filter)
},
findOpts
);
const scimUsers = users.map(({ userId, firstName, lastName, email }) =>
buildScimUser({
userId: userId ?? "",
firstName: firstName ?? "",
lastName: lastName ?? "",
email,
active: true
})
);
return buildScimUserList({
scimUsers,
offset,
limit
});
};
const getScimUser = async ({ userId, orgId }: TGetScimUserDTO) => {
const [membership] = await orgDAL
.findMembership({
userId,
orgId
})
.catch(() => {
throw new ScimRequestError({
detail: "User not found",
status: 404
});
});
if (!membership)
throw new ScimRequestError({
detail: "User not found",
status: 404
});
if (!membership.scimEnabled)
throw new ScimRequestError({
detail: "SCIM is disabled for the organization",
status: 403
});
return buildScimUser({
userId: membership.userId as string,
firstName: membership.firstName as string,
lastName: membership.lastName as string,
email: membership.email,
active: true
});
};
const createScimUser = async ({ firstName, lastName, email, orgId }: TCreateScimUserDTO) => {
const org = await orgDAL.findById(orgId);
if (!org)
throw new ScimRequestError({
detail: "Organization not found",
status: 404
});
if (!org.scimEnabled)
throw new ScimRequestError({
detail: "SCIM is disabled for the organization",
status: 403
});
let user = await userDAL.findOne({
email
});
if (user) {
await userDAL.transaction(async (tx) => {
const [orgMembership] = await orgDAL.findMembership({ userId: user.id, orgId }, { tx });
if (orgMembership)
throw new ScimRequestError({
detail: "User already exists in the database",
status: 409
});
if (!orgMembership) {
await orgDAL.createMembership(
{
userId: user.id,
orgId,
inviteEmail: email,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
},
tx
);
}
});
} else {
user = await userDAL.transaction(async (tx) => {
const newUser = await userDAL.create(
{
email,
firstName,
lastName,
authMethods: [AuthMethod.EMAIL],
isGhost: false
},
tx
);
await orgDAL.createMembership(
{
inviteEmail: email,
orgId,
userId: newUser.id,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
},
tx
);
return newUser;
});
}
const appCfg = getConfig();
await smtpService.sendMail({
template: SmtpTemplates.ScimUserProvisioned,
subjectLine: "Infisical organization invitation",
recipients: [email],
substitutions: {
organizationName: org.name,
callback_url: `${appCfg.SITE_URL}/api/v1/sso/redirect/saml2/organizations/${org.slug}`
}
});
return buildScimUser({
userId: user.id,
firstName: user.firstName as string,
lastName: user.lastName as string,
email: user.email,
active: true
});
};
const updateScimUser = async ({ userId, orgId, operations }: TUpdateScimUserDTO) => {
const [membership] = await orgDAL
.findMembership({
userId,
orgId
})
.catch(() => {
throw new ScimRequestError({
detail: "User not found",
status: 404
});
});
if (!membership)
throw new ScimRequestError({
detail: "User not found",
status: 404
});
if (!membership.scimEnabled)
throw new ScimRequestError({
detail: "SCIM is disabled for the organization",
status: 403
});
let active = true;
operations.forEach((operation) => {
if (operation.op.toLowerCase() === "replace") {
if (operation.path === "active" && operation.value === "False") {
// azure scim op format
active = false;
} else if (typeof operation.value === "object" && operation.value.active === false) {
// okta scim op format
active = false;
}
}
});
if (!active) {
await deleteOrgMembership({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectDAL,
projectMembershipDAL
});
}
return buildScimUser({
userId: membership.userId as string,
firstName: membership.firstName as string,
lastName: membership.lastName as string,
email: membership.email,
active
});
};
const replaceScimUser = async ({ userId, active, orgId }: TReplaceScimUserDTO) => {
const [membership] = await orgDAL
.findMembership({
userId,
orgId
})
.catch(() => {
throw new ScimRequestError({
detail: "User not found",
status: 404
});
});
if (!membership)
throw new ScimRequestError({
detail: "User not found",
status: 404
});
if (!membership.scimEnabled)
throw new ScimRequestError({
detail: "SCIM is disabled for the organization",
status: 403
});
if (!active) {
// tx
await deleteOrgMembership({
orgMembershipId: membership.id,
orgId: membership.orgId,
orgDAL,
projectDAL,
projectMembershipDAL
});
}
return buildScimUser({
userId: membership.userId as string,
firstName: membership.firstName as string,
lastName: membership.lastName as string,
email: membership.email,
active
});
};
const fnValidateScimToken = async (token: TScimTokenJwtPayload) => {
const scimToken = await scimDAL.findById(token.scimTokenId);
if (!scimToken) throw new UnauthorizedError();
const { ttlDays, createdAt } = scimToken;
// ttl check
if (Number(ttlDays) > 0) {
const currentDate = new Date();
const scimTokenCreatedAt = new Date(createdAt);
const ttlInMilliseconds = Number(scimToken.ttlDays) * 86400 * 1000;
const expirationDate = new Date(scimTokenCreatedAt.getTime() + ttlInMilliseconds);
if (currentDate > expirationDate)
throw new ScimRequestError({
detail: "The access token expired",
status: 401
});
}
return { scimTokenId: scimToken.id, orgId: scimToken.orgId };
};
return {
createScimToken,
listScimTokens,
deleteScimToken,
listScimUsers,
getScimUser,
createScimUser,
updateScimUser,
replaceScimUser,
fnValidateScimToken
};
};

View File

@ -1,87 +0,0 @@
import { TOrgPermission } from "@app/lib/types";
export type TCreateScimTokenDTO = {
description: string;
ttlDays: number;
} & TOrgPermission;
export type TDeleteScimTokenDTO = {
scimTokenId: string;
} & Omit<TOrgPermission, "orgId">;
// SCIM server endpoint types
export type TListScimUsersDTO = {
offset: number;
limit: number;
filter?: string;
orgId: string;
};
export type TListScimUsers = {
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"];
totalResults: number;
Resources: TScimUser[];
itemsPerPage: number;
startIndex: number;
};
export type TGetScimUserDTO = {
userId: string;
orgId: string;
};
export type TCreateScimUserDTO = {
email: string;
firstName: string;
lastName: string;
orgId: string;
};
export type TUpdateScimUserDTO = {
userId: string;
orgId: string;
operations: {
op: string;
path?: string;
value?:
| string
| {
active: boolean;
};
}[];
};
export type TReplaceScimUserDTO = {
userId: string;
active: boolean;
orgId: string;
};
export type TScimTokenJwtPayload = {
scimTokenId: string;
authTokenType: string;
};
export type TScimUser = {
schemas: string[];
id: string;
userName: string;
displayName: string;
name: {
givenName: string;
middleName: null;
familyName: string;
};
emails: {
primary: boolean;
value: string;
type: string;
}[];
active: boolean;
groups: string[];
meta: {
resourceType: string;
location: null;
};
};

View File

@ -44,7 +44,6 @@ export const secretApprovalPolicyServiceFactory = ({
name, name,
actor, actor,
actorId, actorId,
actorOrgId,
approvals, approvals,
approvers, approvers,
projectId, projectId,
@ -54,7 +53,7 @@ export const secretApprovalPolicyServiceFactory = ({
if (approvals > approvers.length) if (approvals > approvers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" }); throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
@ -97,19 +96,13 @@ export const secretApprovalPolicyServiceFactory = ({
name, name,
actorId, actorId,
actor, actor,
actorOrgId,
approvals, approvals,
secretPolicyId secretPolicyId
}: TUpdateSapDTO) => { }: TUpdateSapDTO) => {
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId); const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
if (!secretApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" }); if (!secretApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
const { permission } = await permissionService.getProjectPermission( const { permission } = await permissionService.getProjectPermission(actor, actorId, secretApprovalPolicy.projectId);
actor,
actorId,
secretApprovalPolicy.projectId,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => { const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
@ -152,16 +145,11 @@ export const secretApprovalPolicyServiceFactory = ({
}; };
}; };
const deleteSecretApprovalPolicy = async ({ secretPolicyId, actor, actorId, actorOrgId }: TDeleteSapDTO) => { const deleteSecretApprovalPolicy = async ({ secretPolicyId, actor, actorId }: TDeleteSapDTO) => {
const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId); const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
if (!sapPolicy) throw new BadRequestError({ message: "Secret approval policy not found" }); if (!sapPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
const { permission } = await permissionService.getProjectPermission( const { permission } = await permissionService.getProjectPermission(actor, actorId, sapPolicy.projectId);
actor,
actorId,
sapPolicy.projectId,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
@ -171,8 +159,8 @@ export const secretApprovalPolicyServiceFactory = ({
return sapPolicy; return sapPolicy;
}; };
const getSecretApprovalPolicyByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TListSapDTO) => { const getSecretApprovalPolicyByProjectId = async ({ actorId, actor, projectId }: TListSapDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId }); const sapPolicies = await secretApprovalPolicyDAL.find({ projectId });
@ -200,11 +188,10 @@ export const secretApprovalPolicyServiceFactory = ({
projectId, projectId,
actor, actor,
actorId, actorId,
actorOrgId,
environment, environment,
secretPath secretPath
}: TGetBoardSapDTO) => { }: TGetBoardSapDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { secretPath, environment }) subject(ProjectPermissionSub.Secrets, { secretPath, environment })

View File

@ -1,13 +1,8 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { import { SecretApprovalRequestsSecretsSchema, TableName, TSecretTags } from "@app/db/schemas";
SecretApprovalRequestsSecretsSchema, import { DatabaseError } from "@app/lib/errors";
TableName,
TSecretApprovalRequestsSecrets,
TSecretTags
} from "@app/db/schemas";
import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
export type TSecretApprovalRequestSecretDALFactory = ReturnType<typeof secretApprovalRequestSecretDALFactory>; export type TSecretApprovalRequestSecretDALFactory = ReturnType<typeof secretApprovalRequestSecretDALFactory>;
@ -16,35 +11,6 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
const secretApprovalRequestSecretOrm = ormify(db, TableName.SecretApprovalRequestSecret); const secretApprovalRequestSecretOrm = ormify(db, TableName.SecretApprovalRequestSecret);
const secretApprovalRequestSecretTagOrm = ormify(db, TableName.SecretApprovalRequestSecretTag); const secretApprovalRequestSecretTagOrm = ormify(db, TableName.SecretApprovalRequestSecretTag);
const bulkUpdateNoVersionIncrement = async (data: TSecretApprovalRequestsSecrets[], tx?: Knex) => {
try {
const existingApprovalSecrets = await secretApprovalRequestSecretOrm.find(
{
$in: {
id: data.map((el) => el.id)
}
},
{ tx }
);
if (existingApprovalSecrets.length !== data.length) {
throw new BadRequestError({ message: "Some of the secret approvals do not exist" });
}
if (data.length === 0) return [];
const updatedApprovalSecrets = await (tx || db)(TableName.SecretApprovalRequestSecret)
.insert(data)
.onConflict("id") // this will cause a conflict then merge the data
.merge() // Merge the data with the existing data
.returning("*");
return updatedApprovalSecrets;
} catch (error) {
throw new DatabaseError({ error, name: "bulk update secret" });
}
};
const findByRequestId = async (requestId: string, tx?: Knex) => { const findByRequestId = async (requestId: string, tx?: Knex) => {
try { try {
const doc = await (tx || db)({ const doc = await (tx || db)({
@ -224,7 +190,6 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
return { return {
...secretApprovalRequestSecretOrm, ...secretApprovalRequestSecretOrm,
findByRequestId, findByRequestId,
bulkUpdateNoVersionIncrement,
insertApprovalSecretTags: secretApprovalRequestSecretTagOrm.insertMany insertApprovalSecretTags: secretApprovalRequestSecretTagOrm.insertMany
}; };
}; };

View File

@ -11,7 +11,6 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { groupBy, pick, unique } from "@app/lib/fn"; import { groupBy, pick, unique } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TSecretQueueFactory } from "@app/services/secret/secret-queue"; import { TSecretQueueFactory } from "@app/services/secret/secret-queue";
import { TSecretServiceFactory } from "@app/services/secret/secret-service"; import { TSecretServiceFactory } from "@app/services/secret/secret-service";
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal"; import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
@ -48,7 +47,6 @@ type TSecretApprovalRequestServiceFactoryDep = {
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">; secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">; snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany">; secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany">;
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus">;
secretService: Pick< secretService: Pick<
TSecretServiceFactory, TSecretServiceFactory,
| "fnSecretBulkInsert" | "fnSecretBulkInsert"
@ -69,22 +67,16 @@ export const secretApprovalRequestServiceFactory = ({
secretApprovalRequestReviewerDAL, secretApprovalRequestReviewerDAL,
secretApprovalRequestSecretDAL, secretApprovalRequestSecretDAL,
secretBlindIndexDAL, secretBlindIndexDAL,
projectDAL,
permissionService, permissionService,
snapshotService, snapshotService,
secretService, secretService,
secretVersionDAL, secretVersionDAL,
secretQueueService secretQueueService
}: TSecretApprovalRequestServiceFactoryDep) => { }: TSecretApprovalRequestServiceFactoryDep) => {
const requestCount = async ({ projectId, actor, actorId, actorOrgId }: TApprovalRequestCountDTO) => { const requestCount = async ({ projectId, actor, actorId }: TApprovalRequestCountDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { membership } = await permissionService.getProjectPermission( const { membership } = await permissionService.getProjectPermission(actor as ActorType.USER, actorId, projectId);
actor as ActorType.USER,
actorId,
projectId,
actorOrgId
);
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, membership.id); const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, membership.id);
return count; return count;
@ -94,7 +86,6 @@ export const secretApprovalRequestServiceFactory = ({
projectId, projectId,
actorId, actorId,
actor, actor,
actorOrgId,
status, status,
environment, environment,
committer, committer,
@ -103,7 +94,7 @@ export const secretApprovalRequestServiceFactory = ({
}: TListApprovalsDTO) => { }: TListApprovalsDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId);
const approvals = await secretApprovalRequestDAL.findByProjectId({ const approvals = await secretApprovalRequestDAL.findByProjectId({
projectId, projectId,
committer, committer,
@ -116,7 +107,7 @@ export const secretApprovalRequestServiceFactory = ({
return approvals; return approvals;
}; };
const getSecretApprovalDetails = async ({ actor, actorId, actorOrgId, id }: TSecretApprovalDetailsDTO) => { const getSecretApprovalDetails = async ({ actor, actorId, id }: TSecretApprovalDetailsDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id); const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
@ -126,8 +117,7 @@ export const secretApprovalRequestServiceFactory = ({
const { membership } = await permissionService.getProjectPermission( const { membership } = await permissionService.getProjectPermission(
actor, actor,
actorId, actorId,
secretApprovalRequest.projectId, secretApprovalRequest.projectId
actorOrgId
); );
if ( if (
membership.role !== ProjectMembershipRole.Admin && membership.role !== ProjectMembershipRole.Admin &&
@ -144,7 +134,7 @@ export const secretApprovalRequestServiceFactory = ({
return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets }; return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets };
}; };
const reviewApproval = async ({ approvalId, actor, status, actorId, actorOrgId }: TReviewRequestDTO) => { const reviewApproval = async ({ approvalId, actor, status, actorId }: TReviewRequestDTO) => {
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId); const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" }); if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" }); if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
@ -153,8 +143,7 @@ export const secretApprovalRequestServiceFactory = ({
const { membership } = await permissionService.getProjectPermission( const { membership } = await permissionService.getProjectPermission(
ActorType.USER, ActorType.USER,
actorId, actorId,
secretApprovalRequest.projectId, secretApprovalRequest.projectId
actorOrgId
); );
if ( if (
membership.role !== ProjectMembershipRole.Admin && membership.role !== ProjectMembershipRole.Admin &&
@ -186,7 +175,7 @@ export const secretApprovalRequestServiceFactory = ({
return reviewStatus; return reviewStatus;
}; };
const updateApprovalStatus = async ({ actorId, status, approvalId, actor, actorOrgId }: TStatusChangeDTO) => { const updateApprovalStatus = async ({ actorId, status, approvalId, actor }: TStatusChangeDTO) => {
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId); const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" }); if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" }); if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
@ -195,8 +184,7 @@ export const secretApprovalRequestServiceFactory = ({
const { membership } = await permissionService.getProjectPermission( const { membership } = await permissionService.getProjectPermission(
ActorType.USER, ActorType.USER,
actorId, actorId,
secretApprovalRequest.projectId, secretApprovalRequest.projectId
actorOrgId
); );
if ( if (
membership.role !== ProjectMembershipRole.Admin && membership.role !== ProjectMembershipRole.Admin &&
@ -219,18 +207,13 @@ export const secretApprovalRequestServiceFactory = ({
return { ...secretApprovalRequest, ...updatedRequest }; return { ...secretApprovalRequest, ...updatedRequest };
}; };
const mergeSecretApprovalRequest = async ({ const mergeSecretApprovalRequest = async ({ approvalId, actor, actorId }: TMergeSecretApprovalRequestDTO) => {
approvalId,
actor,
actorId,
actorOrgId
}: TMergeSecretApprovalRequestDTO) => {
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId); const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" }); if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" }); if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const { policy, folderId, projectId } = secretApprovalRequest; const { policy, folderId, projectId } = secretApprovalRequest;
const { membership } = await permissionService.getProjectPermission(ActorType.USER, actorId, projectId, actorOrgId); const { membership } = await permissionService.getProjectPermission(ActorType.USER, actorId, projectId);
if ( if (
membership.role !== ProjectMembershipRole.Admin && membership.role !== ProjectMembershipRole.Admin &&
secretApprovalRequest.committerId !== membership.id && secretApprovalRequest.committerId !== membership.id &&
@ -418,7 +401,6 @@ export const secretApprovalRequestServiceFactory = ({
data, data,
actorId, actorId,
actor, actor,
actorOrgId,
policy, policy,
projectId, projectId,
secretPath, secretPath,
@ -426,19 +408,12 @@ export const secretApprovalRequestServiceFactory = ({
}: TGenerateSecretApprovalRequestDTO) => { }: TGenerateSecretApprovalRequestDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { permission, membership } = await permissionService.getProjectPermission( const { permission, membership } = await permissionService.getProjectPermission(actor, actorId, projectId);
actor,
actorId,
projectId,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath }) subject(ProjectPermissionSub.Secrets, { environment, secretPath })
); );
await projectDAL.checkProjectUpgradeStatus(projectId);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "GenSecretApproval" }); if (!folder) throw new BadRequestError({ message: "Folder not found", name: "GenSecretApproval" });
const folderId = folder.id; const folderId = folder.id;

View File

@ -14,7 +14,13 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/pr
import { TSecretRotationDALFactory } from "./secret-rotation-dal"; import { TSecretRotationDALFactory } from "./secret-rotation-dal";
import { TSecretRotationQueueFactory } from "./secret-rotation-queue"; import { TSecretRotationQueueFactory } from "./secret-rotation-queue";
import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types"; import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types";
import { TCreateSecretRotationDTO, TDeleteDTO, TListByProjectIdDTO, TRestartDTO } from "./secret-rotation-types"; import {
TCreateSecretRotationDTO,
TDeleteDTO,
TGetByIdDTO,
TListByProjectIdDTO,
TRestartDTO
} from "./secret-rotation-types";
import { rotationTemplates } from "./templates"; import { rotationTemplates } from "./templates";
type TSecretRotationServiceFactoryDep = { type TSecretRotationServiceFactoryDep = {
@ -39,8 +45,8 @@ export const secretRotationServiceFactory = ({
folderDAL, folderDAL,
secretDAL secretDAL
}: TSecretRotationServiceFactoryDep) => { }: TSecretRotationServiceFactoryDep) => {
const getProviderTemplates = async ({ actor, actorId, actorOrgId, projectId }: TProjectPermission) => { const getProviderTemplates = async ({ actor, actorId, projectId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
return { return {
@ -53,7 +59,6 @@ export const secretRotationServiceFactory = ({
projectId, projectId,
actorId, actorId,
actor, actor,
actorOrgId,
inputs, inputs,
outputs, outputs,
interval, interval,
@ -61,7 +66,7 @@ export const secretRotationServiceFactory = ({
secretPath, secretPath,
environment environment
}: TCreateSecretRotationDTO) => { }: TCreateSecretRotationDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRotation ProjectPermissionSub.SecretRotation
@ -139,14 +144,23 @@ export const secretRotationServiceFactory = ({
return secretRotation; return secretRotation;
}; };
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId }: TListByProjectIdDTO) => { const getById = async ({ rotationId, actor, actorId }: TGetByIdDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const [doc] = await secretRotationDAL.find({ id: rotationId });
if (!doc) throw new BadRequestError({ message: "Rotation not found" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
return doc;
};
const getByProjectId = async ({ actorId, projectId, actor }: TListByProjectIdDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
const doc = await secretRotationDAL.find({ projectId }); const doc = await secretRotationDAL.find({ projectId });
return doc; return doc;
}; };
const restartById = async ({ actor, actorId, actorOrgId, rotationId }: TRestartDTO) => { const restartById = async ({ actor, actorId, rotationId }: TRestartDTO) => {
const doc = await secretRotationDAL.findById(rotationId); const doc = await secretRotationDAL.findById(rotationId);
if (!doc) throw new BadRequestError({ message: "Rotation not found" }); if (!doc) throw new BadRequestError({ message: "Rotation not found" });
@ -157,18 +171,18 @@ export const secretRotationServiceFactory = ({
message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation." message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
}); });
const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
await secretRotationQueue.removeFromQueue(doc.id, doc.interval); await secretRotationQueue.removeFromQueue(doc.id, doc.interval);
await secretRotationQueue.addToQueue(doc.id, doc.interval); await secretRotationQueue.addToQueue(doc.id, doc.interval);
return doc; return doc;
}; };
const deleteById = async ({ actor, actorId, actorOrgId, rotationId }: TDeleteDTO) => { const deleteById = async ({ actor, actorId, rotationId }: TDeleteDTO) => {
const doc = await secretRotationDAL.findById(rotationId); const doc = await secretRotationDAL.findById(rotationId);
if (!doc) throw new BadRequestError({ message: "Rotation not found" }); if (!doc) throw new BadRequestError({ message: "Rotation not found" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretRotation ProjectPermissionSub.SecretRotation
@ -183,6 +197,7 @@ export const secretRotationServiceFactory = ({
return { return {
getProviderTemplates, getProviderTemplates,
getById,
getByProjectId, getByProjectId,
createRotation, createRotation,
restartById, restartById,

View File

@ -18,3 +18,7 @@ export type TDeleteDTO = {
export type TRestartDTO = { export type TRestartDTO = {
rotationId: string; rotationId: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TGetByIdDTO = {
rotationId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@ -39,8 +39,8 @@ export const secretScanningServiceFactory = ({
permissionService, permissionService,
secretScanningQueue secretScanningQueue
}: TSecretScanningServiceFactoryDep) => { }: TSecretScanningServiceFactoryDep) => {
const createInstallationSession = async ({ actor, orgId, actorId, actorOrgId }: TInstallAppSessionDTO) => { const createInstallationSession = async ({ actor, orgId, actorId }: TInstallAppSessionDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
const sessionId = crypto.randomBytes(16).toString("hex"); const sessionId = crypto.randomBytes(16).toString("hex");
@ -48,17 +48,11 @@ export const secretScanningServiceFactory = ({
return { sessionId }; return { sessionId };
}; };
const linkInstallationToOrg = async ({ const linkInstallationToOrg = async ({ sessionId, actorId, installationId, actor }: TLinkInstallSessionDTO) => {
sessionId,
actorId,
installationId,
actor,
actorOrgId
}: TLinkInstallSessionDTO) => {
const session = await gitAppInstallSessionDAL.findOne({ sessionId }); const session = await gitAppInstallSessionDAL.findOne({ sessionId });
if (!session) throw new UnauthorizedError({ message: "Session not found" }); if (!session) throw new UnauthorizedError({ message: "Session not found" });
const { permission } = await permissionService.getOrgPermission(actor, actorId, session.orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, session.orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
const installatedApp = await gitAppOrgDAL.transaction(async (tx) => { const installatedApp = await gitAppOrgDAL.transaction(async (tx) => {
await gitAppInstallSessionDAL.deleteById(session.id, tx); await gitAppInstallSessionDAL.deleteById(session.id, tx);
@ -89,23 +83,23 @@ export const secretScanningServiceFactory = ({
return { installatedApp }; return { installatedApp };
}; };
const getOrgInstallationStatus = async ({ actorId, orgId, actor, actorOrgId }: TGetOrgInstallStatusDTO) => { const getOrgInstallationStatus = async ({ actorId, orgId, actor }: TGetOrgInstallStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const appInstallation = await gitAppOrgDAL.findOne({ orgId }); const appInstallation = await gitAppOrgDAL.findOne({ orgId });
return Boolean(appInstallation); return Boolean(appInstallation);
}; };
const getRisksByOrg = async ({ actor, orgId, actorId, actorOrgId }: TGetOrgRisksDTO) => { const getRisksByOrg = async ({ actor, orgId, actorId }: TGetOrgRisksDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] }); const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] });
return { risks }; return { risks };
}; };
const updateRiskStatus = async ({ actorId, orgId, actor, actorOrgId, riskId, status }: TUpdateRiskStatusDTO) => { const updateRiskStatus = async ({ actorId, orgId, actor, riskId, status }: TUpdateRiskStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId); const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
const isRiskResolved = Boolean( const isRiskResolved = Boolean(

View File

@ -58,10 +58,9 @@ export const secretSnapshotServiceFactory = ({
projectId, projectId,
actorId, actorId,
actor, actor,
actorOrgId,
path path
}: TProjectSnapshotCountDTO) => { }: TProjectSnapshotCountDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
const folder = await folderDAL.findBySecretPath(projectId, environment, path); const folder = await folderDAL.findBySecretPath(projectId, environment, path);
@ -76,12 +75,11 @@ export const secretSnapshotServiceFactory = ({
projectId, projectId,
actorId, actorId,
actor, actor,
actorOrgId,
path, path,
limit = 20, limit = 20,
offset = 0 offset = 0
}: TProjectSnapshotListDTO) => { }: TProjectSnapshotListDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
const folder = await folderDAL.findBySecretPath(projectId, environment, path); const folder = await folderDAL.findBySecretPath(projectId, environment, path);
@ -91,10 +89,10 @@ export const secretSnapshotServiceFactory = ({
return snapshots; return snapshots;
}; };
const getSnapshotData = async ({ actorId, actor, actorOrgId, id }: TGetSnapshotDataDTO) => { const getSnapshotData = async ({ actorId, actor, id }: TGetSnapshotDataDTO) => {
const snapshot = await snapshotDAL.findSecretSnapshotDataById(id); const snapshot = await snapshotDAL.findSecretSnapshotDataById(id);
if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" }); if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
return snapshot; return snapshot;
}; };
@ -145,11 +143,11 @@ export const secretSnapshotServiceFactory = ({
} }
}; };
const rollbackSnapshot = async ({ id: snapshotId, actor, actorId, actorOrgId }: TRollbackSnapshotDTO) => { const rollbackSnapshot = async ({ id: snapshotId, actor, actorId }: TRollbackSnapshotDTO) => {
const snapshot = await snapshotDAL.findById(snapshotId); const snapshot = await snapshotDAL.findById(snapshotId);
if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" }); if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback

View File

@ -26,8 +26,8 @@ export const trustedIpServiceFactory = ({
licenseService, licenseService,
projectDAL projectDAL
}: TTrustedIpServiceFactoryDep) => { }: TTrustedIpServiceFactoryDep) => {
const listIpsByProjectId = async ({ projectId, actor, actorId, actorOrgId }: TProjectPermission) => { const listIpsByProjectId = async ({ projectId, actor, actorId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
const trustedIps = await trustedIpDAL.find({ const trustedIps = await trustedIpDAL.find({
projectId projectId
@ -35,16 +35,8 @@ export const trustedIpServiceFactory = ({
return trustedIps; return trustedIps;
}; };
const addProjectIp = async ({ const addProjectIp = async ({ projectId, actorId, actor, ipAddress: ip, comment, isActive }: TCreateIpDTO) => {
projectId, const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
actorId,
actor,
actorOrgId,
ipAddress: ip,
comment,
isActive
}: TCreateIpDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId); const project = await projectDAL.findById(projectId);
@ -73,16 +65,8 @@ export const trustedIpServiceFactory = ({
return { trustedIp, project }; // for audit log return { trustedIp, project }; // for audit log
}; };
const updateProjectIp = async ({ const updateProjectIp = async ({ projectId, actorId, actor, ipAddress: ip, comment, trustedIpId }: TUpdateIpDTO) => {
projectId, const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
actorId,
actor,
actorOrgId,
ipAddress: ip,
comment,
trustedIpId
}: TUpdateIpDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId); const project = await projectDAL.findById(projectId);
@ -113,8 +97,8 @@ export const trustedIpServiceFactory = ({
return { trustedIp, project }; // for audit log return { trustedIp, project }; // for audit log
}; };
const deleteProjectIp = async ({ projectId, actorId, actor, actorOrgId, trustedIpId }: TDeleteIpDTO) => { const deleteProjectIp = async ({ projectId, actorId, actor, trustedIpId }: TDeleteIpDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId); const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId); const project = await projectDAL.findById(projectId);

View File

@ -95,7 +95,7 @@ const envSchema = z
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()), SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()), SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
// LICENCE // LICENCE
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")), LICENSE_SERVER_URL: zpStr(z.string().optional()),
LICENSE_SERVER_KEY: zpStr(z.string().optional()), LICENSE_SERVER_KEY: zpStr(z.string().optional()),
LICENSE_KEY: zpStr(z.string().optional()), LICENSE_KEY: zpStr(z.string().optional()),
STANDALONE_MODE: z STANDALONE_MODE: z

View File

@ -8,9 +8,6 @@ import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
import { getConfig } from "../config/env"; import { getConfig } from "../config/env";
export const decodeBase64 = (s: string) => naclUtils.decodeBase64(s);
export const encodeBase64 = (u: Uint8Array) => naclUtils.encodeBase64(u);
export type TDecryptSymmetricInput = { export type TDecryptSymmetricInput = {
ciphertext: string; ciphertext: string;
iv: string; iv: string;
@ -47,7 +44,7 @@ export const encryptSymmetric = (plaintext: string, key: string) => {
}; };
}; };
export const encryptSymmetric128BitHexKeyUTF8 = (plaintext: string, key: string | Buffer) => { export const encryptSymmetric128BitHexKeyUTF8 = (plaintext: string, key: string) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16); const iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16);
const cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv); const cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv);
@ -61,12 +58,7 @@ export const encryptSymmetric128BitHexKeyUTF8 = (plaintext: string, key: string
}; };
}; };
export const decryptSymmetric128BitHexKeyUTF8 = ({ export const decryptSymmetric128BitHexKeyUTF8 = ({ ciphertext, iv, tag, key }: TDecryptSymmetricInput): string => {
ciphertext,
iv,
tag,
key
}: Omit<TDecryptSymmetricInput, "key"> & { key: string | Buffer }): string => {
const decipher = crypto.createDecipheriv(SecretEncryptionAlgo.AES_256_GCM, key, Buffer.from(iv, "base64")); const decipher = crypto.createDecipheriv(SecretEncryptionAlgo.AES_256_GCM, key, Buffer.from(iv, "base64"));
decipher.setAuthTag(Buffer.from(tag, "base64")); decipher.setAuthTag(Buffer.from(tag, "base64"));

View File

@ -1,20 +1,12 @@
export { export {
buildSecretBlindIndexFromName, buildSecretBlindIndexFromName,
createSecretBlindIndex, createSecretBlindIndex,
decodeBase64,
decryptAsymmetric, decryptAsymmetric,
decryptSymmetric, decryptSymmetric,
decryptSymmetric128BitHexKeyUTF8, decryptSymmetric128BitHexKeyUTF8,
encodeBase64,
encryptAsymmetric, encryptAsymmetric,
encryptSymmetric, encryptSymmetric,
encryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8,
generateAsymmetricKeyPair generateAsymmetricKeyPair
} from "./encryption"; } from "./encryption";
export {
decryptIntegrationAuths,
decryptSecretApprovals,
decryptSecrets,
decryptSecretVersions
} from "./secret-encryption";
export { generateSrpServerKey, srpCheckClientProof } from "./srp"; export { generateSrpServerKey, srpCheckClientProof } from "./srp";

View File

@ -1,293 +0,0 @@
import crypto from "crypto";
import { z } from "zod";
import {
IntegrationAuthsSchema,
SecretApprovalRequestsSecretsSchema,
SecretsSchema,
SecretVersionsSchema,
TIntegrationAuths,
TProjectKeys,
TSecretApprovalRequestsSecrets,
TSecrets,
TSecretVersions
} from "../../db/schemas";
import { decryptAsymmetric } from "./encryption";
const DecryptedValuesSchema = z.object({
id: z.string(),
secretKey: z.string(),
secretValue: z.string(),
secretComment: z.string().optional()
});
const DecryptedSecretSchema = z.object({
decrypted: DecryptedValuesSchema,
original: SecretsSchema
});
const DecryptedIntegrationAuthsSchema = z.object({
decrypted: z.object({
id: z.string(),
access: z.string(),
accessId: z.string(),
refresh: z.string()
}),
original: IntegrationAuthsSchema
});
const DecryptedSecretVersionsSchema = z.object({
decrypted: DecryptedValuesSchema,
original: SecretVersionsSchema
});
const DecryptedSecretApprovalsSchema = z.object({
decrypted: DecryptedValuesSchema,
original: SecretApprovalRequestsSecretsSchema
});
type DecryptedSecret = z.infer<typeof DecryptedSecretSchema>;
type DecryptedSecretVersions = z.infer<typeof DecryptedSecretVersionsSchema>;
type DecryptedSecretApprovals = z.infer<typeof DecryptedSecretApprovalsSchema>;
type DecryptedIntegrationAuths = z.infer<typeof DecryptedIntegrationAuthsSchema>;
type TLatestKey = TProjectKeys & {
sender: {
publicKey: string;
};
};
const decryptCipher = ({
ciphertext,
iv,
tag,
key
}: {
ciphertext: string;
iv: string;
tag: string;
key: string | Buffer;
}) => {
const decipher = crypto.createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "base64"));
decipher.setAuthTag(Buffer.from(tag, "base64"));
let cleartext = decipher.update(ciphertext, "base64", "utf8");
cleartext += decipher.final("utf8");
return cleartext;
};
const getDecryptedValues = (data: Array<{ ciphertext: string; iv: string; tag: string }>, key: string | Buffer) => {
const results: string[] = [];
for (const { ciphertext, iv, tag } of data) {
if (!ciphertext || !iv || !tag) {
results.push("");
} else {
results.push(decryptCipher({ ciphertext, iv, tag, key }));
}
}
return results;
};
export const decryptSecrets = (encryptedSecrets: TSecrets[], privateKey: string, latestKey: TLatestKey) => {
const key = decryptAsymmetric({
ciphertext: latestKey.encryptedKey,
nonce: latestKey.nonce,
publicKey: latestKey.sender.publicKey,
privateKey
});
const decryptedSecrets: DecryptedSecret[] = [];
encryptedSecrets.forEach((encSecret) => {
const [secretKey, secretValue, secretComment] = getDecryptedValues(
[
{
ciphertext: encSecret.secretKeyCiphertext,
iv: encSecret.secretKeyIV,
tag: encSecret.secretKeyTag
},
{
ciphertext: encSecret.secretValueCiphertext,
iv: encSecret.secretValueIV,
tag: encSecret.secretValueTag
},
{
ciphertext: encSecret.secretCommentCiphertext || "",
iv: encSecret.secretCommentIV || "",
tag: encSecret.secretCommentTag || ""
}
],
key
);
const decryptedSecret: DecryptedSecret = {
decrypted: {
secretKey,
secretValue,
secretComment,
id: encSecret.id
},
original: encSecret
};
decryptedSecrets.push(DecryptedSecretSchema.parse(decryptedSecret));
});
return decryptedSecrets;
};
export const decryptSecretVersions = (
encryptedSecretVersions: TSecretVersions[],
privateKey: string,
latestKey: TLatestKey
) => {
const key = decryptAsymmetric({
ciphertext: latestKey.encryptedKey,
nonce: latestKey.nonce,
publicKey: latestKey.sender.publicKey,
privateKey
});
const decryptedSecrets: DecryptedSecretVersions[] = [];
encryptedSecretVersions.forEach((encSecret) => {
const [secretKey, secretValue, secretComment] = getDecryptedValues(
[
{
ciphertext: encSecret.secretKeyCiphertext,
iv: encSecret.secretKeyIV,
tag: encSecret.secretKeyTag
},
{
ciphertext: encSecret.secretValueCiphertext,
iv: encSecret.secretValueIV,
tag: encSecret.secretValueTag
},
{
ciphertext: encSecret.secretCommentCiphertext || "",
iv: encSecret.secretCommentIV || "",
tag: encSecret.secretCommentTag || ""
}
],
key
);
const decryptedSecret: DecryptedSecretVersions = {
decrypted: {
secretKey,
secretValue,
secretComment,
id: encSecret.id
},
original: encSecret
};
decryptedSecrets.push(DecryptedSecretVersionsSchema.parse(decryptedSecret));
});
return decryptedSecrets;
};
export const decryptSecretApprovals = (
encryptedSecretApprovals: TSecretApprovalRequestsSecrets[],
privateKey: string,
latestKey: TLatestKey
) => {
const key = decryptAsymmetric({
ciphertext: latestKey.encryptedKey,
nonce: latestKey.nonce,
publicKey: latestKey.sender.publicKey,
privateKey
});
const decryptedSecrets: DecryptedSecretApprovals[] = [];
encryptedSecretApprovals.forEach((encApproval) => {
const [secretKey, secretValue, secretComment] = getDecryptedValues(
[
{
ciphertext: encApproval.secretKeyCiphertext,
iv: encApproval.secretKeyIV,
tag: encApproval.secretKeyTag
},
{
ciphertext: encApproval.secretValueCiphertext,
iv: encApproval.secretValueIV,
tag: encApproval.secretValueTag
},
{
ciphertext: encApproval.secretCommentCiphertext || "",
iv: encApproval.secretCommentIV || "",
tag: encApproval.secretCommentTag || ""
}
],
key
);
const decryptedSecret: DecryptedSecretApprovals = {
decrypted: {
secretKey,
secretValue,
secretComment,
id: encApproval.id
},
original: encApproval
};
decryptedSecrets.push(DecryptedSecretApprovalsSchema.parse(decryptedSecret));
});
return decryptedSecrets;
};
export const decryptIntegrationAuths = (
encryptedIntegrationAuths: TIntegrationAuths[],
privateKey: string,
latestKey: TLatestKey
) => {
const key = decryptAsymmetric({
ciphertext: latestKey.encryptedKey,
nonce: latestKey.nonce,
publicKey: latestKey.sender.publicKey,
privateKey
});
const decryptedIntegrationAuths: DecryptedIntegrationAuths[] = [];
encryptedIntegrationAuths.forEach((encAuth) => {
const [access, accessId, refresh] = getDecryptedValues(
[
{
ciphertext: encAuth.accessCiphertext || "",
iv: encAuth.accessIV || "",
tag: encAuth.accessTag || ""
},
{
ciphertext: encAuth.accessIdCiphertext || "",
iv: encAuth.accessIdIV || "",
tag: encAuth.accessIdTag || ""
},
{
ciphertext: encAuth.refreshCiphertext || "",
iv: encAuth.refreshIV || "",
tag: encAuth.refreshTag || ""
}
],
key
);
decryptedIntegrationAuths.push({
decrypted: {
id: encAuth.id,
access,
accessId,
refresh
},
original: encAuth
});
});
return decryptedIntegrationAuths;
};

View File

@ -1,12 +1,4 @@
import argon2 from "argon2";
import crypto from "crypto";
import jsrp from "jsrp"; import jsrp from "jsrp";
import nacl from "tweetnacl";
import tweetnacl from "tweetnacl-util";
import { TUserEncryptionKeys } from "@app/db/schemas";
import { decryptSymmetric, encryptAsymmetric, encryptSymmetric } from "./encryption";
export const generateSrpServerKey = async (salt: string, verifier: string) => { export const generateSrpServerKey = async (salt: string, verifier: string) => {
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
@ -32,99 +24,3 @@ export const srpCheckClientProof = async (
server.setClientPublicKey(clientPublicKey); server.setClientPublicKey(clientPublicKey);
return server.checkClientProof(clientProof); return server.checkClientProof(clientProof);
}; };
// Ghost user related:
// This functionality is intended for ghost user logic. This happens on the frontend when a user is being created.
// We replicate the same functionality on the backend when creating a ghost user.
export const generateUserSrpKeys = async (email: string, password: string) => {
const pair = nacl.box.keyPair();
const secretKeyUint8Array = pair.secretKey;
const publicKeyUint8Array = pair.publicKey;
const privateKey = tweetnacl.encodeBase64(secretKeyUint8Array);
const publicKey = tweetnacl.encodeBase64(publicKeyUint8Array);
// eslint-disable-next-line
const client = new jsrp.client();
await new Promise((resolve) => {
client.init({ username: email, 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: argon2.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,
plainPrivateKey: privateKey,
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: argon2.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 };
};

View File

@ -58,35 +58,3 @@ export class BadRequestError extends Error {
this.error = error; this.error = error;
} }
} }
export class ScimRequestError extends Error {
name: string;
schemas: string[];
detail: string;
status: number;
error: unknown;
constructor({
name,
error,
detail,
status
}: {
message?: string;
name?: string;
error?: unknown;
detail: string;
status: number;
}) {
super(detail ?? "The request is invalid");
this.name = name || "ScimRequestError";
this.schemas = ["urn:ietf:params:scim:api:messages:2.0:Error"];
this.error = error;
this.detail = detail;
this.status = status;
}
}

View File

@ -4,14 +4,12 @@ export type TOrgPermission = {
actor: ActorType; actor: ActorType;
actorId: string; actorId: string;
orgId: string; orgId: string;
actorOrgId?: string;
}; };
export type TProjectPermission = { export type TProjectPermission = {
actor: ActorType; actor: ActorType;
actorId: string; actorId: string;
projectId: string; projectId: string;
actorOrgId?: string;
}; };
export type RequiredKeys<T> = { export type RequiredKeys<T> = {

View File

@ -1,7 +1,6 @@
import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq"; import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq";
import Redis from "ioredis"; import Redis from "ioredis";
import { SecretKeyEncoding } from "@app/db/schemas";
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types"; import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
import { import {
TScanFullRepoEventPayload, TScanFullRepoEventPayload,
@ -16,8 +15,7 @@ export enum QueueName {
IntegrationSync = "sync-integrations", IntegrationSync = "sync-integrations",
SecretWebhook = "secret-webhook", SecretWebhook = "secret-webhook",
SecretFullRepoScan = "secret-full-repo-scan", SecretFullRepoScan = "secret-full-repo-scan",
SecretPushEventScan = "secret-push-event-scan", SecretPushEventScan = "secret-push-event-scan"
UpgradeProjectToGhost = "upgrade-project-to-ghost"
} }
export enum QueueJobs { export enum QueueJobs {
@ -27,8 +25,7 @@ export enum QueueJobs {
AuditLogPrune = "audit-log-prune-job", AuditLogPrune = "audit-log-prune-job",
SecWebhook = "secret-webhook-trigger", SecWebhook = "secret-webhook-trigger",
IntegrationSync = "secret-integration-pull", IntegrationSync = "secret-integration-pull",
SecretScan = "secret-scan", SecretScan = "secret-scan"
UpgradeProjectToGhost = "upgrade-project-to-ghost-job"
} }
export type TQueueJobTypes = { export type TQueueJobTypes = {
@ -67,20 +64,6 @@ export type TQueueJobTypes = {
payload: TScanFullRepoEventPayload; payload: TScanFullRepoEventPayload;
}; };
[QueueName.SecretPushEventScan]: { name: QueueJobs.SecretScan; payload: TScanPushEventPayload }; [QueueName.SecretPushEventScan]: { name: QueueJobs.SecretScan; payload: TScanPushEventPayload };
[QueueName.UpgradeProjectToGhost]: {
name: QueueJobs.UpgradeProjectToGhost;
payload: {
projectId: string;
startedByUserId: string;
encryptedPrivateKey: {
encryptedKey: string;
encryptedKeyIv: string;
encryptedKeyTag: string;
keyEncoding: SecretKeyEncoding;
};
};
};
}; };
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>; export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;

View File

@ -37,7 +37,7 @@ type TMain = {
export const main = async ({ db, smtp, logger, queue }: TMain) => { export const main = async ({ db, smtp, logger, queue }: TMain) => {
const appCfg = getConfig(); const appCfg = getConfig();
const server = fasitfy({ const server = fasitfy({
logger: appCfg.NODE_ENV === "test" ? false : logger, logger,
trustProxy: true, trustProxy: true,
connectionTimeout: 30 * 1000, connectionTimeout: 30 * 1000,
ignoreTrailingSlash: true ignoreTrailingSlash: true

View File

@ -1,17 +0,0 @@
import { FastifyRequest } from "fastify";
import { ActorType } from "@app/services/auth/auth-type";
// this is a unique id for sending posthog event
export const getTelemetryDistinctId = (req: FastifyRequest) => {
if (req.auth.actor === ActorType.USER) {
return req.auth.user.email;
}
if (req.auth.actor === ActorType.IDENTITY) {
return `identity-${req.auth.identityId}`;
}
if (req.auth.actor === ActorType.SERVICE) {
return req.auth.serviceToken.createdByEmail || `service-token-null-creator-${req.auth.serviceTokenId}`; // when user gets removed from system
}
return "unknown-auth-data";
};

View File

@ -63,11 +63,6 @@ export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
identityId: req.auth.identityId identityId: req.auth.identityId
} }
}; };
} else if (req.auth.actor === ActorType.SCIM_CLIENT) {
payload.actor = {
type: ActorType.SCIM_CLIENT,
metadata: {}
};
} else { } else {
throw new BadRequestError({ message: "Missing logic for other actor" }); throw new BadRequestError({ message: "Missing logic for other actor" });
} }

View File

@ -3,7 +3,6 @@ import fp from "fastify-plugin";
import jwt, { JwtPayload } from "jsonwebtoken"; import jwt, { JwtPayload } from "jsonwebtoken";
import { TServiceTokens, TUsers } from "@app/db/schemas"; import { TServiceTokens, TUsers } from "@app/db/schemas";
import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors"; import { UnauthorizedError } from "@app/lib/errors";
import { ActorType, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; import { ActorType, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
@ -11,7 +10,6 @@ import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-to
export type TAuthMode = export type TAuthMode =
| { | {
orgId?: string;
authMode: AuthMode.JWT; authMode: AuthMode.JWT;
actor: ActorType.USER; actor: ActorType.USER;
userId: string; userId: string;
@ -23,11 +21,10 @@ export type TAuthMode =
actor: ActorType.USER; actor: ActorType.USER;
userId: string; userId: string;
user: TUsers; user: TUsers;
orgId?: string;
} }
| { | {
authMode: AuthMode.SERVICE_TOKEN; authMode: AuthMode.SERVICE_TOKEN;
serviceToken: TServiceTokens & { createdByEmail: string }; serviceToken: TServiceTokens;
actor: ActorType.SERVICE; actor: ActorType.SERVICE;
serviceTokenId: string; serviceTokenId: string;
} }
@ -36,12 +33,6 @@ export type TAuthMode =
actor: ActorType.IDENTITY; actor: ActorType.IDENTITY;
identityId: string; identityId: string;
identityName: string; identityName: string;
}
| {
authMode: AuthMode.SCIM_TOKEN;
actor: ActorType.SCIM_CLIENT;
scimTokenId: string;
orgId: string;
}; };
const extractAuth = async (req: FastifyRequest, jwtSecret: string) => { const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
@ -62,7 +53,6 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
} }
const decodedToken = jwt.verify(authTokenValue, jwtSecret) as JwtPayload; const decodedToken = jwt.verify(authTokenValue, jwtSecret) as JwtPayload;
switch (decodedToken.authTokenType) { switch (decodedToken.authTokenType) {
case AuthTokenType.ACCESS_TOKEN: case AuthTokenType.ACCESS_TOKEN:
return { return {
@ -78,12 +68,6 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
token: decodedToken as TIdentityAccessTokenJwtPayload, token: decodedToken as TIdentityAccessTokenJwtPayload,
actor: ActorType.IDENTITY actor: ActorType.IDENTITY
} as const; } as const;
case AuthTokenType.SCIM_TOKEN:
return {
authMode: AuthMode.SCIM_TOKEN,
token: decodedToken as TScimTokenJwtPayload,
actor: ActorType.SCIM_CLIENT
} as const;
default: default:
return { authMode: null, token: null } as const; return { authMode: null, token: null } as const;
} }
@ -98,8 +82,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
switch (authMode) { switch (authMode) {
case AuthMode.JWT: { case AuthMode.JWT: {
const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); const { user, tokenVersionId } = await server.services.authToken.fnValidateJwtIdentity(token);
req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor, orgId }; req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor };
break; break;
} }
case AuthMode.IDENTITY_ACCESS_TOKEN: { case AuthMode.IDENTITY_ACCESS_TOKEN: {
@ -127,11 +111,6 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
req.auth = { authMode: AuthMode.API_KEY as const, userId: user.id, actor, user }; req.auth = { authMode: AuthMode.API_KEY as const, userId: user.id, actor, user };
break; break;
} }
case AuthMode.SCIM_TOKEN: {
const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token);
req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId };
break;
}
default: default:
throw new UnauthorizedError({ name: "Unknown token strategy" }); throw new UnauthorizedError({ name: "Unknown token strategy" });
} }

View File

@ -9,13 +9,11 @@ export const injectPermission = fp(async (server) => {
if (!req.auth) return; if (!req.auth) return;
if (req.auth.actor === ActorType.USER) { if (req.auth.actor === ActorType.USER) {
req.permission = { type: ActorType.USER, id: req.auth.userId, orgId: req.auth?.orgId }; req.permission = { type: ActorType.USER, id: req.auth.userId };
} else if (req.auth.actor === ActorType.IDENTITY) { } else if (req.auth.actor === ActorType.IDENTITY) {
req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId }; req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId };
} else if (req.auth.actor === ActorType.SERVICE) { } else if (req.auth.actor === ActorType.SERVICE) {
req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId }; req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId };
} else if (req.auth.actor === ActorType.SCIM_CLIENT) {
req.permission = { type: ActorType.SCIM_CLIENT, id: req.auth.scimTokenId, orgId: req.auth.orgId };
} }
}); });
}); });

View File

@ -2,13 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import fastifyPlugin from "fastify-plugin"; import fastifyPlugin from "fastify-plugin";
import { ZodError } from "zod"; import { ZodError } from "zod";
import { import { BadRequestError, DatabaseError, InternalServerError, UnauthorizedError } from "@app/lib/errors";
BadRequestError,
DatabaseError,
InternalServerError,
ScimRequestError,
UnauthorizedError
} from "@app/lib/errors";
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => { export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
server.setErrorHandler((error, req, res) => { server.setErrorHandler((error, req, res) => {
@ -27,12 +21,6 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
error: "PermissionDenied", error: "PermissionDenied",
message: `You are not allowed to ${error.action} on ${error.subjectType}` message: `You are not allowed to ${error.action} on ${error.subjectType}`
}); });
} else if (error instanceof ScimRequestError) {
void res.status(error.status).send({
schemas: error.schemas,
status: error.status,
detail: error.detail
});
} else { } else {
void res.send(error); void res.send(error);
} }

View File

@ -25,13 +25,13 @@ export const fastifySwagger = fp(async (fastify) => {
], ],
components: { components: {
securitySchemes: { securitySchemes: {
bearerAuth: { bearer: {
type: "http", type: "http",
scheme: "bearer", scheme: "bearer",
bearerFormat: "JWT", bearerFormat: "JWT",
description: "An access token in Infisical" description: "A service token in Infisical"
}, },
apiKeyAuth: { apiKey: {
type: "apiKey", type: "apiKey",
in: "header", in: "header",
name: "X-API-Key", name: "X-API-Key",

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