mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 18:26:16 +00:00
Merge remote-tracking branch 'origin/main' into feat-secret-input-autocomplete
This commit is contained in:
+3
-3
@@ -3,9 +3,6 @@
|
|||||||
# 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://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
|
||||||
|
|
||||||
# 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
|
||||||
@@ -16,6 +13,9 @@ POSTGRES_PASSWORD=infisical
|
|||||||
POSTGRES_USER=infisical
|
POSTGRES_USER=infisical
|
||||||
POSTGRES_DB=infisical
|
POSTGRES_DB=infisical
|
||||||
|
|
||||||
|
# Required
|
||||||
|
DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
|
||||||
|
|
||||||
# Redis
|
# Redis
|
||||||
REDIS_URL=redis://redis:6379
|
REDIS_URL=redis://redis:6379
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ jobs:
|
|||||||
load: true
|
load: true
|
||||||
context: backend
|
context: backend
|
||||||
tags: infisical/infisical:test
|
tags: infisical/infisical:test
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
- name: ⏻ Spawn backend container and dependencies
|
- name: ⏻ Spawn backend container and dependencies
|
||||||
run: |
|
run: |
|
||||||
docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
|
docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
|
||||||
@@ -92,6 +93,7 @@ jobs:
|
|||||||
project: 64mmf0n610
|
project: 64mmf0n610
|
||||||
context: frontend
|
context: frontend
|
||||||
tags: infisical/frontend:test
|
tags: infisical/frontend:test
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
build-args: |
|
build-args: |
|
||||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||||
NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
NEXT_INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
name: Deployment pipeline
|
||||||
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
infisical-image:
|
||||||
|
name: Build backend image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: ☁️ Checkout source
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: 📦 Install dependencies to test all dependencies
|
||||||
|
run: npm ci --only-production
|
||||||
|
working-directory: backend
|
||||||
|
- name: Save commit hashes for tag
|
||||||
|
id: commit
|
||||||
|
uses: pr-mpt/actions-commit-hash@v2
|
||||||
|
- name: 🔧 Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
- name: 🐋 Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Set up Depot CLI
|
||||||
|
uses: depot/setup-action@v1
|
||||||
|
- name: 🏗️ Build backend and push to docker hub
|
||||||
|
uses: depot/build-push-action@v1
|
||||||
|
with:
|
||||||
|
project: 64mmf0n610
|
||||||
|
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
||||||
|
push: true
|
||||||
|
context: .
|
||||||
|
file: Dockerfile.standalone-infisical
|
||||||
|
tags: |
|
||||||
|
infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||||
|
infisical/staging_infisical:latest
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
build-args: |
|
||||||
|
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
||||||
|
INFISICAL_PLATFORM_VERSION=${{ steps.commit.outputs.short }}
|
||||||
|
|
||||||
|
gamma-deployment:
|
||||||
|
name: Deploy to gamma
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [infisical-image]
|
||||||
|
environment:
|
||||||
|
name: Gamma
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Node.js environment
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- name: Change directory to backend and install dependencies
|
||||||
|
env:
|
||||||
|
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
npm run migration:latest
|
||||||
|
- name: Configure AWS Credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
with:
|
||||||
|
audience: sts.amazonaws.com
|
||||||
|
aws-region: us-east-1
|
||||||
|
role-to-assume: arn:aws:iam::905418227878:role/deploy-new-ecs-img
|
||||||
|
- name: Save commit hashes for tag
|
||||||
|
id: commit
|
||||||
|
uses: pr-mpt/actions-commit-hash@v2
|
||||||
|
- name: Download task definition
|
||||||
|
run: |
|
||||||
|
aws ecs describe-task-definition --task-definition infisical-prod-platform --query taskDefinition > task-definition.json
|
||||||
|
- name: Render Amazon ECS task definition
|
||||||
|
id: render-web-container
|
||||||
|
uses: aws-actions/amazon-ecs-render-task-definition@v1
|
||||||
|
with:
|
||||||
|
task-definition: task-definition.json
|
||||||
|
container-name: infisical-prod-platform
|
||||||
|
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||||
|
environment-variables: "LOG_LEVEL=info"
|
||||||
|
- name: Deploy to Amazon ECS service
|
||||||
|
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
||||||
|
with:
|
||||||
|
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
||||||
|
service: infisical-prod-platform
|
||||||
|
cluster: infisical-prod-platform
|
||||||
|
wait-for-service-stability: true
|
||||||
|
|
||||||
|
production-postgres-deployment:
|
||||||
|
name: Deploy to production
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [gamma-deployment]
|
||||||
|
environment:
|
||||||
|
name: Production
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Node.js environment
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- name: Change directory to backend and install dependencies
|
||||||
|
env:
|
||||||
|
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
npm run migration:latest
|
||||||
|
- name: Configure AWS Credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
with:
|
||||||
|
audience: sts.amazonaws.com
|
||||||
|
aws-region: us-east-1
|
||||||
|
role-to-assume: arn:aws:iam::381492033652:role/gha-make-prod-deployment
|
||||||
|
- name: Save commit hashes for tag
|
||||||
|
id: commit
|
||||||
|
uses: pr-mpt/actions-commit-hash@v2
|
||||||
|
- name: Download task definition
|
||||||
|
run: |
|
||||||
|
aws ecs describe-task-definition --task-definition infisical-prod-platform --query taskDefinition > task-definition.json
|
||||||
|
- name: Render Amazon ECS task definition
|
||||||
|
id: render-web-container
|
||||||
|
uses: aws-actions/amazon-ecs-render-task-definition@v1
|
||||||
|
with:
|
||||||
|
task-definition: task-definition.json
|
||||||
|
container-name: infisical-prod-platform
|
||||||
|
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||||
|
environment-variables: "LOG_LEVEL=info"
|
||||||
|
- name: Deploy to Amazon ECS service
|
||||||
|
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
||||||
|
with:
|
||||||
|
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
||||||
|
service: infisical-prod-platform
|
||||||
|
cluster: infisical-prod-platform
|
||||||
|
wait-for-service-stability: true
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
name: Build, Publish and Deploy to Gamma
|
|
||||||
on: [workflow_dispatch]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
infisical-image:
|
|
||||||
name: Build backend image
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: ☁️ Checkout source
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: 📦 Install dependencies to test all dependencies
|
|
||||||
run: npm ci --only-production
|
|
||||||
working-directory: backend
|
|
||||||
# - name: 🧪 Run tests
|
|
||||||
# run: npm run test:ci
|
|
||||||
# working-directory: backend
|
|
||||||
- name: Save commit hashes for tag
|
|
||||||
id: commit
|
|
||||||
uses: pr-mpt/actions-commit-hash@v2
|
|
||||||
- name: 🔧 Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
- name: 🐋 Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Set up Depot CLI
|
|
||||||
uses: depot/setup-action@v1
|
|
||||||
- name: 📦 Build backend and export to Docker
|
|
||||||
uses: depot/build-push-action@v1
|
|
||||||
with:
|
|
||||||
project: 64mmf0n610
|
|
||||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
|
||||||
load: true
|
|
||||||
context: .
|
|
||||||
file: Dockerfile.standalone-infisical
|
|
||||||
tags: infisical/infisical:test
|
|
||||||
# - name: ⏻ Spawn backend container and dependencies
|
|
||||||
# run: |
|
|
||||||
# docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
|
|
||||||
# - name: 🧪 Test backend image
|
|
||||||
# run: |
|
|
||||||
# ./.github/resources/healthcheck.sh infisical-backend-test
|
|
||||||
# - name: ⏻ Shut down backend container and dependencies
|
|
||||||
# run: |
|
|
||||||
# docker compose -f .github/resources/docker-compose.be-test.yml down
|
|
||||||
- name: 🏗️ Build backend and push
|
|
||||||
uses: depot/build-push-action@v1
|
|
||||||
with:
|
|
||||||
project: 64mmf0n610
|
|
||||||
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
|
|
||||||
push: true
|
|
||||||
context: .
|
|
||||||
file: Dockerfile.standalone-infisical
|
|
||||||
tags: |
|
|
||||||
infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
|
||||||
infisical/staging_infisical:latest
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
build-args: |
|
|
||||||
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
|
|
||||||
INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
|
|
||||||
postgres-migration:
|
|
||||||
name: Run latest migration files
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [infisical-image]
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Setup Node.js environment
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: "20"
|
|
||||||
- name: Change directory to backend and install dependencies
|
|
||||||
env:
|
|
||||||
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
|
||||||
run: |
|
|
||||||
cd backend
|
|
||||||
npm install
|
|
||||||
npm run migration:latest
|
|
||||||
# - name: Run postgres DB migration files
|
|
||||||
# env:
|
|
||||||
# DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
|
||||||
# run: npm run migration:latest
|
|
||||||
gamma-deployment:
|
|
||||||
name: Deploy to gamma
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [postgres-migration]
|
|
||||||
steps:
|
|
||||||
- name: ☁️ Checkout source
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Install Helm
|
|
||||||
uses: azure/setup-helm@v3
|
|
||||||
with:
|
|
||||||
version: v3.10.0
|
|
||||||
- name: Install infisical helm chart
|
|
||||||
run: |
|
|
||||||
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
|
|
||||||
helm repo update
|
|
||||||
- name: Install kubectl
|
|
||||||
uses: azure/setup-kubectl@v3
|
|
||||||
- name: Install doctl
|
|
||||||
uses: digitalocean/action-doctl@v2
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
|
|
||||||
- name: Save DigitalOcean kubeconfig with short-lived credentials
|
|
||||||
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 infisical-gamma-postgres
|
|
||||||
- name: switch to gamma namespace
|
|
||||||
run: kubectl config set-context --current --namespace=gamma
|
|
||||||
- name: test kubectl
|
|
||||||
run: kubectl get ingress
|
|
||||||
- name: Download helm values to file and upgrade gamma deploy
|
|
||||||
run: |
|
|
||||||
wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml
|
|
||||||
helm upgrade infisical infisical-helm-charts/infisical-standalone --values values.yaml --wait --install
|
|
||||||
if [[ $(helm status infisical) == *"FAILED"* ]]; then
|
|
||||||
echo "Helm upgrade failed"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "Helm upgrade was successful"
|
|
||||||
fi
|
|
||||||
@@ -5,6 +5,7 @@ on:
|
|||||||
types: [opened, synchronize]
|
types: [opened, synchronize]
|
||||||
paths:
|
paths:
|
||||||
- "backend/src/server/routes/**"
|
- "backend/src/server/routes/**"
|
||||||
|
- "backend/src/ee/routes/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-be-api-changes:
|
check-be-api-changes:
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ yarn-error.log*
|
|||||||
# Infisical init
|
# Infisical init
|
||||||
.infisical.json
|
.infisical.json
|
||||||
|
|
||||||
|
.infisicalignore
|
||||||
|
|
||||||
# Editor specific
|
# Editor specific
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,5 @@
|
|||||||
.github/resources/docker-compose.be-test.yml:generic-api-key:16
|
.github/resources/docker-compose.be-test.yml:generic-api-key:16
|
||||||
|
frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/IdentityRbacSection.tsx:generic-api-key:206
|
||||||
|
frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:304
|
||||||
|
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/MemberRbacSection.tsx:generic-api-key:206
|
||||||
|
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:292
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
ARG POSTHOG_HOST=https://app.posthog.com
|
ARG POSTHOG_HOST=https://app.posthog.com
|
||||||
ARG POSTHOG_API_KEY=posthog-api-key
|
ARG POSTHOG_API_KEY=posthog-api-key
|
||||||
ARG INTERCOM_ID=intercom-id
|
ARG INTERCOM_ID=intercom-id
|
||||||
|
ARG SAML_ORG_SLUG=saml-org-slug-default
|
||||||
|
|
||||||
FROM node:20-alpine AS base
|
FROM node:20-alpine AS base
|
||||||
|
|
||||||
@@ -35,6 +36,8 @@ ARG INTERCOM_ID
|
|||||||
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
|
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
|
||||||
ARG INFISICAL_PLATFORM_VERSION
|
ARG INFISICAL_PLATFORM_VERSION
|
||||||
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
||||||
|
ARG SAML_ORG_SLUG
|
||||||
|
ENV NEXT_PUBLIC_SAML_ORG_SLUG=$SAML_ORG_SLUG
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
@@ -100,6 +103,9 @@ ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
|
|||||||
ARG INTERCOM_ID=intercom-id
|
ARG INTERCOM_ID=intercom-id
|
||||||
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
||||||
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
|
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
|
||||||
|
ARG SAML_ORG_SLUG
|
||||||
|
ENV NEXT_PUBLIC_SAML_ORG_SLUG=$SAML_ORG_SLUG \
|
||||||
|
BAKED_NEXT_PUBLIC_SAML_ORG_SLUG=$SAML_ORG_SLUG
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
@@ -118,9 +124,6 @@ WORKDIR /backend
|
|||||||
|
|
||||||
ENV TELEMETRY_ENABLED true
|
ENV TELEMETRY_ENABLED true
|
||||||
|
|
||||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
|
|
||||||
CMD node healthcheck.js
|
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
EXPOSE 443
|
EXPOSE 443
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
<a href="https://infisical.com/">Infisical Cloud</a> |
|
<a href="https://infisical.com/">Infisical Cloud</a> |
|
||||||
<a href="https://infisical.com/docs/self-hosting/overview">Self-Hosting</a> |
|
<a href="https://infisical.com/docs/self-hosting/overview">Self-Hosting</a> |
|
||||||
<a href="https://infisical.com/docs/documentation/getting-started/introduction">Docs</a> |
|
<a href="https://infisical.com/docs/documentation/getting-started/introduction">Docs</a> |
|
||||||
<a href="https://www.infisical.com">Website</a>
|
<a href="https://www.infisical.com">Website</a> |
|
||||||
|
<a href="https://infisical.com/careers">Hiring (Remote/SF)</a>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
|||||||
@@ -23,16 +23,17 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ["./e2e-test/**/*"],
|
files: ["./e2e-test/**/*", "./src/db/migrations/**/*"],
|
||||||
rules: {
|
rules: {
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
"@typescript-eslint/no-unsafe-argument": "off",
|
"@typescript-eslint/no-unsafe-argument": "off",
|
||||||
"@typescript-eslint/no-unsafe-return": "off",
|
"@typescript-eslint/no-unsafe-return": "off",
|
||||||
"@typescript-eslint/no-unsafe-call": "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",
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const deleteSecretImport = async (id: string) => {
|
|||||||
|
|
||||||
describe("Secret Import Router", async () => {
|
describe("Secret Import Router", async () => {
|
||||||
test.each([
|
test.each([
|
||||||
{ importEnv: "dev", importPath: "/" }, // one in root
|
{ importEnv: "prod", importPath: "/" }, // one in root
|
||||||
{ importEnv: "staging", importPath: "/" } // then create a deep one creating intermediate ones
|
{ importEnv: "staging", importPath: "/" } // then create a deep one creating intermediate ones
|
||||||
])("Create secret import $importEnv with path $importPath", async ({ importPath, importEnv }) => {
|
])("Create secret import $importEnv with path $importPath", async ({ importPath, importEnv }) => {
|
||||||
// check for default environments
|
// check for default environments
|
||||||
@@ -66,7 +66,7 @@ describe("Secret Import Router", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Get secret imports", async () => {
|
test("Get secret imports", async () => {
|
||||||
const createdImport1 = await createSecretImport("/", "dev");
|
const createdImport1 = await createSecretImport("/", "prod");
|
||||||
const createdImport2 = await createSecretImport("/", "staging");
|
const createdImport2 = await createSecretImport("/", "staging");
|
||||||
const res = await testServer.inject({
|
const res = await testServer.inject({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -103,10 +103,10 @@ describe("Secret Import Router", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Update secret import position", async () => {
|
test("Update secret import position", async () => {
|
||||||
const devImportDetails = { path: "/", envSlug: "dev" };
|
const prodImportDetails = { path: "/", envSlug: "prod" };
|
||||||
const stagingImportDetails = { path: "/", envSlug: "staging" };
|
const stagingImportDetails = { path: "/", envSlug: "staging" };
|
||||||
|
|
||||||
const createdImport1 = await createSecretImport(devImportDetails.path, devImportDetails.envSlug);
|
const createdImport1 = await createSecretImport(prodImportDetails.path, prodImportDetails.envSlug);
|
||||||
const createdImport2 = await createSecretImport(stagingImportDetails.path, stagingImportDetails.envSlug);
|
const createdImport2 = await createSecretImport(stagingImportDetails.path, stagingImportDetails.envSlug);
|
||||||
|
|
||||||
const updateImportRes = await testServer.inject({
|
const updateImportRes = await testServer.inject({
|
||||||
@@ -136,7 +136,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.stringMatching(prodImportDetails.envSlug),
|
||||||
id: expect.any(String)
|
id: expect.any(String)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -166,7 +166,7 @@ describe("Secret Import Router", async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("Delete secret import position", async () => {
|
test("Delete secret import position", async () => {
|
||||||
const createdImport1 = await createSecretImport("/", "dev");
|
const createdImport1 = await createSecretImport("/", "prod");
|
||||||
const createdImport2 = await createSecretImport("/", "staging");
|
const createdImport2 = await createSecretImport("/", "staging");
|
||||||
const deletedImport = await deleteSecretImport(createdImport1.id);
|
const deletedImport = await deleteSecretImport(createdImport1.id);
|
||||||
// check for default environments
|
// check for default environments
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { seedData1 } from "@app/db/seed-data";
|
|||||||
import { initEnvConfig } from "@app/lib/config/env";
|
import { initEnvConfig } from "@app/lib/config/env";
|
||||||
import { initLogger } from "@app/lib/logger";
|
import { initLogger } from "@app/lib/logger";
|
||||||
import { main } from "@app/server/app";
|
import { main } from "@app/server/app";
|
||||||
import { AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import { mockQueue } from "./mocks/queue";
|
import { mockQueue } from "./mocks/queue";
|
||||||
import { mockSmtpServer } from "./mocks/smtp";
|
import { mockSmtpServer } from "./mocks/smtp";
|
||||||
@@ -52,6 +52,8 @@ export default {
|
|||||||
authTokenType: AuthTokenType.ACCESS_TOKEN,
|
authTokenType: AuthTokenType.ACCESS_TOKEN,
|
||||||
userId: seedData1.id,
|
userId: seedData1.id,
|
||||||
tokenVersionId: seedData1.token.id,
|
tokenVersionId: seedData1.token.id,
|
||||||
|
authMethod: AuthMethod.EMAIL,
|
||||||
|
organizationId: seedData1.organization.id,
|
||||||
accessVersion: 1
|
accessVersion: 1
|
||||||
},
|
},
|
||||||
cfg.AUTH_SECRET,
|
cfg.AUTH_SECRET,
|
||||||
|
|||||||
Generated
+262
-182
@@ -47,10 +47,11 @@
|
|||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"mysql2": "^3.9.1",
|
"mysql2": "^3.9.4",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
"nodemailer": "^6.9.9",
|
"nodemailer": "^6.9.9",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
|
"oracledb": "^6.4.0",
|
||||||
"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",
|
||||||
@@ -1708,6 +1709,22 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/android-arm": {
|
"node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.18.20",
|
"version": "0.18.20",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
||||||
@@ -3162,9 +3179,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz",
|
||||||
"integrity": "sha512-zdTObFRoNENrdPpnTNnhOljYIcOX7aI7+7wyrSpPFFIOf/nRdedE6IYsjaBE7tjukphh1tMTojgJ7p3lKY8x6Q==",
|
"integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -3175,9 +3192,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz",
|
||||||
"integrity": "sha512-aiItwP48BiGpMFS9Znjo/xCNQVwTQVcRKkFKsO81m8exrGjHkCBDvm9PHay2kpa8RPnZzzKcD1iQ9KaLY4fPQQ==",
|
"integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -3188,9 +3205,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz",
|
||||||
"integrity": "sha512-zhNIS+L4ZYkYQUjIQUR6Zl0RXhbbA0huvNIWjmPc2SL0cB1h5Djkcy+RZ3/Bwszfb6vgwUvcVJYD6e6Zkpsi8g==",
|
"integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -3201,9 +3218,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz",
|
||||||
"integrity": "sha512-A/FAHFRNQYrELrb/JHncRWzTTXB2ticiRFztP4ggIUAfa9Up1qfW8aG2w/mN9jNiZ+HB0t0u0jpJgFXG6BfRTA==",
|
"integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3214,9 +3231,22 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz",
|
||||||
"integrity": "sha512-JsidBnh3p2IJJA4/2xOF2puAYqbaczB3elZDT0qHxn362EIoIkq7hrR43Xa8RisgI6/WPfvb2umbGsuvf7E37A==",
|
"integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
|
"version": "4.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz",
|
||||||
|
"integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -3227,9 +3257,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz",
|
||||||
"integrity": "sha512-hBNCnqw3EVCkaPB0Oqd24bv8SklETptQWcJz06kb9OtiShn9jK1VuTgi7o4zPSt6rNGWQOTDEAccbk0OqJmS+g==",
|
"integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -3240,9 +3270,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz",
|
||||||
"integrity": "sha512-Fw9ChYfJPdltvi9ALJ9wzdCdxGw4wtq4t1qY028b2O7GwB5qLNSGtqMsAel1lfWTZvf4b6/+4HKp0GlSYg0ahA==",
|
"integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -3252,10 +3282,23 @@
|
|||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||||
|
"version": "4.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz",
|
||||||
|
"integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz",
|
||||||
"integrity": "sha512-BH5xIh7tOzS9yBi8dFrCTG8Z6iNIGWGltd3IpTSKp6+pNWWO6qy8eKoRxOtwFbMrid5NZaidLYN6rHh9aB8bEw==",
|
"integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -3265,10 +3308,23 @@
|
|||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
|
"version": "4.14.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz",
|
||||||
|
"integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz",
|
||||||
"integrity": "sha512-PmvAj8k6EuWiyLbkNpd6BLv5XeYFpqWuRvRNRl80xVfpGXK/z6KYXmAgbI4ogz7uFiJxCnYcqyvZVD0dgFog7Q==",
|
"integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3279,9 +3335,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz",
|
||||||
"integrity": "sha512-mdxnlW2QUzXwY+95TuxZ+CurrhgrPAMveDWI97EQlA9bfhR8tw3Pt7SUlc/eSlCNxlWktpmT//EAA8UfCHOyXg==",
|
"integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -3292,9 +3348,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz",
|
||||||
"integrity": "sha512-ge7saUz38aesM4MA7Cad8CHo0Fyd1+qTaqoIo+Jtk+ipBi4ATSrHWov9/S4u5pbEQmLjgUjB7BJt+MiKG2kzmA==",
|
"integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -3305,9 +3361,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz",
|
||||||
"integrity": "sha512-p9E3PZlzurhlsN5h9g7zIP1DnqKXJe8ZUkFwAazqSvHuWfihlIISPxG9hCHCoA+dOOspL/c7ty1eeEVFTE0UTw==",
|
"integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -3318,9 +3374,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz",
|
||||||
"integrity": "sha512-kb4/auKXkYKqlUYTE8s40FcJIj5soOyRLHKd4ugR0dCq0G2EfcF54eYcfQiGkHzjidZ40daB4ulsFdtqNKZtBg==",
|
"integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -5916,12 +5972,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.1",
|
"version": "1.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||||
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
|
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "3.1.2",
|
"bytes": "3.1.2",
|
||||||
"content-type": "~1.0.4",
|
"content-type": "~1.0.5",
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"depd": "2.0.0",
|
"depd": "2.0.0",
|
||||||
"destroy": "1.2.0",
|
"destroy": "1.2.0",
|
||||||
@@ -5929,7 +5985,7 @@
|
|||||||
"iconv-lite": "0.4.24",
|
"iconv-lite": "0.4.24",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"qs": "6.11.0",
|
"qs": "6.11.0",
|
||||||
"raw-body": "2.5.1",
|
"raw-body": "2.5.2",
|
||||||
"type-is": "~1.6.18",
|
"type-is": "~1.6.18",
|
||||||
"unpipe": "1.0.0"
|
"unpipe": "1.0.0"
|
||||||
},
|
},
|
||||||
@@ -7379,16 +7435,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.18.2",
|
"version": "4.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
|
||||||
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
"body-parser": "1.20.1",
|
"body-parser": "1.20.2",
|
||||||
"content-disposition": "0.5.4",
|
"content-disposition": "0.5.4",
|
||||||
"content-type": "~1.0.4",
|
"content-type": "~1.0.4",
|
||||||
"cookie": "0.5.0",
|
"cookie": "0.6.0",
|
||||||
"cookie-signature": "1.0.6",
|
"cookie-signature": "1.0.6",
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"depd": "2.0.0",
|
"depd": "2.0.0",
|
||||||
@@ -7419,6 +7475,14 @@
|
|||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/express/node_modules/cookie": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/express/node_modules/cookie-signature": {
|
"node_modules/express/node_modules/cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
@@ -7749,9 +7813,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.15.4",
|
"version": "1.15.6",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||||
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
|
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@@ -9759,9 +9823,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mysql2": {
|
"node_modules/mysql2": {
|
||||||
"version": "3.9.1",
|
"version": "3.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.4.tgz",
|
||||||
"integrity": "sha512-3njoWAAhGBYy0tWBabqUQcLtczZUxrmmtc2vszQUekg3kTJyZ5/IeLC3Fo04u6y6Iy5Sba7pIIa2P/gs8D3ZeQ==",
|
"integrity": "sha512-OEESQuwxMza803knC1YSt7NMuc1BrK9j7gZhCSs2WAyxr1vfiI7QLaLOKTh5c9SWGz98qVyQUbK8/WckevNQhg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"denque": "^2.1.0",
|
"denque": "^2.1.0",
|
||||||
"generate-function": "^2.3.1",
|
"generate-function": "^2.3.1",
|
||||||
@@ -10231,6 +10295,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/oracledb": {
|
||||||
|
"version": "6.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/oracledb/-/oracledb-6.4.0.tgz",
|
||||||
|
"integrity": "sha512-TJI08qzQlf/l7T49VojP9BoQpjEr14NXZmpSzzcLrbNs7qSl0QA/Mc9gGiEdkg5WmwH0wqUjtMC7jlf1WamlYA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/p-limit": {
|
"node_modules/p-limit": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||||
@@ -10818,9 +10891,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.32",
|
"version": "8.4.38",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||||
"integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
|
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10839,7 +10912,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
@@ -11234,9 +11307,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/raw-body": {
|
"node_modules/raw-body": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||||
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
|
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "3.1.2",
|
"bytes": "3.1.2",
|
||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
@@ -11511,10 +11584,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.8.0",
|
"version": "4.14.3",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz",
|
||||||
"integrity": "sha512-NpsklK2fach5CdI+PScmlE5R4Ao/FSWtF7LkoIrHDxPACY/xshNasPsbpG0VVHxUTbf74tJbVT4PrP8JsJ6ZDA==",
|
"integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "1.0.5"
|
||||||
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
},
|
},
|
||||||
@@ -11523,19 +11599,22 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.8.0",
|
"@rollup/rollup-android-arm-eabi": "4.14.3",
|
||||||
"@rollup/rollup-android-arm64": "4.8.0",
|
"@rollup/rollup-android-arm64": "4.14.3",
|
||||||
"@rollup/rollup-darwin-arm64": "4.8.0",
|
"@rollup/rollup-darwin-arm64": "4.14.3",
|
||||||
"@rollup/rollup-darwin-x64": "4.8.0",
|
"@rollup/rollup-darwin-x64": "4.14.3",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.8.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.14.3",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.8.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.14.3",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.8.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.14.3",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.8.0",
|
"@rollup/rollup-linux-arm64-musl": "4.14.3",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.8.0",
|
"@rollup/rollup-linux-powerpc64le-gnu": "4.14.3",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.8.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.14.3",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.8.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.14.3",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.8.0",
|
"@rollup/rollup-linux-x64-gnu": "4.14.3",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.8.0",
|
"@rollup/rollup-linux-x64-musl": "4.14.3",
|
||||||
|
"@rollup/rollup-win32-arm64-msvc": "4.14.3",
|
||||||
|
"@rollup/rollup-win32-ia32-msvc": "4.14.3",
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "4.14.3",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -11887,9 +11966,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.0.2",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -12249,9 +12328,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar": {
|
"node_modules/tar": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||||
"integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==",
|
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chownr": "^2.0.0",
|
"chownr": "^2.0.0",
|
||||||
"fs-minipass": "^2.0.0",
|
"fs-minipass": "^2.0.0",
|
||||||
@@ -13447,14 +13526,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.0.12",
|
"version": "5.2.9",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.9.tgz",
|
||||||
"integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==",
|
"integrity": "sha512-uOQWfuZBlc6Y3W/DTuQ1Sr+oIXWvqljLvS881SVmAj00d5RdgShLcuXWxseWPd4HXwiYBFW/vXHfKFeqj9uQnw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.19.3",
|
"esbuild": "^0.20.1",
|
||||||
"postcss": "^8.4.32",
|
"postcss": "^8.4.38",
|
||||||
"rollup": "^4.2.0"
|
"rollup": "^4.13.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"vite": "bin/vite.js"
|
"vite": "bin/vite.js"
|
||||||
@@ -13589,9 +13668,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/android-arm": {
|
"node_modules/vite/node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
||||||
"integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==",
|
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -13605,9 +13684,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/android-arm64": {
|
"node_modules/vite/node_modules/@esbuild/android-arm64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
||||||
"integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==",
|
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -13621,9 +13700,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/android-x64": {
|
"node_modules/vite/node_modules/@esbuild/android-x64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
||||||
"integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==",
|
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -13637,9 +13716,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/darwin-arm64": {
|
"node_modules/vite/node_modules/@esbuild/darwin-arm64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
||||||
"integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==",
|
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -13653,9 +13732,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/darwin-x64": {
|
"node_modules/vite/node_modules/@esbuild/darwin-x64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
||||||
"integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==",
|
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -13669,9 +13748,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
|
"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
||||||
"integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==",
|
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -13685,9 +13764,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
|
"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
||||||
"integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==",
|
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -13701,9 +13780,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-arm": {
|
"node_modules/vite/node_modules/@esbuild/linux-arm": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
||||||
"integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==",
|
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -13717,9 +13796,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-arm64": {
|
"node_modules/vite/node_modules/@esbuild/linux-arm64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
||||||
"integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==",
|
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -13733,9 +13812,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-ia32": {
|
"node_modules/vite/node_modules/@esbuild/linux-ia32": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
||||||
"integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==",
|
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -13749,9 +13828,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-loong64": {
|
"node_modules/vite/node_modules/@esbuild/linux-loong64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
||||||
"integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==",
|
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -13765,9 +13844,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
|
"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
||||||
"integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==",
|
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"mips64el"
|
"mips64el"
|
||||||
],
|
],
|
||||||
@@ -13781,9 +13860,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
|
"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
||||||
"integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==",
|
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -13797,9 +13876,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
|
"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
||||||
"integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==",
|
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -13813,9 +13892,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-s390x": {
|
"node_modules/vite/node_modules/@esbuild/linux-s390x": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
||||||
"integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==",
|
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -13829,9 +13908,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/linux-x64": {
|
"node_modules/vite/node_modules/@esbuild/linux-x64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
||||||
"integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==",
|
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -13845,9 +13924,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
|
"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
||||||
"integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==",
|
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -13861,9 +13940,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
|
"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
||||||
"integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==",
|
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -13877,9 +13956,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/sunos-x64": {
|
"node_modules/vite/node_modules/@esbuild/sunos-x64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
||||||
"integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==",
|
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -13893,9 +13972,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/win32-arm64": {
|
"node_modules/vite/node_modules/@esbuild/win32-arm64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
||||||
"integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==",
|
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -13909,9 +13988,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/win32-ia32": {
|
"node_modules/vite/node_modules/@esbuild/win32-ia32": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
||||||
"integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==",
|
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -13925,9 +14004,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/@esbuild/win32-x64": {
|
"node_modules/vite/node_modules/@esbuild/win32-x64": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
||||||
"integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==",
|
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -13941,9 +14020,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite/node_modules/esbuild": {
|
"node_modules/vite/node_modules/esbuild": {
|
||||||
"version": "0.19.9",
|
"version": "0.20.2",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
||||||
"integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==",
|
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -13953,28 +14032,29 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@esbuild/android-arm": "0.19.9",
|
"@esbuild/aix-ppc64": "0.20.2",
|
||||||
"@esbuild/android-arm64": "0.19.9",
|
"@esbuild/android-arm": "0.20.2",
|
||||||
"@esbuild/android-x64": "0.19.9",
|
"@esbuild/android-arm64": "0.20.2",
|
||||||
"@esbuild/darwin-arm64": "0.19.9",
|
"@esbuild/android-x64": "0.20.2",
|
||||||
"@esbuild/darwin-x64": "0.19.9",
|
"@esbuild/darwin-arm64": "0.20.2",
|
||||||
"@esbuild/freebsd-arm64": "0.19.9",
|
"@esbuild/darwin-x64": "0.20.2",
|
||||||
"@esbuild/freebsd-x64": "0.19.9",
|
"@esbuild/freebsd-arm64": "0.20.2",
|
||||||
"@esbuild/linux-arm": "0.19.9",
|
"@esbuild/freebsd-x64": "0.20.2",
|
||||||
"@esbuild/linux-arm64": "0.19.9",
|
"@esbuild/linux-arm": "0.20.2",
|
||||||
"@esbuild/linux-ia32": "0.19.9",
|
"@esbuild/linux-arm64": "0.20.2",
|
||||||
"@esbuild/linux-loong64": "0.19.9",
|
"@esbuild/linux-ia32": "0.20.2",
|
||||||
"@esbuild/linux-mips64el": "0.19.9",
|
"@esbuild/linux-loong64": "0.20.2",
|
||||||
"@esbuild/linux-ppc64": "0.19.9",
|
"@esbuild/linux-mips64el": "0.20.2",
|
||||||
"@esbuild/linux-riscv64": "0.19.9",
|
"@esbuild/linux-ppc64": "0.20.2",
|
||||||
"@esbuild/linux-s390x": "0.19.9",
|
"@esbuild/linux-riscv64": "0.20.2",
|
||||||
"@esbuild/linux-x64": "0.19.9",
|
"@esbuild/linux-s390x": "0.20.2",
|
||||||
"@esbuild/netbsd-x64": "0.19.9",
|
"@esbuild/linux-x64": "0.20.2",
|
||||||
"@esbuild/openbsd-x64": "0.19.9",
|
"@esbuild/netbsd-x64": "0.20.2",
|
||||||
"@esbuild/sunos-x64": "0.19.9",
|
"@esbuild/openbsd-x64": "0.20.2",
|
||||||
"@esbuild/win32-arm64": "0.19.9",
|
"@esbuild/sunos-x64": "0.20.2",
|
||||||
"@esbuild/win32-ia32": "0.19.9",
|
"@esbuild/win32-arm64": "0.20.2",
|
||||||
"@esbuild/win32-x64": "0.19.9"
|
"@esbuild/win32-ia32": "0.20.2",
|
||||||
|
"@esbuild/win32-x64": "0.20.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitest": {
|
"node_modules/vitest": {
|
||||||
|
|||||||
@@ -108,10 +108,11 @@
|
|||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"mysql2": "^3.9.1",
|
"mysql2": "^3.9.4",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
"nodemailer": "^6.9.9",
|
"nodemailer": "^6.9.9",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
|
"oracledb": "^6.4.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -103,11 +103,15 @@ export const ${dalName} = (db: TDbClient) => {
|
|||||||
`import { z } from "zod";
|
`import { z } from "zod";
|
||||||
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 { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
|
||||||
export const register${pascalCase}Router = async (server: FastifyZodProvider) => {
|
export const register${pascalCase}Router = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({}),
|
params: z.object({}),
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ const prompt = promptSync({ sigint: true });
|
|||||||
|
|
||||||
const migrationName = prompt("Enter name for migration: ");
|
const migrationName = prompt("Enter name for migration: ");
|
||||||
|
|
||||||
|
// Remove spaces from migration name and replace with hyphens
|
||||||
|
const formattedMigrationName = migrationName.replace(/\s+/g, "-");
|
||||||
|
|
||||||
execSync(
|
execSync(
|
||||||
`npx knex migrate:make --knexfile ${path.join(
|
`npx knex migrate:make --knexfile ${path.join(__dirname, "../src/db/knexfile.ts")} -x ts ${formattedMigrationName}`,
|
||||||
__dirname,
|
|
||||||
"../src/db/knexfile.ts"
|
|
||||||
)} -x ts ${migrationName}`,
|
|
||||||
{ stdio: "inherit" }
|
{ stdio: "inherit" }
|
||||||
);
|
);
|
||||||
|
|||||||
Vendored
+15
-2
@@ -3,9 +3,14 @@ import "fastify";
|
|||||||
import { TUsers } from "@app/db/schemas";
|
import { TUsers } from "@app/db/schemas";
|
||||||
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service";
|
||||||
|
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
||||||
|
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
||||||
|
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
||||||
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||||
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 { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-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 { 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";
|
||||||
@@ -19,8 +24,9 @@ import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
|||||||
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
|
||||||
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
|
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
|
||||||
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
|
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
|
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||||
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
||||||
@@ -59,9 +65,10 @@ declare module "fastify" {
|
|||||||
// 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
|
||||||
auth: TAuthMode;
|
auth: TAuthMode;
|
||||||
permission: {
|
permission: {
|
||||||
|
authMethod: ActorAuthMethod;
|
||||||
type: ActorType;
|
type: ActorType;
|
||||||
id: string;
|
id: string;
|
||||||
orgId?: string;
|
orgId: string;
|
||||||
};
|
};
|
||||||
// passport data
|
// passport data
|
||||||
passportUser: {
|
passportUser: {
|
||||||
@@ -84,6 +91,8 @@ declare module "fastify" {
|
|||||||
orgRole: TOrgRoleServiceFactory;
|
orgRole: TOrgRoleServiceFactory;
|
||||||
superAdmin: TSuperAdminServiceFactory;
|
superAdmin: TSuperAdminServiceFactory;
|
||||||
user: TUserServiceFactory;
|
user: TUserServiceFactory;
|
||||||
|
group: TGroupServiceFactory;
|
||||||
|
groupProject: TGroupProjectServiceFactory;
|
||||||
apiKey: TApiKeyServiceFactory;
|
apiKey: TApiKeyServiceFactory;
|
||||||
project: TProjectServiceFactory;
|
project: TProjectServiceFactory;
|
||||||
projectMembership: TProjectMembershipServiceFactory;
|
projectMembership: TProjectMembershipServiceFactory;
|
||||||
@@ -116,6 +125,10 @@ declare module "fastify" {
|
|||||||
trustedIp: TTrustedIpServiceFactory;
|
trustedIp: TTrustedIpServiceFactory;
|
||||||
secretBlindIndex: TSecretBlindIndexServiceFactory;
|
secretBlindIndex: TSecretBlindIndexServiceFactory;
|
||||||
telemetry: TTelemetryServiceFactory;
|
telemetry: TTelemetryServiceFactory;
|
||||||
|
dynamicSecret: TDynamicSecretServiceFactory;
|
||||||
|
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
|
||||||
|
projectUserAdditionalPrivilege: TProjectUserAdditionalPrivilegeServiceFactory;
|
||||||
|
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
|||||||
Vendored
+56
@@ -17,12 +17,27 @@ import {
|
|||||||
TBackupPrivateKey,
|
TBackupPrivateKey,
|
||||||
TBackupPrivateKeyInsert,
|
TBackupPrivateKeyInsert,
|
||||||
TBackupPrivateKeyUpdate,
|
TBackupPrivateKeyUpdate,
|
||||||
|
TDynamicSecretLeases,
|
||||||
|
TDynamicSecretLeasesInsert,
|
||||||
|
TDynamicSecretLeasesUpdate,
|
||||||
|
TDynamicSecrets,
|
||||||
|
TDynamicSecretsInsert,
|
||||||
|
TDynamicSecretsUpdate,
|
||||||
TGitAppInstallSessions,
|
TGitAppInstallSessions,
|
||||||
TGitAppInstallSessionsInsert,
|
TGitAppInstallSessionsInsert,
|
||||||
TGitAppInstallSessionsUpdate,
|
TGitAppInstallSessionsUpdate,
|
||||||
TGitAppOrg,
|
TGitAppOrg,
|
||||||
TGitAppOrgInsert,
|
TGitAppOrgInsert,
|
||||||
TGitAppOrgUpdate,
|
TGitAppOrgUpdate,
|
||||||
|
TGroupProjectMembershipRoles,
|
||||||
|
TGroupProjectMembershipRolesInsert,
|
||||||
|
TGroupProjectMembershipRolesUpdate,
|
||||||
|
TGroupProjectMemberships,
|
||||||
|
TGroupProjectMembershipsInsert,
|
||||||
|
TGroupProjectMembershipsUpdate,
|
||||||
|
TGroups,
|
||||||
|
TGroupsInsert,
|
||||||
|
TGroupsUpdate,
|
||||||
TIdentities,
|
TIdentities,
|
||||||
TIdentitiesInsert,
|
TIdentitiesInsert,
|
||||||
TIdentitiesUpdate,
|
TIdentitiesUpdate,
|
||||||
@@ -32,6 +47,9 @@ import {
|
|||||||
TIdentityOrgMemberships,
|
TIdentityOrgMemberships,
|
||||||
TIdentityOrgMembershipsInsert,
|
TIdentityOrgMembershipsInsert,
|
||||||
TIdentityOrgMembershipsUpdate,
|
TIdentityOrgMembershipsUpdate,
|
||||||
|
TIdentityProjectAdditionalPrivilege,
|
||||||
|
TIdentityProjectAdditionalPrivilegeInsert,
|
||||||
|
TIdentityProjectAdditionalPrivilegeUpdate,
|
||||||
TIdentityProjectMembershipRole,
|
TIdentityProjectMembershipRole,
|
||||||
TIdentityProjectMembershipRoleInsert,
|
TIdentityProjectMembershipRoleInsert,
|
||||||
TIdentityProjectMembershipRoleUpdate,
|
TIdentityProjectMembershipRoleUpdate,
|
||||||
@@ -86,6 +104,9 @@ import {
|
|||||||
TProjects,
|
TProjects,
|
||||||
TProjectsInsert,
|
TProjectsInsert,
|
||||||
TProjectsUpdate,
|
TProjectsUpdate,
|
||||||
|
TProjectUserAdditionalPrivilege,
|
||||||
|
TProjectUserAdditionalPrivilegeInsert,
|
||||||
|
TProjectUserAdditionalPrivilegeUpdate,
|
||||||
TProjectUserMembershipRoles,
|
TProjectUserMembershipRoles,
|
||||||
TProjectUserMembershipRolesInsert,
|
TProjectUserMembershipRolesInsert,
|
||||||
TProjectUserMembershipRolesUpdate,
|
TProjectUserMembershipRolesUpdate,
|
||||||
@@ -176,6 +197,9 @@ import {
|
|||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
TUserEncryptionKeysInsert,
|
TUserEncryptionKeysInsert,
|
||||||
TUserEncryptionKeysUpdate,
|
TUserEncryptionKeysUpdate,
|
||||||
|
TUserGroupMembership,
|
||||||
|
TUserGroupMembershipInsert,
|
||||||
|
TUserGroupMembershipUpdate,
|
||||||
TUsers,
|
TUsers,
|
||||||
TUsersInsert,
|
TUsersInsert,
|
||||||
TUsersUpdate,
|
TUsersUpdate,
|
||||||
@@ -187,6 +211,22 @@ import {
|
|||||||
declare module "knex/types/tables" {
|
declare module "knex/types/tables" {
|
||||||
interface Tables {
|
interface Tables {
|
||||||
[TableName.Users]: Knex.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
|
[TableName.Users]: Knex.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
|
||||||
|
[TableName.Groups]: Knex.CompositeTableType<TGroups, TGroupsInsert, TGroupsUpdate>;
|
||||||
|
[TableName.UserGroupMembership]: Knex.CompositeTableType<
|
||||||
|
TUserGroupMembership,
|
||||||
|
TUserGroupMembershipInsert,
|
||||||
|
TUserGroupMembershipUpdate
|
||||||
|
>;
|
||||||
|
[TableName.GroupProjectMembership]: Knex.CompositeTableType<
|
||||||
|
TGroupProjectMemberships,
|
||||||
|
TGroupProjectMembershipsInsert,
|
||||||
|
TGroupProjectMembershipsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.GroupProjectMembershipRole]: Knex.CompositeTableType<
|
||||||
|
TGroupProjectMembershipRoles,
|
||||||
|
TGroupProjectMembershipRolesInsert,
|
||||||
|
TGroupProjectMembershipRolesUpdate
|
||||||
|
>;
|
||||||
[TableName.UserAliases]: Knex.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>;
|
[TableName.UserAliases]: Knex.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>;
|
||||||
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
|
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
|
||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
@@ -233,6 +273,11 @@ declare module "knex/types/tables" {
|
|||||||
TProjectUserMembershipRolesUpdate
|
TProjectUserMembershipRolesUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.ProjectRoles]: Knex.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>;
|
[TableName.ProjectRoles]: Knex.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>;
|
||||||
|
[TableName.ProjectUserAdditionalPrivilege]: Knex.CompositeTableType<
|
||||||
|
TProjectUserAdditionalPrivilege,
|
||||||
|
TProjectUserAdditionalPrivilegeInsert,
|
||||||
|
TProjectUserAdditionalPrivilegeUpdate
|
||||||
|
>;
|
||||||
[TableName.ProjectKeys]: Knex.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>;
|
[TableName.ProjectKeys]: Knex.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>;
|
||||||
[TableName.Secret]: Knex.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>;
|
[TableName.Secret]: Knex.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>;
|
||||||
[TableName.SecretBlindIndex]: Knex.CompositeTableType<
|
[TableName.SecretBlindIndex]: Knex.CompositeTableType<
|
||||||
@@ -288,6 +333,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityProjectMembershipRoleInsert,
|
TIdentityProjectMembershipRoleInsert,
|
||||||
TIdentityProjectMembershipRoleUpdate
|
TIdentityProjectMembershipRoleUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityProjectAdditionalPrivilege]: Knex.CompositeTableType<
|
||||||
|
TIdentityProjectAdditionalPrivilege,
|
||||||
|
TIdentityProjectAdditionalPrivilegeInsert,
|
||||||
|
TIdentityProjectAdditionalPrivilegeUpdate
|
||||||
|
>;
|
||||||
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
|
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
|
||||||
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
|
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
|
||||||
TSecretApprovalPolicies,
|
TSecretApprovalPolicies,
|
||||||
@@ -340,6 +390,12 @@ declare module "knex/types/tables" {
|
|||||||
TSecretSnapshotFoldersInsert,
|
TSecretSnapshotFoldersInsert,
|
||||||
TSecretSnapshotFoldersUpdate
|
TSecretSnapshotFoldersUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.DynamicSecret]: Knex.CompositeTableType<TDynamicSecrets, TDynamicSecretsInsert, TDynamicSecretsUpdate>;
|
||||||
|
[TableName.DynamicSecretLease]: Knex.CompositeTableType<
|
||||||
|
TDynamicSecretLeases,
|
||||||
|
TDynamicSecretLeasesInsert,
|
||||||
|
TDynamicSecretLeasesUpdate
|
||||||
|
>;
|
||||||
[TableName.SamlConfig]: Knex.CompositeTableType<TSamlConfigs, TSamlConfigsInsert, TSamlConfigsUpdate>;
|
[TableName.SamlConfig]: Knex.CompositeTableType<TSamlConfigs, TSamlConfigsInsert, TSamlConfigsUpdate>;
|
||||||
[TableName.LdapConfig]: Knex.CompositeTableType<TLdapConfigs, TLdapConfigsInsert, TLdapConfigsUpdate>;
|
[TableName.LdapConfig]: Knex.CompositeTableType<TLdapConfigs, TLdapConfigsInsert, TLdapConfigsUpdate>;
|
||||||
[TableName.OrgBot]: Knex.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>;
|
[TableName.OrgBot]: Knex.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>;
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesTableExist = await knex.schema.hasTable(TableName.DynamicSecret);
|
||||||
|
if (!doesTableExist) {
|
||||||
|
await knex.schema.createTable(TableName.DynamicSecret, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("name").notNullable();
|
||||||
|
t.integer("version").notNullable();
|
||||||
|
t.string("type").notNullable();
|
||||||
|
t.string("defaultTTL").notNullable();
|
||||||
|
t.string("maxTTL");
|
||||||
|
t.string("inputIV").notNullable();
|
||||||
|
t.text("inputCiphertext").notNullable();
|
||||||
|
t.string("inputTag").notNullable();
|
||||||
|
t.string("algorithm").notNullable().defaultTo(SecretEncryptionAlgo.AES_256_GCM);
|
||||||
|
t.string("keyEncoding").notNullable().defaultTo(SecretKeyEncoding.UTF8);
|
||||||
|
t.uuid("folderId").notNullable();
|
||||||
|
// for background process communication
|
||||||
|
t.string("status");
|
||||||
|
t.string("statusDetails");
|
||||||
|
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
|
||||||
|
t.unique(["name", "folderId"]);
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.DynamicSecret);
|
||||||
|
|
||||||
|
const doesTableDynamicSecretLease = await knex.schema.hasTable(TableName.DynamicSecretLease);
|
||||||
|
if (!doesTableDynamicSecretLease) {
|
||||||
|
await knex.schema.createTable(TableName.DynamicSecretLease, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.integer("version").notNullable();
|
||||||
|
t.string("externalEntityId").notNullable();
|
||||||
|
t.datetime("expireAt").notNullable();
|
||||||
|
// for background process communication
|
||||||
|
t.string("status");
|
||||||
|
t.string("statusDetails");
|
||||||
|
t.uuid("dynamicSecretId").notNullable();
|
||||||
|
t.foreign("dynamicSecretId").references("id").inTable(TableName.DynamicSecret).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.DynamicSecretLease);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.DynamicSecretLease);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.DynamicSecretLease);
|
||||||
|
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.DynamicSecret);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.DynamicSecret);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.ProjectUserAdditionalPrivilege))) {
|
||||||
|
await knex.schema.createTable(TableName.ProjectUserAdditionalPrivilege, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("slug", 60).notNullable();
|
||||||
|
t.uuid("projectMembershipId").notNullable();
|
||||||
|
t.foreign("projectMembershipId").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
|
||||||
|
t.boolean("isTemporary").notNullable().defaultTo(false);
|
||||||
|
t.string("temporaryMode");
|
||||||
|
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
|
||||||
|
t.datetime("temporaryAccessStartTime");
|
||||||
|
t.datetime("temporaryAccessEndTime");
|
||||||
|
t.jsonb("permissions").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ProjectUserAdditionalPrivilege);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ProjectUserAdditionalPrivilege);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ProjectUserAdditionalPrivilege);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.IdentityProjectAdditionalPrivilege))) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityProjectAdditionalPrivilege, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("slug", 60).notNullable();
|
||||||
|
t.uuid("projectMembershipId").notNullable();
|
||||||
|
t.foreign("projectMembershipId")
|
||||||
|
.references("id")
|
||||||
|
.inTable(TableName.IdentityProjectMembership)
|
||||||
|
.onDelete("CASCADE");
|
||||||
|
t.boolean("isTemporary").notNullable().defaultTo(false);
|
||||||
|
t.string("temporaryMode");
|
||||||
|
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
|
||||||
|
t.datetime("temporaryAccessStartTime");
|
||||||
|
t.datetime("temporaryAccessEndTime");
|
||||||
|
t.jsonb("permissions").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.IdentityProjectAdditionalPrivilege);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.IdentityProjectAdditionalPrivilege);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityProjectAdditionalPrivilege);
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TableName, TOrgMemberships } from "../schemas";
|
||||||
|
|
||||||
|
const validateOrgMembership = (membershipToValidate: TOrgMemberships, firstMembership: TOrgMemberships) => {
|
||||||
|
const firstOrgId = firstMembership.orgId;
|
||||||
|
const firstUserId = firstMembership.userId;
|
||||||
|
|
||||||
|
if (membershipToValidate.id === firstMembership.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (membershipToValidate.inviteEmail !== firstMembership.inviteEmail) {
|
||||||
|
throw new Error(`Invite emails are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
|
||||||
|
}
|
||||||
|
if (membershipToValidate.orgId !== firstMembership.orgId) {
|
||||||
|
throw new Error(`OrgIds are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
|
||||||
|
}
|
||||||
|
if (membershipToValidate.role !== firstMembership.role) {
|
||||||
|
throw new Error(`Roles are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
|
||||||
|
}
|
||||||
|
if (membershipToValidate.roleId !== firstMembership.roleId) {
|
||||||
|
throw new Error(`RoleIds are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
|
||||||
|
}
|
||||||
|
if (membershipToValidate.status !== firstMembership.status) {
|
||||||
|
throw new Error(`Statuses are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
|
||||||
|
}
|
||||||
|
if (membershipToValidate.userId !== firstMembership.userId) {
|
||||||
|
throw new Error(`UserIds are different for the same userId and orgId: ${firstUserId}, ${firstOrgId}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const RowSchema = z.object({
|
||||||
|
userId: z.string(),
|
||||||
|
orgId: z.string(),
|
||||||
|
cnt: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Transactional find and delete duplicate rows
|
||||||
|
await knex.transaction(async (tx) => {
|
||||||
|
const duplicateRows = await tx(TableName.OrgMembership)
|
||||||
|
.select("userId", "orgId") // Select the userId and orgId so we can group by them
|
||||||
|
.count("* as cnt") // Count the number of rows for each userId and orgId, so we can make sure there are more than 1 row (a duplicate)
|
||||||
|
.groupBy("userId", "orgId")
|
||||||
|
.havingRaw("count(*) > ?", [1]); // Using havingRaw for direct SQL expressions
|
||||||
|
|
||||||
|
// Parse the rows to ensure they are in the correct format, and for type safety
|
||||||
|
const parsedRows = RowSchema.array().parse(duplicateRows);
|
||||||
|
|
||||||
|
// For each of the duplicate rows, loop through and find the actual memberships to delete
|
||||||
|
for (const row of parsedRows) {
|
||||||
|
const count = Number(row.cnt);
|
||||||
|
|
||||||
|
// An extra check to ensure that the count is actually a number, and the number is greater than 2
|
||||||
|
if (typeof count !== "number" || count < 2) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all the organization memberships that have the same userId and orgId
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const rowsToDelete = await tx(TableName.OrgMembership).where({
|
||||||
|
userId: row.userId,
|
||||||
|
orgId: row.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure that all the rows have exactly the same value, except id, createdAt, updatedAt
|
||||||
|
for (const rowToDelete of rowsToDelete) {
|
||||||
|
validateOrgMembership(rowToDelete, rowsToDelete[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the row with the latest createdAt, which we will keep
|
||||||
|
|
||||||
|
let lowestCreatedAt: number | null = null;
|
||||||
|
let latestCreatedRow: TOrgMemberships | null = null;
|
||||||
|
|
||||||
|
for (const rowToDelete of rowsToDelete) {
|
||||||
|
if (lowestCreatedAt === null || rowToDelete.createdAt.getTime() < lowestCreatedAt) {
|
||||||
|
lowestCreatedAt = rowToDelete.createdAt.getTime();
|
||||||
|
latestCreatedRow = rowToDelete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!latestCreatedRow) {
|
||||||
|
throw new Error("Failed to find last created membership");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out the latest row from the rows to delete
|
||||||
|
const membershipIdsToDelete = rowsToDelete.map((r) => r.id).filter((id) => id !== latestCreatedRow!.id);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const numberOfRowsDeleted = await tx(TableName.OrgMembership).whereIn("id", membershipIdsToDelete).delete();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(
|
||||||
|
`Deleted ${numberOfRowsDeleted} duplicate organization memberships for ${row.userId} and ${row.orgId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.OrgMembership, (table) => {
|
||||||
|
table.unique(["userId", "orgId"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.OrgMembership, (table) => {
|
||||||
|
table.dropUnique(["userId", "orgId"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
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.Groups))) {
|
||||||
|
await knex.schema.createTable(TableName.Groups, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.uuid("orgId").notNullable();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
t.string("name").notNullable();
|
||||||
|
t.string("slug").notNullable();
|
||||||
|
t.unique(["orgId", "slug"]);
|
||||||
|
t.string("role").notNullable();
|
||||||
|
t.uuid("roleId");
|
||||||
|
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.Groups);
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.UserGroupMembership))) {
|
||||||
|
await knex.schema.createTable(TableName.UserGroupMembership, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); // link to user and link to groups cascade on groups
|
||||||
|
t.uuid("userId").notNullable();
|
||||||
|
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
t.uuid("groupId").notNullable();
|
||||||
|
t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.UserGroupMembership);
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.GroupProjectMembership))) {
|
||||||
|
await knex.schema.createTable(TableName.GroupProjectMembership, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("projectId").notNullable();
|
||||||
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
t.uuid("groupId").notNullable();
|
||||||
|
t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await createOnUpdateTrigger(knex, TableName.GroupProjectMembership);
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.GroupProjectMembershipRole))) {
|
||||||
|
await knex.schema.createTable(TableName.GroupProjectMembershipRole, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("role").notNullable();
|
||||||
|
t.uuid("projectMembershipId").notNullable();
|
||||||
|
t.foreign("projectMembershipId").references("id").inTable(TableName.GroupProjectMembership).onDelete("CASCADE");
|
||||||
|
// until role is changed/removed the role should not deleted
|
||||||
|
t.uuid("customRoleId");
|
||||||
|
t.foreign("customRoleId").references("id").inTable(TableName.ProjectRoles);
|
||||||
|
t.boolean("isTemporary").notNullable().defaultTo(false);
|
||||||
|
t.string("temporaryMode");
|
||||||
|
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
|
||||||
|
t.datetime("temporaryAccessStartTime");
|
||||||
|
t.datetime("temporaryAccessEndTime");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.GroupProjectMembershipRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.GroupProjectMembershipRole);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.GroupProjectMembershipRole);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.UserGroupMembership);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.UserGroupMembership);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.GroupProjectMembership);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.GroupProjectMembership);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.Groups);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.Groups);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { ProjectMembershipRole, TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesProjectRoleFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "role");
|
||||||
|
const doesProjectRoleIdFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "roleId");
|
||||||
|
await knex.schema.alterTable(TableName.ProjectMembership, (t) => {
|
||||||
|
if (doesProjectRoleFieldExist) t.dropColumn("roleId");
|
||||||
|
if (doesProjectRoleIdFieldExist) t.dropColumn("role");
|
||||||
|
});
|
||||||
|
|
||||||
|
const doesIdentityProjectRoleFieldExist = await knex.schema.hasColumn(TableName.IdentityProjectMembership, "role");
|
||||||
|
const doesIdentityProjectRoleIdFieldExist = await knex.schema.hasColumn(
|
||||||
|
TableName.IdentityProjectMembership,
|
||||||
|
"roleId"
|
||||||
|
);
|
||||||
|
await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => {
|
||||||
|
if (doesIdentityProjectRoleFieldExist) t.dropColumn("roleId");
|
||||||
|
if (doesIdentityProjectRoleIdFieldExist) t.dropColumn("role");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const doesProjectRoleFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "role");
|
||||||
|
const doesProjectRoleIdFieldExist = await knex.schema.hasColumn(TableName.ProjectMembership, "roleId");
|
||||||
|
await knex.schema.alterTable(TableName.ProjectMembership, (t) => {
|
||||||
|
if (!doesProjectRoleFieldExist) t.string("role").defaultTo(ProjectMembershipRole.Member);
|
||||||
|
if (!doesProjectRoleIdFieldExist) {
|
||||||
|
t.uuid("roleId");
|
||||||
|
t.foreign("roleId").references("id").inTable(TableName.ProjectRoles);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const doesIdentityProjectRoleFieldExist = await knex.schema.hasColumn(TableName.IdentityProjectMembership, "role");
|
||||||
|
const doesIdentityProjectRoleIdFieldExist = await knex.schema.hasColumn(
|
||||||
|
TableName.IdentityProjectMembership,
|
||||||
|
"roleId"
|
||||||
|
);
|
||||||
|
await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => {
|
||||||
|
if (!doesIdentityProjectRoleFieldExist) t.string("role").defaultTo(ProjectMembershipRole.Member);
|
||||||
|
if (!doesIdentityProjectRoleIdFieldExist) {
|
||||||
|
t.uuid("roleId");
|
||||||
|
t.foreign("roleId").references("id").inTable(TableName.ProjectRoles);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const DynamicSecretLeasesSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
version: z.number(),
|
||||||
|
externalEntityId: z.string(),
|
||||||
|
expireAt: z.date(),
|
||||||
|
status: z.string().nullable().optional(),
|
||||||
|
statusDetails: z.string().nullable().optional(),
|
||||||
|
dynamicSecretId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TDynamicSecretLeases = z.infer<typeof DynamicSecretLeasesSchema>;
|
||||||
|
export type TDynamicSecretLeasesInsert = Omit<z.input<typeof DynamicSecretLeasesSchema>, TImmutableDBKeys>;
|
||||||
|
export type TDynamicSecretLeasesUpdate = Partial<Omit<z.input<typeof DynamicSecretLeasesSchema>, TImmutableDBKeys>>;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// 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 DynamicSecretsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
version: z.number(),
|
||||||
|
type: z.string(),
|
||||||
|
defaultTTL: z.string(),
|
||||||
|
maxTTL: z.string().nullable().optional(),
|
||||||
|
inputIV: z.string(),
|
||||||
|
inputCiphertext: z.string(),
|
||||||
|
inputTag: z.string(),
|
||||||
|
algorithm: z.string().default("aes-256-gcm"),
|
||||||
|
keyEncoding: z.string().default("utf8"),
|
||||||
|
folderId: z.string().uuid(),
|
||||||
|
status: z.string().nullable().optional(),
|
||||||
|
statusDetails: z.string().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
|
||||||
|
export type TDynamicSecretsInsert = Omit<z.input<typeof DynamicSecretsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TDynamicSecretsUpdate = Partial<Omit<z.input<typeof DynamicSecretsSchema>, TImmutableDBKeys>>;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// 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 GroupProjectMembershipRolesSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
role: z.string(),
|
||||||
|
projectMembershipId: z.string().uuid(),
|
||||||
|
customRoleId: z.string().uuid().nullable().optional(),
|
||||||
|
isTemporary: z.boolean().default(false),
|
||||||
|
temporaryMode: z.string().nullable().optional(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGroupProjectMembershipRoles = z.infer<typeof GroupProjectMembershipRolesSchema>;
|
||||||
|
export type TGroupProjectMembershipRolesInsert = Omit<
|
||||||
|
z.input<typeof GroupProjectMembershipRolesSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TGroupProjectMembershipRolesUpdate = Partial<
|
||||||
|
Omit<z.input<typeof GroupProjectMembershipRolesSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// 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 GroupProjectMembershipsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
projectId: z.string(),
|
||||||
|
groupId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGroupProjectMemberships = z.infer<typeof GroupProjectMembershipsSchema>;
|
||||||
|
export type TGroupProjectMembershipsInsert = Omit<z.input<typeof GroupProjectMembershipsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TGroupProjectMembershipsUpdate = Partial<
|
||||||
|
Omit<z.input<typeof GroupProjectMembershipsSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const GroupsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
roleId: z.string().uuid().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGroups = z.infer<typeof GroupsSchema>;
|
||||||
|
export type TGroupsInsert = Omit<z.input<typeof GroupsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TGroupsUpdate = Partial<Omit<z.input<typeof GroupsSchema>, TImmutableDBKeys>>;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// 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 IdentityProjectAdditionalPrivilegeSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
slug: z.string(),
|
||||||
|
projectMembershipId: z.string().uuid(),
|
||||||
|
isTemporary: z.boolean().default(false),
|
||||||
|
temporaryMode: z.string().nullable().optional(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||||
|
permissions: z.unknown(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIdentityProjectAdditionalPrivilege = z.infer<typeof IdentityProjectAdditionalPrivilegeSchema>;
|
||||||
|
export type TIdentityProjectAdditionalPrivilegeInsert = Omit<
|
||||||
|
z.input<typeof IdentityProjectAdditionalPrivilegeSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TIdentityProjectAdditionalPrivilegeUpdate = Partial<
|
||||||
|
Omit<z.input<typeof IdentityProjectAdditionalPrivilegeSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -9,8 +9,6 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const IdentityProjectMembershipsSchema = z.object({
|
export const IdentityProjectMembershipsSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
role: z.string(),
|
|
||||||
roleId: z.string().uuid().nullable().optional(),
|
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
identityId: z.string().uuid(),
|
identityId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
|
|||||||
@@ -3,11 +3,17 @@ export * from "./audit-logs";
|
|||||||
export * from "./auth-token-sessions";
|
export * from "./auth-token-sessions";
|
||||||
export * from "./auth-tokens";
|
export * from "./auth-tokens";
|
||||||
export * from "./backup-private-key";
|
export * from "./backup-private-key";
|
||||||
|
export * from "./dynamic-secret-leases";
|
||||||
|
export * from "./dynamic-secrets";
|
||||||
export * from "./git-app-install-sessions";
|
export * from "./git-app-install-sessions";
|
||||||
export * from "./git-app-org";
|
export * from "./git-app-org";
|
||||||
|
export * from "./group-project-membership-roles";
|
||||||
|
export * from "./group-project-memberships";
|
||||||
|
export * from "./groups";
|
||||||
export * from "./identities";
|
export * from "./identities";
|
||||||
export * from "./identity-access-tokens";
|
export * from "./identity-access-tokens";
|
||||||
export * from "./identity-org-memberships";
|
export * from "./identity-org-memberships";
|
||||||
|
export * from "./identity-project-additional-privilege";
|
||||||
export * from "./identity-project-membership-role";
|
export * from "./identity-project-membership-role";
|
||||||
export * from "./identity-project-memberships";
|
export * from "./identity-project-memberships";
|
||||||
export * from "./identity-ua-client-secrets";
|
export * from "./identity-ua-client-secrets";
|
||||||
@@ -26,6 +32,7 @@ export * from "./project-environments";
|
|||||||
export * from "./project-keys";
|
export * from "./project-keys";
|
||||||
export * from "./project-memberships";
|
export * from "./project-memberships";
|
||||||
export * from "./project-roles";
|
export * from "./project-roles";
|
||||||
|
export * from "./project-user-additional-privilege";
|
||||||
export * from "./project-user-membership-roles";
|
export * from "./project-user-membership-roles";
|
||||||
export * from "./projects";
|
export * from "./projects";
|
||||||
export * from "./saml-configs";
|
export * from "./saml-configs";
|
||||||
@@ -57,5 +64,6 @@ export * from "./trusted-ips";
|
|||||||
export * from "./user-actions";
|
export * from "./user-actions";
|
||||||
export * from "./user-aliases";
|
export * from "./user-aliases";
|
||||||
export * from "./user-encryption-keys";
|
export * from "./user-encryption-keys";
|
||||||
|
export * from "./user-group-membership";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
export * from "./webhooks";
|
export * from "./webhooks";
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export enum TableName {
|
export enum TableName {
|
||||||
Users = "users",
|
Users = "users",
|
||||||
|
Groups = "groups",
|
||||||
|
GroupProjectMembership = "group_project_memberships",
|
||||||
|
GroupProjectMembershipRole = "group_project_membership_roles",
|
||||||
|
UserGroupMembership = "user_group_membership",
|
||||||
UserAliases = "user_aliases",
|
UserAliases = "user_aliases",
|
||||||
UserEncryptionKey = "user_encryption_keys",
|
UserEncryptionKey = "user_encryption_keys",
|
||||||
AuthTokens = "auth_tokens",
|
AuthTokens = "auth_tokens",
|
||||||
@@ -20,6 +24,7 @@ export enum TableName {
|
|||||||
Environment = "project_environments",
|
Environment = "project_environments",
|
||||||
ProjectMembership = "project_memberships",
|
ProjectMembership = "project_memberships",
|
||||||
ProjectRoles = "project_roles",
|
ProjectRoles = "project_roles",
|
||||||
|
ProjectUserAdditionalPrivilege = "project_user_additional_privilege",
|
||||||
ProjectUserMembershipRole = "project_user_membership_roles",
|
ProjectUserMembershipRole = "project_user_membership_roles",
|
||||||
ProjectKeys = "project_keys",
|
ProjectKeys = "project_keys",
|
||||||
Secret = "secrets",
|
Secret = "secrets",
|
||||||
@@ -43,6 +48,7 @@ export enum TableName {
|
|||||||
IdentityOrgMembership = "identity_org_memberships",
|
IdentityOrgMembership = "identity_org_memberships",
|
||||||
IdentityProjectMembership = "identity_project_memberships",
|
IdentityProjectMembership = "identity_project_memberships",
|
||||||
IdentityProjectMembershipRole = "identity_project_membership_role",
|
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||||
|
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
||||||
ScimToken = "scim_tokens",
|
ScimToken = "scim_tokens",
|
||||||
SecretApprovalPolicy = "secret_approval_policies",
|
SecretApprovalPolicy = "secret_approval_policies",
|
||||||
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
||||||
@@ -59,6 +65,8 @@ export enum TableName {
|
|||||||
GitAppOrg = "git_app_org",
|
GitAppOrg = "git_app_org",
|
||||||
SecretScanningGitRisk = "secret_scanning_git_risks",
|
SecretScanningGitRisk = "secret_scanning_git_risks",
|
||||||
TrustedIps = "trusted_ips",
|
TrustedIps = "trusted_ips",
|
||||||
|
DynamicSecret = "dynamic_secrets",
|
||||||
|
DynamicSecretLease = "dynamic_secret_leases",
|
||||||
// junction tables with tags
|
// junction tables with tags
|
||||||
JnSecretTag = "secret_tag_junction",
|
JnSecretTag = "secret_tag_junction",
|
||||||
SecretVersionTag = "secret_version_tag_junction"
|
SecretVersionTag = "secret_version_tag_junction"
|
||||||
|
|||||||
@@ -9,12 +9,10 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const ProjectMembershipsSchema = z.object({
|
export const ProjectMembershipsSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
role: z.string(),
|
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
userId: z.string().uuid(),
|
userId: z.string().uuid(),
|
||||||
projectId: z.string(),
|
projectId: z.string()
|
||||||
roleId: z.string().uuid().nullable().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjectMemberships = z.infer<typeof ProjectMembershipsSchema>;
|
export type TProjectMemberships = z.infer<typeof ProjectMembershipsSchema>;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// 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 ProjectUserAdditionalPrivilegeSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
slug: z.string(),
|
||||||
|
projectMembershipId: z.string().uuid(),
|
||||||
|
isTemporary: z.boolean().default(false),
|
||||||
|
temporaryMode: z.string().nullable().optional(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||||
|
permissions: z.unknown(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TProjectUserAdditionalPrivilege = z.infer<typeof ProjectUserAdditionalPrivilegeSchema>;
|
||||||
|
export type TProjectUserAdditionalPrivilegeInsert = Omit<
|
||||||
|
z.input<typeof ProjectUserAdditionalPrivilegeSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TProjectUserAdditionalPrivilegeUpdate = Partial<
|
||||||
|
Omit<z.input<typeof ProjectUserAdditionalPrivilegeSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// 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 UserGroupMembershipSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
userId: z.string().uuid(),
|
||||||
|
groupId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TUserGroupMembership = z.infer<typeof UserGroupMembershipSchema>;
|
||||||
|
export type TUserGroupMembershipInsert = Omit<z.input<typeof UserGroupMembershipSchema>, TImmutableDBKeys>;
|
||||||
|
export type TUserGroupMembershipUpdate = Partial<Omit<z.input<typeof UserGroupMembershipSchema>, TImmutableDBKeys>>;
|
||||||
@@ -33,8 +33,7 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
const projectMembership = await knex(TableName.ProjectMembership)
|
const projectMembership = await knex(TableName.ProjectMembership)
|
||||||
.insert({
|
.insert({
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
userId: seedData1.id,
|
userId: seedData1.id
|
||||||
role: ProjectMembershipRole.Admin
|
|
||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
await knex(TableName.ProjectUserMembershipRole).insert({
|
await knex(TableName.ProjectUserMembershipRole).insert({
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
const identityProjectMembership = await knex(TableName.IdentityProjectMembership)
|
const identityProjectMembership = await knex(TableName.IdentityProjectMembership)
|
||||||
.insert({
|
.insert({
|
||||||
identityId: seedData1.machineIdentity.id,
|
identityId: seedData1.machineIdentity.id,
|
||||||
projectId: seedData1.project.id,
|
projectId: seedData1.project.id
|
||||||
role: ProjectMembershipRole.Admin
|
|
||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
||||||
|
import { DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs";
|
||||||
|
import { daysToMillisecond } from "@app/lib/dates";
|
||||||
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
dynamicSecretName: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.dynamicSecretName).toLowerCase(),
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.projectSlug),
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.describe(DYNAMIC_SECRET_LEASES.CREATE.ttl)
|
||||||
|
.superRefine((val, ctx) => {
|
||||||
|
if (!val) return;
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||||
|
if (valMs > daysToMillisecond(1))
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
}),
|
||||||
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRET_LEASES.CREATE.path),
|
||||||
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.path)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
lease: DynamicSecretLeasesSchema,
|
||||||
|
dynamicSecret: SanitizedDynamicSecretSchema,
|
||||||
|
data: z.unknown()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { data, lease, dynamicSecret } = await server.services.dynamicSecretLease.create({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
name: req.body.dynamicSecretName,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
return { lease, data, dynamicSecret };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:leaseId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.leaseId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.projectSlug),
|
||||||
|
path: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.trim()
|
||||||
|
.default("/")
|
||||||
|
.transform(removeTrailingSlash)
|
||||||
|
.describe(DYNAMIC_SECRET_LEASES.DELETE.path),
|
||||||
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.DELETE.environmentSlug),
|
||||||
|
isForced: z.boolean().default(false).describe(DYNAMIC_SECRET_LEASES.DELETE.isForced)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
lease: DynamicSecretLeasesSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const lease = await server.services.dynamicSecretLease.revokeLease({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
leaseId: req.params.leaseId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
return { lease };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:leaseId/renew",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.leaseId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
ttl: z
|
||||||
|
.string()
|
||||||
|
.describe(DYNAMIC_SECRET_LEASES.RENEW.ttl)
|
||||||
|
.optional()
|
||||||
|
.superRefine((val, ctx) => {
|
||||||
|
if (!val) return;
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||||
|
if (valMs > daysToMillisecond(1))
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
}),
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.projectSlug),
|
||||||
|
path: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.trim()
|
||||||
|
.default("/")
|
||||||
|
.transform(removeTrailingSlash)
|
||||||
|
.describe(DYNAMIC_SECRET_LEASES.RENEW.path),
|
||||||
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.ttl)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
lease: DynamicSecretLeasesSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const lease = await server.services.dynamicSecretLease.renewLease({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
leaseId: req.params.leaseId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
return { lease };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:leaseId",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
leaseId: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.leaseId)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.projectSlug),
|
||||||
|
path: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.default("/")
|
||||||
|
.transform(removeTrailingSlash)
|
||||||
|
.describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.path),
|
||||||
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.GET_BY_LEASEID.environmentSlug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
lease: DynamicSecretLeasesSchema.extend({
|
||||||
|
dynamicSecret: SanitizedDynamicSecretSchema
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const lease = await server.services.dynamicSecretLease.getLeaseDetails({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
leaseId: req.params.leaseId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
return { lease };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
||||||
|
import { DynamicSecretProviderSchema } from "@app/ee/services/dynamic-secret/providers/models";
|
||||||
|
import { DYNAMIC_SECRETS } from "@app/lib/api-docs";
|
||||||
|
import { daysToMillisecond } from "@app/lib/dates";
|
||||||
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerDynamicSecretRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.CREATE.projectSlug),
|
||||||
|
provider: DynamicSecretProviderSchema.describe(DYNAMIC_SECRETS.CREATE.provider),
|
||||||
|
defaultTTL: z
|
||||||
|
.string()
|
||||||
|
.describe(DYNAMIC_SECRETS.CREATE.defaultTTL)
|
||||||
|
.superRefine((val, ctx) => {
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||||
|
if (valMs > daysToMillisecond(1))
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
}),
|
||||||
|
maxTTL: z
|
||||||
|
.string()
|
||||||
|
.describe(DYNAMIC_SECRETS.CREATE.maxTTL)
|
||||||
|
.optional()
|
||||||
|
.superRefine((val, ctx) => {
|
||||||
|
if (!val) return;
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||||
|
if (valMs > daysToMillisecond(1))
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
|
path: z.string().describe(DYNAMIC_SECRETS.CREATE.path).trim().default("/").transform(removeTrailingSlash),
|
||||||
|
environmentSlug: z.string().describe(DYNAMIC_SECRETS.CREATE.environmentSlug).min(1),
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.describe(DYNAMIC_SECRETS.CREATE.name)
|
||||||
|
.min(1)
|
||||||
|
.toLowerCase()
|
||||||
|
.max(64)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid"
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
dynamicSecret: SanitizedDynamicSecretSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const dynamicSecretCfg = await server.services.dynamicSecret.create({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
return { dynamicSecret: dynamicSecretCfg };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:name",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.UPDATE.name)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.UPDATE.projectSlug),
|
||||||
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRETS.UPDATE.path),
|
||||||
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.UPDATE.environmentSlug),
|
||||||
|
data: z.object({
|
||||||
|
inputs: z.any().optional().describe(DYNAMIC_SECRETS.UPDATE.inputs),
|
||||||
|
defaultTTL: z
|
||||||
|
.string()
|
||||||
|
.describe(DYNAMIC_SECRETS.UPDATE.defaultTTL)
|
||||||
|
.optional()
|
||||||
|
.superRefine((val, ctx) => {
|
||||||
|
if (!val) return;
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||||
|
if (valMs > daysToMillisecond(1))
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
}),
|
||||||
|
maxTTL: z
|
||||||
|
.string()
|
||||||
|
.describe(DYNAMIC_SECRETS.UPDATE.maxTTL)
|
||||||
|
.optional()
|
||||||
|
.superRefine((val, ctx) => {
|
||||||
|
if (!val) return;
|
||||||
|
const valMs = ms(val);
|
||||||
|
if (valMs < 60 * 1000)
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
|
||||||
|
if (valMs > daysToMillisecond(1))
|
||||||
|
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
|
newName: z.string().describe(DYNAMIC_SECRETS.UPDATE.newName).optional()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
dynamicSecret: SanitizedDynamicSecretSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const dynamicSecretCfg = await server.services.dynamicSecret.updateByName({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
name: req.params.name,
|
||||||
|
path: req.body.path,
|
||||||
|
projectSlug: req.body.projectSlug,
|
||||||
|
environmentSlug: req.body.environmentSlug,
|
||||||
|
...req.body.data
|
||||||
|
});
|
||||||
|
return { dynamicSecret: dynamicSecretCfg };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:name",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
name: z.string().toLowerCase().describe(DYNAMIC_SECRETS.DELETE.name)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.DELETE.projectSlug),
|
||||||
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRETS.DELETE.path),
|
||||||
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.DELETE.environmentSlug),
|
||||||
|
isForced: z.boolean().default(false).describe(DYNAMIC_SECRETS.DELETE.isForced)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
dynamicSecret: SanitizedDynamicSecretSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const dynamicSecretCfg = await server.services.dynamicSecret.deleteByName({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
name: req.params.name,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
return { dynamicSecret: dynamicSecretCfg };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:name",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
name: z.string().min(1).describe(DYNAMIC_SECRETS.GET_BY_NAME.name)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.GET_BY_NAME.projectSlug),
|
||||||
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRETS.GET_BY_NAME.path),
|
||||||
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.GET_BY_NAME.environmentSlug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
dynamicSecret: SanitizedDynamicSecretSchema.extend({
|
||||||
|
inputs: z.unknown()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const dynamicSecretCfg = await server.services.dynamicSecret.getDetails({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
name: req.params.name,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
return { dynamicSecret: dynamicSecretCfg };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST.projectSlug),
|
||||||
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRETS.LIST.path),
|
||||||
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST.environmentSlug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
dynamicSecrets: SanitizedDynamicSecretSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const dynamicSecretCfgs = await server.services.dynamicSecret.list({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
return { dynamicSecrets: dynamicSecretCfgs };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:name/leases",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
name: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.name)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
projectSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.projectSlug),
|
||||||
|
path: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.default("/")
|
||||||
|
.transform(removeTrailingSlash)
|
||||||
|
.describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.path),
|
||||||
|
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRETS.LIST_LEAES_BY_NAME.environmentSlug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
leases: DynamicSecretLeasesSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const leases = await server.services.dynamicSecretLease.listLeases({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
name: req.params.name,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
return { leases };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { GroupsSchema, OrgMembershipRole, UsersSchema } from "@app/db/schemas";
|
||||||
|
import { GROUPS } from "@app/lib/api-docs";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "POST",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().trim().min(1).max(50).describe(GROUPS.CREATE.name),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(5)
|
||||||
|
.max(36)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(GROUPS.CREATE.slug),
|
||||||
|
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(GROUPS.CREATE.role)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: GroupsSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await server.services.group.createGroup({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:currentSlug",
|
||||||
|
method: "PATCH",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
currentSlug: z.string().trim().describe(GROUPS.UPDATE.currentSlug)
|
||||||
|
}),
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
name: z.string().trim().min(1).describe(GROUPS.UPDATE.name),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(5)
|
||||||
|
.max(36)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.describe(GROUPS.UPDATE.slug),
|
||||||
|
role: z.string().trim().min(1).describe(GROUPS.UPDATE.role)
|
||||||
|
})
|
||||||
|
.partial(),
|
||||||
|
response: {
|
||||||
|
200: GroupsSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await server.services.group.updateGroup({
|
||||||
|
currentSlug: req.params.currentSlug,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:slug",
|
||||||
|
method: "DELETE",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
slug: z.string().trim().describe(GROUPS.DELETE.slug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: GroupsSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await server.services.group.deleteGroup({
|
||||||
|
groupSlug: req.params.slug,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:slug/users",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
slug: z.string().trim().describe(GROUPS.LIST_USERS.slug)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
|
||||||
|
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
|
||||||
|
username: z.string().optional().describe(GROUPS.LIST_USERS.username)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
users: UsersSchema.pick({
|
||||||
|
email: true,
|
||||||
|
username: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
id: true
|
||||||
|
})
|
||||||
|
.merge(
|
||||||
|
z.object({
|
||||||
|
isPartOfGroup: z.boolean()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.array(),
|
||||||
|
totalCount: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { users, totalCount } = await server.services.group.listGroupUsers({
|
||||||
|
groupSlug: req.params.slug,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
return { users, totalCount };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:slug/users/:username",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
slug: z.string().trim().describe(GROUPS.ADD_USER.slug),
|
||||||
|
username: z.string().trim().describe(GROUPS.ADD_USER.username)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: UsersSchema.pick({
|
||||||
|
email: true,
|
||||||
|
username: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
id: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await server.services.group.addUserToGroup({
|
||||||
|
groupSlug: req.params.slug,
|
||||||
|
username: req.params.username,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:slug/users/:username",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
slug: z.string().trim().describe(GROUPS.DELETE_USER.slug),
|
||||||
|
username: z.string().trim().describe(GROUPS.DELETE_USER.username)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: UsersSchema.pick({
|
||||||
|
email: true,
|
||||||
|
username: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
id: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const user = await server.services.group.removeUserFromGroup({
|
||||||
|
groupSlug: req.params.slug,
|
||||||
|
username: req.params.username,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { IdentityProjectAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||||
|
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
||||||
|
import { ProjectPermissionSet } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/permanent",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Create a permanent or a non expiry specific privilege for identity.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
body: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
|
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.body,
|
||||||
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
|
isTemporary: false,
|
||||||
|
permissions: JSON.stringify(packRules(req.body.permissions))
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/temporary",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Create a temporary or a expiring specific privilege for identity.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
body: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.projectSlug),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
|
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
||||||
|
temporaryMode: z
|
||||||
|
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
||||||
|
temporaryRange: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilege.create({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.body,
|
||||||
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
|
isTemporary: true,
|
||||||
|
permissions: JSON.stringify(packRules(req.body.permissions))
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Update a specific privilege of an identity.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
body: z.object({
|
||||||
|
// disallow empty string
|
||||||
|
privilegeSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.projectSlug),
|
||||||
|
privilegeDetails: z
|
||||||
|
.object({
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.newSlug),
|
||||||
|
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||||
|
isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
||||||
|
temporaryMode: z
|
||||||
|
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
|
||||||
|
temporaryRange: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.temporaryAccessStartTime)
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const updatedInfo = req.body.privilegeDetails;
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilege.updateBySlug({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
slug: req.body.privilegeSlug,
|
||||||
|
identityId: req.body.identityId,
|
||||||
|
projectSlug: req.body.projectSlug,
|
||||||
|
data: {
|
||||||
|
...updatedInfo,
|
||||||
|
permissions: updatedInfo?.permissions ? JSON.stringify(packRules(updatedInfo.permissions)) : undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Delete a specific privilege of an identity.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
body: z.object({
|
||||||
|
privilegeSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.DELETE.slug),
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.DELETE.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.DELETE.projectSlug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilege.deleteBySlug({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
slug: req.body.privilegeSlug,
|
||||||
|
identityId: req.body.identityId,
|
||||||
|
projectSlug: req.body.projectSlug
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:privilegeSlug",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Retrieve details of a specific privilege by privilege slug.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
privilegeSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.GET_BY_SLUG.slug)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.GET_BY_SLUG.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.GET_BY_SLUG.projectSlug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: IdentityProjectAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilege.getPrivilegeDetailsBySlug({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
slug: req.params.privilegeSlug,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "List of a specific privilege of an identity in a project.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
querystring: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.projectSlug),
|
||||||
|
unpacked: z
|
||||||
|
.enum(["false", "true"])
|
||||||
|
.transform((el) => el === "true")
|
||||||
|
.default("true")
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.unpacked)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privileges: IdentityProjectAdditionalPrivilegeSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privileges = await server.services.identityProjectAdditionalPrivilege.listIdentityProjectPrivileges({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
if (req.query.unpacked) {
|
||||||
|
return {
|
||||||
|
privileges: privileges.map(({ permissions, ...el }) => ({
|
||||||
|
...el,
|
||||||
|
permissions: unpackRules(permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { privileges };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
||||||
|
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
||||||
|
import { registerGroupRouter } from "./group-router";
|
||||||
|
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
||||||
import { registerLdapRouter } from "./ldap-router";
|
import { registerLdapRouter } from "./ldap-router";
|
||||||
import { registerLicenseRouter } from "./license-router";
|
import { registerLicenseRouter } from "./license-router";
|
||||||
import { registerOrgRoleRouter } from "./org-role-router";
|
import { registerOrgRoleRouter } from "./org-role-router";
|
||||||
@@ -13,6 +17,7 @@ import { registerSecretScanningRouter } from "./secret-scanning-router";
|
|||||||
import { registerSecretVersionRouter } from "./secret-version-router";
|
import { registerSecretVersionRouter } from "./secret-version-router";
|
||||||
import { registerSnapshotRouter } from "./snapshot-router";
|
import { registerSnapshotRouter } from "./snapshot-router";
|
||||||
import { registerTrustedIpRouter } from "./trusted-ip-router";
|
import { registerTrustedIpRouter } from "./trusted-ip-router";
|
||||||
|
import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router";
|
||||||
|
|
||||||
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||||
// org role starts with organization
|
// org role starts with organization
|
||||||
@@ -34,10 +39,27 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerSecretRotationProviderRouter, {
|
await server.register(registerSecretRotationProviderRouter, {
|
||||||
prefix: "/secret-rotation-providers"
|
prefix: "/secret-rotation-providers"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await server.register(
|
||||||
|
async (dynamicSecretRouter) => {
|
||||||
|
await dynamicSecretRouter.register(registerDynamicSecretRouter);
|
||||||
|
await dynamicSecretRouter.register(registerDynamicSecretLeaseRouter, { prefix: "/leases" });
|
||||||
|
},
|
||||||
|
{ prefix: "/dynamic-secrets" }
|
||||||
|
);
|
||||||
|
|
||||||
await server.register(registerSamlRouter, { prefix: "/sso" });
|
await server.register(registerSamlRouter, { prefix: "/sso" });
|
||||||
await server.register(registerScimRouter, { prefix: "/scim" });
|
await server.register(registerScimRouter, { prefix: "/scim" });
|
||||||
await server.register(registerLdapRouter, { prefix: "/ldap" });
|
await server.register(registerLdapRouter, { prefix: "/ldap" });
|
||||||
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" });
|
||||||
|
await server.register(registerGroupRouter, { prefix: "/groups" });
|
||||||
|
await server.register(
|
||||||
|
async (privilegeRouter) => {
|
||||||
|
await privilegeRouter.register(registerUserAdditionalPrivilegeRouter, { prefix: "/users" });
|
||||||
|
await privilegeRouter.register(registerIdentityProjectAdditionalPrivilegeRouter, { prefix: "/identity" });
|
||||||
|
},
|
||||||
|
{ prefix: "/additional-privilege" }
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { z } from "zod";
|
|||||||
import { LdapConfigsSchema } from "@app/db/schemas";
|
import { LdapConfigsSchema } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
@@ -97,8 +98,11 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
@@ -122,6 +126,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
orgId: req.query.organizationId,
|
orgId: req.query.organizationId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
return ldap;
|
return ldap;
|
||||||
@@ -129,8 +134,11 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -151,6 +159,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
orgId: req.body.organizationId,
|
orgId: req.body.organizationId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
@@ -162,6 +171,9 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
url: "/config",
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z
|
body: z
|
||||||
@@ -184,6 +196,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
orgId: req.body.organizationId,
|
orgId: req.body.organizationId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,13 +3,17 @@
|
|||||||
// TODO(akhilmhdh): Fix this when licence service gets it type
|
// TODO(akhilmhdh): Fix this when licence service gets it type
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/plans/table",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/plans/table",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({ billingCycle: z.enum(["monthly", "yearly"]) }),
|
querystring: z.object({ billingCycle: z.enum(["monthly", "yearly"]) }),
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
@@ -24,6 +28,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId,
|
orgId: req.params.organizationId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
billingCycle: req.query.billingCycle
|
billingCycle: req.query.billingCycle
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -31,8 +36,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/plan",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/plan",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -45,6 +53,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
return { plan };
|
return { plan };
|
||||||
@@ -52,8 +61,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/plans",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/plans",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
querystring: z.object({ workspaceId: z.string().trim().optional() }),
|
querystring: z.object({ workspaceId: z.string().trim().optional() }),
|
||||||
@@ -66,6 +78,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
const data = await server.services.license.getOrgPlan({
|
const data = 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -73,8 +87,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/session/trial",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:organizationId/session/trial",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({ success_url: z.string().trim() }),
|
body: z.object({ success_url: z.string().trim() }),
|
||||||
@@ -89,6 +106,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId,
|
orgId: req.params.organizationId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
success_url: req.body.success_url
|
success_url: req.body.success_url
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -98,6 +116,9 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/customer-portal-session",
|
url: "/:organizationId/customer-portal-session",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -110,6 +131,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -117,8 +139,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/plan/billing",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/plan/billing",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -131,6 +156,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -138,8 +164,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/plan/table",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/plan/table",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -152,6 +181,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -159,8 +189,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/billing-details",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -173,6 +206,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -180,8 +214,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/:organizationId/billing-details",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -198,6 +235,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
orgId: req.params.organizationId,
|
orgId: req.params.organizationId,
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
email: req.body.email
|
email: req.body.email
|
||||||
@@ -207,8 +245,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/payment-methods",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/billing-details/payment-methods",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -221,6 +262,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
@@ -228,8 +270,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/payment-methods",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:organizationId/billing-details/payment-methods",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -246,6 +291,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
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
|
||||||
@@ -255,8 +301,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/payment-methods/:pmtMethodId",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:organizationId/billing-details/payment-methods/:pmtMethodId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
@@ -271,6 +320,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId,
|
orgId: req.params.organizationId,
|
||||||
pmtMethodId: req.params.pmtMethodId
|
pmtMethodId: req.params.pmtMethodId
|
||||||
@@ -280,8 +330,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/tax-ids",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/billing-details/tax-ids",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -295,6 +348,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
@@ -303,8 +357,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/tax-ids",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:organizationId/billing-details/tax-ids",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -322,6 +379,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId,
|
orgId: req.params.organizationId,
|
||||||
type: req.body.type,
|
type: req.body.type,
|
||||||
@@ -332,8 +390,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/billing-details/tax-ids/:taxId",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:organizationId/billing-details/tax-ids/:taxId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
@@ -348,6 +409,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId,
|
orgId: req.params.organizationId,
|
||||||
taxId: req.params.taxId
|
taxId: req.params.taxId
|
||||||
@@ -357,8 +419,11 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/invoices",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/invoices",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -373,15 +438,19 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId,
|
||||||
|
actorAuthMethod: req.permission.authMethod
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:organizationId/licenses",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:organizationId/licenses",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -396,6 +465,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import slugify from "@sindresorhus/slugify";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
@@ -9,6 +10,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:organizationId/roles",
|
url: "/:organizationId/roles",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -19,7 +23,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
.min(1)
|
.min(1)
|
||||||
.trim()
|
.trim()
|
||||||
.refine(
|
.refine(
|
||||||
(val) => Object.keys(OrgMembershipRole).includes(val),
|
(val) => !Object.keys(OrgMembershipRole).includes(val),
|
||||||
"Please choose a different slug, the slug you have entered is reserved"
|
"Please choose a different slug, the slug you have entered is reserved"
|
||||||
)
|
)
|
||||||
.refine((v) => slugify(v) === v, {
|
.refine((v) => slugify(v) === v, {
|
||||||
@@ -41,6 +45,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
req.permission.id,
|
req.permission.id,
|
||||||
req.params.organizationId,
|
req.params.organizationId,
|
||||||
req.body,
|
req.body,
|
||||||
|
req.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
return { role };
|
return { role };
|
||||||
@@ -50,6 +55,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:organizationId/roles/:roleId",
|
url: "/:organizationId/roles/:roleId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
@@ -84,6 +92,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
req.params.organizationId,
|
req.params.organizationId,
|
||||||
req.params.roleId,
|
req.params.roleId,
|
||||||
req.body,
|
req.body,
|
||||||
|
req.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
return { role };
|
return { role };
|
||||||
@@ -93,6 +102,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:organizationId/roles/:roleId",
|
url: "/:organizationId/roles/:roleId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
@@ -110,6 +122,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.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
return { role };
|
return { role };
|
||||||
@@ -119,6 +132,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:organizationId/roles",
|
url: "/:organizationId/roles",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -138,6 +154,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
const roles = await server.services.orgRole.listRoles(
|
const roles = await server.services.orgRole.listRoles(
|
||||||
req.permission.id,
|
req.permission.id,
|
||||||
req.params.organizationId,
|
req.params.organizationId,
|
||||||
|
req.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
return { data: { roles } };
|
return { data: { roles } };
|
||||||
@@ -147,6 +164,9 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:organizationId/permissions",
|
url: "/:organizationId/permissions",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
@@ -163,6 +183,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
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.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
return { permissions, membership };
|
return { permissions, membership };
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
import { ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
@@ -8,6 +9,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:projectId/roles",
|
url: "/:projectId/roles",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim()
|
projectId: z.string().trim()
|
||||||
@@ -31,6 +35,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
req.permission.id,
|
req.permission.id,
|
||||||
req.params.projectId,
|
req.params.projectId,
|
||||||
req.body,
|
req.body,
|
||||||
|
req.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
return { role };
|
return { role };
|
||||||
@@ -40,6 +45,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:projectId/roles/:roleId",
|
url: "/:projectId/roles/:roleId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim(),
|
projectId: z.string().trim(),
|
||||||
@@ -65,6 +73,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
req.params.projectId,
|
req.params.projectId,
|
||||||
req.params.roleId,
|
req.params.roleId,
|
||||||
req.body,
|
req.body,
|
||||||
|
req.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
return { role };
|
return { role };
|
||||||
@@ -74,6 +83,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/:projectId/roles/:roleId",
|
url: "/:projectId/roles/:roleId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim(),
|
projectId: z.string().trim(),
|
||||||
@@ -92,6 +104,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.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
return { role };
|
return { role };
|
||||||
@@ -101,6 +114,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:projectId/roles",
|
url: "/:projectId/roles",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim()
|
projectId: z.string().trim()
|
||||||
@@ -121,6 +137,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.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
return { data: { roles } };
|
return { data: { roles } };
|
||||||
@@ -130,6 +147,9 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:projectId/permissions",
|
url: "/:projectId/permissions",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim()
|
projectId: z.string().trim()
|
||||||
@@ -148,8 +168,10 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
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.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
|
|
||||||
return { data: { permissions, membership } };
|
return { data: { permissions, membership } };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
|
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
|
||||||
|
import { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
@@ -10,22 +12,24 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/secret-snapshots",
|
url: "/:workspaceId/secret-snapshots",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Return project secret snapshots ids",
|
description: "Return project secret snapshots ids",
|
||||||
security: [
|
security: [
|
||||||
{
|
{
|
||||||
apiKeyAuth: [],
|
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.workspaceId)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.environment),
|
||||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(PROJECTS.GET_SNAPSHOTS.path),
|
||||||
offset: z.coerce.number().default(0),
|
offset: z.coerce.number().default(0).describe(PROJECTS.GET_SNAPSHOTS.offset),
|
||||||
limit: z.coerce.number().default(20)
|
limit: z.coerce.number().default(20).describe(PROJECTS.GET_SNAPSHOTS.limit)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -37,6 +41,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const secretSnapshots = await server.services.snapshot.listSnapshots({
|
const secretSnapshots = await server.services.snapshot.listSnapshots({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.params.workspaceId,
|
projectId: req.params.workspaceId,
|
||||||
@@ -49,6 +54,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/secret-snapshots/count",
|
url: "/:workspaceId/secret-snapshots/count",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -68,6 +76,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.params.workspaceId,
|
projectId: req.params.workspaceId,
|
||||||
environment: req.query.environment,
|
environment: req.query.environment,
|
||||||
@@ -80,25 +89,27 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/audit-logs",
|
url: "/:workspaceId/audit-logs",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Return audit logs",
|
description: "Return audit logs",
|
||||||
security: [
|
security: [
|
||||||
{
|
{
|
||||||
bearerAuth: [],
|
bearerAuth: []
|
||||||
apiKeyAuth: []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.workspaceId)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
eventType: z.nativeEnum(EventType).optional(),
|
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
||||||
userAgentType: z.nativeEnum(UserAgentType).optional(),
|
userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType),
|
||||||
startDate: z.string().datetime().optional(),
|
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
|
||||||
endDate: z.string().datetime().optional(),
|
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
|
||||||
offset: z.coerce.number().default(0),
|
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
|
||||||
limit: z.coerce.number().default(20),
|
limit: z.coerce.number().default(20).describe(AUDIT_LOGS.EXPORT.limit),
|
||||||
actor: z.string().optional()
|
actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -129,8 +140,10 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
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,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId: req.params.workspaceId,
|
projectId: req.params.workspaceId,
|
||||||
...req.query,
|
...req.query,
|
||||||
|
startDate: req.query.endDate || getLastMidnightDateISO(),
|
||||||
auditLogActor: req.query.actor,
|
auditLogActor: req.query.actor,
|
||||||
actor: req.permission.type
|
actor: req.permission.type
|
||||||
});
|
});
|
||||||
@@ -141,6 +154,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/audit-logs/filters/actors",
|
url: "/:workspaceId/audit-logs/filters/actors",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml
|
|||||||
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 { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
@@ -203,8 +204,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
@@ -231,6 +235,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
orgId: req.query.organizationId,
|
orgId: req.query.organizationId,
|
||||||
type: "org"
|
type: "org"
|
||||||
});
|
});
|
||||||
@@ -239,8 +244,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -259,6 +267,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.body.organizationId,
|
orgId: req.body.organizationId,
|
||||||
...req.body
|
...req.body
|
||||||
@@ -268,8 +277,11 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/config",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z
|
body: z
|
||||||
@@ -290,6 +302,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.body.organizationId,
|
orgId: req.body.organizationId,
|
||||||
...req.body
|
...req.body
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ScimTokensSchema } from "@app/db/schemas";
|
import { ScimTokensSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
@@ -20,6 +21,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/scim-tokens",
|
url: "/scim-tokens",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
@@ -39,6 +43,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.body.organizationId,
|
orgId: req.body.organizationId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
description: req.body.description,
|
description: req.body.description,
|
||||||
ttlDays: req.body.ttlDays
|
ttlDays: req.body.ttlDays
|
||||||
});
|
});
|
||||||
@@ -50,6 +55,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/scim-tokens",
|
url: "/scim-tokens",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
@@ -65,6 +73,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
const scimTokens = await server.services.scim.listScimTokens({
|
const scimTokens = await server.services.scim.listScimTokens({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.query.organizationId
|
orgId: req.query.organizationId
|
||||||
});
|
});
|
||||||
@@ -76,6 +85,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/scim-tokens/:scimTokenId",
|
url: "/scim-tokens/:scimTokenId",
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
@@ -92,6 +104,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
scimTokenId: req.params.scimTokenId,
|
scimTokenId: req.params.scimTokenId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -143,7 +156,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
offset: req.query.startIndex,
|
offset: req.query.startIndex,
|
||||||
limit: req.query.count,
|
limit: req.query.count,
|
||||||
filter: req.query.filter,
|
filter: req.query.filter,
|
||||||
orgId: req.permission.orgId as string
|
orgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
@@ -181,7 +194,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const user = await req.server.services.scim.getScimUser({
|
const user = await req.server.services.scim.getScimUser({
|
||||||
userId: req.params.userId,
|
userId: req.params.userId,
|
||||||
orgId: req.permission.orgId as string
|
orgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@@ -193,7 +206,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
schemas: z.array(z.string()),
|
schemas: z.array(z.string()),
|
||||||
userName: z.string().trim().email(),
|
userName: z.string().trim(),
|
||||||
name: z.object({
|
name: z.object({
|
||||||
familyName: z.string().trim(),
|
familyName: z.string().trim(),
|
||||||
givenName: z.string().trim()
|
givenName: z.string().trim()
|
||||||
@@ -214,7 +227,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
schemas: z.array(z.string()),
|
schemas: z.array(z.string()),
|
||||||
id: z.string().trim(),
|
id: z.string().trim(),
|
||||||
userName: z.string().trim().email(),
|
userName: z.string().trim(),
|
||||||
name: z.object({
|
name: z.object({
|
||||||
familyName: z.string().trim(),
|
familyName: z.string().trim(),
|
||||||
givenName: z.string().trim()
|
givenName: z.string().trim()
|
||||||
@@ -240,7 +253,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
email: primaryEmail,
|
email: primaryEmail,
|
||||||
firstName: req.body.name.givenName,
|
firstName: req.body.name.givenName,
|
||||||
lastName: req.body.name.familyName,
|
lastName: req.body.name.familyName,
|
||||||
orgId: req.permission.orgId as string
|
orgId: req.permission.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
@@ -249,38 +262,257 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/Users/:userId",
|
url: "/Users/:userId",
|
||||||
method: "PATCH",
|
method: "DELETE",
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
userId: z.string().trim()
|
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: {
|
response: {
|
||||||
200: z.object({})
|
200: z.object({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const user = await req.server.services.scim.updateScimUser({
|
const user = await req.server.services.scim.deleteScimUser({
|
||||||
userId: req.params.userId,
|
userId: req.params.userId,
|
||||||
orgId: req.permission.orgId as string,
|
orgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z.array(z.any()).length(0).optional() // okta-specific
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z.array(z.any()).length(0),
|
||||||
|
meta: z.object({
|
||||||
|
resourceType: z.string().trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await req.server.services.scim.createScimGroup({
|
||||||
|
displayName: req.body.displayName,
|
||||||
|
orgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups",
|
||||||
|
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({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z.array(z.any()).length(0),
|
||||||
|
meta: z.object({
|
||||||
|
resourceType: z.string().trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
),
|
||||||
|
itemsPerPage: z.number(),
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
startIndex: z.number(),
|
||||||
|
totalResults: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const groups = await req.server.services.scim.listScimGroups({
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
offset: req.query.startIndex,
|
||||||
|
limit: req.query.count
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups/:groupId",
|
||||||
|
method: "GET",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
groupId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z.array(
|
||||||
|
z.object({
|
||||||
|
value: z.string(),
|
||||||
|
display: z.string()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
meta: z.object({
|
||||||
|
resourceType: z.string().trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await req.server.services.scim.getScimGroup({
|
||||||
|
groupId: req.params.groupId,
|
||||||
|
orgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups/:groupId",
|
||||||
|
method: "PUT",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
groupId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z.array(z.any()).length(0)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z.array(
|
||||||
|
z.object({
|
||||||
|
value: z.string(),
|
||||||
|
display: z.string()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
meta: z.object({
|
||||||
|
resourceType: z.string().trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await req.server.services.scim.updateScimGroupNamePut({
|
||||||
|
groupId: req.params.groupId,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
displayName: req.body.displayName
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups/:groupId",
|
||||||
|
method: "PATCH",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
groupId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
Operations: z.array(
|
||||||
|
z.union([
|
||||||
|
z.object({
|
||||||
|
op: z.literal("replace"),
|
||||||
|
value: z.object({
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
op: z.literal("remove"),
|
||||||
|
path: z.string().trim()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
op: z.literal("add"),
|
||||||
|
value: z.object({
|
||||||
|
value: z.string().trim(),
|
||||||
|
display: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
schemas: z.array(z.string()),
|
||||||
|
id: z.string().trim(),
|
||||||
|
displayName: z.string().trim(),
|
||||||
|
members: z.array(
|
||||||
|
z.object({
|
||||||
|
value: z.string(),
|
||||||
|
display: z.string()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
meta: z.object({
|
||||||
|
resourceType: z.string().trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
// console.log("PATCH /Groups/:groupId req.body: ", req.body);
|
||||||
|
// console.log("PATCH /Groups/:groupId req.body: ", req.body.Operations[0]);
|
||||||
|
const group = await req.server.services.scim.updateScimGroupNamePatch({
|
||||||
|
groupId: req.params.groupId,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
operations: req.body.Operations
|
operations: req.body.Operations
|
||||||
});
|
});
|
||||||
return user;
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/Groups/:groupId",
|
||||||
|
method: "DELETE",
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
groupId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const group = await req.server.services.scim.deleteScimGroup({
|
||||||
|
groupId: req.params.groupId,
|
||||||
|
orgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -327,7 +559,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const user = await req.server.services.scim.replaceScimUser({
|
const user = await req.server.services.scim.replaceScimUser({
|
||||||
userId: req.params.userId,
|
userId: req.params.userId,
|
||||||
orgId: req.permission.orgId as string,
|
orgId: req.permission.orgId,
|
||||||
active: req.body.active
|
active: req.body.active
|
||||||
});
|
});
|
||||||
return user;
|
return user;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -9,6 +10,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
@@ -34,6 +38,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.body.workspaceId,
|
projectId: req.body.workspaceId,
|
||||||
...req.body,
|
...req.body,
|
||||||
@@ -46,6 +51,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:sapId",
|
url: "/:sapId",
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sapId: z.string()
|
sapId: z.string()
|
||||||
@@ -72,6 +80,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
secretPolicyId: req.params.sapId
|
secretPolicyId: req.params.sapId
|
||||||
@@ -83,6 +92,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:sapId",
|
url: "/:sapId",
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
sapId: z.string()
|
sapId: z.string()
|
||||||
@@ -98,6 +110,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
secretPolicyId: req.params.sapId
|
secretPolicyId: req.params.sapId
|
||||||
});
|
});
|
||||||
@@ -108,6 +121,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -123,6 +139,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.query.workspaceId
|
projectId: req.query.workspaceId
|
||||||
});
|
});
|
||||||
@@ -133,6 +150,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/board",
|
url: "/board",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
@@ -150,6 +170,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.query.workspaceId,
|
projectId: req.query.workspaceId,
|
||||||
...req.query
|
...req.query
|
||||||
|
|||||||
@@ -10,13 +10,17 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
export const registerSecretApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
@@ -52,6 +56,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.query,
|
...req.query,
|
||||||
projectId: req.query.workspaceId
|
projectId: req.query.workspaceId
|
||||||
@@ -61,8 +66,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/count",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/count",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -81,6 +89,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.query.workspaceId
|
projectId: req.query.workspaceId
|
||||||
});
|
});
|
||||||
@@ -91,6 +100,9 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:id/merge",
|
url: "/:id/merge",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
id: z.string()
|
id: z.string()
|
||||||
@@ -106,6 +118,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
approvalId: req.params.id
|
approvalId: req.params.id
|
||||||
});
|
});
|
||||||
@@ -114,8 +127,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:id/review",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:id/review",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
id: z.string()
|
id: z.string()
|
||||||
@@ -134,6 +150,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
approvalId: req.params.id,
|
approvalId: req.params.id,
|
||||||
status: req.body.status
|
status: req.body.status
|
||||||
@@ -143,8 +160,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:id/status",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:id/status",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
id: z.string()
|
id: z.string()
|
||||||
@@ -163,6 +183,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
approvalId: req.params.id,
|
approvalId: req.params.id,
|
||||||
status: req.body.status
|
status: req.body.status
|
||||||
@@ -198,8 +219,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
.array()
|
.array()
|
||||||
.optional();
|
.optional();
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:id",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
id: z.string()
|
id: z.string()
|
||||||
@@ -271,6 +295,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.id
|
id: req.params.id
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
export const registerSecretRotationProviderRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationProviderRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:workspaceId",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:workspaceId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -30,6 +34,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.params.workspaceId
|
projectId: req.params.workspaceId
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas";
|
import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
export const registerSecretRotationRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretRotationRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
@@ -39,6 +43,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const secretRotation = await server.services.secretRotation.createRotation({
|
const secretRotation = await server.services.secretRotation.createRotation({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
@@ -51,6 +56,9 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/restart",
|
url: "/restart",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
id: z.string().trim()
|
id: z.string().trim()
|
||||||
@@ -74,6 +82,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
rotationId: req.body.id
|
rotationId: req.body.id
|
||||||
});
|
});
|
||||||
@@ -84,6 +93,9 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -125,6 +137,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.query.workspaceId
|
projectId: req.query.workspaceId
|
||||||
});
|
});
|
||||||
@@ -133,8 +146,11 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:id",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
id: z.string().trim()
|
id: z.string().trim()
|
||||||
@@ -158,6 +174,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
rotationId: req.params.id
|
rotationId: req.params.id
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
||||||
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
export const registerSecretScanningRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretScanningRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/create-installation-session/organization",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/create-installation-session/organization",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({ organizationId: z.string().trim() }),
|
body: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -22,6 +26,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.body.organizationId
|
orgId: req.body.organizationId
|
||||||
});
|
});
|
||||||
@@ -30,8 +35,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/link-installation",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/link-installation",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
installationId: z.string(),
|
installationId: z.string(),
|
||||||
@@ -46,6 +54,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
@@ -54,8 +63,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/installation-status/organization/:organizationId",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/installation-status/organization/:organizationId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -67,6 +79,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
@@ -77,6 +90,9 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/organization/:organizationId/risks",
|
url: "/organization/:organizationId/risks",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
response: {
|
response: {
|
||||||
@@ -88,6 +104,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId
|
||||||
});
|
});
|
||||||
@@ -96,8 +113,11 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/organization/:organizationId/risks/:riskId/status",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/organization/:organizationId/risks/:riskId/status",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim(), riskId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim(), riskId: z.string().trim() }),
|
||||||
body: z.object({ status: z.nativeEnum(SecretScanningRiskStatus) }),
|
body: z.object({ status: z.nativeEnum(SecretScanningRiskStatus) }),
|
||||||
@@ -110,6 +130,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId,
|
orgId: req.params.organizationId,
|
||||||
riskId: req.params.riskId,
|
riskId: req.params.riskId,
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretVersionsSchema } from "@app/db/schemas";
|
import { SecretVersionsSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
export const registerSecretVersionRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretVersionRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:secretId/secret-versions",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:secretId/secret-versions",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretId: z.string()
|
secretId: z.string()
|
||||||
@@ -27,6 +31,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
limit: req.query.limit,
|
limit: req.query.limit,
|
||||||
offset: req.query.offset,
|
offset: req.query.offset,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretSnapshotsSchema, SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas";
|
import { SecretSnapshotsSchema, SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas";
|
||||||
|
import { PROJECTS } from "@app/lib/api-docs";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
@@ -8,6 +10,9 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:secretSnapshotId",
|
url: "/:secretSnapshotId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretSnapshotId: z.string().trim()
|
secretSnapshotId: z.string().trim()
|
||||||
@@ -46,6 +51,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.secretSnapshotId
|
id: req.params.secretSnapshotId
|
||||||
});
|
});
|
||||||
@@ -56,16 +62,18 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/:secretSnapshotId/rollback",
|
url: "/:secretSnapshotId/rollback",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Roll back project secrets to those captured in a secret snapshot version.",
|
description: "Roll back project secrets to those captured in a secret snapshot version.",
|
||||||
security: [
|
security: [
|
||||||
{
|
{
|
||||||
apiKeyAuth: [],
|
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretSnapshotId: z.string().trim()
|
secretSnapshotId: z.string().trim().describe(PROJECTS.ROLLBACK_TO_SNAPSHOT.secretSnapshotId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -78,6 +86,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.secretSnapshotId
|
id: req.params.secretSnapshotId
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { TrustedIpsSchema } from "@app/db/schemas";
|
import { TrustedIpsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
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";
|
||||||
|
|
||||||
export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:workspaceId/trusted-ips",
|
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
url: "/:workspaceId/trusted-ips",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -22,6 +26,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const trustedIps = await server.services.trustedIp.listIpsByProjectId({
|
const trustedIps = await server.services.trustedIp.listIpsByProjectId({
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId: req.params.workspaceId,
|
projectId: req.params.workspaceId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -32,8 +37,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:workspaceId/trusted-ips",
|
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
url: "/:workspaceId/trusted-ips",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim()
|
||||||
@@ -52,6 +60,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { trustedIp, project } = await server.services.trustedIp.addProjectIp({
|
const { trustedIp, project } = await server.services.trustedIp.addProjectIp({
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId: req.params.workspaceId,
|
projectId: req.params.workspaceId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -76,8 +85,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
@@ -99,6 +111,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
trustedIpId: req.params.trustedIpId,
|
trustedIpId: req.params.trustedIpId,
|
||||||
...req.body
|
...req.body
|
||||||
@@ -121,8 +134,11 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
url: "/:workspaceId/trusted-ips/:trustedIpId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim(),
|
||||||
@@ -140,6 +156,7 @@ 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,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
trustedIpId: req.params.trustedIpId
|
trustedIpId: req.params.trustedIpId
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,256 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||||
|
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
||||||
|
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/permanent",
|
||||||
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
|
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.projectUserAdditionalPrivilege.create({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.body,
|
||||||
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
|
isTemporary: false,
|
||||||
|
permissions: JSON.stringify(req.body.permissions)
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/temporary",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
|
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
||||||
|
temporaryMode: z
|
||||||
|
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
||||||
|
temporaryRange: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.projectUserAdditionalPrivilege.create({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.body,
|
||||||
|
slug: req.body.slug ? slugify(req.body.slug) : `privilege-${slugify(alphaNumericNanoId(12))}`,
|
||||||
|
isTemporary: true,
|
||||||
|
permissions: JSON.stringify(req.body.permissions)
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:privilegeId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
privilegeId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.privilegeId)
|
||||||
|
}),
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
||||||
|
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||||
|
isTemporary: z.boolean().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
||||||
|
temporaryMode: z
|
||||||
|
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
|
||||||
|
temporaryRange: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryAccessStartTime)
|
||||||
|
})
|
||||||
|
.partial(),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.projectUserAdditionalPrivilege.updateById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.body,
|
||||||
|
permissions: req.body.permissions ? JSON.stringify(req.body.permissions) : undefined,
|
||||||
|
privilegeId: req.params.privilegeId
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:privilegeId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.DELETE.privilegeId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.projectUserAdditionalPrivilege.deleteById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
privilegeId: req.params.privilegeId
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
projectMembershipId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.LIST.projectMembershipId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privileges: ProjectUserAdditionalPrivilegeSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privileges = await server.services.projectUserAdditionalPrivilege.listPrivileges({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
projectMembershipId: req.query.projectMembershipId
|
||||||
|
});
|
||||||
|
return { privileges };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:privilegeId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.GET_BY_PRIVILEGEID.privilegeId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: ProjectUserAdditionalPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.projectUserAdditionalPrivilege.getPrivilegeDetailsById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
privilegeId: req.params.privilegeId
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -31,10 +31,17 @@ export const auditLogServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
projectId,
|
projectId,
|
||||||
auditLogActor
|
auditLogActor
|
||||||
}: TListProjectAuditLogDTO) => {
|
}: TListProjectAuditLogDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
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,
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { DynamicSecretLeasesSchema, TableName } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TDynamicSecretLeaseDALFactory = ReturnType<typeof dynamicSecretLeaseDALFactory>;
|
||||||
|
|
||||||
|
export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.DynamicSecretLease);
|
||||||
|
|
||||||
|
const countLeasesForDynamicSecret = async (dynamicSecretId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const doc = await (tx || db)(TableName.DynamicSecretLease).count("*").where({ dynamicSecretId }).first();
|
||||||
|
return parseInt(doc || "0", 10);
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "DynamicSecretCountLeases" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const doc = await (tx || db)(TableName.DynamicSecretLease)
|
||||||
|
.where({ [`${TableName.DynamicSecretLease}.id` as "id"]: id })
|
||||||
|
.first()
|
||||||
|
.join(
|
||||||
|
TableName.DynamicSecret,
|
||||||
|
`${TableName.DynamicSecretLease}.dynamicSecretId`,
|
||||||
|
`${TableName.DynamicSecret}.id`
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.DynamicSecretLease))
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.DynamicSecret).as("dynId"),
|
||||||
|
db.ref("name").withSchema(TableName.DynamicSecret).as("dynName"),
|
||||||
|
db.ref("version").withSchema(TableName.DynamicSecret).as("dynVersion"),
|
||||||
|
db.ref("type").withSchema(TableName.DynamicSecret).as("dynType"),
|
||||||
|
db.ref("defaultTTL").withSchema(TableName.DynamicSecret).as("dynDefaultTTL"),
|
||||||
|
db.ref("maxTTL").withSchema(TableName.DynamicSecret).as("dynMaxTTL"),
|
||||||
|
db.ref("inputIV").withSchema(TableName.DynamicSecret).as("dynInputIV"),
|
||||||
|
db.ref("inputTag").withSchema(TableName.DynamicSecret).as("dynInputTag"),
|
||||||
|
db.ref("inputCiphertext").withSchema(TableName.DynamicSecret).as("dynInputCiphertext"),
|
||||||
|
db.ref("algorithm").withSchema(TableName.DynamicSecret).as("dynAlgorithm"),
|
||||||
|
db.ref("keyEncoding").withSchema(TableName.DynamicSecret).as("dynKeyEncoding"),
|
||||||
|
db.ref("folderId").withSchema(TableName.DynamicSecret).as("dynFolderId"),
|
||||||
|
db.ref("status").withSchema(TableName.DynamicSecret).as("dynStatus"),
|
||||||
|
db.ref("statusDetails").withSchema(TableName.DynamicSecret).as("dynStatusDetails"),
|
||||||
|
db.ref("createdAt").withSchema(TableName.DynamicSecret).as("dynCreatedAt"),
|
||||||
|
db.ref("updatedAt").withSchema(TableName.DynamicSecret).as("dynUpdatedAt")
|
||||||
|
);
|
||||||
|
if (!doc) return;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...DynamicSecretLeasesSchema.parse(doc),
|
||||||
|
dynamicSecret: {
|
||||||
|
id: doc.dynId,
|
||||||
|
name: doc.dynName,
|
||||||
|
version: doc.dynVersion,
|
||||||
|
type: doc.dynType,
|
||||||
|
defaultTTL: doc.dynDefaultTTL,
|
||||||
|
maxTTL: doc.dynMaxTTL,
|
||||||
|
inputIV: doc.dynInputIV,
|
||||||
|
inputTag: doc.dynInputTag,
|
||||||
|
inputCiphertext: doc.dynInputCiphertext,
|
||||||
|
algorithm: doc.dynAlgorithm,
|
||||||
|
keyEncoding: doc.dynKeyEncoding,
|
||||||
|
folderId: doc.dynFolderId,
|
||||||
|
status: doc.dynStatus,
|
||||||
|
statusDetails: doc.dynStatusDetails,
|
||||||
|
createdAt: doc.dynCreatedAt,
|
||||||
|
updatedAt: doc.dynUpdatedAt
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "DynamicSecretLeaseFindById" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...orm, findById, countLeasesForDynamicSecret };
|
||||||
|
};
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue";
|
||||||
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
|
|
||||||
|
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
||||||
|
import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types";
|
||||||
|
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
||||||
|
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
||||||
|
|
||||||
|
type TDynamicSecretLeaseQueueServiceFactoryDep = {
|
||||||
|
queueService: TQueueServiceFactory;
|
||||||
|
dynamicSecretLeaseDAL: Pick<TDynamicSecretLeaseDALFactory, "findById" | "deleteById" | "find" | "updateById">;
|
||||||
|
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findById" | "deleteById" | "updateById">;
|
||||||
|
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDynamicSecretLeaseQueueServiceFactory = ReturnType<typeof dynamicSecretLeaseQueueServiceFactory>;
|
||||||
|
|
||||||
|
export const dynamicSecretLeaseQueueServiceFactory = ({
|
||||||
|
queueService,
|
||||||
|
dynamicSecretDAL,
|
||||||
|
dynamicSecretProviders,
|
||||||
|
dynamicSecretLeaseDAL
|
||||||
|
}: TDynamicSecretLeaseQueueServiceFactoryDep) => {
|
||||||
|
const pruneDynamicSecret = async (dynamicSecretCfgId: string) => {
|
||||||
|
await queueService.queue(
|
||||||
|
QueueName.DynamicSecretRevocation,
|
||||||
|
QueueJobs.DynamicSecretPruning,
|
||||||
|
{ dynamicSecretCfgId },
|
||||||
|
{
|
||||||
|
jobId: dynamicSecretCfgId,
|
||||||
|
backoff: {
|
||||||
|
type: "exponential",
|
||||||
|
delay: 3000
|
||||||
|
},
|
||||||
|
removeOnFail: {
|
||||||
|
count: 3
|
||||||
|
},
|
||||||
|
removeOnComplete: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLeaseRevocation = async (leaseId: string, expiry: number) => {
|
||||||
|
await queueService.queue(
|
||||||
|
QueueName.DynamicSecretRevocation,
|
||||||
|
QueueJobs.DynamicSecretRevocation,
|
||||||
|
{ leaseId },
|
||||||
|
{
|
||||||
|
jobId: leaseId,
|
||||||
|
backoff: {
|
||||||
|
type: "exponential",
|
||||||
|
delay: 3000
|
||||||
|
},
|
||||||
|
delay: expiry,
|
||||||
|
removeOnFail: {
|
||||||
|
count: 3
|
||||||
|
},
|
||||||
|
removeOnComplete: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsetLeaseRevocation = async (leaseId: string) => {
|
||||||
|
await queueService.stopJobById(QueueName.DynamicSecretRevocation, leaseId);
|
||||||
|
};
|
||||||
|
|
||||||
|
queueService.start(QueueName.DynamicSecretRevocation, async (job) => {
|
||||||
|
try {
|
||||||
|
if (job.name === QueueJobs.DynamicSecretRevocation) {
|
||||||
|
const { leaseId } = job.data as { leaseId: string };
|
||||||
|
logger.info("Dynamic secret lease revocation started: ", leaseId, job.id);
|
||||||
|
|
||||||
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
|
if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId);
|
||||||
|
await dynamicSecretLeaseDAL.deleteById(dynamicSecretLease.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job.name === QueueJobs.DynamicSecretPruning) {
|
||||||
|
const { dynamicSecretCfgId } = job.data as { dynamicSecretCfgId: string };
|
||||||
|
logger.info("Dynamic secret pruning started: ", dynamicSecretCfgId, job.id);
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findById(dynamicSecretCfgId);
|
||||||
|
if (!dynamicSecretCfg) throw new DisableRotationErrors({ message: "Dynamic secret not found" });
|
||||||
|
if ((dynamicSecretCfg.status as DynamicSecretStatus) !== DynamicSecretStatus.Deleting)
|
||||||
|
throw new DisableRotationErrors({ message: "Document not deleted" });
|
||||||
|
|
||||||
|
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfgId });
|
||||||
|
if (dynamicSecretLeases.length) {
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));
|
||||||
|
await Promise.all(
|
||||||
|
dynamicSecretLeases.map(({ externalEntityId }) =>
|
||||||
|
selectedProvider.revoke(decryptedStoredInput, externalEntityId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await dynamicSecretDAL.deleteById(dynamicSecretCfgId);
|
||||||
|
}
|
||||||
|
logger.info("Finished dynamic secret job", job.id);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
|
||||||
|
if (job?.name === QueueJobs.DynamicSecretPruning) {
|
||||||
|
const { dynamicSecretCfgId } = job.data as { dynamicSecretCfgId: string };
|
||||||
|
await dynamicSecretDAL.updateById(dynamicSecretCfgId, {
|
||||||
|
status: DynamicSecretStatus.FailedDeletion,
|
||||||
|
statusDetails: (error as Error)?.message?.slice(0, 255)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (job?.name === QueueJobs.DynamicSecretRevocation) {
|
||||||
|
const { leaseId } = job.data as { leaseId: string };
|
||||||
|
await dynamicSecretLeaseDAL.updateById(leaseId, {
|
||||||
|
status: DynamicSecretStatus.FailedDeletion,
|
||||||
|
statusDetails: (error as Error)?.message?.slice(0, 255)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error instanceof DisableRotationErrors) {
|
||||||
|
if (job.id) {
|
||||||
|
await queueService.stopRepeatableJobByJobId(QueueName.DynamicSecretRevocation, job.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// propogate to next part
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
pruneDynamicSecret,
|
||||||
|
setLeaseRevocation,
|
||||||
|
unsetLeaseRevocation
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
|
|
||||||
|
import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
||||||
|
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
||||||
|
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
||||||
|
import { TDynamicSecretLeaseQueueServiceFactory } from "./dynamic-secret-lease-queue";
|
||||||
|
import {
|
||||||
|
DynamicSecretLeaseStatus,
|
||||||
|
TCreateDynamicSecretLeaseDTO,
|
||||||
|
TDeleteDynamicSecretLeaseDTO,
|
||||||
|
TDetailsDynamicSecretLeaseDTO,
|
||||||
|
TListDynamicSecretLeasesDTO,
|
||||||
|
TRenewDynamicSecretLeaseDTO
|
||||||
|
} from "./dynamic-secret-lease-types";
|
||||||
|
|
||||||
|
type TDynamicSecretLeaseServiceFactoryDep = {
|
||||||
|
dynamicSecretLeaseDAL: TDynamicSecretLeaseDALFactory;
|
||||||
|
dynamicSecretDAL: Pick<TDynamicSecretDALFactory, "findOne">;
|
||||||
|
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
||||||
|
dynamicSecretQueueService: TDynamicSecretLeaseQueueServiceFactory;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDynamicSecretLeaseServiceFactory = ReturnType<typeof dynamicSecretLeaseServiceFactory>;
|
||||||
|
|
||||||
|
export const dynamicSecretLeaseServiceFactory = ({
|
||||||
|
dynamicSecretLeaseDAL,
|
||||||
|
dynamicSecretProviders,
|
||||||
|
dynamicSecretDAL,
|
||||||
|
folderDAL,
|
||||||
|
permissionService,
|
||||||
|
dynamicSecretQueueService,
|
||||||
|
projectDAL,
|
||||||
|
licenseService
|
||||||
|
}: TDynamicSecretLeaseServiceFactoryDep) => {
|
||||||
|
const create = async ({
|
||||||
|
environmentSlug,
|
||||||
|
path,
|
||||||
|
name,
|
||||||
|
projectSlug,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
ttl
|
||||||
|
}: TCreateDynamicSecretLeaseDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
|
||||||
|
const projectId = project.id;
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan?.dynamicSecret) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to create lease due to plan restriction. Upgrade plan to create dynamic secret."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
|
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||||
|
|
||||||
|
const totalLeasesTaken = await dynamicSecretLeaseDAL.countLeasesForDynamicSecret(dynamicSecretCfg.id);
|
||||||
|
if (totalLeasesTaken >= appCfg.MAX_LEASE_LIMIT)
|
||||||
|
throw new BadRequestError({ message: `Max lease limit reached. Limit: ${appCfg.MAX_LEASE_LIMIT}` });
|
||||||
|
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
|
||||||
|
const { maxTTL } = dynamicSecretCfg;
|
||||||
|
const expireAt = new Date(new Date().getTime() + ms(selectedTTL));
|
||||||
|
if (maxTTL) {
|
||||||
|
const maxExpiryDate = new Date(new Date().getTime() + ms(maxTTL));
|
||||||
|
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max TTL" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entityId, data } = await selectedProvider.create(decryptedStoredInput, expireAt.getTime());
|
||||||
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.create({
|
||||||
|
expireAt,
|
||||||
|
version: 1,
|
||||||
|
dynamicSecretId: dynamicSecretCfg.id,
|
||||||
|
externalEntityId: entityId
|
||||||
|
});
|
||||||
|
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(expireAt) - Number(new Date()));
|
||||||
|
return { lease: dynamicSecretLease, dynamicSecret: dynamicSecretCfg, data };
|
||||||
|
};
|
||||||
|
|
||||||
|
const renewLease = async ({
|
||||||
|
ttl,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
projectSlug,
|
||||||
|
path,
|
||||||
|
environmentSlug,
|
||||||
|
leaseId
|
||||||
|
}: TRenewDynamicSecretLeaseDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
|
||||||
|
const projectId = project.id;
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan?.dynamicSecret) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to renew lease due to plan restriction. Upgrade plan to create dynamic secret."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
|
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
const selectedTTL = ttl ?? dynamicSecretCfg.defaultTTL;
|
||||||
|
const { maxTTL } = dynamicSecretCfg;
|
||||||
|
const expireAt = new Date(dynamicSecretLease.expireAt.getTime() + ms(selectedTTL));
|
||||||
|
if (maxTTL) {
|
||||||
|
const maxExpiryDate = new Date(dynamicSecretLease.createdAt.getTime() + ms(maxTTL));
|
||||||
|
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max ttl" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { entityId } = await selectedProvider.renew(
|
||||||
|
decryptedStoredInput,
|
||||||
|
dynamicSecretLease.externalEntityId,
|
||||||
|
expireAt.getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id);
|
||||||
|
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(expireAt) - Number(new Date()));
|
||||||
|
const updatedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
|
||||||
|
expireAt,
|
||||||
|
externalEntityId: entityId
|
||||||
|
});
|
||||||
|
return updatedDynamicSecretLease;
|
||||||
|
};
|
||||||
|
|
||||||
|
const revokeLease = async ({
|
||||||
|
leaseId,
|
||||||
|
environmentSlug,
|
||||||
|
path,
|
||||||
|
projectSlug,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
isForced
|
||||||
|
}: TDeleteDynamicSecretLeaseDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
|
||||||
|
const projectId = project.id;
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Delete,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
|
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
|
||||||
|
const revokeResponse = await selectedProvider
|
||||||
|
.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId)
|
||||||
|
.catch(async (err) => {
|
||||||
|
// only propogate this error if forced is false
|
||||||
|
if (!isForced) return { error: err as Error };
|
||||||
|
});
|
||||||
|
|
||||||
|
if ((revokeResponse as { error?: Error })?.error) {
|
||||||
|
const { error } = revokeResponse as { error?: Error };
|
||||||
|
logger.error(error?.message, "Failed to revoke lease");
|
||||||
|
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.updateById(dynamicSecretLease.id, {
|
||||||
|
status: DynamicSecretLeaseStatus.FailedDeletion,
|
||||||
|
statusDetails: error?.message?.slice(0, 255)
|
||||||
|
});
|
||||||
|
return deletedDynamicSecretLease;
|
||||||
|
}
|
||||||
|
|
||||||
|
await dynamicSecretQueueService.unsetLeaseRevocation(dynamicSecretLease.id);
|
||||||
|
const deletedDynamicSecretLease = await dynamicSecretLeaseDAL.deleteById(dynamicSecretLease.id);
|
||||||
|
return deletedDynamicSecretLease;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listLeases = async ({
|
||||||
|
path,
|
||||||
|
name,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectSlug,
|
||||||
|
actorOrgId,
|
||||||
|
environmentSlug,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TListDynamicSecretLeasesDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
|
||||||
|
const projectId = project.id;
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
|
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||||
|
|
||||||
|
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
||||||
|
return dynamicSecretLeases;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLeaseDetails = async ({
|
||||||
|
projectSlug,
|
||||||
|
actorOrgId,
|
||||||
|
path,
|
||||||
|
environmentSlug,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
leaseId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TDetailsDynamicSecretLeaseDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
|
||||||
|
const projectId = project.id;
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
|
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
|
return dynamicSecretLease;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
create,
|
||||||
|
listLeases,
|
||||||
|
revokeLease,
|
||||||
|
renewLease,
|
||||||
|
getLeaseDetails
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export enum DynamicSecretLeaseStatus {
|
||||||
|
FailedDeletion = "Failed to delete"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TCreateDynamicSecretLeaseDTO = {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
ttl?: string;
|
||||||
|
projectSlug: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDetailsDynamicSecretLeaseDTO = {
|
||||||
|
leaseId: string;
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
projectSlug: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TListDynamicSecretLeasesDTO = {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
projectSlug: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDeleteDynamicSecretLeaseDTO = {
|
||||||
|
leaseId: string;
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
projectSlug: string;
|
||||||
|
isForced?: boolean;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TRenewDynamicSecretLeaseDTO = {
|
||||||
|
leaseId: string;
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
ttl?: string;
|
||||||
|
projectSlug: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TDynamicSecretDALFactory = ReturnType<typeof dynamicSecretDALFactory>;
|
||||||
|
|
||||||
|
export const dynamicSecretDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.DynamicSecret);
|
||||||
|
return orm;
|
||||||
|
};
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
|
|
||||||
|
import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal";
|
||||||
|
import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue";
|
||||||
|
import { TDynamicSecretDALFactory } from "./dynamic-secret-dal";
|
||||||
|
import {
|
||||||
|
DynamicSecretStatus,
|
||||||
|
TCreateDynamicSecretDTO,
|
||||||
|
TDeleteDynamicSecretDTO,
|
||||||
|
TDetailsDynamicSecretDTO,
|
||||||
|
TListDynamicSecretsDTO,
|
||||||
|
TUpdateDynamicSecretDTO
|
||||||
|
} from "./dynamic-secret-types";
|
||||||
|
import { DynamicSecretProviders, TDynamicProviderFns } from "./providers/models";
|
||||||
|
|
||||||
|
type TDynamicSecretServiceFactoryDep = {
|
||||||
|
dynamicSecretDAL: TDynamicSecretDALFactory;
|
||||||
|
dynamicSecretLeaseDAL: Pick<TDynamicSecretLeaseDALFactory, "find">;
|
||||||
|
dynamicSecretProviders: Record<DynamicSecretProviders, TDynamicProviderFns>;
|
||||||
|
dynamicSecretQueueService: Pick<
|
||||||
|
TDynamicSecretLeaseQueueServiceFactory,
|
||||||
|
"pruneDynamicSecret" | "unsetLeaseRevocation"
|
||||||
|
>;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDynamicSecretServiceFactory = ReturnType<typeof dynamicSecretServiceFactory>;
|
||||||
|
|
||||||
|
export const dynamicSecretServiceFactory = ({
|
||||||
|
dynamicSecretDAL,
|
||||||
|
dynamicSecretLeaseDAL,
|
||||||
|
licenseService,
|
||||||
|
folderDAL,
|
||||||
|
dynamicSecretProviders,
|
||||||
|
permissionService,
|
||||||
|
dynamicSecretQueueService,
|
||||||
|
projectDAL
|
||||||
|
}: TDynamicSecretServiceFactoryDep) => {
|
||||||
|
const create = async ({
|
||||||
|
path,
|
||||||
|
actor,
|
||||||
|
name,
|
||||||
|
actorId,
|
||||||
|
maxTTL,
|
||||||
|
provider,
|
||||||
|
environmentSlug,
|
||||||
|
projectSlug,
|
||||||
|
actorOrgId,
|
||||||
|
defaultTTL,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TCreateDynamicSecretDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
|
||||||
|
const projectId = project.id;
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan?.dynamicSecret) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to create dynamic secret due to plan restriction. Upgrade plan to create dynamic secret."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
|
if (existingDynamicSecret)
|
||||||
|
throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" });
|
||||||
|
|
||||||
|
const selectedProvider = dynamicSecretProviders[provider.type];
|
||||||
|
const inputs = await selectedProvider.validateProviderInputs(provider.inputs);
|
||||||
|
|
||||||
|
const isConnected = await selectedProvider.validateConnection(provider.inputs);
|
||||||
|
if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" });
|
||||||
|
|
||||||
|
const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(inputs));
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.create({
|
||||||
|
type: provider.type,
|
||||||
|
version: 1,
|
||||||
|
inputIV: encryptedInput.iv,
|
||||||
|
inputTag: encryptedInput.tag,
|
||||||
|
inputCiphertext: encryptedInput.ciphertext,
|
||||||
|
algorithm: encryptedInput.algorithm,
|
||||||
|
keyEncoding: encryptedInput.encoding,
|
||||||
|
maxTTL,
|
||||||
|
defaultTTL,
|
||||||
|
folderId: folder.id,
|
||||||
|
name
|
||||||
|
});
|
||||||
|
return dynamicSecretCfg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateByName = async ({
|
||||||
|
name,
|
||||||
|
maxTTL,
|
||||||
|
defaultTTL,
|
||||||
|
inputs,
|
||||||
|
environmentSlug,
|
||||||
|
projectSlug,
|
||||||
|
path,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
newName,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TUpdateDynamicSecretDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan?.dynamicSecret) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to update dynamic secret due to plan restriction. Upgrade plan to create dynamic secret."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
|
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||||
|
|
||||||
|
if (newName) {
|
||||||
|
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name: newName, folderId: folder.id });
|
||||||
|
if (existingDynamicSecret)
|
||||||
|
throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
const newInput = { ...decryptedStoredInput, ...(inputs || {}) };
|
||||||
|
const updatedInput = await selectedProvider.validateProviderInputs(newInput);
|
||||||
|
|
||||||
|
const isConnected = await selectedProvider.validateConnection(newInput);
|
||||||
|
if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" });
|
||||||
|
|
||||||
|
const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(updatedInput));
|
||||||
|
const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, {
|
||||||
|
inputIV: encryptedInput.iv,
|
||||||
|
inputTag: encryptedInput.tag,
|
||||||
|
inputCiphertext: encryptedInput.ciphertext,
|
||||||
|
algorithm: encryptedInput.algorithm,
|
||||||
|
keyEncoding: encryptedInput.encoding,
|
||||||
|
maxTTL,
|
||||||
|
defaultTTL,
|
||||||
|
name: newName ?? name,
|
||||||
|
status: null,
|
||||||
|
statusDetails: null
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedDynamicCfg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteByName = async ({
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
projectSlug,
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
environmentSlug,
|
||||||
|
isForced
|
||||||
|
}: TDeleteDynamicSecretDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
|
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||||
|
|
||||||
|
const leases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
||||||
|
// when not forced we check with the external system to first remove the things
|
||||||
|
// we introduce a forced concept because consider the external lease got deleted by some other external like a human or another system
|
||||||
|
// this allows user to clean up it from infisical
|
||||||
|
if (isForced) {
|
||||||
|
// clear all queues for lease revocations
|
||||||
|
await Promise.all(leases.map(({ id: leaseId }) => dynamicSecretQueueService.unsetLeaseRevocation(leaseId)));
|
||||||
|
|
||||||
|
const deletedDynamicSecretCfg = await dynamicSecretDAL.deleteById(dynamicSecretCfg.id);
|
||||||
|
return deletedDynamicSecretCfg;
|
||||||
|
}
|
||||||
|
// if leases exist we should flag it as deleting and then remove leases in background
|
||||||
|
// then delete the main one
|
||||||
|
if (leases.length) {
|
||||||
|
const updatedDynamicSecretCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, {
|
||||||
|
status: DynamicSecretStatus.Deleting
|
||||||
|
});
|
||||||
|
await dynamicSecretQueueService.pruneDynamicSecret(updatedDynamicSecretCfg.id);
|
||||||
|
return updatedDynamicSecretCfg;
|
||||||
|
}
|
||||||
|
// if no leases just delete the config
|
||||||
|
const deletedDynamicSecretCfg = await dynamicSecretDAL.deleteById(dynamicSecretCfg.id);
|
||||||
|
return deletedDynamicSecretCfg;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDetails = async ({
|
||||||
|
name,
|
||||||
|
projectSlug,
|
||||||
|
path,
|
||||||
|
environmentSlug,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actorId,
|
||||||
|
actor
|
||||||
|
}: TDetailsDynamicSecretDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
|
||||||
|
const projectId = project.id;
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
|
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
||||||
|
const decryptedStoredInput = JSON.parse(
|
||||||
|
infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
|
ciphertext: dynamicSecretCfg.inputCiphertext,
|
||||||
|
tag: dynamicSecretCfg.inputTag,
|
||||||
|
iv: dynamicSecretCfg.inputIV
|
||||||
|
})
|
||||||
|
) as object;
|
||||||
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
|
const providerInputs = (await selectedProvider.validateProviderInputs(decryptedStoredInput)) as object;
|
||||||
|
return { ...dynamicSecretCfg, inputs: providerInputs };
|
||||||
|
};
|
||||||
|
|
||||||
|
const list = async ({
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
projectSlug,
|
||||||
|
path,
|
||||||
|
environmentSlug
|
||||||
|
}: TListDynamicSecretsDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
|
||||||
|
const projectId = project.id;
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
|
const dynamicSecretCfg = await dynamicSecretDAL.find({ folderId: folder.id });
|
||||||
|
return dynamicSecretCfg;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
create,
|
||||||
|
updateByName,
|
||||||
|
deleteByName,
|
||||||
|
getDetails,
|
||||||
|
list
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { DynamicSecretProviderSchema } from "./providers/models";
|
||||||
|
|
||||||
|
// various status for dynamic secret that happens in background
|
||||||
|
export enum DynamicSecretStatus {
|
||||||
|
Deleting = "Revocation in process",
|
||||||
|
FailedDeletion = "Failed to delete"
|
||||||
|
}
|
||||||
|
|
||||||
|
type TProvider = z.infer<typeof DynamicSecretProviderSchema>;
|
||||||
|
export type TCreateDynamicSecretDTO = {
|
||||||
|
provider: TProvider;
|
||||||
|
defaultTTL: string;
|
||||||
|
maxTTL?: string | null;
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
name: string;
|
||||||
|
projectSlug: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateDynamicSecretDTO = {
|
||||||
|
name: string;
|
||||||
|
newName?: string;
|
||||||
|
defaultTTL?: string;
|
||||||
|
maxTTL?: string | null;
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
inputs?: TProvider["inputs"];
|
||||||
|
projectSlug: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDeleteDynamicSecretDTO = {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
projectSlug: string;
|
||||||
|
isForced?: boolean;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDetailsDynamicSecretDTO = {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
projectSlug: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TListDynamicSecretsDTO = {
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
projectSlug: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { DynamicSecretProviders } from "./models";
|
||||||
|
import { SqlDatabaseProvider } from "./sql-database";
|
||||||
|
|
||||||
|
export const buildDynamicSecretProviders = () => ({
|
||||||
|
[DynamicSecretProviders.SqlDatabase]: SqlDatabaseProvider()
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export enum SqlProviders {
|
||||||
|
Postgres = "postgres",
|
||||||
|
MySQL = "mysql2",
|
||||||
|
Oracle = "oracledb"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DynamicSecretSqlDBSchema = z.object({
|
||||||
|
client: z.nativeEnum(SqlProviders),
|
||||||
|
host: z.string().toLowerCase(),
|
||||||
|
port: z.number(),
|
||||||
|
database: z.string(),
|
||||||
|
username: z.string(),
|
||||||
|
password: z.string(),
|
||||||
|
creationStatement: z.string(),
|
||||||
|
revocationStatement: z.string(),
|
||||||
|
renewStatement: z.string().optional(),
|
||||||
|
ca: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export enum DynamicSecretProviders {
|
||||||
|
SqlDatabase = "sql-database"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema })
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type TDynamicProviderFns = {
|
||||||
|
create: (inputs: unknown, expireAt: number) => Promise<{ entityId: string; data: unknown }>;
|
||||||
|
validateConnection: (inputs: unknown) => Promise<boolean>;
|
||||||
|
validateProviderInputs: (inputs: object) => Promise<unknown>;
|
||||||
|
revoke: (inputs: unknown, entityId: string) => Promise<{ entityId: string }>;
|
||||||
|
renew: (inputs: unknown, entityId: string, expireAt: number) => Promise<{ entityId: string }>;
|
||||||
|
};
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import handlebars from "handlebars";
|
||||||
|
import knex from "knex";
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { getDbConnectionHost } from "@app/lib/knex";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { DynamicSecretSqlDBSchema, SqlProviders, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
|
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
||||||
|
|
||||||
|
const generatePassword = (provider: SqlProviders) => {
|
||||||
|
// oracle has limit of 48 password length
|
||||||
|
const size = provider === SqlProviders.Oracle ? 30 : 48;
|
||||||
|
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
||||||
|
return customAlphabet(charset, 48)(size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateUsername = (provider: SqlProviders) => {
|
||||||
|
// For oracle, the client assumes everything is upper case when not using quotes around the password
|
||||||
|
if (provider === SqlProviders.Oracle) return alphaNumericNanoId(32).toUpperCase();
|
||||||
|
|
||||||
|
return alphaNumericNanoId(32);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
||||||
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
|
||||||
|
|
||||||
|
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
|
||||||
|
if (
|
||||||
|
// localhost
|
||||||
|
providerInputs.host === "localhost" ||
|
||||||
|
providerInputs.host === "127.0.0.1" ||
|
||||||
|
// database infisical uses
|
||||||
|
dbHost === providerInputs.host ||
|
||||||
|
// internal ips
|
||||||
|
providerInputs.host === "host.docker.internal" ||
|
||||||
|
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
|
||||||
|
providerInputs.host.match(/^192\.168\.\d+\.\d+/)
|
||||||
|
)
|
||||||
|
throw new BadRequestError({ message: "Invalid db host" });
|
||||||
|
return providerInputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClient = async (providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>) => {
|
||||||
|
const ssl = providerInputs.ca ? { rejectUnauthorized: false, ca: providerInputs.ca } : undefined;
|
||||||
|
const db = knex({
|
||||||
|
client: providerInputs.client,
|
||||||
|
connection: {
|
||||||
|
database: providerInputs.database,
|
||||||
|
port: providerInputs.port,
|
||||||
|
host: providerInputs.host,
|
||||||
|
user: providerInputs.username,
|
||||||
|
password: providerInputs.password,
|
||||||
|
ssl,
|
||||||
|
pool: { min: 0, max: 1 }
|
||||||
|
},
|
||||||
|
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
|
});
|
||||||
|
return db;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateConnection = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const db = await getClient(providerInputs);
|
||||||
|
// oracle needs from keyword
|
||||||
|
const testStatement = providerInputs.client === SqlProviders.Oracle ? "SELECT 1 FROM DUAL" : "SELECT 1";
|
||||||
|
|
||||||
|
const isConnected = await db.raw(testStatement).then(() => true);
|
||||||
|
await db.destroy();
|
||||||
|
return isConnected;
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = async (inputs: unknown, expireAt: number) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const db = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const username = generateUsername(providerInputs.client);
|
||||||
|
const password = generatePassword(providerInputs.client);
|
||||||
|
const { database } = providerInputs;
|
||||||
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
|
||||||
|
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
expiration,
|
||||||
|
database
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.transaction(async (tx) =>
|
||||||
|
Promise.all(
|
||||||
|
creationStatement
|
||||||
|
.toString()
|
||||||
|
.split(";")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((query) => tx.raw(query))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await db.destroy();
|
||||||
|
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const revoke = async (inputs: unknown, entityId: string) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const db = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const username = entityId;
|
||||||
|
const { database } = providerInputs;
|
||||||
|
|
||||||
|
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username, database });
|
||||||
|
await db.transaction(async (tx) =>
|
||||||
|
Promise.all(
|
||||||
|
revokeStatement
|
||||||
|
.toString()
|
||||||
|
.split(";")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((query) => tx.raw(query))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.destroy();
|
||||||
|
return { entityId: username };
|
||||||
|
};
|
||||||
|
|
||||||
|
const renew = async (inputs: unknown, entityId: string, expireAt: number) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const db = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const username = entityId;
|
||||||
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
const { database } = providerInputs;
|
||||||
|
|
||||||
|
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration, database });
|
||||||
|
if (renewStatement)
|
||||||
|
await db.transaction(async (tx) =>
|
||||||
|
Promise.all(
|
||||||
|
renewStatement
|
||||||
|
.toString()
|
||||||
|
.split(";")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((query) => tx.raw(query))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.destroy();
|
||||||
|
return { entityId: username };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
validateProviderInputs,
|
||||||
|
validateConnection,
|
||||||
|
create,
|
||||||
|
revoke,
|
||||||
|
renew
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName, TGroups } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TGroupDALFactory = ReturnType<typeof groupDALFactory>;
|
||||||
|
|
||||||
|
export const groupDALFactory = (db: TDbClient) => {
|
||||||
|
const groupOrm = ormify(db, TableName.Groups);
|
||||||
|
|
||||||
|
const findGroups = async (filter: TFindFilter<TGroups>, { offset, limit, sort, tx }: TFindOpt<TGroups> = {}) => {
|
||||||
|
try {
|
||||||
|
const query = (tx || db)(TableName.Groups)
|
||||||
|
// eslint-disable-next-line
|
||||||
|
.where(buildFindFilter(filter))
|
||||||
|
.select(selectAllTableCols(TableName.Groups));
|
||||||
|
|
||||||
|
if (limit) void query.limit(limit);
|
||||||
|
if (offset) void query.limit(offset);
|
||||||
|
if (sort) {
|
||||||
|
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await query;
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
throw new DatabaseError({ error: err, name: "Find groups" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findByOrgId = async (orgId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const docs = await (tx || db)(TableName.Groups)
|
||||||
|
.where(`${TableName.Groups}.orgId`, orgId)
|
||||||
|
.leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`)
|
||||||
|
.select(selectAllTableCols(TableName.Groups))
|
||||||
|
// cr stands for custom role
|
||||||
|
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
|
||||||
|
.select(db.ref("name").as("crName").withSchema(TableName.OrgRoles))
|
||||||
|
.select(db.ref("slug").as("crSlug").withSchema(TableName.OrgRoles))
|
||||||
|
.select(db.ref("description").as("crDescription").withSchema(TableName.OrgRoles))
|
||||||
|
.select(db.ref("permissions").as("crPermission").withSchema(TableName.OrgRoles));
|
||||||
|
return docs.map(({ crId, crDescription, crSlug, crPermission, crName, ...el }) => ({
|
||||||
|
...el,
|
||||||
|
customRole: el.roleId
|
||||||
|
? {
|
||||||
|
id: crId,
|
||||||
|
name: crName,
|
||||||
|
slug: crSlug,
|
||||||
|
permissions: crPermission,
|
||||||
|
description: crDescription
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "FindByOrgId" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const countAllGroupMembers = async ({ orgId, groupId }: { orgId: string; groupId: string }) => {
|
||||||
|
try {
|
||||||
|
interface CountResult {
|
||||||
|
count: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = await db<CountResult>(TableName.OrgMembership)
|
||||||
|
.where(`${TableName.OrgMembership}.orgId`, orgId)
|
||||||
|
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.UserGroupMembership, function () {
|
||||||
|
this.on(`${TableName.UserGroupMembership}.userId`, "=", `${TableName.Users}.id`).andOn(
|
||||||
|
`${TableName.UserGroupMembership}.groupId`,
|
||||||
|
"=",
|
||||||
|
db.raw("?", [groupId])
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.where({ isGhost: false })
|
||||||
|
.count(`${TableName.Users}.id`)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return parseInt((doc?.count as string) || "0", 10);
|
||||||
|
} catch (err) {
|
||||||
|
throw new DatabaseError({ error: err, name: "Count all group members" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// special query
|
||||||
|
const findAllGroupMembers = async ({
|
||||||
|
orgId,
|
||||||
|
groupId,
|
||||||
|
offset = 0,
|
||||||
|
limit,
|
||||||
|
username
|
||||||
|
}: {
|
||||||
|
orgId: string;
|
||||||
|
groupId: string;
|
||||||
|
offset?: number;
|
||||||
|
limit?: number;
|
||||||
|
username?: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
let query = db(TableName.OrgMembership)
|
||||||
|
.where(`${TableName.OrgMembership}.orgId`, orgId)
|
||||||
|
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.UserGroupMembership, function () {
|
||||||
|
this.on(`${TableName.UserGroupMembership}.userId`, "=", `${TableName.Users}.id`).andOn(
|
||||||
|
`${TableName.UserGroupMembership}.groupId`,
|
||||||
|
"=",
|
||||||
|
db.raw("?", [groupId])
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.OrgMembership),
|
||||||
|
db.ref("groupId").withSchema(TableName.UserGroupMembership),
|
||||||
|
db.ref("email").withSchema(TableName.Users),
|
||||||
|
db.ref("username").withSchema(TableName.Users),
|
||||||
|
db.ref("firstName").withSchema(TableName.Users),
|
||||||
|
db.ref("lastName").withSchema(TableName.Users),
|
||||||
|
db.ref("id").withSchema(TableName.Users).as("userId")
|
||||||
|
)
|
||||||
|
.where({ isGhost: false })
|
||||||
|
.offset(offset);
|
||||||
|
|
||||||
|
if (limit) {
|
||||||
|
query = query.limit(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username) {
|
||||||
|
query = query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const members = await query;
|
||||||
|
|
||||||
|
return members.map(
|
||||||
|
({ email, username: memberUsername, firstName, lastName, userId, groupId: memberGroupId }) => ({
|
||||||
|
id: userId,
|
||||||
|
email,
|
||||||
|
username: memberUsername,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
isPartOfGroup: !!memberGroupId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find all org members" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
findGroups,
|
||||||
|
findByOrgId,
|
||||||
|
countAllGroupMembers,
|
||||||
|
findAllGroupMembers,
|
||||||
|
...groupOrm
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,474 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
|
||||||
|
import { OrgMembershipRole, SecretKeyEncoding, TOrgRoles } from "@app/db/schemas";
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
|
import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
|
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { TGroupProjectDALFactory } from "../../../services/group-project/group-project-dal";
|
||||||
|
import { TOrgDALFactory } from "../../../services/org/org-dal";
|
||||||
|
import { TProjectDALFactory } from "../../../services/project/project-dal";
|
||||||
|
import { TProjectBotDALFactory } from "../../../services/project-bot/project-bot-dal";
|
||||||
|
import { TProjectKeyDALFactory } from "../../../services/project-key/project-key-dal";
|
||||||
|
import { TUserDALFactory } from "../../../services/user/user-dal";
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { TGroupDALFactory } from "./group-dal";
|
||||||
|
import {
|
||||||
|
TAddUserToGroupDTO,
|
||||||
|
TCreateGroupDTO,
|
||||||
|
TDeleteGroupDTO,
|
||||||
|
TListGroupUsersDTO,
|
||||||
|
TRemoveUserFromGroupDTO,
|
||||||
|
TUpdateGroupDTO
|
||||||
|
} from "./group-types";
|
||||||
|
import { TUserGroupMembershipDALFactory } from "./user-group-membership-dal";
|
||||||
|
|
||||||
|
type TGroupServiceFactoryDep = {
|
||||||
|
userDAL: Pick<TUserDALFactory, "findOne" | "findUserEncKeyByUsername">;
|
||||||
|
groupDAL: Pick<
|
||||||
|
TGroupDALFactory,
|
||||||
|
"create" | "findOne" | "update" | "delete" | "findAllGroupMembers" | "countAllGroupMembers"
|
||||||
|
>;
|
||||||
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
|
orgDAL: Pick<TOrgDALFactory, "findMembership">;
|
||||||
|
userGroupMembershipDAL: Pick<
|
||||||
|
TUserGroupMembershipDALFactory,
|
||||||
|
"findOne" | "create" | "delete" | "filterProjectsByUserMembership"
|
||||||
|
>;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
|
||||||
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "create" | "delete" | "findLatestProjectKey">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
|
||||||
|
|
||||||
|
export const groupServiceFactory = ({
|
||||||
|
userDAL,
|
||||||
|
groupDAL,
|
||||||
|
groupProjectDAL,
|
||||||
|
orgDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
projectDAL,
|
||||||
|
projectBotDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
permissionService,
|
||||||
|
licenseService
|
||||||
|
}: TGroupServiceFactoryDep) => {
|
||||||
|
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
|
||||||
|
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.groups)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to create group due to plan restriction. Upgrade plan to create group."
|
||||||
|
});
|
||||||
|
|
||||||
|
const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole(
|
||||||
|
role,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const isCustomRole = Boolean(customRole);
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged group" });
|
||||||
|
|
||||||
|
const group = await groupDAL.create({
|
||||||
|
name,
|
||||||
|
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
|
||||||
|
orgId: actorOrgId,
|
||||||
|
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
||||||
|
roleId: customRole?.id
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGroup = async ({
|
||||||
|
currentSlug,
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
role,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdateGroupDTO) => {
|
||||||
|
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.groups)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to update group due to plan restrictio Upgrade plan to update group."
|
||||||
|
});
|
||||||
|
|
||||||
|
const group = await groupDAL.findOne({ orgId: actorOrgId, slug: currentSlug });
|
||||||
|
if (!group) throw new BadRequestError({ message: `Failed to find group with slug ${currentSlug}` });
|
||||||
|
|
||||||
|
let customRole: TOrgRoles | undefined;
|
||||||
|
if (role) {
|
||||||
|
const { permission: rolePermission, role: customOrgRole } = await permissionService.getOrgPermissionByRole(
|
||||||
|
role,
|
||||||
|
group.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
const isCustomRole = Boolean(customOrgRole);
|
||||||
|
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
|
if (!hasRequiredNewRolePermission)
|
||||||
|
throw new BadRequestError({ message: "Failed to create a more privileged group" });
|
||||||
|
if (isCustomRole) customRole = customOrgRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updatedGroup] = await groupDAL.update(
|
||||||
|
{
|
||||||
|
orgId: actorOrgId,
|
||||||
|
slug: currentSlug
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
slug: slug ? slugify(slug) : undefined,
|
||||||
|
...(role
|
||||||
|
? {
|
||||||
|
role: customRole ? OrgMembershipRole.Custom : role,
|
||||||
|
roleId: customRole?.id ?? null
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return updatedGroup;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteGroup = async ({ groupSlug, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => {
|
||||||
|
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
|
||||||
|
if (!plan.groups)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to delete group due to plan restriction. Upgrade plan to delete group."
|
||||||
|
});
|
||||||
|
|
||||||
|
const [group] = await groupDAL.delete({
|
||||||
|
orgId: actorOrgId,
|
||||||
|
slug: groupSlug
|
||||||
|
});
|
||||||
|
|
||||||
|
return group;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listGroupUsers = async ({
|
||||||
|
groupSlug,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
username,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TListGroupUsersDTO) => {
|
||||||
|
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
|
const group = await groupDAL.findOne({
|
||||||
|
orgId: actorOrgId,
|
||||||
|
slug: groupSlug
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!group)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to find group with slug ${groupSlug}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const users = await groupDAL.findAllGroupMembers({
|
||||||
|
orgId: group.orgId,
|
||||||
|
groupId: group.id,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
username
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalCount = await groupDAL.countAllGroupMembers({
|
||||||
|
orgId: group.orgId,
|
||||||
|
groupId: group.id
|
||||||
|
});
|
||||||
|
|
||||||
|
return { users, totalCount };
|
||||||
|
};
|
||||||
|
|
||||||
|
const addUserToGroup = async ({
|
||||||
|
groupSlug,
|
||||||
|
username,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TAddUserToGroupDTO) => {
|
||||||
|
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
|
// check if group with slug exists
|
||||||
|
const group = await groupDAL.findOne({
|
||||||
|
orgId: actorOrgId,
|
||||||
|
slug: groupSlug
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!group)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to find group with slug ${groupSlug}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
||||||
|
|
||||||
|
// check if user has broader or equal to privileges than group
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, groupRolePermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to add user to more privileged group" });
|
||||||
|
|
||||||
|
// get user with username
|
||||||
|
const user = await userDAL.findUserEncKeyByUsername({
|
||||||
|
username
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to find user with username ${username}`
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if user group membership already exists
|
||||||
|
const existingUserGroupMembership = await userGroupMembershipDAL.findOne({
|
||||||
|
groupId: group.id,
|
||||||
|
userId: user.userId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUserGroupMembership)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `User ${username} is already part of the group ${groupSlug}`
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if user is even part of the organization
|
||||||
|
const existingUserOrgMembership = await orgDAL.findMembership({
|
||||||
|
userId: user.userId,
|
||||||
|
orgId: actorOrgId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingUserOrgMembership)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `User ${username} is not part of the organization`
|
||||||
|
});
|
||||||
|
|
||||||
|
await userGroupMembershipDAL.create({
|
||||||
|
userId: user.userId,
|
||||||
|
groupId: group.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// check which projects the group is part of
|
||||||
|
const projectIds = (
|
||||||
|
await groupProjectDAL.find({
|
||||||
|
groupId: group.id
|
||||||
|
})
|
||||||
|
).map((gp) => gp.projectId);
|
||||||
|
|
||||||
|
const keys = await projectKeyDAL.find({
|
||||||
|
receiverId: user.userId,
|
||||||
|
$in: {
|
||||||
|
projectId: projectIds
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const keysSet = new Set(keys.map((k) => k.projectId));
|
||||||
|
const projectsToAddKeyFor = projectIds.filter((p) => !keysSet.has(p));
|
||||||
|
|
||||||
|
for await (const projectId of projectsToAddKeyFor) {
|
||||||
|
const ghostUser = await projectDAL.findProjectGhostUser(projectId);
|
||||||
|
|
||||||
|
if (!ghostUser) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to find sudo user"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId);
|
||||||
|
|
||||||
|
if (!ghostUserLatestKey) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to find sudo user latest key"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const bot = await projectBotDAL.findOne({ projectId });
|
||||||
|
|
||||||
|
if (!bot) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to find bot"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const botPrivateKey = infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
||||||
|
iv: bot.iv,
|
||||||
|
tag: bot.tag,
|
||||||
|
ciphertext: bot.encryptedPrivateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const plaintextProjectKey = decryptAsymmetric({
|
||||||
|
ciphertext: ghostUserLatestKey.encryptedKey,
|
||||||
|
nonce: ghostUserLatestKey.nonce,
|
||||||
|
publicKey: ghostUserLatestKey.sender.publicKey,
|
||||||
|
privateKey: botPrivateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ciphertext: encryptedKey, nonce } = encryptAsymmetric(plaintextProjectKey, user.publicKey, botPrivateKey);
|
||||||
|
|
||||||
|
await projectKeyDAL.create({
|
||||||
|
encryptedKey,
|
||||||
|
nonce,
|
||||||
|
senderId: ghostUser.id,
|
||||||
|
receiverId: user.userId,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeUserFromGroup = async ({
|
||||||
|
groupSlug,
|
||||||
|
username,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TRemoveUserFromGroupDTO) => {
|
||||||
|
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
|
// check if group with slug exists
|
||||||
|
const group = await groupDAL.findOne({
|
||||||
|
orgId: actorOrgId,
|
||||||
|
slug: groupSlug
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!group)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to find group with slug ${groupSlug}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
||||||
|
|
||||||
|
// check if user has broader or equal to privileges than group
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, groupRolePermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to delete user from more privileged group" });
|
||||||
|
|
||||||
|
const user = await userDAL.findOne({
|
||||||
|
username
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Failed to find user with username ${username}`
|
||||||
|
});
|
||||||
|
|
||||||
|
// check if user group membership already exists
|
||||||
|
const existingUserGroupMembership = await userGroupMembershipDAL.findOne({
|
||||||
|
groupId: group.id,
|
||||||
|
userId: user.id
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingUserGroupMembership)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `User ${username} is not part of the group ${groupSlug}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectIds = (
|
||||||
|
await groupProjectDAL.find({
|
||||||
|
groupId: group.id
|
||||||
|
})
|
||||||
|
).map((gp) => gp.projectId);
|
||||||
|
|
||||||
|
const t = await userGroupMembershipDAL.filterProjectsByUserMembership(user.id, group.id, projectIds);
|
||||||
|
|
||||||
|
const projectsToDeleteKeyFor = projectIds.filter((p) => !t.has(p));
|
||||||
|
|
||||||
|
if (projectsToDeleteKeyFor.length) {
|
||||||
|
await projectKeyDAL.delete({
|
||||||
|
receiverId: user.id,
|
||||||
|
$in: {
|
||||||
|
projectId: projectsToDeleteKeyFor
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await userGroupMembershipDAL.delete({
|
||||||
|
groupId: group.id,
|
||||||
|
userId: user.id
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createGroup,
|
||||||
|
updateGroup,
|
||||||
|
deleteGroup,
|
||||||
|
listGroupUsers,
|
||||||
|
addUserToGroup,
|
||||||
|
removeUserFromGroup
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { TGenericPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type TCreateGroupDTO = {
|
||||||
|
name: string;
|
||||||
|
slug?: string;
|
||||||
|
role: string;
|
||||||
|
} & TGenericPermission;
|
||||||
|
|
||||||
|
export type TUpdateGroupDTO = {
|
||||||
|
currentSlug: string;
|
||||||
|
} & Partial<{
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
role: string;
|
||||||
|
}> &
|
||||||
|
TGenericPermission;
|
||||||
|
|
||||||
|
export type TDeleteGroupDTO = {
|
||||||
|
groupSlug: string;
|
||||||
|
} & TGenericPermission;
|
||||||
|
|
||||||
|
export type TListGroupUsersDTO = {
|
||||||
|
groupSlug: string;
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
username?: string;
|
||||||
|
} & TGenericPermission;
|
||||||
|
|
||||||
|
export type TAddUserToGroupDTO = {
|
||||||
|
groupSlug: string;
|
||||||
|
username: string;
|
||||||
|
} & TGenericPermission;
|
||||||
|
|
||||||
|
export type TRemoveUserFromGroupDTO = {
|
||||||
|
groupSlug: string;
|
||||||
|
username: string;
|
||||||
|
} & TGenericPermission;
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TUserGroupMembershipDALFactory = ReturnType<typeof userGroupMembershipDALFactory>;
|
||||||
|
|
||||||
|
export const userGroupMembershipDALFactory = (db: TDbClient) => {
|
||||||
|
const userGroupMembershipOrm = ormify(db, TableName.UserGroupMembership);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a sub-set of projectIds fed into this function corresponding to projects where either:
|
||||||
|
* - The user is a direct member of the project.
|
||||||
|
* - The user is a member of a group that is a member of the project, excluding projects that they are part of
|
||||||
|
* through the group with id [groupId].
|
||||||
|
*/
|
||||||
|
const filterProjectsByUserMembership = async (userId: string, groupId: string, projectIds: string[]) => {
|
||||||
|
const userProjectMemberships: string[] = await db(TableName.ProjectMembership)
|
||||||
|
.where(`${TableName.ProjectMembership}.userId`, userId)
|
||||||
|
.whereIn(`${TableName.ProjectMembership}.projectId`, projectIds)
|
||||||
|
.pluck(`${TableName.ProjectMembership}.projectId`);
|
||||||
|
|
||||||
|
const userGroupMemberships: string[] = await db(TableName.UserGroupMembership)
|
||||||
|
.where(`${TableName.UserGroupMembership}.userId`, userId)
|
||||||
|
.whereNot(`${TableName.UserGroupMembership}.groupId`, groupId)
|
||||||
|
.join(
|
||||||
|
TableName.GroupProjectMembership,
|
||||||
|
`${TableName.UserGroupMembership}.groupId`,
|
||||||
|
`${TableName.GroupProjectMembership}.groupId`
|
||||||
|
)
|
||||||
|
.whereIn(`${TableName.GroupProjectMembership}.projectId`, projectIds)
|
||||||
|
.pluck(`${TableName.GroupProjectMembership}.projectId`);
|
||||||
|
|
||||||
|
return new Set(userProjectMemberships.concat(userGroupMemberships));
|
||||||
|
};
|
||||||
|
|
||||||
|
// special query
|
||||||
|
const findUserGroupMembershipsInProject = async (usernames: string[], projectId: string) => {
|
||||||
|
try {
|
||||||
|
const usernameDocs: string[] = await db(TableName.UserGroupMembership)
|
||||||
|
.join(
|
||||||
|
TableName.GroupProjectMembership,
|
||||||
|
`${TableName.UserGroupMembership}.groupId`,
|
||||||
|
`${TableName.GroupProjectMembership}.groupId`
|
||||||
|
)
|
||||||
|
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||||
|
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
|
||||||
|
.whereIn(`${TableName.Users}.username`, usernames) // TODO: pluck usernames
|
||||||
|
.pluck(`${TableName.Users}.id`);
|
||||||
|
|
||||||
|
return usernameDocs;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find user group members in project" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of users that are part of the group with id [groupId]
|
||||||
|
* that have not yet been added individually to project with id [projectId].
|
||||||
|
*
|
||||||
|
* Note: Filters out users that are part of other groups in the project.
|
||||||
|
* @param groupId
|
||||||
|
* @param projectId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const findGroupMembersNotInProject = async (groupId: string, projectId: string) => {
|
||||||
|
try {
|
||||||
|
// get list of groups in the project with id [projectId]
|
||||||
|
// that that are not the group with id [groupId]
|
||||||
|
const groups: string[] = await db(TableName.GroupProjectMembership)
|
||||||
|
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
|
||||||
|
.whereNot(`${TableName.GroupProjectMembership}.groupId`, groupId)
|
||||||
|
.pluck(`${TableName.GroupProjectMembership}.groupId`);
|
||||||
|
|
||||||
|
// main query
|
||||||
|
const members = await db(TableName.UserGroupMembership)
|
||||||
|
.where(`${TableName.UserGroupMembership}.groupId`, groupId)
|
||||||
|
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.ProjectMembership, function () {
|
||||||
|
this.on(`${TableName.Users}.id`, "=", `${TableName.ProjectMembership}.userId`).andOn(
|
||||||
|
`${TableName.ProjectMembership}.projectId`,
|
||||||
|
"=",
|
||||||
|
db.raw("?", [projectId])
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.whereNull(`${TableName.ProjectMembership}.userId`)
|
||||||
|
.leftJoin<TUserEncryptionKeys>(
|
||||||
|
TableName.UserEncryptionKey,
|
||||||
|
`${TableName.UserEncryptionKey}.userId`,
|
||||||
|
`${TableName.Users}.id`
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.UserGroupMembership),
|
||||||
|
db.ref("groupId").withSchema(TableName.UserGroupMembership),
|
||||||
|
db.ref("email").withSchema(TableName.Users),
|
||||||
|
db.ref("username").withSchema(TableName.Users),
|
||||||
|
db.ref("firstName").withSchema(TableName.Users),
|
||||||
|
db.ref("lastName").withSchema(TableName.Users),
|
||||||
|
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||||
|
db.ref("publicKey").withSchema(TableName.UserEncryptionKey)
|
||||||
|
)
|
||||||
|
.where({ isGhost: false }) // MAKE SURE USER IS NOT A GHOST USER
|
||||||
|
.whereNotIn(`${TableName.UserGroupMembership}.userId`, function () {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
this.select(`${TableName.UserGroupMembership}.userId`)
|
||||||
|
.from(TableName.UserGroupMembership)
|
||||||
|
.whereIn(`${TableName.UserGroupMembership}.groupId`, groups);
|
||||||
|
});
|
||||||
|
|
||||||
|
return members.map(({ email, username, firstName, lastName, userId, publicKey, ...data }) => ({
|
||||||
|
...data,
|
||||||
|
user: { email, username, firstName, lastName, id: userId, publicKey }
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find group members not in project" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...userGroupMembershipOrm,
|
||||||
|
filterProjectsByUserMembership,
|
||||||
|
findUserGroupMembershipsInProject,
|
||||||
|
findGroupMembersNotInProject
|
||||||
|
};
|
||||||
|
};
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TIdentityProjectAdditionalPrivilegeDALFactory = ReturnType<
|
||||||
|
typeof identityProjectAdditionalPrivilegeDALFactory
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const identityProjectAdditionalPrivilegeDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.IdentityProjectAdditionalPrivilege);
|
||||||
|
return orm;
|
||||||
|
};
|
||||||
+297
@@ -0,0 +1,297 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
|
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
|
import { TIdentityProjectAdditionalPrivilegeDALFactory } from "./identity-project-additional-privilege-dal";
|
||||||
|
import {
|
||||||
|
IdentityProjectAdditionalPrivilegeTemporaryMode,
|
||||||
|
TCreateIdentityPrivilegeDTO,
|
||||||
|
TDeleteIdentityPrivilegeDTO,
|
||||||
|
TGetIdentityPrivilegeDetailsDTO,
|
||||||
|
TListIdentityPrivilegesDTO,
|
||||||
|
TUpdateIdentityPrivilegeDTO
|
||||||
|
} from "./identity-project-additional-privilege-types";
|
||||||
|
|
||||||
|
type TIdentityProjectAdditionalPrivilegeServiceFactoryDep = {
|
||||||
|
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeDALFactory;
|
||||||
|
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
|
||||||
|
typeof identityProjectAdditionalPrivilegeServiceFactory
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||||
|
identityProjectAdditionalPrivilegeDAL,
|
||||||
|
identityProjectDAL,
|
||||||
|
permissionService,
|
||||||
|
projectDAL
|
||||||
|
}: TIdentityProjectAdditionalPrivilegeServiceFactoryDep) => {
|
||||||
|
const create = async ({
|
||||||
|
slug,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityId,
|
||||||
|
projectSlug,
|
||||||
|
permissions: customPermission,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
...dto
|
||||||
|
}: TCreateIdentityPrivilegeDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
|
||||||
|
if (!dto.isTemporary) {
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
|
projectMembershipId: identityProjectMembership.id,
|
||||||
|
slug,
|
||||||
|
permissions: customPermission
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
|
projectMembershipId: identityProjectMembership.id,
|
||||||
|
slug,
|
||||||
|
permissions: customPermission,
|
||||||
|
isTemporary: true,
|
||||||
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
|
temporaryRange: dto.temporaryRange,
|
||||||
|
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateBySlug = async ({
|
||||||
|
projectSlug,
|
||||||
|
slug,
|
||||||
|
identityId,
|
||||||
|
data,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TUpdateIdentityPrivilegeDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityProjectMembership.identityId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
||||||
|
if (data?.slug) {
|
||||||
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug: data.slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (existingSlug && existingSlug.id !== identityPrivilege.id)
|
||||||
|
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
||||||
|
if (isTemporary) {
|
||||||
|
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
||||||
|
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
|
...data,
|
||||||
|
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
|
...data,
|
||||||
|
isTemporary: false,
|
||||||
|
temporaryAccessStartTime: null,
|
||||||
|
temporaryAccessEndTime: null,
|
||||||
|
temporaryRange: null,
|
||||||
|
temporaryMode: null
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteBySlug = async ({
|
||||||
|
actorId,
|
||||||
|
slug,
|
||||||
|
identityId,
|
||||||
|
projectSlug,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TDeleteIdentityPrivilegeDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityProjectMembership.identityId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
|
||||||
|
|
||||||
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
||||||
|
|
||||||
|
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||||
|
return deletedPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPrivilegeDetailsBySlug = async ({
|
||||||
|
projectSlug,
|
||||||
|
identityId,
|
||||||
|
slug,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TGetIdentityPrivilegeDetailsDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
||||||
|
|
||||||
|
return identityPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listIdentityProjectPrivileges = async ({
|
||||||
|
identityId,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectSlug
|
||||||
|
}: TListIdentityPrivilegesDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
|
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
return identityPrivileges;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
create,
|
||||||
|
updateBySlug,
|
||||||
|
deleteBySlug,
|
||||||
|
getPrivilegeDetailsBySlug,
|
||||||
|
listIdentityProjectPrivileges
|
||||||
|
};
|
||||||
|
};
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
||||||
|
Relative = "relative"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TCreateIdentityPrivilegeDTO = {
|
||||||
|
permissions: unknown;
|
||||||
|
identityId: string;
|
||||||
|
projectSlug: string;
|
||||||
|
slug: string;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
isTemporary: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
isTemporary: true;
|
||||||
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}
|
||||||
|
) &
|
||||||
|
Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateIdentityPrivilegeDTO = { slug: string; identityId: string; projectSlug: string } & Omit<
|
||||||
|
TProjectPermission,
|
||||||
|
"projectId"
|
||||||
|
> & {
|
||||||
|
data: Partial<{
|
||||||
|
permissions: unknown;
|
||||||
|
slug: string;
|
||||||
|
isTemporary: boolean;
|
||||||
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteIdentityPrivilegeDTO = Omit<TProjectPermission, "projectId"> & {
|
||||||
|
slug: string;
|
||||||
|
identityId: string;
|
||||||
|
projectSlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetIdentityPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & {
|
||||||
|
slug: string;
|
||||||
|
identityId: string;
|
||||||
|
projectSlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
|
||||||
|
identityId: string;
|
||||||
|
projectSlug: string;
|
||||||
|
};
|
||||||
@@ -12,7 +12,6 @@ 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 { TOrgPermission } from "@app/lib/types";
|
|
||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMethod, 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";
|
||||||
@@ -24,7 +23,7 @@ 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 { TLdapConfigDALFactory } from "./ldap-config-dal";
|
import { TLdapConfigDALFactory } from "./ldap-config-dal";
|
||||||
import { TCreateLdapCfgDTO, TLdapLoginDTO, TUpdateLdapCfgDTO } from "./ldap-config-types";
|
import { TCreateLdapCfgDTO, TGetLdapCfgDTO, TLdapLoginDTO, TUpdateLdapCfgDTO } from "./ldap-config-types";
|
||||||
|
|
||||||
type TLdapConfigServiceFactoryDep = {
|
type TLdapConfigServiceFactoryDep = {
|
||||||
ldapConfigDAL: TLdapConfigDALFactory;
|
ldapConfigDAL: TLdapConfigDALFactory;
|
||||||
@@ -55,6 +54,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
orgId,
|
orgId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
isActive,
|
isActive,
|
||||||
url,
|
url,
|
||||||
bindDN,
|
bindDN,
|
||||||
@@ -62,7 +62,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
searchBase,
|
searchBase,
|
||||||
caCert
|
caCert
|
||||||
}: TCreateLdapCfgDTO) => {
|
}: TCreateLdapCfgDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
@@ -149,13 +149,14 @@ export const ldapConfigServiceFactory = ({
|
|||||||
orgId,
|
orgId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
isActive,
|
isActive,
|
||||||
|
actorAuthMethod,
|
||||||
url,
|
url,
|
||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
searchBase,
|
searchBase,
|
||||||
caCert
|
caCert
|
||||||
}: TUpdateLdapCfgDTO) => {
|
}: TUpdateLdapCfgDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
@@ -274,8 +275,14 @@ export const ldapConfigServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLdapCfgWithPermissionCheck = async ({ actor, actorId, orgId, actorOrgId }: TOrgPermission) => {
|
const getLdapCfgWithPermissionCheck = async ({
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
actor,
|
||||||
|
actorId,
|
||||||
|
orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TGetLdapCfgDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
||||||
return getLdapCfg({
|
return getLdapCfg({
|
||||||
orgId
|
orgId
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export type TCreateLdapCfgDTO = {
|
export type TCreateLdapCfgDTO = {
|
||||||
|
orgId: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
bindDN: string;
|
bindDN: string;
|
||||||
@@ -9,7 +10,9 @@ export type TCreateLdapCfgDTO = {
|
|||||||
caCert: string;
|
caCert: string;
|
||||||
} & TOrgPermission;
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TUpdateLdapCfgDTO = Partial<{
|
export type TUpdateLdapCfgDTO = {
|
||||||
|
orgId: string;
|
||||||
|
} & Partial<{
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
bindDN: string;
|
bindDN: string;
|
||||||
@@ -19,6 +22,10 @@ export type TUpdateLdapCfgDTO = Partial<{
|
|||||||
}> &
|
}> &
|
||||||
TOrgPermission;
|
TOrgPermission;
|
||||||
|
|
||||||
|
export type TGetLdapCfgDTO = {
|
||||||
|
orgId: string;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TLdapLoginDTO = {
|
export type TLdapLoginDTO = {
|
||||||
externalId: string;
|
externalId: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const getDefaultOnPremFeatures = () => {
|
|||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
|
groups: false,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
membersUsed: 0,
|
membersUsed: 0,
|
||||||
environmentLimit: null,
|
environmentLimit: null,
|
||||||
environmentsUsed: 0,
|
environmentsUsed: 0,
|
||||||
|
dynamicSecret: false,
|
||||||
secretVersioning: true,
|
secretVersioning: true,
|
||||||
pitRecovery: false,
|
pitRecovery: false,
|
||||||
ipAllowlisting: false,
|
ipAllowlisting: false,
|
||||||
@@ -26,6 +27,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
|
groups: false,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
|
|
||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { verifyOfflineLicense } from "@app/lib/crypto";
|
||||||
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 { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
@@ -26,6 +27,7 @@ import {
|
|||||||
TFeatureSet,
|
TFeatureSet,
|
||||||
TGetOrgBillInfoDTO,
|
TGetOrgBillInfoDTO,
|
||||||
TGetOrgTaxIdDTO,
|
TGetOrgTaxIdDTO,
|
||||||
|
TOfflineLicenseContents,
|
||||||
TOrgInvoiceDTO,
|
TOrgInvoiceDTO,
|
||||||
TOrgLicensesDTO,
|
TOrgLicensesDTO,
|
||||||
TOrgPlanDTO,
|
TOrgPlanDTO,
|
||||||
@@ -96,6 +98,36 @@ export const licenseServiceFactory = ({
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appCfg.LICENSE_KEY_OFFLINE) {
|
||||||
|
let isValidOfflineLicense = true;
|
||||||
|
const contents: TOfflineLicenseContents = JSON.parse(
|
||||||
|
Buffer.from(appCfg.LICENSE_KEY_OFFLINE, "base64").toString("utf8")
|
||||||
|
);
|
||||||
|
const isVerified = await verifyOfflineLicense(JSON.stringify(contents.license), contents.signature);
|
||||||
|
|
||||||
|
if (!isVerified) {
|
||||||
|
isValidOfflineLicense = false;
|
||||||
|
logger.warn(`Infisical EE offline license verification failed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contents.license.terminatesAt) {
|
||||||
|
const terminationDate = new Date(contents.license.terminatesAt);
|
||||||
|
if (terminationDate < new Date()) {
|
||||||
|
isValidOfflineLicense = false;
|
||||||
|
logger.warn(`Infisical EE offline license has expired`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidOfflineLicense) {
|
||||||
|
onPremFeatures = contents.license.features;
|
||||||
|
instanceType = InstanceType.EnterpriseOnPrem;
|
||||||
|
logger.info(`Instance type: ${InstanceType.EnterpriseOnPrem}`);
|
||||||
|
isValidLicense = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// this means this is self hosted oss version
|
// this means this is self hosted oss version
|
||||||
// else it would reach catch statement
|
// else it would reach catch statement
|
||||||
isValidLicense = true;
|
isValidLicense = true;
|
||||||
@@ -192,9 +224,10 @@ export const licenseServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
billingCycle
|
billingCycle
|
||||||
}: TOrgPlansTableDTO) => {
|
}: TOrgPlansTableDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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}`
|
||||||
@@ -202,15 +235,22 @@ export const licenseServiceFactory = ({
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, projectId }: TOrgPlanDTO) => {
|
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, actorAuthMethod, projectId }: TOrgPlanDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
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 ({
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
orgId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
success_url
|
||||||
|
}: TStartOrgTrialDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
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);
|
||||||
|
|
||||||
@@ -231,8 +271,14 @@ export const licenseServiceFactory = ({
|
|||||||
return { url };
|
return { url };
|
||||||
};
|
};
|
||||||
|
|
||||||
const createOrganizationPortalSession = async ({ orgId, actorId, actor, actorOrgId }: TCreateOrgPortalSession) => {
|
const createOrganizationPortalSession = async ({
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
orgId,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: TCreateOrgPortalSession) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
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);
|
||||||
|
|
||||||
@@ -278,8 +324,8 @@ export const licenseServiceFactory = ({
|
|||||||
return { url };
|
return { url };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => {
|
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -295,8 +341,8 @@ export const licenseServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 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, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -311,8 +357,8 @@ export const licenseServiceFactory = ({
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => {
|
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -332,11 +378,12 @@ export const licenseServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
email
|
email
|
||||||
}: TUpdateOrgBillingDetailsDTO) => {
|
}: TUpdateOrgBillingDetailsDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -355,8 +402,8 @@ export const licenseServiceFactory = ({
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorOrgId }: TOrgPmtMethodsDTO) => {
|
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -378,11 +425,12 @@ export const licenseServiceFactory = ({
|
|||||||
orgId,
|
orgId,
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
success_url,
|
success_url,
|
||||||
cancel_url
|
cancel_url
|
||||||
}: TAddOrgPmtMethodDTO) => {
|
}: TAddOrgPmtMethodDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -403,8 +451,15 @@ export const licenseServiceFactory = ({
|
|||||||
return { url };
|
return { url };
|
||||||
};
|
};
|
||||||
|
|
||||||
const delOrgPmtMethods = async ({ actorId, actor, actorOrgId, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => {
|
const delOrgPmtMethods = async ({
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
orgId,
|
||||||
|
pmtMethodId
|
||||||
|
}: TDelOrgPmtMethodDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -420,8 +475,8 @@ export const licenseServiceFactory = ({
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrgTaxIds = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgTaxIdDTO) => {
|
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -438,8 +493,8 @@ export const licenseServiceFactory = ({
|
|||||||
return taxIds;
|
return taxIds;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addOrgTaxId = async ({ actorId, actor, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
|
const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -459,8 +514,8 @@ export const licenseServiceFactory = ({
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const delOrgTaxId = async ({ orgId, actor, actorId, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
|
const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -476,8 +531,8 @@ export const licenseServiceFactory = ({
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, orgId }: TOrgInvoiceDTO) => {
|
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
@@ -493,8 +548,8 @@ export const licenseServiceFactory = ({
|
|||||||
return invoices;
|
return invoices;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOrgLicenses = async ({ orgId, actor, actorId, actorOrgId }: TOrgLicensesDTO) => {
|
const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, 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);
|
||||||
|
|||||||
@@ -6,12 +6,28 @@ export enum InstanceType {
|
|||||||
Cloud = "cloud"
|
Cloud = "cloud"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TOfflineLicenseContents = {
|
||||||
|
license: TOfflineLicense;
|
||||||
|
signature: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TOfflineLicense = {
|
||||||
|
issuedTo: string;
|
||||||
|
licenseId: string;
|
||||||
|
customerId: string | null;
|
||||||
|
issuedAt: string;
|
||||||
|
expiresAt: string | null;
|
||||||
|
terminatesAt: string | null;
|
||||||
|
features: TFeatureSet;
|
||||||
|
};
|
||||||
|
|
||||||
export type TFeatureSet = {
|
export type TFeatureSet = {
|
||||||
_id: null;
|
_id: null;
|
||||||
slug: null;
|
slug: null;
|
||||||
tier: -1;
|
tier: -1;
|
||||||
workspaceLimit: null;
|
workspaceLimit: null;
|
||||||
workspacesUsed: 0;
|
workspacesUsed: 0;
|
||||||
|
dynamicSecret: false;
|
||||||
memberLimit: null;
|
memberLimit: null;
|
||||||
membersUsed: 0;
|
membersUsed: 0;
|
||||||
environmentLimit: null;
|
environmentLimit: null;
|
||||||
@@ -27,6 +43,7 @@ export type TFeatureSet = {
|
|||||||
samlSSO: false;
|
samlSSO: false;
|
||||||
scim: false;
|
scim: false;
|
||||||
ldap: false;
|
ldap: false;
|
||||||
|
groups: false;
|
||||||
status: null;
|
status: null;
|
||||||
trial_end: null;
|
trial_end: null;
|
||||||
has_used_trial: true;
|
has_used_trial: true;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export enum OrgPermissionSubjects {
|
|||||||
Sso = "sso",
|
Sso = "sso",
|
||||||
Scim = "scim",
|
Scim = "scim",
|
||||||
Ldap = "ldap",
|
Ldap = "ldap",
|
||||||
|
Groups = "groups",
|
||||||
Billing = "billing",
|
Billing = "billing",
|
||||||
SecretScanning = "secret-scanning",
|
SecretScanning = "secret-scanning",
|
||||||
Identity = "identity"
|
Identity = "identity"
|
||||||
@@ -33,6 +34,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Groups]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
||||||
@@ -83,6 +85,11 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Groups);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Groups);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Groups);
|
||||||
|
|
||||||
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);
|
||||||
@@ -105,6 +112,7 @@ const buildMemberPermission = () => {
|
|||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||||
|
|||||||
@@ -45,6 +45,42 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const getProjectPermission = async (userId: string, projectId: string) => {
|
const getProjectPermission = async (userId: string, projectId: string) => {
|
||||||
try {
|
try {
|
||||||
|
const groups: string[] = await db(TableName.GroupProjectMembership)
|
||||||
|
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
|
||||||
|
.pluck(`${TableName.GroupProjectMembership}.groupId`);
|
||||||
|
|
||||||
|
const groupDocs = await db(TableName.UserGroupMembership)
|
||||||
|
.where(`${TableName.UserGroupMembership}.userId`, userId)
|
||||||
|
.whereIn(`${TableName.UserGroupMembership}.groupId`, groups)
|
||||||
|
.join(
|
||||||
|
TableName.GroupProjectMembership,
|
||||||
|
`${TableName.GroupProjectMembership}.groupId`,
|
||||||
|
`${TableName.UserGroupMembership}.groupId`
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
TableName.GroupProjectMembershipRole,
|
||||||
|
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
|
||||||
|
`${TableName.GroupProjectMembership}.id`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ProjectRoles,
|
||||||
|
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
||||||
|
`${TableName.ProjectRoles}.id`
|
||||||
|
)
|
||||||
|
.join(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||||
|
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||||
|
.select(selectAllTableCols(TableName.GroupProjectMembershipRole))
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
|
||||||
|
db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("membershipCreatedAt"),
|
||||||
|
db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("membershipUpdatedAt"),
|
||||||
|
db.ref("projectId").withSchema(TableName.GroupProjectMembership),
|
||||||
|
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||||
|
db.ref("orgId").withSchema(TableName.Project),
|
||||||
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
||||||
|
)
|
||||||
|
.select("permissions");
|
||||||
|
|
||||||
const docs = await db(TableName.ProjectMembership)
|
const docs = await db(TableName.ProjectMembership)
|
||||||
.join(
|
.join(
|
||||||
TableName.ProjectUserMembershipRole,
|
TableName.ProjectUserMembershipRole,
|
||||||
@@ -56,6 +92,11 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.ProjectUserMembershipRole}.customRoleId`,
|
`${TableName.ProjectUserMembershipRole}.customRoleId`,
|
||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ProjectUserAdditionalPrivilege,
|
||||||
|
`${TableName.ProjectUserAdditionalPrivilege}.projectMembershipId`,
|
||||||
|
`${TableName.ProjectMembership}.id`
|
||||||
|
)
|
||||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||||
.where("userId", userId)
|
.where("userId", userId)
|
||||||
@@ -63,31 +104,35 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
.select(selectAllTableCols(TableName.ProjectUserMembershipRole))
|
.select(selectAllTableCols(TableName.ProjectUserMembershipRole))
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"),
|
db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"),
|
||||||
// TODO(roll-forward-migration): remove this field when we drop this in next migration after a week
|
|
||||||
db.ref("role").withSchema(TableName.ProjectMembership).as("oldRoleField"),
|
|
||||||
db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"),
|
db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"),
|
||||||
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
|
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
|
||||||
|
db.ref("projectId").withSchema(TableName.ProjectMembership),
|
||||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||||
db.ref("orgId").withSchema(TableName.Project),
|
db.ref("orgId").withSchema(TableName.Project),
|
||||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||||
)
|
db.ref("permissions").withSchema(TableName.ProjectRoles),
|
||||||
.select("permissions");
|
db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApId"),
|
||||||
|
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApPermissions"),
|
||||||
|
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryMode"),
|
||||||
|
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApIsTemporary"),
|
||||||
|
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryRange"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
|
.as("userApTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
|
.as("userApTemporaryAccessEndTime")
|
||||||
|
);
|
||||||
|
|
||||||
const permission = sqlNestRelationships({
|
const permission = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "membershipId",
|
key: "projectId",
|
||||||
parentMapper: ({
|
parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt }) => ({
|
||||||
orgId,
|
|
||||||
orgAuthEnforced,
|
|
||||||
membershipId,
|
|
||||||
membershipCreatedAt,
|
|
||||||
membershipUpdatedAt,
|
|
||||||
oldRoleField
|
|
||||||
}) => ({
|
|
||||||
orgId,
|
orgId,
|
||||||
orgAuthEnforced,
|
orgAuthEnforced,
|
||||||
userId,
|
userId,
|
||||||
role: oldRoleField,
|
|
||||||
id: membershipId,
|
id: membershipId,
|
||||||
projectId,
|
projectId,
|
||||||
createdAt: membershipCreatedAt,
|
createdAt: membershipCreatedAt,
|
||||||
@@ -102,15 +147,83 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
permissions: z.unknown(),
|
permissions: z.unknown(),
|
||||||
customRoleSlug: z.string().optional().nullable()
|
customRoleSlug: z.string().optional().nullable()
|
||||||
}).parse(data)
|
}).parse(data)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "userApId",
|
||||||
|
label: "additionalPrivileges" as const,
|
||||||
|
mapper: ({
|
||||||
|
userApId,
|
||||||
|
userApPermissions,
|
||||||
|
userApIsTemporary,
|
||||||
|
userApTemporaryMode,
|
||||||
|
userApTemporaryRange,
|
||||||
|
userApTemporaryAccessEndTime,
|
||||||
|
userApTemporaryAccessStartTime
|
||||||
|
}) => ({
|
||||||
|
id: userApId,
|
||||||
|
permissions: userApPermissions,
|
||||||
|
temporaryRange: userApTemporaryRange,
|
||||||
|
temporaryMode: userApTemporaryMode,
|
||||||
|
temporaryAccessEndTime: userApTemporaryAccessEndTime,
|
||||||
|
temporaryAccessStartTime: userApTemporaryAccessStartTime,
|
||||||
|
isTemporary: userApIsTemporary
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const groupPermission = groupDocs.length
|
||||||
|
? sqlNestRelationships({
|
||||||
|
data: groupDocs,
|
||||||
|
key: "projectId",
|
||||||
|
parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt }) => ({
|
||||||
|
orgId,
|
||||||
|
orgAuthEnforced,
|
||||||
|
userId,
|
||||||
|
id: membershipId,
|
||||||
|
projectId,
|
||||||
|
createdAt: membershipCreatedAt,
|
||||||
|
updatedAt: membershipUpdatedAt
|
||||||
|
}),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "id",
|
||||||
|
label: "roles" as const,
|
||||||
|
mapper: (data) =>
|
||||||
|
ProjectUserMembershipRolesSchema.extend({
|
||||||
|
permissions: z.unknown(),
|
||||||
|
customRoleSlug: z.string().optional().nullable()
|
||||||
|
}).parse(data)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (!permission?.[0] && !groupPermission[0]) return undefined;
|
||||||
|
|
||||||
// when introducting cron mode change it here
|
// when introducting cron mode change it here
|
||||||
const activeRoles = permission?.[0]?.roles.filter(
|
const activeRoles =
|
||||||
|
permission?.[0]?.roles?.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
const activeGroupRoles =
|
||||||
|
groupPermission?.[0]?.roles?.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
const activeAdditionalPrivileges = permission?.[0]?.additionalPrivileges?.filter(
|
||||||
({ isTemporary, temporaryAccessEndTime }) =>
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
);
|
);
|
||||||
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
|
|
||||||
|
return {
|
||||||
|
...(permission[0] || groupPermission[0]),
|
||||||
|
roles: [...activeRoles, ...activeGroupRoles],
|
||||||
|
additionalPrivileges: activeAdditionalPrivileges
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
||||||
}
|
}
|
||||||
@@ -129,31 +242,60 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityProjectAdditionalPrivilege,
|
||||||
|
`${TableName.IdentityProjectAdditionalPrivilege}.projectMembershipId`,
|
||||||
|
`${TableName.IdentityProjectMembership}.id`
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
// Join the Project table to later select orgId
|
||||||
|
TableName.Project,
|
||||||
|
`${TableName.IdentityProjectMembership}.projectId`,
|
||||||
|
`${TableName.Project}.id`
|
||||||
|
)
|
||||||
.where("identityId", identityId)
|
.where("identityId", identityId)
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
|
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
|
||||||
db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"),
|
db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project
|
||||||
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
||||||
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
||||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||||
)
|
db.ref("permissions").withSchema(TableName.ProjectRoles),
|
||||||
.select("permissions");
|
db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"),
|
||||||
|
db.ref("permissions").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApPermissions"),
|
||||||
|
db
|
||||||
|
.ref("temporaryMode")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryMode"),
|
||||||
|
db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"),
|
||||||
|
db
|
||||||
|
.ref("temporaryRange")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryRange"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryAccessEndTime")
|
||||||
|
);
|
||||||
|
|
||||||
const permission = sqlNestRelationships({
|
const permission = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "membershipId",
|
key: "membershipId",
|
||||||
parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, oldRoleField }) => ({
|
parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, orgId }) => ({
|
||||||
id: membershipId,
|
id: membershipId,
|
||||||
identityId,
|
identityId,
|
||||||
projectId,
|
projectId,
|
||||||
role: oldRoleField,
|
|
||||||
createdAt: membershipCreatedAt,
|
createdAt: membershipCreatedAt,
|
||||||
updatedAt: membershipUpdatedAt,
|
updatedAt: membershipUpdatedAt,
|
||||||
|
orgId,
|
||||||
// just a prefilled value
|
// just a prefilled value
|
||||||
orgAuthEnforced: false,
|
orgAuthEnforced: false
|
||||||
orgId: ""
|
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
@@ -164,16 +306,44 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
permissions: z.unknown(),
|
permissions: z.unknown(),
|
||||||
customRoleSlug: z.string().optional().nullable()
|
customRoleSlug: z.string().optional().nullable()
|
||||||
}).parse(data)
|
}).parse(data)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "identityApId",
|
||||||
|
label: "additionalPrivileges" as const,
|
||||||
|
mapper: ({
|
||||||
|
identityApId,
|
||||||
|
identityApPermissions,
|
||||||
|
identityApIsTemporary,
|
||||||
|
identityApTemporaryMode,
|
||||||
|
identityApTemporaryRange,
|
||||||
|
identityApTemporaryAccessEndTime,
|
||||||
|
identityApTemporaryAccessStartTime
|
||||||
|
}) => ({
|
||||||
|
id: identityApId,
|
||||||
|
permissions: identityApPermissions,
|
||||||
|
temporaryRange: identityApTemporaryRange,
|
||||||
|
temporaryMode: identityApTemporaryMode,
|
||||||
|
temporaryAccessEndTime: identityApTemporaryAccessEndTime,
|
||||||
|
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
|
||||||
|
isTemporary: identityApIsTemporary
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!permission?.[0]) return undefined;
|
||||||
|
|
||||||
// when introducting cron mode change it here
|
// when introducting cron mode change it here
|
||||||
const activeRoles = permission?.[0]?.roles.filter(
|
const activeRoles = permission?.[0]?.roles.filter(
|
||||||
({ isTemporary, temporaryAccessEndTime }) =>
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
);
|
);
|
||||||
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
|
const activeAdditionalPrivileges = permission?.[0]?.additionalPrivileges?.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...permission[0], roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
|
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { TOrganizations } from "@app/db/schemas";
|
||||||
|
import { UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
||||||
|
if (!actorAuthMethod) return false;
|
||||||
|
|
||||||
|
return [
|
||||||
|
AuthMethod.AZURE_SAML,
|
||||||
|
AuthMethod.OKTA_SAML,
|
||||||
|
AuthMethod.JUMPCLOUD_SAML,
|
||||||
|
AuthMethod.GOOGLE_SAML,
|
||||||
|
AuthMethod.KEYCLOAK_SAML
|
||||||
|
].includes(actorAuthMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateOrgSAML(actorAuthMethod: ActorAuthMethod, isSamlEnforced: TOrganizations["authEnforced"]) {
|
||||||
|
if (actorAuthMethod === undefined) {
|
||||||
|
throw new UnauthorizedError({ name: "No auth method defined" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSamlEnforced && actorAuthMethod !== null && !isAuthMethodSaml(actorAuthMethod)) {
|
||||||
|
throw new UnauthorizedError({ name: "Cannot access org-scoped resource" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { isAuthMethodSaml, validateOrgSAML };
|
||||||
@@ -11,13 +11,15 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { conditionsMatcher } from "@app/lib/casl";
|
import { conditionsMatcher } from "@app/lib/casl";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal";
|
import { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal";
|
||||||
import { TServiceTokenDALFactory } from "@app/services/service-token/service-token-dal";
|
import { TServiceTokenDALFactory } from "@app/services/service-token/service-token-dal";
|
||||||
|
|
||||||
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
|
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
|
||||||
import { TPermissionDALFactory } from "./permission-dal";
|
import { TPermissionDALFactory } from "./permission-dal";
|
||||||
|
import { validateOrgSAML } from "./permission-fns";
|
||||||
import { TBuildProjectPermissionDTO } from "./permission-types";
|
import { TBuildProjectPermissionDTO } from "./permission-types";
|
||||||
import {
|
import {
|
||||||
buildServiceTokenProjectPermission,
|
buildServiceTokenProjectPermission,
|
||||||
@@ -32,6 +34,7 @@ type TPermissionServiceFactoryDep = {
|
|||||||
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
|
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
|
||||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "findOne">;
|
projectRoleDAL: Pick<TProjectRoleDALFactory, "findOne">;
|
||||||
serviceTokenDAL: Pick<TServiceTokenDALFactory, "findById">;
|
serviceTokenDAL: Pick<TServiceTokenDALFactory, "findById">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
permissionDAL: TPermissionDALFactory;
|
permissionDAL: TPermissionDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,7 +44,8 @@ export const permissionServiceFactory = ({
|
|||||||
permissionDAL,
|
permissionDAL,
|
||||||
orgRoleDAL,
|
orgRoleDAL,
|
||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
serviceTokenDAL
|
serviceTokenDAL,
|
||||||
|
projectDAL
|
||||||
}: TPermissionServiceFactoryDep) => {
|
}: TPermissionServiceFactoryDep) => {
|
||||||
const buildOrgPermission = (role: string, permission?: unknown) => {
|
const buildOrgPermission = (role: string, permission?: unknown) => {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
@@ -98,16 +102,30 @@ 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,
|
||||||
|
authMethod: ActorAuthMethod,
|
||||||
|
userOrgId?: 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" });
|
// If the org ID is API_KEY, the request is being made with an API Key.
|
||||||
|
// Since we can't scope API keys to an organization, we'll need to do an arbitrary check to see if the user is a member of the organization.
|
||||||
|
|
||||||
|
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
|
||||||
|
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
|
||||||
|
if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) {
|
||||||
|
throw new UnauthorizedError({ name: "You are not logged into this organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateOrgSAML(authMethod, membership.orgAuthEnforced);
|
||||||
|
|
||||||
return { permission: buildOrgPermission(membership.role, membership.permissions), membership };
|
return { permission: buildOrgPermission(membership.role, membership.permissions), membership };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -120,10 +138,16 @@ 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,
|
||||||
|
authMethod: ActorAuthMethod,
|
||||||
|
actorOrgId: string | undefined
|
||||||
|
) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ActorType.USER:
|
case ActorType.USER:
|
||||||
return getUserOrgPermission(id, orgId, actorOrgId);
|
return getUserOrgPermission(id, orgId, authMethod, actorOrgId);
|
||||||
case ActorType.IDENTITY:
|
case ActorType.IDENTITY:
|
||||||
return getIdentityOrgPermission(id, orgId);
|
return getIdentityOrgPermission(id, orgId);
|
||||||
default:
|
default:
|
||||||
@@ -153,6 +177,7 @@ export const permissionServiceFactory = ({
|
|||||||
const getUserProjectPermission = async (
|
const getUserProjectPermission = async (
|
||||||
userId: string,
|
userId: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
|
authMethod: ActorAuthMethod,
|
||||||
userOrgId?: string
|
userOrgId?: string
|
||||||
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||||
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||||
@@ -164,12 +189,27 @@ export const permissionServiceFactory = ({
|
|||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new BadRequestError({ name: "Custom permission not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userProjectPermission.orgAuthEnforced && userProjectPermission.orgId !== userOrgId) {
|
// If the org ID is API_KEY, the request is being made with an API Key.
|
||||||
throw new BadRequestError({ name: "Cannot access org-scoped resource" });
|
// Since we can't scope API keys to an organization, we'll need to do an arbitrary check to see if the user is a member of the organization.
|
||||||
|
|
||||||
|
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
|
||||||
|
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
|
||||||
|
if (userOrgId !== "API_KEY" && userProjectPermission.orgId !== userOrgId) {
|
||||||
|
throw new UnauthorizedError({ name: "You are not logged into this organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validateOrgSAML(authMethod, userProjectPermission.orgAuthEnforced);
|
||||||
|
|
||||||
|
// join two permissions and pass to build the final permission set
|
||||||
|
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
|
const additionalPrivileges =
|
||||||
|
userProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
|
||||||
|
role: ProjectMembershipRole.Custom,
|
||||||
|
permissions
|
||||||
|
})) || [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission(userProjectPermission.roles),
|
permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)),
|
||||||
membership: userProjectPermission,
|
membership: userProjectPermission,
|
||||||
hasRole: (role: string) =>
|
hasRole: (role: string) =>
|
||||||
userProjectPermission.roles.findIndex(
|
userProjectPermission.roles.findIndex(
|
||||||
@@ -180,7 +220,8 @@ export const permissionServiceFactory = ({
|
|||||||
|
|
||||||
const getIdentityProjectPermission = async (
|
const getIdentityProjectPermission = async (
|
||||||
identityId: string,
|
identityId: string,
|
||||||
projectId: string
|
projectId: string,
|
||||||
|
identityOrgId: string | undefined
|
||||||
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||||
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||||
if (!identityProjectPermission) throw new UnauthorizedError({ name: "Identity not in project" });
|
if (!identityProjectPermission) throw new UnauthorizedError({ name: "Identity not in project" });
|
||||||
@@ -193,8 +234,20 @@ export const permissionServiceFactory = ({
|
|||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new BadRequestError({ name: "Custom permission not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (identityProjectPermission.orgId !== identityOrgId) {
|
||||||
|
throw new UnauthorizedError({ name: "You are not a member of this organization" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const rolePermissions =
|
||||||
|
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
|
const additionalPrivileges =
|
||||||
|
identityProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
|
||||||
|
role: ProjectMembershipRole.Custom,
|
||||||
|
permissions
|
||||||
|
})) || [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission(identityProjectPermission.roles),
|
permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)),
|
||||||
membership: identityProjectPermission,
|
membership: identityProjectPermission,
|
||||||
hasRole: (role: string) =>
|
hasRole: (role: string) =>
|
||||||
identityProjectPermission.roles.findIndex(
|
identityProjectPermission.roles.findIndex(
|
||||||
@@ -203,14 +256,32 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getServiceTokenProjectPermission = async (serviceTokenId: string, projectId: string) => {
|
const getServiceTokenProjectPermission = async (
|
||||||
|
serviceTokenId: string,
|
||||||
|
projectId: string,
|
||||||
|
actorOrgId: string | undefined
|
||||||
|
) => {
|
||||||
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
||||||
if (!serviceToken) throw new BadRequestError({ message: "Service token not found" });
|
if (!serviceToken) throw new BadRequestError({ message: "Service token not found" });
|
||||||
|
|
||||||
|
const serviceTokenProject = await projectDAL.findById(serviceToken.projectId);
|
||||||
|
|
||||||
|
if (!serviceTokenProject) throw new BadRequestError({ message: "Service token not linked to a project" });
|
||||||
|
|
||||||
|
if (serviceTokenProject.orgId !== actorOrgId) {
|
||||||
|
throw new UnauthorizedError({ message: "Service token not a part of this organization" });
|
||||||
|
}
|
||||||
|
|
||||||
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"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (serviceTokenProject.orgId !== actorOrgId)
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "Failed to find service authorization for given project"
|
||||||
|
});
|
||||||
|
|
||||||
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
||||||
return {
|
return {
|
||||||
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
||||||
@@ -238,15 +309,16 @@ export const permissionServiceFactory = ({
|
|||||||
type: T,
|
type: T,
|
||||||
id: string,
|
id: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
actorOrgId?: string
|
actorAuthMethod: ActorAuthMethod,
|
||||||
|
actorOrgId: string | undefined
|
||||||
): 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, actorAuthMethod, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
||||||
case ActorType.SERVICE:
|
case ActorType.SERVICE:
|
||||||
return getServiceTokenProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>;
|
return getServiceTokenProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
||||||
case ActorType.IDENTITY:
|
case ActorType.IDENTITY:
|
||||||
return getIdentityProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>;
|
return getIdentityProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
||||||
default:
|
default:
|
||||||
throw new UnauthorizedError({
|
throw new UnauthorizedError({
|
||||||
message: "Permission not defined",
|
message: "Permission not defined",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export enum ProjectPermissionActions {
|
|||||||
export enum ProjectPermissionSub {
|
export enum ProjectPermissionSub {
|
||||||
Role = "role",
|
Role = "role",
|
||||||
Member = "member",
|
Member = "member",
|
||||||
|
Groups = "groups",
|
||||||
Settings = "settings",
|
Settings = "settings",
|
||||||
Integrations = "integrations",
|
Integrations = "integrations",
|
||||||
Webhooks = "webhooks",
|
Webhooks = "webhooks",
|
||||||
@@ -41,6 +42,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.Groups]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Integrations]
|
| [ProjectPermissionActions, ProjectPermissionSub.Integrations]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Webhooks]
|
| [ProjectPermissionActions, ProjectPermissionSub.Webhooks]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.AuditLogs]
|
| [ProjectPermissionActions, ProjectPermissionSub.AuditLogs]
|
||||||
@@ -82,6 +84,11 @@ const buildAdminPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
|
||||||
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Groups);
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Groups);
|
||||||
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Groups);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
|
||||||
@@ -157,6 +164,8 @@ const buildMemberPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
|
||||||
@@ -209,6 +218,7 @@ const buildViewerPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
||||||
|
|||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TProjectUserAdditionalPrivilegeDALFactory = ReturnType<typeof projectUserAdditionalPrivilegeDALFactory>;
|
||||||
|
|
||||||
|
export const projectUserAdditionalPrivilegeDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.ProjectUserAdditionalPrivilege);
|
||||||
|
return orm;
|
||||||
|
};
|
||||||
+212
@@ -0,0 +1,212 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
|
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
||||||
|
import {
|
||||||
|
ProjectUserAdditionalPrivilegeTemporaryMode,
|
||||||
|
TCreateUserPrivilegeDTO,
|
||||||
|
TDeleteUserPrivilegeDTO,
|
||||||
|
TGetUserPrivilegeDetailsDTO,
|
||||||
|
TListUserPrivilegesDTO,
|
||||||
|
TUpdateUserPrivilegeDTO
|
||||||
|
} from "./project-user-additional-privilege-types";
|
||||||
|
|
||||||
|
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
||||||
|
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
||||||
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType<
|
||||||
|
typeof projectUserAdditionalPrivilegeServiceFactory
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||||
|
projectUserAdditionalPrivilegeDAL,
|
||||||
|
projectMembershipDAL,
|
||||||
|
permissionService
|
||||||
|
}: TProjectUserAdditionalPrivilegeServiceFactoryDep) => {
|
||||||
|
const create = async ({
|
||||||
|
slug,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
permissions: customPermission,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectMembershipId,
|
||||||
|
...dto
|
||||||
|
}: TCreateUserPrivilegeDTO) => {
|
||||||
|
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||||
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({ slug, projectMembershipId });
|
||||||
|
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
|
||||||
|
if (!dto.isTemporary) {
|
||||||
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||||
|
projectMembershipId,
|
||||||
|
slug,
|
||||||
|
permissions: customPermission
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||||
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||||
|
projectMembershipId,
|
||||||
|
slug,
|
||||||
|
permissions: customPermission,
|
||||||
|
isTemporary: true,
|
||||||
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
|
temporaryRange: dto.temporaryRange,
|
||||||
|
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateById = async ({
|
||||||
|
privilegeId,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
...dto
|
||||||
|
}: TUpdateUserPrivilegeDTO) => {
|
||||||
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
|
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||||
|
|
||||||
|
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||||
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
if (dto?.slug) {
|
||||||
|
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug: dto.slug,
|
||||||
|
projectMembershipId: projectMembership.id
|
||||||
|
});
|
||||||
|
if (existingSlug && existingSlug.id !== userPrivilege.id)
|
||||||
|
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary;
|
||||||
|
if (isTemporary) {
|
||||||
|
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || userPrivilege?.temporaryAccessStartTime;
|
||||||
|
const temporaryRange = dto?.temporaryRange || userPrivilege?.temporaryRange;
|
||||||
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||||
|
...dto,
|
||||||
|
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||||
|
...dto,
|
||||||
|
isTemporary: false,
|
||||||
|
temporaryAccessStartTime: null,
|
||||||
|
temporaryAccessEndTime: null,
|
||||||
|
temporaryRange: null,
|
||||||
|
temporaryMode: null
|
||||||
|
});
|
||||||
|
return additionalPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
|
||||||
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
|
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||||
|
|
||||||
|
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||||
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||||
|
return deletedPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPrivilegeDetailsById = async ({
|
||||||
|
privilegeId,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TGetUserPrivilegeDetailsDTO) => {
|
||||||
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
|
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||||
|
|
||||||
|
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||||
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
return userPrivilege;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listPrivileges = async ({
|
||||||
|
projectMembershipId,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TListUserPrivilegesDTO) => {
|
||||||
|
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||||
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
|
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find({ projectMembershipId });
|
||||||
|
return userPrivileges;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
create,
|
||||||
|
updateById,
|
||||||
|
deleteById,
|
||||||
|
getPrivilegeDetailsById,
|
||||||
|
listPrivileges
|
||||||
|
};
|
||||||
|
};
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export enum ProjectUserAdditionalPrivilegeTemporaryMode {
|
||||||
|
Relative = "relative"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TCreateUserPrivilegeDTO = (
|
||||||
|
| {
|
||||||
|
permissions: unknown;
|
||||||
|
projectMembershipId: string;
|
||||||
|
slug: string;
|
||||||
|
isTemporary: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
permissions: unknown;
|
||||||
|
projectMembershipId: string;
|
||||||
|
slug: string;
|
||||||
|
isTemporary: true;
|
||||||
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}
|
||||||
|
) &
|
||||||
|
Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
|
||||||
|
Partial<{
|
||||||
|
permissions: unknown;
|
||||||
|
slug: string;
|
||||||
|
isTemporary: boolean;
|
||||||
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type TDeleteUserPrivilegeDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
||||||
|
|
||||||
|
export type TGetUserPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
||||||
|
|
||||||
|
export type TListUserPrivilegesDTO = Omit<TProjectPermission, "projectId"> & { projectMembershipId: string };
|
||||||
@@ -55,6 +55,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
const createSamlCfg = async ({
|
const createSamlCfg = async ({
|
||||||
cert,
|
cert,
|
||||||
actor,
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
orgId,
|
orgId,
|
||||||
issuer,
|
issuer,
|
||||||
@@ -63,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, actorAuthMethod, actorOrgId);
|
||||||
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);
|
||||||
@@ -146,6 +147,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
orgId,
|
orgId,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
cert,
|
cert,
|
||||||
actorId,
|
actorId,
|
||||||
issuer,
|
issuer,
|
||||||
@@ -153,7 +155,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, actorAuthMethod, actorOrgId);
|
||||||
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)
|
||||||
@@ -238,6 +240,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
dto.actor,
|
dto.actor,
|
||||||
dto.actorId,
|
dto.actorId,
|
||||||
ssoConfig.orgId,
|
ssoConfig.orgId,
|
||||||
|
dto.actorAuthMethod,
|
||||||
dto.actorOrgId
|
dto.actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
|
||||||
@@ -316,6 +319,11 @@ export const samlConfigServiceFactory = ({
|
|||||||
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" });
|
||||||
|
|
||||||
|
// TODO(dangtony98): remove this after aliases update
|
||||||
|
if (authProvider === AuthMethod.KEYCLOAK_SAML && appCfg.LICENSE_SERVER_KEY) {
|
||||||
|
throw new BadRequestError({ message: "Keycloak SAML is not yet available on Infisical Cloud" });
|
||||||
|
}
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
const [orgMembership] = await orgDAL.findMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export enum SamlProviders {
|
export enum SamlProviders {
|
||||||
OKTA_SAML = "okta-saml",
|
OKTA_SAML = "okta-saml",
|
||||||
AZURE_SAML = "azure-saml",
|
AZURE_SAML = "azure-saml",
|
||||||
JUMPCLOUD_SAML = "jumpcloud-saml",
|
JUMPCLOUD_SAML = "jumpcloud-saml",
|
||||||
GOOGLE_SAML = "google-saml"
|
GOOGLE_SAML = "google-saml",
|
||||||
|
KEYCLOAK_SAML = "keycloak-saml"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCreateSamlCfgDTO = {
|
export type TCreateSamlCfgDTO = {
|
||||||
@@ -26,7 +27,14 @@ 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;
|
||||||
|
actorAuthMethod: ActorAuthMethod;
|
||||||
|
actorOrgId: string | undefined;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: "orgSlug";
|
type: "orgSlug";
|
||||||
orgSlug: string;
|
orgSlug: string;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TListScimUsers, TScimUser } from "./scim-types";
|
import { TListScimGroups, TListScimUsers, TScimGroup, TScimUser } from "./scim-types";
|
||||||
|
|
||||||
export const buildScimUserList = ({
|
export const buildScimUserList = ({
|
||||||
scimUsers,
|
scimUsers,
|
||||||
@@ -62,3 +62,47 @@ export const buildScimUser = ({
|
|||||||
|
|
||||||
return scimUser;
|
return scimUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const buildScimGroupList = ({
|
||||||
|
scimGroups,
|
||||||
|
offset,
|
||||||
|
limit
|
||||||
|
}: {
|
||||||
|
scimGroups: TScimGroup[];
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
}): TListScimGroups => {
|
||||||
|
return {
|
||||||
|
Resources: scimGroups,
|
||||||
|
itemsPerPage: limit,
|
||||||
|
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||||
|
startIndex: offset,
|
||||||
|
totalResults: scimGroups.length
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const buildScimGroup = ({
|
||||||
|
groupId,
|
||||||
|
name,
|
||||||
|
members
|
||||||
|
}: {
|
||||||
|
groupId: string;
|
||||||
|
name: string;
|
||||||
|
members: {
|
||||||
|
value: string;
|
||||||
|
display: string;
|
||||||
|
}[];
|
||||||
|
}): TScimGroup => {
|
||||||
|
const scimGroup = {
|
||||||
|
schemas: ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||||
|
id: groupId,
|
||||||
|
displayName: name,
|
||||||
|
members,
|
||||||
|
meta: {
|
||||||
|
resourceType: "Group",
|
||||||
|
location: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return scimGroup;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus, TableName } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups } from "@app/db/schemas";
|
||||||
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
|
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
@@ -17,16 +20,23 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
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 { buildScimUser, buildScimUserList } from "./scim-fns";
|
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList } from "./scim-fns";
|
||||||
import {
|
import {
|
||||||
|
TCreateScimGroupDTO,
|
||||||
TCreateScimTokenDTO,
|
TCreateScimTokenDTO,
|
||||||
TCreateScimUserDTO,
|
TCreateScimUserDTO,
|
||||||
|
TDeleteScimGroupDTO,
|
||||||
TDeleteScimTokenDTO,
|
TDeleteScimTokenDTO,
|
||||||
|
TDeleteScimUserDTO,
|
||||||
|
TGetScimGroupDTO,
|
||||||
TGetScimUserDTO,
|
TGetScimUserDTO,
|
||||||
|
TListScimGroupsDTO,
|
||||||
TListScimUsers,
|
TListScimUsers,
|
||||||
TListScimUsersDTO,
|
TListScimUsersDTO,
|
||||||
TReplaceScimUserDTO,
|
TReplaceScimUserDTO,
|
||||||
TScimTokenJwtPayload,
|
TScimTokenJwtPayload,
|
||||||
|
TUpdateScimGroupNamePatchDTO,
|
||||||
|
TUpdateScimGroupNamePutDTO,
|
||||||
TUpdateScimUserDTO
|
TUpdateScimUserDTO
|
||||||
} from "./scim-types";
|
} from "./scim-types";
|
||||||
|
|
||||||
@@ -39,6 +49,7 @@ type TScimServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
projectDAL: Pick<TProjectDALFactory, "find">;
|
projectDAL: Pick<TProjectDALFactory, "find">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete">;
|
||||||
|
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "findAllGroupMembers" | "update" | "delete" | "findGroups">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
@@ -53,11 +64,20 @@ export const scimServiceFactory = ({
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
smtpService
|
smtpService
|
||||||
}: TScimServiceFactoryDep) => {
|
}: TScimServiceFactoryDep) => {
|
||||||
const createScimToken = async ({ actor, actorId, actorOrgId, orgId, description, ttlDays }: TCreateScimTokenDTO) => {
|
const createScimToken = async ({
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
orgId,
|
||||||
|
description,
|
||||||
|
ttlDays
|
||||||
|
}: TCreateScimTokenDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
@@ -85,8 +105,8 @@ export const scimServiceFactory = ({
|
|||||||
return { scimToken };
|
return { scimToken };
|
||||||
};
|
};
|
||||||
|
|
||||||
const listScimTokens = async ({ actor, actorId, actorOrgId, orgId }: TOrgPermission) => {
|
const listScimTokens = async ({ actor, actorId, actorOrgId, actorAuthMethod, orgId }: TOrgPermission) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
@@ -99,11 +119,17 @@ export const scimServiceFactory = ({
|
|||||||
return scimTokens;
|
return scimTokens;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorOrgId }: TDeleteScimTokenDTO) => {
|
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteScimTokenDTO) => {
|
||||||
let scimToken = await scimDAL.findById(scimTokenId);
|
let scimToken = await scimDAL.findById(scimTokenId);
|
||||||
if (!scimToken) throw new BadRequestError({ message: "Failed to find SCIM token to delete" });
|
if (!scimToken) throw new BadRequestError({ message: "Failed to find SCIM token to delete" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, scimToken.orgId, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
scimToken.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(scimToken.orgId);
|
const plan = await licenseService.getPlan(scimToken.orgId);
|
||||||
@@ -409,6 +435,221 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteScimUser = async ({ userId, orgId }: TDeleteScimUserDTO) => {
|
||||||
|
const [membership] = await orgDAL
|
||||||
|
.findMembership({
|
||||||
|
userId,
|
||||||
|
[`${TableName.OrgMembership}.orgId` as "id"]: 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await deleteOrgMembership({
|
||||||
|
orgMembershipId: membership.id,
|
||||||
|
orgId: membership.orgId,
|
||||||
|
orgDAL,
|
||||||
|
projectDAL,
|
||||||
|
projectMembershipDAL
|
||||||
|
});
|
||||||
|
|
||||||
|
return {}; // intentionally return empty object upon success
|
||||||
|
};
|
||||||
|
|
||||||
|
const listScimGroups = async ({ orgId, offset, limit }: TListScimGroupsDTO) => {
|
||||||
|
const org = await orgDAL.findById(orgId);
|
||||||
|
|
||||||
|
if (!org.scimEnabled)
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: "SCIM is disabled for the organization",
|
||||||
|
status: 403
|
||||||
|
});
|
||||||
|
|
||||||
|
const groups = await groupDAL.findGroups({
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const scimGroups = groups.map((group) =>
|
||||||
|
buildScimGroup({
|
||||||
|
groupId: group.id,
|
||||||
|
name: group.name,
|
||||||
|
members: []
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return buildScimGroupList({
|
||||||
|
scimGroups,
|
||||||
|
offset,
|
||||||
|
limit
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createScimGroup = async ({ displayName, orgId }: TCreateScimGroupDTO) => {
|
||||||
|
const org = await orgDAL.findById(orgId);
|
||||||
|
|
||||||
|
if (!org.scimEnabled)
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: "SCIM is disabled for the organization",
|
||||||
|
status: 403
|
||||||
|
});
|
||||||
|
|
||||||
|
const group = await groupDAL.create({
|
||||||
|
name: displayName,
|
||||||
|
slug: slugify(`${displayName}-${alphaNumericNanoId(4)}`),
|
||||||
|
orgId,
|
||||||
|
role: OrgMembershipRole.NoAccess
|
||||||
|
});
|
||||||
|
|
||||||
|
return buildScimGroup({
|
||||||
|
groupId: group.id,
|
||||||
|
name: group.name,
|
||||||
|
members: []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getScimGroup = async ({ groupId, orgId }: TGetScimGroupDTO) => {
|
||||||
|
const group = await groupDAL.findOne({
|
||||||
|
id: groupId,
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: "Group Not Found",
|
||||||
|
status: 404
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await groupDAL.findAllGroupMembers({
|
||||||
|
orgId: group.orgId,
|
||||||
|
groupId: group.id
|
||||||
|
});
|
||||||
|
|
||||||
|
return buildScimGroup({
|
||||||
|
groupId: group.id,
|
||||||
|
name: group.name,
|
||||||
|
members: users
|
||||||
|
.filter((user) => user.isPartOfGroup)
|
||||||
|
.map((user) => ({
|
||||||
|
value: user.id,
|
||||||
|
display: `${user.firstName} ${user.lastName}`
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateScimGroupNamePut = async ({ groupId, orgId, displayName }: TUpdateScimGroupNamePutDTO) => {
|
||||||
|
const [group] = await groupDAL.update(
|
||||||
|
{
|
||||||
|
id: groupId,
|
||||||
|
orgId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: displayName
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: "Group Not Found",
|
||||||
|
status: 404
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildScimGroup({
|
||||||
|
groupId: group.id,
|
||||||
|
name: group.name,
|
||||||
|
members: []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: add support for add/remove op
|
||||||
|
const updateScimGroupNamePatch = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => {
|
||||||
|
const org = await orgDAL.findById(orgId);
|
||||||
|
|
||||||
|
if (!org.scimEnabled)
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: "SCIM is disabled for the organization",
|
||||||
|
status: 403
|
||||||
|
});
|
||||||
|
|
||||||
|
let group: TGroups | undefined;
|
||||||
|
for await (const operation of operations) {
|
||||||
|
switch (operation.op) {
|
||||||
|
case "replace": {
|
||||||
|
await groupDAL.update(
|
||||||
|
{
|
||||||
|
id: groupId,
|
||||||
|
orgId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: operation.value.displayName
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "add": {
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "remove": {
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: "Invalid Operation",
|
||||||
|
status: 400
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: "Group Not Found",
|
||||||
|
status: 404
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildScimGroup({
|
||||||
|
groupId: group.id,
|
||||||
|
name: group.name,
|
||||||
|
members: []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteScimGroup = async ({ groupId, orgId }: TDeleteScimGroupDTO) => {
|
||||||
|
const [group] = await groupDAL.delete({
|
||||||
|
id: groupId,
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: "Group Not Found",
|
||||||
|
status: 404
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}; // intentionally return empty object upon success
|
||||||
|
};
|
||||||
|
|
||||||
const fnValidateScimToken = async (token: TScimTokenJwtPayload) => {
|
const fnValidateScimToken = async (token: TScimTokenJwtPayload) => {
|
||||||
const scimToken = await scimDAL.findById(token.scimTokenId);
|
const scimToken = await scimDAL.findById(token.scimTokenId);
|
||||||
if (!scimToken) throw new UnauthorizedError();
|
if (!scimToken) throw new UnauthorizedError();
|
||||||
@@ -441,6 +682,13 @@ export const scimServiceFactory = ({
|
|||||||
createScimUser,
|
createScimUser,
|
||||||
updateScimUser,
|
updateScimUser,
|
||||||
replaceScimUser,
|
replaceScimUser,
|
||||||
|
deleteScimUser,
|
||||||
|
listScimGroups,
|
||||||
|
createScimGroup,
|
||||||
|
getScimGroup,
|
||||||
|
deleteScimGroup,
|
||||||
|
updateScimGroupNamePut,
|
||||||
|
updateScimGroupNamePatch,
|
||||||
fnValidateScimToken
|
fnValidateScimToken
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,6 +59,73 @@ export type TReplaceScimUserDTO = {
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TDeleteScimUserDTO = {
|
||||||
|
userId: string;
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TListScimGroupsDTO = {
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TListScimGroups = {
|
||||||
|
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"];
|
||||||
|
totalResults: number;
|
||||||
|
Resources: TScimGroup[];
|
||||||
|
itemsPerPage: number;
|
||||||
|
startIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TCreateScimGroupDTO = {
|
||||||
|
displayName: string;
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetScimGroupDTO = {
|
||||||
|
groupId: string;
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUpdateScimGroupNamePutDTO = {
|
||||||
|
groupId: string;
|
||||||
|
orgId: string;
|
||||||
|
displayName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TUpdateScimGroupNamePatchDTO = {
|
||||||
|
groupId: string;
|
||||||
|
orgId: string;
|
||||||
|
operations: (TRemoveOp | TReplaceOp | TAddOp)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type TReplaceOp = {
|
||||||
|
op: "replace";
|
||||||
|
value: {
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type TRemoveOp = {
|
||||||
|
op: "remove";
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TAddOp = {
|
||||||
|
op: "add";
|
||||||
|
value: {
|
||||||
|
value: string;
|
||||||
|
display?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteScimGroupDTO = {
|
||||||
|
groupId: string;
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TScimTokenJwtPayload = {
|
export type TScimTokenJwtPayload = {
|
||||||
scimTokenId: string;
|
scimTokenId: string;
|
||||||
authTokenType: string;
|
authTokenType: string;
|
||||||
@@ -86,3 +153,17 @@ export type TScimUser = {
|
|||||||
location: null;
|
location: null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TScimGroup = {
|
||||||
|
schemas: string[];
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
members: {
|
||||||
|
value: string;
|
||||||
|
display: string;
|
||||||
|
}[];
|
||||||
|
meta: {
|
||||||
|
resourceType: string;
|
||||||
|
location: null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
approvals,
|
approvals,
|
||||||
approvers,
|
approvers,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -54,7 +55,13 @@ 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,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
@@ -98,6 +105,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
approvals,
|
approvals,
|
||||||
secretPolicyId
|
secretPolicyId
|
||||||
}: TUpdateSapDTO) => {
|
}: TUpdateSapDTO) => {
|
||||||
@@ -108,6 +116,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalPolicy.projectId,
|
secretApprovalPolicy.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
@@ -152,7 +161,13 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteSecretApprovalPolicy = async ({ secretPolicyId, actor, actorId, actorOrgId }: TDeleteSapDTO) => {
|
const deleteSecretApprovalPolicy = async ({
|
||||||
|
secretPolicyId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: 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" });
|
||||||
|
|
||||||
@@ -160,6 +175,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
sapPolicy.projectId,
|
sapPolicy.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
@@ -171,8 +187,20 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
return sapPolicy;
|
return sapPolicy;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecretApprovalPolicyByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TListSapDTO) => {
|
const getSecretApprovalPolicyByProjectId = async ({
|
||||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectId
|
||||||
|
}: TListSapDTO) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
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 });
|
||||||
@@ -201,10 +229,17 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath
|
||||||
}: TGetBoardSapDTO) => {
|
}: TGetBoardSapDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { secretPath, environment })
|
subject(ProjectPermissionSub.Secrets, { secretPath, environment })
|
||||||
|
|||||||
@@ -82,13 +82,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretQueueService
|
secretQueueService
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId }: TApprovalRequestCountDTO) => {
|
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: 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,
|
actor as ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -100,6 +101,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
status,
|
status,
|
||||||
environment,
|
environment,
|
||||||
@@ -109,7 +111,13 @@ 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,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
const approvals = await secretApprovalRequestDAL.findByProjectId({
|
const approvals = await secretApprovalRequestDAL.findByProjectId({
|
||||||
projectId,
|
projectId,
|
||||||
committer,
|
committer,
|
||||||
@@ -122,7 +130,13 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
return approvals;
|
return approvals;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecretApprovalDetails = async ({ actor, actorId, actorOrgId, id }: TSecretApprovalDetailsDTO) => {
|
const getSecretApprovalDetails = async ({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
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);
|
||||||
@@ -133,6 +147,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
secretApprovalRequest.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
@@ -150,7 +165,14 @@ 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,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
}: 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" });
|
||||||
@@ -160,6 +182,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
secretApprovalRequest.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
@@ -192,7 +215,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
return reviewStatus;
|
return reviewStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateApprovalStatus = async ({ actorId, status, approvalId, actor, actorOrgId }: TStatusChangeDTO) => {
|
const updateApprovalStatus = async ({
|
||||||
|
actorId,
|
||||||
|
status,
|
||||||
|
approvalId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: 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" });
|
||||||
@@ -202,6 +232,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
secretApprovalRequest.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
@@ -229,7 +260,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
approvalId,
|
approvalId,
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
actorAuthMethod
|
||||||
}: TMergeSecretApprovalRequestDTO) => {
|
}: 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" });
|
||||||
@@ -240,8 +272,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerId !== membership.id &&
|
secretApprovalRequest.committerId !== membership.id &&
|
||||||
@@ -438,6 +472,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
policy,
|
policy,
|
||||||
projectId,
|
projectId,
|
||||||
secretPath,
|
secretPath,
|
||||||
@@ -449,6 +484,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
|||||||
+12
-1
@@ -9,6 +9,7 @@ import jmespath from "jmespath";
|
|||||||
import knex from "knex";
|
import knex from "knex";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { getDbConnectionHost } from "@app/lib/knex";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
import { TAssignOp, TDbProviderClients, TDirectAssignOp, THttpProviderFunction } from "../templates/types";
|
import { TAssignOp, TDbProviderClients, TDirectAssignOp, THttpProviderFunction } from "../templates/types";
|
||||||
@@ -89,7 +90,17 @@ export const secretRotationDbFn = async ({
|
|||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
|
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
|
||||||
if (host === "localhost" || host === "127.0.0.1" || appCfg.DB_CONNECTION_URI.includes(host))
|
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
|
||||||
|
if (
|
||||||
|
host === "localhost" ||
|
||||||
|
host === "127.0.0.1" ||
|
||||||
|
// database infisical uses
|
||||||
|
dbHost === host ||
|
||||||
|
// internal ips
|
||||||
|
host === "host.docker.internal" ||
|
||||||
|
host.match(/^10\.\d+\.\d+\.\d+/) ||
|
||||||
|
host.match(/^192\.168\.\d+\.\d+/)
|
||||||
|
)
|
||||||
throw new Error("Invalid db host");
|
throw new Error("Invalid db host");
|
||||||
|
|
||||||
const db = knex({
|
const db = knex({
|
||||||
|
|||||||
@@ -39,8 +39,20 @@ export const secretRotationServiceFactory = ({
|
|||||||
folderDAL,
|
folderDAL,
|
||||||
secretDAL
|
secretDAL
|
||||||
}: TSecretRotationServiceFactoryDep) => {
|
}: TSecretRotationServiceFactoryDep) => {
|
||||||
const getProviderTemplates = async ({ actor, actorId, actorOrgId, projectId }: TProjectPermission) => {
|
const getProviderTemplates = async ({
|
||||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectId
|
||||||
|
}: TProjectPermission) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -54,6 +66,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
interval,
|
interval,
|
||||||
@@ -61,7 +74,13 @@ 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,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretRotation
|
ProjectPermissionSub.SecretRotation
|
||||||
@@ -139,14 +158,20 @@ export const secretRotationServiceFactory = ({
|
|||||||
return secretRotation;
|
return secretRotation;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId }: TListByProjectIdDTO) => {
|
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
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, actorOrgId, actorAuthMethod, 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 +182,30 @@ 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,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
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, actorOrgId, actorAuthMethod, 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,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SecretRotation
|
ProjectPermissionSub.SecretRotation
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user