mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-15 09:42:14 +00:00
Compare commits
251 Commits
patch-supe
...
daniel/api
Author | SHA1 | Date | |
---|---|---|---|
519b92d592 | |||
97eff2b480 | |||
11dfeda501 | |||
70bd64d54b | |||
0c88a5466c | |||
36266b30d5 | |||
288577b455 | |||
5194be14fd | |||
bab8f95fde | |||
b4f372f883 | |||
b13365ecf5 | |||
70618420d7 | |||
7feb7ef9c6 | |||
ab1b9fb164 | |||
8c028889a6 | |||
7dc366baf0 | |||
2124d2669f | |||
af83fbea14 | |||
9657b64ab2 | |||
90b55a94e1 | |||
d83d249f29 | |||
151787c60a | |||
ce443b114c | |||
2ca03abec2 | |||
c8bb690736 | |||
6efbdaef9c | |||
7e90493cce | |||
1330c0455a | |||
407248c616 | |||
a6d7d32156 | |||
0f0e2b360c | |||
47906c4dd4 | |||
fc57884035 | |||
4152b3a524 | |||
f1f18e81cd | |||
929f91a738 | |||
fa41b8bb47 | |||
edbb7e2b1e | |||
1d53e0f21b | |||
a232450f20 | |||
6f65f2a63d | |||
9545960e6f | |||
cfa42017b1 | |||
1b74fdb232 | |||
ad1cae6aac | |||
e5d4328e2a | |||
635948c4f4 | |||
d6231d4649 | |||
041535bb47 | |||
3f0c4f0ca9 | |||
5c8b886d7b | |||
51a5bf8181 | |||
822d0692db | |||
e527d99654 | |||
628c641580 | |||
40ccab6576 | |||
9cc3e58561 | |||
1f3fded404 | |||
74b5e8cbeb | |||
522a03c2ad | |||
624fb3d46a | |||
8a27b1b5e6 | |||
56bf82e4f6 | |||
972b80e790 | |||
6cc0d79d8a | |||
163ccd6cdb | |||
06f3a6d262 | |||
b641bbf229 | |||
feb7563eab | |||
7594929042 | |||
f1b7653a52 | |||
0cb6d052e0 | |||
ceb135fc94 | |||
b75289f074 | |||
de86705e64 | |||
f9b6f78e8d | |||
2852a495c8 | |||
6ca56143d9 | |||
ef0e652557 | |||
89e109e404 | |||
48062d9680 | |||
d11fda3be5 | |||
0df5f845fb | |||
ca59488b62 | |||
3a05ae4b27 | |||
dd009182e8 | |||
8ac7a29893 | |||
8a17cd3f5d | |||
99fe43f459 | |||
2e3b10ccfc | |||
79196b0081 | |||
b76ff28414 | |||
2894cf791a | |||
c040b0ca9a | |||
15f60aa7dd | |||
6f68d304ea | |||
0b98feea50 | |||
43d40d7475 | |||
309a106f13 | |||
74d73590a1 | |||
b42b5614c9 | |||
72b89cb989 | |||
6305300b12 | |||
b4ae1e8f3b | |||
36d8b22598 | |||
201dcd971c | |||
ab90745312 | |||
622106045e | |||
e64302b789 | |||
901a7fc294 | |||
359694dd47 | |||
57489a7578 | |||
a4205a8662 | |||
dbf177d667 | |||
f078aec54c | |||
5dfe62e306 | |||
b89925c61c | |||
440a58a49b | |||
6d0bea6d5f | |||
10a40c8ab2 | |||
b910ceacfc | |||
cb66386e13 | |||
889df3dcb1 | |||
ae53f03f71 | |||
7ae024724d | |||
0b2bc1d345 | |||
da5eca3e68 | |||
3375d3ff85 | |||
35a5c9a67f | |||
7d495cfea5 | |||
2eca9d8200 | |||
4d707eee8a | |||
76bd85efa7 | |||
d140e4f3c9 | |||
80623c03f4 | |||
ed6c6e8d1e | |||
7e044ad9ff | |||
8f2b54514c | |||
5f5f46eddf | |||
3174896d37 | |||
919e184305 | |||
c7d08745fc | |||
d6d780a7b4 | |||
03e965ec5a | |||
cd0df2d617 | |||
e72e6dd6ee | |||
327c5e2429 | |||
f29dd6effa | |||
7987a1ea2b | |||
e6036175c1 | |||
171a70ddc1 | |||
a845f4ee5c | |||
71cd4425b4 | |||
deb22bf8ad | |||
8e25631fb0 | |||
0912903e0d | |||
1b1a95ab78 | |||
cf4f26ab90 | |||
84249f535b | |||
c7bbe82f4a | |||
d8d2741868 | |||
f45074a2dd | |||
564b6b8ef6 | |||
fafd963a8a | |||
9e38076d45 | |||
d3a6da187b | |||
7a90fa472d | |||
756c1e5098 | |||
0dd34eae60 | |||
846e2f21cc | |||
d8860e1ce3 | |||
68296c1b99 | |||
2192985291 | |||
16acace648 | |||
e3e4a98cd6 | |||
4afb20ad0d | |||
60134cf8ac | |||
22d5f97793 | |||
3fa529dcb0 | |||
d12c4b7580 | |||
5feb942d79 | |||
b6f3cf512e | |||
4dbee7df06 | |||
323c412f5e | |||
ae2706542c | |||
d5861493bf | |||
53044f3d39 | |||
93268f5767 | |||
318dedb987 | |||
291edf71aa | |||
342665783e | |||
6a7241d7d1 | |||
51fb680f9c | |||
0710c9a84a | |||
e46bce1520 | |||
3919393d33 | |||
c8b7c37aee | |||
2641fccce5 | |||
213f2ed29b | |||
4dcd000dd1 | |||
c2fe6eb90c | |||
f64cb10282 | |||
a0ea2627ed | |||
5c40b538af | |||
8dd94a4e10 | |||
041c4a20a0 | |||
4a2a5f42a8 | |||
9fcdf17a04 | |||
97ac8cb45a | |||
e952659415 | |||
1f3f061a06 | |||
5096ce3bdc | |||
621683f787 | |||
f63850e9e9 | |||
4ee0a2ec6c | |||
9569d3971a | |||
443b8f747b | |||
803393c385 | |||
8e95189fd2 | |||
c5f38b6ade | |||
bbad2ba047 | |||
1445df7015 | |||
4748b546c2 | |||
96887cdbfa | |||
fb8c4bd415 | |||
48bf41ac8c | |||
1ad916a784 | |||
c91456838e | |||
79efe64504 | |||
db9f21be87 | |||
449617d271 | |||
3641875b24 | |||
a04a9a1bd3 | |||
04d729df92 | |||
5ca1b1d77e | |||
2d9526ad8d | |||
768cc64af6 | |||
a28431bfe7 | |||
91068229bf | |||
9ba4b939a4 | |||
1c088b3a58 | |||
a33c50b75a | |||
8c31566e17 | |||
bfee74ff4e | |||
97a7b66c6c | |||
639c78358f | |||
5053069bfc | |||
b1d049c677 | |||
9012012503 | |||
a8678c14e8 | |||
541fa10964 |
@ -4,7 +4,7 @@
|
|||||||
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
|
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
|
||||||
|
|
||||||
# Required
|
# Required
|
||||||
DB_CONNECTION_URI=postgres://infisical:infisical@db:5432/infisical
|
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
|
||||||
|
190
.github/resources/changelog-generator.py
vendored
Normal file
190
.github/resources/changelog-generator.py
vendored
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
# inspired by https://www.photoroom.com/inside-photoroom/how-we-automated-our-changelog-thanks-to-chatgpt
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
from openai import OpenAI
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
REPO_OWNER = "infisical"
|
||||||
|
REPO_NAME = "infisical"
|
||||||
|
TOKEN = os.environ["GITHUB_TOKEN"]
|
||||||
|
SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]
|
||||||
|
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
|
||||||
|
SLACK_MSG_COLOR = "#36a64f"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {TOKEN}",
|
||||||
|
"Accept": "application/vnd.github+json",
|
||||||
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def set_multiline_output(name, value):
|
||||||
|
with open(os.environ['GITHUB_OUTPUT'], 'a') as fh:
|
||||||
|
delimiter = uuid.uuid1()
|
||||||
|
print(f'{name}<<{delimiter}', file=fh)
|
||||||
|
print(value, file=fh)
|
||||||
|
print(delimiter, file=fh)
|
||||||
|
|
||||||
|
def post_changelog_to_slack(changelog, tag):
|
||||||
|
slack_payload = {
|
||||||
|
"text": "Hey team, it's changelog time! :wave:",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"color": SLACK_MSG_COLOR,
|
||||||
|
"title": f"🗓️Infisical Changelog - {tag}",
|
||||||
|
"text": changelog,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(SLACK_WEBHOOK_URL, json=slack_payload)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception("Failed to post changelog to Slack.")
|
||||||
|
|
||||||
|
def find_previous_release_tag(release_tag:str):
|
||||||
|
previous_tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0", f"{release_tag}^"]).decode("utf-8").strip()
|
||||||
|
while not(previous_tag.startswith("infisical/")):
|
||||||
|
previous_tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0", f"{previous_tag}^"]).decode("utf-8").strip()
|
||||||
|
return previous_tag
|
||||||
|
|
||||||
|
def get_tag_creation_date(tag_name):
|
||||||
|
url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/git/refs/tags/{tag_name}"
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
response.raise_for_status()
|
||||||
|
commit_sha = response.json()['object']['sha']
|
||||||
|
|
||||||
|
commit_url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/commits/{commit_sha}"
|
||||||
|
commit_response = requests.get(commit_url, headers=headers)
|
||||||
|
commit_response.raise_for_status()
|
||||||
|
creation_date = commit_response.json()['commit']['author']['date']
|
||||||
|
|
||||||
|
return datetime.strptime(creation_date, '%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_prs_between_tags(previous_tag_date:datetime, release_tag_date:datetime):
|
||||||
|
# Use GitHub API to fetch PRs merged between the commits
|
||||||
|
url = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/pulls?state=closed&merged=true"
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception("Error fetching PRs from GitHub API!")
|
||||||
|
|
||||||
|
prs = []
|
||||||
|
for pr in response.json():
|
||||||
|
# the idea is as tags happen recently we get last 100 closed PRs and then filter by tag creation date
|
||||||
|
if pr["merged_at"] and datetime.strptime(pr["merged_at"],'%Y-%m-%dT%H:%M:%SZ') < release_tag_date and datetime.strptime(pr["merged_at"],'%Y-%m-%dT%H:%M:%SZ') > previous_tag_date:
|
||||||
|
prs.append(pr)
|
||||||
|
|
||||||
|
return prs
|
||||||
|
|
||||||
|
|
||||||
|
def extract_commit_details_from_prs(prs):
|
||||||
|
commit_details = []
|
||||||
|
for pr in prs:
|
||||||
|
commit_message = pr["title"]
|
||||||
|
commit_url = pr["html_url"]
|
||||||
|
pr_number = pr["number"]
|
||||||
|
branch_name = pr["head"]["ref"]
|
||||||
|
issue_numbers = re.findall(r"(www-\d+|web-\d+)", branch_name)
|
||||||
|
|
||||||
|
# If no issue numbers are found, add the PR details without issue numbers and URLs
|
||||||
|
if not issue_numbers:
|
||||||
|
commit_details.append(
|
||||||
|
{
|
||||||
|
"message": commit_message,
|
||||||
|
"pr_number": pr_number,
|
||||||
|
"pr_url": commit_url,
|
||||||
|
"issue_number": None,
|
||||||
|
"issue_url": None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for issue in issue_numbers:
|
||||||
|
commit_details.append(
|
||||||
|
{
|
||||||
|
"message": commit_message,
|
||||||
|
"pr_number": pr_number,
|
||||||
|
"pr_url": commit_url,
|
||||||
|
"issue_number": issue,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return commit_details
|
||||||
|
|
||||||
|
# Function to generate changelog using OpenAI
|
||||||
|
def generate_changelog_with_openai(commit_details):
|
||||||
|
commit_messages = []
|
||||||
|
for details in commit_details:
|
||||||
|
base_message = f"{details['pr_url']} - {details['message']}"
|
||||||
|
# Add the issue URL if available
|
||||||
|
# if details["issue_url"]:
|
||||||
|
# base_message += f" (Linear Issue: {details['issue_url']})"
|
||||||
|
commit_messages.append(base_message)
|
||||||
|
|
||||||
|
commit_list = "\n".join(commit_messages)
|
||||||
|
prompt = """
|
||||||
|
Generate a changelog for Infisical, opensource secretops
|
||||||
|
The changelog should:
|
||||||
|
1. Be Informative: Using the provided list of GitHub commits, break them down into categories such as Features, Fixes & Improvements, and Technical Updates. Summarize each commit concisely, ensuring the key points are highlighted.
|
||||||
|
2. Have a Professional yet Friendly tone: The tone should be balanced, not too corporate or too informal.
|
||||||
|
3. Celebratory Introduction and Conclusion: Start the changelog with a celebratory note acknowledging the team's hard work and progress. End with a shoutout to the team and wishes for a pleasant weekend.
|
||||||
|
4. Formatting: you cannot use Markdown formatting, and you can only use emojis for the introductory paragraph or the conclusion paragraph, nowhere else.
|
||||||
|
5. Links: the syntax to create links is the following: `<http://www.example.com|This message is a link>`.
|
||||||
|
6. Linear Links: note that the Linear link is optional, include it only if provided.
|
||||||
|
7. Do not wrap your answer in a codeblock. Just output the text, nothing else
|
||||||
|
Here's a good example to follow, please try to match the formatting as closely as possible, only changing the content of the changelog and have some liberty with the introduction. Notice the importance of the formatting of a changelog item:
|
||||||
|
- <https://github.com/facebook/react/pull/27304/%7C#27304>: We optimize our ci to strip comments and minify production builds. (<https://linear.app/example/issue/WEB-1234/%7CWEB-1234>))
|
||||||
|
And here's an example of the full changelog:
|
||||||
|
|
||||||
|
*Features*
|
||||||
|
• <https://github.com/facebook/react/pull/27304/%7C#27304>: We optimize our ci to strip comments and minify production builds. (<https://linear.app/example/issue/WEB-1234/%7CWEB-1234>)
|
||||||
|
*Fixes & Improvements*
|
||||||
|
• <https://github.com/facebook/react/pull/27304/%7C#27304>: We optimize our ci to strip comments and minify production builds. (<https://linear.app/example/issue/WEB-1234/%7CWEB-1234>)
|
||||||
|
*Technical Updates*
|
||||||
|
• <https://github.com/facebook/react/pull/27304/%7C#27304>: We optimize our ci to strip comments and minify production builds. (<https://linear.app/example/issue/WEB-1234/%7CWEB-1234>)
|
||||||
|
|
||||||
|
Stay tuned for more exciting updates coming soon!
|
||||||
|
And here are the commits:
|
||||||
|
{}
|
||||||
|
""".format(
|
||||||
|
commit_list
|
||||||
|
)
|
||||||
|
|
||||||
|
client = OpenAI(api_key=OPENAI_API_KEY)
|
||||||
|
messages = [{"role": "user", "content": prompt}]
|
||||||
|
response = client.chat.completions.create(model="gpt-3.5-turbo", messages=messages)
|
||||||
|
|
||||||
|
if "error" in response.choices[0].message:
|
||||||
|
raise Exception("Error generating changelog with OpenAI!")
|
||||||
|
|
||||||
|
return response.choices[0].message.content.strip()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
# Get the latest and previous release tags
|
||||||
|
latest_tag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"]).decode("utf-8").strip()
|
||||||
|
previous_tag = find_previous_release_tag(latest_tag)
|
||||||
|
|
||||||
|
latest_tag_date = get_tag_creation_date(latest_tag)
|
||||||
|
previous_tag_date = get_tag_creation_date(previous_tag)
|
||||||
|
|
||||||
|
prs = fetch_prs_between_tags(previous_tag_date,latest_tag_date)
|
||||||
|
pr_details = extract_commit_details_from_prs(prs)
|
||||||
|
|
||||||
|
# Generate changelog
|
||||||
|
changelog = generate_changelog_with_openai(pr_details)
|
||||||
|
|
||||||
|
post_changelog_to_slack(changelog,latest_tag)
|
||||||
|
# Print or post changelog to Slack
|
||||||
|
# set_multiline_output("changelog", changelog)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(str(e))
|
2
.github/values.yaml
vendored
2
.github/values.yaml
vendored
@ -27,7 +27,7 @@ infisical:
|
|||||||
deploymentAnnotations:
|
deploymentAnnotations:
|
||||||
secrets.infisical.com/auto-reload: "true"
|
secrets.infisical.com/auto-reload: "true"
|
||||||
|
|
||||||
kubeSecretRef: "infisical-gamma-secrets"
|
kubeSecretRef: "managed-secret"
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
## @param ingress.enabled Enable ingress
|
## @param ingress.enabled Enable ingress
|
||||||
|
34
.github/workflows/generate-release-changelog.yml
vendored
Normal file
34
.github/workflows/generate-release-changelog.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
name: Generate Changelog
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "infisical/v*.*.*-postgres"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate_changelog:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-tags: true
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12.0"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install requests openai
|
||||||
|
- name: Generate Changelog and Post to Slack
|
||||||
|
id: gen-changelog
|
||||||
|
run: python .github/resources/changelog-generator.py
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
@ -23,6 +23,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: 🔧 Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
- run: git fetch --force --tags
|
- run: git fetch --force --tags
|
||||||
- run: echo "Ref name ${{github.ref_name}}"
|
- run: echo "Ref name ${{github.ref_name}}"
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
|
@ -190,10 +190,34 @@ dockers:
|
|||||||
- dockerfile: docker/alpine
|
- dockerfile: docker/alpine
|
||||||
goos: linux
|
goos: linux
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
|
use: buildx
|
||||||
ids:
|
ids:
|
||||||
- all-other-builds
|
- all-other-builds
|
||||||
image_templates:
|
image_templates:
|
||||||
- "infisical/cli:{{ .Version }}"
|
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-amd64"
|
||||||
- "infisical/cli:{{ .Major }}.{{ .Minor }}"
|
- "infisical/cli:latest-amd64"
|
||||||
- "infisical/cli:{{ .Major }}"
|
build_flag_templates:
|
||||||
- "infisical/cli:latest"
|
- "--pull"
|
||||||
|
- "--platform=linux/amd64"
|
||||||
|
- dockerfile: docker/alpine
|
||||||
|
goos: linux
|
||||||
|
goarch: amd64
|
||||||
|
use: buildx
|
||||||
|
ids:
|
||||||
|
- all-other-builds
|
||||||
|
image_templates:
|
||||||
|
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-arm64"
|
||||||
|
- "infisical/cli:latest-arm64"
|
||||||
|
build_flag_templates:
|
||||||
|
- "--pull"
|
||||||
|
- "--platform=linux/arm64"
|
||||||
|
|
||||||
|
docker_manifests:
|
||||||
|
- name_template: "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}"
|
||||||
|
image_templates:
|
||||||
|
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-amd64"
|
||||||
|
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-arm64"
|
||||||
|
- name_template: "infisical/cli:latest"
|
||||||
|
image_templates:
|
||||||
|
- "infisical/cli:latest-amd64"
|
||||||
|
- "infisical/cli:latest-arm64"
|
||||||
|
3
Makefile
3
Makefile
@ -7,6 +7,9 @@ push:
|
|||||||
up-dev:
|
up-dev:
|
||||||
docker compose -f docker-compose.dev.yml up --build
|
docker compose -f docker-compose.dev.yml up --build
|
||||||
|
|
||||||
|
up-dev-ldap:
|
||||||
|
docker compose -f docker-compose.dev.yml --profile ldap up --build
|
||||||
|
|
||||||
up-prod:
|
up-prod:
|
||||||
docker-compose -f docker-compose.prod.yml up --build
|
docker-compose -f docker-compose.prod.yml up --build
|
||||||
|
|
||||||
|
1057
backend/package-lock.json
generated
1057
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -70,6 +70,7 @@
|
|||||||
"vitest": "^1.2.2"
|
"vitest": "^1.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-iam": "^3.525.0",
|
||||||
"@aws-sdk/client-secrets-manager": "^3.504.0",
|
"@aws-sdk/client-secrets-manager": "^3.504.0",
|
||||||
"@casl/ability": "^6.5.0",
|
"@casl/ability": "^6.5.0",
|
||||||
"@fastify/cookie": "^9.3.1",
|
"@fastify/cookie": "^9.3.1",
|
||||||
@ -106,18 +107,20 @@
|
|||||||
"knex": "^3.0.1",
|
"knex": "^3.0.1",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"ms": "^2.1.3",
|
||||||
"mysql2": "^3.9.1",
|
"mysql2": "^3.9.1",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
"node-cache": "^5.1.2",
|
|
||||||
"nodemailer": "^6.9.9",
|
"nodemailer": "^6.9.9",
|
||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
"passport-github": "^1.1.0",
|
"passport-github": "^1.1.0",
|
||||||
"passport-gitlab2": "^5.0.0",
|
"passport-gitlab2": "^5.0.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
|
"passport-ldapauth": "^3.0.1",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
|
"pg-query-stream": "^4.5.3",
|
||||||
"picomatch": "^3.0.1",
|
"picomatch": "^3.0.1",
|
||||||
"pino": "^8.16.2",
|
"pino": "^8.16.2",
|
||||||
"posthog-node": "^3.6.0",
|
"posthog-node": "^3.6.2",
|
||||||
"probot": "^13.0.0",
|
"probot": "^13.0.0",
|
||||||
"smee-client": "^2.0.0",
|
"smee-client": "^2.0.0",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
|
3
backend/src/@types/fastify.d.ts
vendored
3
backend/src/@types/fastify.d.ts
vendored
@ -3,6 +3,7 @@ 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 { 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 { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||||
@ -69,6 +70,7 @@ declare module "fastify" {
|
|||||||
};
|
};
|
||||||
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
|
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
|
||||||
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
|
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
|
||||||
|
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
@ -107,6 +109,7 @@ declare module "fastify" {
|
|||||||
snapshot: TSecretSnapshotServiceFactory;
|
snapshot: TSecretSnapshotServiceFactory;
|
||||||
saml: TSamlConfigServiceFactory;
|
saml: TSamlConfigServiceFactory;
|
||||||
scim: TScimServiceFactory;
|
scim: TScimServiceFactory;
|
||||||
|
ldap: TLdapConfigServiceFactory;
|
||||||
auditLog: TAuditLogServiceFactory;
|
auditLog: TAuditLogServiceFactory;
|
||||||
secretScanning: TSecretScanningServiceFactory;
|
secretScanning: TSecretScanningServiceFactory;
|
||||||
license: TLicenseServiceFactory;
|
license: TLicenseServiceFactory;
|
||||||
|
24
backend/src/@types/knex.d.ts
vendored
24
backend/src/@types/knex.d.ts
vendored
@ -32,6 +32,9 @@ import {
|
|||||||
TIdentityOrgMemberships,
|
TIdentityOrgMemberships,
|
||||||
TIdentityOrgMembershipsInsert,
|
TIdentityOrgMembershipsInsert,
|
||||||
TIdentityOrgMembershipsUpdate,
|
TIdentityOrgMembershipsUpdate,
|
||||||
|
TIdentityProjectMembershipRole,
|
||||||
|
TIdentityProjectMembershipRoleInsert,
|
||||||
|
TIdentityProjectMembershipRoleUpdate,
|
||||||
TIdentityProjectMemberships,
|
TIdentityProjectMemberships,
|
||||||
TIdentityProjectMembershipsInsert,
|
TIdentityProjectMembershipsInsert,
|
||||||
TIdentityProjectMembershipsUpdate,
|
TIdentityProjectMembershipsUpdate,
|
||||||
@ -50,6 +53,9 @@ import {
|
|||||||
TIntegrations,
|
TIntegrations,
|
||||||
TIntegrationsInsert,
|
TIntegrationsInsert,
|
||||||
TIntegrationsUpdate,
|
TIntegrationsUpdate,
|
||||||
|
TLdapConfigs,
|
||||||
|
TLdapConfigsInsert,
|
||||||
|
TLdapConfigsUpdate,
|
||||||
TOrganizations,
|
TOrganizations,
|
||||||
TOrganizationsInsert,
|
TOrganizationsInsert,
|
||||||
TOrganizationsUpdate,
|
TOrganizationsUpdate,
|
||||||
@ -80,6 +86,9 @@ import {
|
|||||||
TProjects,
|
TProjects,
|
||||||
TProjectsInsert,
|
TProjectsInsert,
|
||||||
TProjectsUpdate,
|
TProjectsUpdate,
|
||||||
|
TProjectUserMembershipRoles,
|
||||||
|
TProjectUserMembershipRolesInsert,
|
||||||
|
TProjectUserMembershipRolesUpdate,
|
||||||
TSamlConfigs,
|
TSamlConfigs,
|
||||||
TSamlConfigsInsert,
|
TSamlConfigsInsert,
|
||||||
TSamlConfigsUpdate,
|
TSamlConfigsUpdate,
|
||||||
@ -161,6 +170,9 @@ import {
|
|||||||
TUserActions,
|
TUserActions,
|
||||||
TUserActionsInsert,
|
TUserActionsInsert,
|
||||||
TUserActionsUpdate,
|
TUserActionsUpdate,
|
||||||
|
TUserAliases,
|
||||||
|
TUserAliasesInsert,
|
||||||
|
TUserAliasesUpdate,
|
||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
TUserEncryptionKeysInsert,
|
TUserEncryptionKeysInsert,
|
||||||
TUserEncryptionKeysUpdate,
|
TUserEncryptionKeysUpdate,
|
||||||
@ -175,6 +187,7 @@ 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.UserAliases]: Knex.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>;
|
||||||
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
|
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
|
||||||
TUserEncryptionKeys,
|
TUserEncryptionKeys,
|
||||||
TUserEncryptionKeysInsert,
|
TUserEncryptionKeysInsert,
|
||||||
@ -214,6 +227,11 @@ declare module "knex/types/tables" {
|
|||||||
TProjectEnvironmentsUpdate
|
TProjectEnvironmentsUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.ProjectBot]: Knex.CompositeTableType<TProjectBots, TProjectBotsInsert, TProjectBotsUpdate>;
|
[TableName.ProjectBot]: Knex.CompositeTableType<TProjectBots, TProjectBotsInsert, TProjectBotsUpdate>;
|
||||||
|
[TableName.ProjectUserMembershipRole]: Knex.CompositeTableType<
|
||||||
|
TProjectUserMembershipRoles,
|
||||||
|
TProjectUserMembershipRolesInsert,
|
||||||
|
TProjectUserMembershipRolesUpdate
|
||||||
|
>;
|
||||||
[TableName.ProjectRoles]: Knex.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>;
|
[TableName.ProjectRoles]: Knex.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>;
|
||||||
[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>;
|
||||||
@ -265,6 +283,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityProjectMembershipsInsert,
|
TIdentityProjectMembershipsInsert,
|
||||||
TIdentityProjectMembershipsUpdate
|
TIdentityProjectMembershipsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityProjectMembershipRole]: Knex.CompositeTableType<
|
||||||
|
TIdentityProjectMembershipRole,
|
||||||
|
TIdentityProjectMembershipRoleInsert,
|
||||||
|
TIdentityProjectMembershipRoleUpdate
|
||||||
|
>;
|
||||||
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
|
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
|
||||||
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
|
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
|
||||||
TSecretApprovalPolicies,
|
TSecretApprovalPolicies,
|
||||||
@ -318,6 +341,7 @@ declare module "knex/types/tables" {
|
|||||||
TSecretSnapshotFoldersUpdate
|
TSecretSnapshotFoldersUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.SamlConfig]: Knex.CompositeTableType<TSamlConfigs, TSamlConfigsInsert, TSamlConfigsUpdate>;
|
[TableName.SamlConfig]: Knex.CompositeTableType<TSamlConfigs, TSamlConfigsInsert, TSamlConfigsUpdate>;
|
||||||
|
[TableName.LdapConfig]: Knex.CompositeTableType<TLdapConfigs, TLdapConfigsInsert, TLdapConfigsUpdate>;
|
||||||
[TableName.OrgBot]: Knex.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>;
|
[TableName.OrgBot]: Knex.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>;
|
||||||
[TableName.AuditLog]: Knex.CompositeTableType<TAuditLogs, TAuditLogsInsert, TAuditLogsUpdate>;
|
[TableName.AuditLog]: Knex.CompositeTableType<TAuditLogs, TAuditLogsInsert, TAuditLogsUpdate>;
|
||||||
[TableName.GitAppInstallSession]: Knex.CompositeTableType<
|
[TableName.GitAppInstallSession]: Knex.CompositeTableType<
|
||||||
|
@ -6,6 +6,13 @@ export const initDbConnection = ({ dbConnectionUri, dbRootCert }: { dbConnection
|
|||||||
client: "pg",
|
client: "pg",
|
||||||
connection: {
|
connection: {
|
||||||
connectionString: dbConnectionUri,
|
connectionString: dbConnectionUri,
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
// @ts-expect-error I have no clue why only for the port there is a type error
|
||||||
|
// eslint-disable-next-line
|
||||||
|
port: process.env.DB_PORT,
|
||||||
|
user: process.env.DB_USER,
|
||||||
|
database: process.env.DB_NAME,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
ssl: dbRootCert
|
ssl: dbRootCert
|
||||||
? {
|
? {
|
||||||
rejectUnauthorized: true,
|
rejectUnauthorized: true,
|
||||||
|
@ -7,17 +7,29 @@ import path from "path";
|
|||||||
|
|
||||||
// Update with your config settings. .
|
// Update with your config settings. .
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
path: path.join(__dirname, "../../../.env.migration"),
|
path: path.join(__dirname, "../../../.env.migration")
|
||||||
debug: true
|
|
||||||
});
|
});
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
path: path.join(__dirname, "../../../.env"),
|
path: path.join(__dirname, "../../../.env")
|
||||||
debug: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
development: {
|
development: {
|
||||||
client: "postgres",
|
client: "postgres",
|
||||||
connection: process.env.DB_CONNECTION_URI,
|
connection: {
|
||||||
|
connectionString: process.env.DB_CONNECTION_URI,
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
port: process.env.DB_PORT,
|
||||||
|
user: process.env.DB_USER,
|
||||||
|
database: process.env.DB_NAME,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
ssl: process.env.DB_ROOT_CERT
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: Buffer.from(process.env.DB_ROOT_CERT, "base64").toString("ascii")
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
},
|
||||||
pool: {
|
pool: {
|
||||||
min: 2,
|
min: 2,
|
||||||
max: 10
|
max: 10
|
||||||
@ -31,7 +43,20 @@ export default {
|
|||||||
},
|
},
|
||||||
production: {
|
production: {
|
||||||
client: "postgres",
|
client: "postgres",
|
||||||
connection: process.env.DB_CONNECTION_URI,
|
connection: {
|
||||||
|
connectionString: process.env.DB_CONNECTION_URI,
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
port: process.env.DB_PORT,
|
||||||
|
user: process.env.DB_USER,
|
||||||
|
database: process.env.DB_NAME,
|
||||||
|
password: process.env.DB_PASSWORD,
|
||||||
|
ssl: process.env.DB_ROOT_CERT
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: true,
|
||||||
|
ca: Buffer.from(process.env.DB_ROOT_CERT, "base64").toString("ascii")
|
||||||
|
}
|
||||||
|
: false
|
||||||
|
},
|
||||||
pool: {
|
pool: {
|
||||||
min: 2,
|
min: 2,
|
||||||
max: 10
|
max: 10
|
||||||
|
@ -10,16 +10,10 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||||
t.uuid("instanceId").notNullable().defaultTo(knex.fn.uuid());
|
t.uuid("instanceId").notNullable().defaultTo(knex.fn.uuid());
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line
|
|
||||||
await knex(TableName.SuperAdmin)
|
|
||||||
.update({ id: ADMIN_CONFIG_UUID })
|
|
||||||
.whereNotNull("id")
|
|
||||||
.andWhere("id", "<>", ADMIN_CONFIG_UUID)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
const superUserConfigExists = await knex(TableName.SuperAdmin).where("id", ADMIN_CONFIG_UUID).first();
|
const superUserConfigExists = await knex(TableName.SuperAdmin).where("id", ADMIN_CONFIG_UUID).first();
|
||||||
|
|
||||||
if (!superUserConfigExists) {
|
if (!superUserConfigExists) {
|
||||||
|
// eslint-disable-next-line
|
||||||
await knex(TableName.SuperAdmin).update({ id: ADMIN_CONFIG_UUID }).whereNotNull("id").limit(1);
|
await knex(TableName.SuperAdmin).update({ id: ADMIN_CONFIG_UUID }).whereNotNull("id").limit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.Integration, (t) => {
|
||||||
|
t.datetime("lastUsed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.Integration, (t) => {
|
||||||
|
t.dropColumn("lastUsed");
|
||||||
|
});
|
||||||
|
}
|
68
backend/src/db/migrations/20240311210135_ldap-config.ts
Normal file
68
backend/src/db/migrations/20240311210135_ldap-config.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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.LdapConfig))) {
|
||||||
|
await knex.schema.createTable(TableName.LdapConfig, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.uuid("orgId").notNullable().unique();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
t.boolean("isActive").notNullable();
|
||||||
|
t.string("url").notNullable();
|
||||||
|
t.string("encryptedBindDN").notNullable();
|
||||||
|
t.string("bindDNIV").notNullable();
|
||||||
|
t.string("bindDNTag").notNullable();
|
||||||
|
t.string("encryptedBindPass").notNullable();
|
||||||
|
t.string("bindPassIV").notNullable();
|
||||||
|
t.string("bindPassTag").notNullable();
|
||||||
|
t.string("searchBase").notNullable();
|
||||||
|
t.text("encryptedCACert").notNullable();
|
||||||
|
t.string("caCertIV").notNullable();
|
||||||
|
t.string("caCertTag").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.LdapConfig);
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.UserAliases))) {
|
||||||
|
await knex.schema.createTable(TableName.UserAliases, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.uuid("userId").notNullable();
|
||||||
|
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
t.string("username").notNullable();
|
||||||
|
t.string("aliasType").notNullable();
|
||||||
|
t.string("externalId").notNullable();
|
||||||
|
t.specificType("emails", "text[]");
|
||||||
|
t.uuid("orgId").nullable();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.UserAliases);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Users, (t) => {
|
||||||
|
t.string("username").unique();
|
||||||
|
t.string("email").nullable().alter();
|
||||||
|
t.dropUnique(["email"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex(TableName.Users).update("username", knex.ref("email"));
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Users, (t) => {
|
||||||
|
t.string("username").notNullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.LdapConfig);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.UserAliases);
|
||||||
|
await knex.schema.alterTable(TableName.Users, (t) => {
|
||||||
|
t.dropColumn("username");
|
||||||
|
// t.string("email").notNullable().alter();
|
||||||
|
});
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.LdapConfig);
|
||||||
|
}
|
50
backend/src/db/migrations/20240312162549_temp-roles.ts
Normal file
50
backend/src/db/migrations/20240312162549_temp-roles.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesTableExist = await knex.schema.hasTable(TableName.ProjectUserMembershipRole);
|
||||||
|
if (!doesTableExist) {
|
||||||
|
await knex.schema.createTable(TableName.ProjectUserMembershipRole, (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.ProjectMembership).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.ProjectUserMembershipRole);
|
||||||
|
|
||||||
|
const projectMemberships = await knex(TableName.ProjectMembership).select(
|
||||||
|
"id",
|
||||||
|
"role",
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt",
|
||||||
|
knex.ref("roleId").withSchema(TableName.ProjectMembership).as("customRoleId")
|
||||||
|
);
|
||||||
|
if (projectMemberships.length)
|
||||||
|
await knex.batchInsert(
|
||||||
|
TableName.ProjectUserMembershipRole,
|
||||||
|
projectMemberships.map((data) => ({ ...data, projectMembershipId: data.id }))
|
||||||
|
);
|
||||||
|
// will be dropped later
|
||||||
|
// await knex.schema.alterTable(TableName.ProjectMembership, (t) => {
|
||||||
|
// t.dropColumn("roleId");
|
||||||
|
// t.dropColumn("role");
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ProjectUserMembershipRole);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ProjectUserMembershipRole);
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesTableExist = await knex.schema.hasTable(TableName.IdentityProjectMembershipRole);
|
||||||
|
if (!doesTableExist) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityProjectMembershipRole, (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.IdentityProjectMembership)
|
||||||
|
.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.IdentityProjectMembershipRole);
|
||||||
|
|
||||||
|
const identityMemberships = await knex(TableName.IdentityProjectMembership).select(
|
||||||
|
"id",
|
||||||
|
"role",
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt",
|
||||||
|
knex.ref("roleId").withSchema(TableName.IdentityProjectMembership).as("customRoleId")
|
||||||
|
);
|
||||||
|
if (identityMemberships.length)
|
||||||
|
await knex.batchInsert(
|
||||||
|
TableName.IdentityProjectMembershipRole,
|
||||||
|
identityMemberships.map((data) => ({ ...data, projectMembershipId: data.id }))
|
||||||
|
);
|
||||||
|
// await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => {
|
||||||
|
// t.dropColumn("roleId");
|
||||||
|
// t.dropColumn("role");
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityProjectMembershipRole);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.IdentityProjectMembershipRole);
|
||||||
|
}
|
31
backend/src/db/schemas/identity-project-membership-role.ts
Normal file
31
backend/src/db/schemas/identity-project-membership-role.ts
Normal file
@ -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 IdentityProjectMembershipRoleSchema = 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 TIdentityProjectMembershipRole = z.infer<typeof IdentityProjectMembershipRoleSchema>;
|
||||||
|
export type TIdentityProjectMembershipRoleInsert = Omit<
|
||||||
|
z.input<typeof IdentityProjectMembershipRoleSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TIdentityProjectMembershipRoleUpdate = Partial<
|
||||||
|
Omit<z.input<typeof IdentityProjectMembershipRoleSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
@ -8,12 +8,14 @@ export * from "./git-app-org";
|
|||||||
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-membership-role";
|
||||||
export * from "./identity-project-memberships";
|
export * from "./identity-project-memberships";
|
||||||
export * from "./identity-ua-client-secrets";
|
export * from "./identity-ua-client-secrets";
|
||||||
export * from "./identity-universal-auths";
|
export * from "./identity-universal-auths";
|
||||||
export * from "./incident-contacts";
|
export * from "./incident-contacts";
|
||||||
export * from "./integration-auths";
|
export * from "./integration-auths";
|
||||||
export * from "./integrations";
|
export * from "./integrations";
|
||||||
|
export * from "./ldap-configs";
|
||||||
export * from "./models";
|
export * from "./models";
|
||||||
export * from "./org-bots";
|
export * from "./org-bots";
|
||||||
export * from "./org-memberships";
|
export * from "./org-memberships";
|
||||||
@ -24,6 +26,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-membership-roles";
|
||||||
export * from "./projects";
|
export * from "./projects";
|
||||||
export * from "./saml-configs";
|
export * from "./saml-configs";
|
||||||
export * from "./scim-tokens";
|
export * from "./scim-tokens";
|
||||||
@ -52,6 +55,7 @@ export * from "./service-tokens";
|
|||||||
export * from "./super-admin";
|
export * from "./super-admin";
|
||||||
export * from "./trusted-ips";
|
export * from "./trusted-ips";
|
||||||
export * from "./user-actions";
|
export * from "./user-actions";
|
||||||
|
export * from "./user-aliases";
|
||||||
export * from "./user-encryption-keys";
|
export * from "./user-encryption-keys";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
export * from "./webhooks";
|
export * from "./webhooks";
|
||||||
|
@ -27,7 +27,8 @@ export const IntegrationsSchema = z.object({
|
|||||||
envId: z.string().uuid(),
|
envId: z.string().uuid(),
|
||||||
secretPath: z.string().default("/"),
|
secretPath: z.string().default("/"),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
lastUsed: z.date().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIntegrations = z.infer<typeof IntegrationsSchema>;
|
export type TIntegrations = z.infer<typeof IntegrationsSchema>;
|
||||||
|
31
backend/src/db/schemas/ldap-configs.ts
Normal file
31
backend/src/db/schemas/ldap-configs.ts
Normal file
@ -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 LdapConfigsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
isActive: z.boolean(),
|
||||||
|
url: z.string(),
|
||||||
|
encryptedBindDN: z.string(),
|
||||||
|
bindDNIV: z.string(),
|
||||||
|
bindDNTag: z.string(),
|
||||||
|
encryptedBindPass: z.string(),
|
||||||
|
bindPassIV: z.string(),
|
||||||
|
bindPassTag: z.string(),
|
||||||
|
searchBase: z.string(),
|
||||||
|
encryptedCACert: z.string(),
|
||||||
|
caCertIV: z.string(),
|
||||||
|
caCertTag: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TLdapConfigs = z.infer<typeof LdapConfigsSchema>;
|
||||||
|
export type TLdapConfigsInsert = Omit<z.input<typeof LdapConfigsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TLdapConfigsUpdate = Partial<Omit<z.input<typeof LdapConfigsSchema>, TImmutableDBKeys>>;
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export enum TableName {
|
export enum TableName {
|
||||||
Users = "users",
|
Users = "users",
|
||||||
|
UserAliases = "user_aliases",
|
||||||
UserEncryptionKey = "user_encryption_keys",
|
UserEncryptionKey = "user_encryption_keys",
|
||||||
AuthTokens = "auth_tokens",
|
AuthTokens = "auth_tokens",
|
||||||
AuthTokenSession = "auth_token_sessions",
|
AuthTokenSession = "auth_token_sessions",
|
||||||
@ -19,6 +20,7 @@ export enum TableName {
|
|||||||
Environment = "project_environments",
|
Environment = "project_environments",
|
||||||
ProjectMembership = "project_memberships",
|
ProjectMembership = "project_memberships",
|
||||||
ProjectRoles = "project_roles",
|
ProjectRoles = "project_roles",
|
||||||
|
ProjectUserMembershipRole = "project_user_membership_roles",
|
||||||
ProjectKeys = "project_keys",
|
ProjectKeys = "project_keys",
|
||||||
Secret = "secrets",
|
Secret = "secrets",
|
||||||
SecretBlindIndex = "secret_blind_indexes",
|
SecretBlindIndex = "secret_blind_indexes",
|
||||||
@ -40,6 +42,7 @@ export enum TableName {
|
|||||||
IdentityUaClientSecret = "identity_ua_client_secrets",
|
IdentityUaClientSecret = "identity_ua_client_secrets",
|
||||||
IdentityOrgMembership = "identity_org_memberships",
|
IdentityOrgMembership = "identity_org_memberships",
|
||||||
IdentityProjectMembership = "identity_project_memberships",
|
IdentityProjectMembership = "identity_project_memberships",
|
||||||
|
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||||
ScimToken = "scim_tokens",
|
ScimToken = "scim_tokens",
|
||||||
SecretApprovalPolicy = "secret_approval_policies",
|
SecretApprovalPolicy = "secret_approval_policies",
|
||||||
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
|
||||||
@ -50,6 +53,7 @@ export enum TableName {
|
|||||||
SecretRotation = "secret_rotations",
|
SecretRotation = "secret_rotations",
|
||||||
SecretRotationOutput = "secret_rotation_outputs",
|
SecretRotationOutput = "secret_rotation_outputs",
|
||||||
SamlConfig = "saml_configs",
|
SamlConfig = "saml_configs",
|
||||||
|
LdapConfig = "ldap_configs",
|
||||||
AuditLog = "audit_logs",
|
AuditLog = "audit_logs",
|
||||||
GitAppInstallSession = "git_app_install_sessions",
|
GitAppInstallSession = "git_app_install_sessions",
|
||||||
GitAppOrg = "git_app_org",
|
GitAppOrg = "git_app_org",
|
||||||
|
31
backend/src/db/schemas/project-user-membership-roles.ts
Normal file
31
backend/src/db/schemas/project-user-membership-roles.ts
Normal file
@ -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 ProjectUserMembershipRolesSchema = 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 TProjectUserMembershipRoles = z.infer<typeof ProjectUserMembershipRolesSchema>;
|
||||||
|
export type TProjectUserMembershipRolesInsert = Omit<
|
||||||
|
z.input<typeof ProjectUserMembershipRolesSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TProjectUserMembershipRolesUpdate = Partial<
|
||||||
|
Omit<z.input<typeof ProjectUserMembershipRolesSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
24
backend/src/db/schemas/user-aliases.ts
Normal file
24
backend/src/db/schemas/user-aliases.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const UserAliasesSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
userId: z.string().uuid(),
|
||||||
|
username: z.string(),
|
||||||
|
aliasType: z.string(),
|
||||||
|
externalId: z.string(),
|
||||||
|
emails: z.string().array().nullable().optional(),
|
||||||
|
orgId: z.string().uuid().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TUserAliases = z.infer<typeof UserAliasesSchema>;
|
||||||
|
export type TUserAliasesInsert = Omit<z.input<typeof UserAliasesSchema>, TImmutableDBKeys>;
|
||||||
|
export type TUserAliasesUpdate = Partial<Omit<z.input<typeof UserAliasesSchema>, TImmutableDBKeys>>;
|
@ -9,7 +9,7 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const UsersSchema = z.object({
|
export const UsersSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
email: z.string(),
|
email: z.string().nullable().optional(),
|
||||||
authMethods: z.string().array().nullable().optional(),
|
authMethods: z.string().array().nullable().optional(),
|
||||||
superAdmin: z.boolean().default(false).nullable().optional(),
|
superAdmin: z.boolean().default(false).nullable().optional(),
|
||||||
firstName: z.string().nullable().optional(),
|
firstName: z.string().nullable().optional(),
|
||||||
@ -20,7 +20,8 @@ export const UsersSchema = z.object({
|
|||||||
devices: z.unknown().nullable().optional(),
|
devices: z.unknown().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
isGhost: z.boolean().default(false)
|
isGhost: z.boolean().default(false),
|
||||||
|
username: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TUsers = z.infer<typeof UsersSchema>;
|
export type TUsers = z.infer<typeof UsersSchema>;
|
||||||
|
@ -21,6 +21,7 @@ export let userPublicKey: string | undefined;
|
|||||||
|
|
||||||
export const seedData1 = {
|
export const seedData1 = {
|
||||||
id: "3dafd81d-4388-432b-a4c5-f735616868c1",
|
id: "3dafd81d-4388-432b-a4c5-f735616868c1",
|
||||||
|
username: process.env.TEST_USER_USERNAME || "test@localhost.local",
|
||||||
email: process.env.TEST_USER_EMAIL || "test@localhost.local",
|
email: process.env.TEST_USER_EMAIL || "test@localhost.local",
|
||||||
password: process.env.TEST_USER_PASSWORD || "testInfisical@1",
|
password: process.env.TEST_USER_PASSWORD || "testInfisical@1",
|
||||||
organization: {
|
organization: {
|
||||||
|
@ -9,7 +9,12 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
await knex(TableName.Users).del();
|
await knex(TableName.Users).del();
|
||||||
await knex(TableName.UserEncryptionKey).del();
|
await knex(TableName.UserEncryptionKey).del();
|
||||||
await knex(TableName.SuperAdmin).del();
|
await knex(TableName.SuperAdmin).del();
|
||||||
await knex(TableName.SuperAdmin).insert([{ initialized: true, allowSignUp: true }]);
|
|
||||||
|
await knex(TableName.SuperAdmin).insert([
|
||||||
|
// eslint-disable-next-line
|
||||||
|
// @ts-ignore
|
||||||
|
{ id: "00000000-0000-0000-0000-000000000000", initialized: true, allowSignUp: true }
|
||||||
|
]);
|
||||||
// Inserts seed entries
|
// Inserts seed entries
|
||||||
const [user] = await knex(TableName.Users)
|
const [user] = await knex(TableName.Users)
|
||||||
.insert([
|
.insert([
|
||||||
@ -17,6 +22,7 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
id: seedData1.id,
|
id: seedData1.id,
|
||||||
|
username: seedData1.username,
|
||||||
email: seedData1.email,
|
email: seedData1.email,
|
||||||
superAdmin: true,
|
superAdmin: true,
|
||||||
firstName: "test",
|
firstName: "test",
|
||||||
|
@ -4,7 +4,7 @@ import { Knex } from "knex";
|
|||||||
|
|
||||||
import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
|
|
||||||
import { OrgMembershipRole, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
|
import { ProjectMembershipRole, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
|
||||||
import { buildUserProjectKey, getUserPrivateKey, seedData1 } from "../seed-data";
|
import { buildUserProjectKey, getUserPrivateKey, seedData1 } from "../seed-data";
|
||||||
|
|
||||||
export const DEFAULT_PROJECT_ENVS = [
|
export const DEFAULT_PROJECT_ENVS = [
|
||||||
@ -30,10 +30,16 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
await knex(TableName.ProjectMembership).insert({
|
const projectMembership = await knex(TableName.ProjectMembership)
|
||||||
projectId: project.id,
|
.insert({
|
||||||
role: OrgMembershipRole.Admin,
|
projectId: project.id,
|
||||||
userId: seedData1.id
|
userId: seedData1.id,
|
||||||
|
role: ProjectMembershipRole.Admin
|
||||||
|
})
|
||||||
|
.returning("*");
|
||||||
|
await knex(TableName.ProjectUserMembershipRole).insert({
|
||||||
|
role: ProjectMembershipRole.Admin,
|
||||||
|
projectMembershipId: projectMembership[0].id
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await knex(TableName.UserEncryptionKey).where({ userId: seedData1.id }).first();
|
const user = await knex(TableName.UserEncryptionKey).where({ userId: seedData1.id }).first();
|
||||||
|
@ -75,9 +75,16 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await knex(TableName.IdentityProjectMembership).insert({
|
const identityProjectMembership = await knex(TableName.IdentityProjectMembership)
|
||||||
identityId: seedData1.machineIdentity.id,
|
.insert({
|
||||||
|
identityId: seedData1.machineIdentity.id,
|
||||||
|
projectId: seedData1.project.id,
|
||||||
|
role: ProjectMembershipRole.Admin
|
||||||
|
})
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
await knex(TableName.IdentityProjectMembershipRole).insert({
|
||||||
role: ProjectMembershipRole.Admin,
|
role: ProjectMembershipRole.Admin,
|
||||||
projectId: seedData1.project.id
|
projectMembershipId: identityProjectMembership[0].id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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";
|
||||||
import { registerProjectRoleRouter } from "./project-role-router";
|
import { registerProjectRoleRouter } from "./project-role-router";
|
||||||
@ -35,6 +36,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
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(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" });
|
||||||
|
194
backend/src/ee/routes/v1/ldap-router.ts
Normal file
194
backend/src/ee/routes/v1/ldap-router.ts
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
|
// All the any rules are disabled because passport typesense with fastify is really poor
|
||||||
|
|
||||||
|
import { IncomingMessage } from "node:http";
|
||||||
|
|
||||||
|
import { Authenticator } from "@fastify/passport";
|
||||||
|
import fastifySession from "@fastify/session";
|
||||||
|
import { FastifyRequest } from "fastify";
|
||||||
|
import LdapStrategy from "passport-ldapauth";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { LdapConfigsSchema } from "@app/db/schemas";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const passport = new Authenticator({ key: "ldap", userProperty: "passportUser" });
|
||||||
|
await server.register(fastifySession, { secret: appCfg.COOKIE_SECRET_SIGN_KEY });
|
||||||
|
await server.register(passport.initialize());
|
||||||
|
await server.register(passport.secureSession());
|
||||||
|
|
||||||
|
const getLdapPassportOpts = (req: FastifyRequest, done: any) => {
|
||||||
|
const { organizationSlug } = req.body as {
|
||||||
|
organizationSlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
process.nextTick(async () => {
|
||||||
|
try {
|
||||||
|
const { opts, ldapConfig } = await server.services.ldap.bootLdap(organizationSlug);
|
||||||
|
req.ldapConfig = ldapConfig;
|
||||||
|
done(null, opts);
|
||||||
|
} catch (err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
passport.use(
|
||||||
|
new LdapStrategy(
|
||||||
|
getLdapPassportOpts as any,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
async (req: IncomingMessage, user, cb) => {
|
||||||
|
try {
|
||||||
|
const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({
|
||||||
|
externalId: user.uidNumber,
|
||||||
|
username: user.uid,
|
||||||
|
firstName: user.givenName,
|
||||||
|
lastName: user.sn,
|
||||||
|
emails: user.mail ? [user.mail] : [],
|
||||||
|
relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState,
|
||||||
|
orgId: (req as unknown as FastifyRequest).ldapConfig.organization
|
||||||
|
});
|
||||||
|
|
||||||
|
return cb(null, { isUserCompleted, providerAuthToken });
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
return cb(err, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/login",
|
||||||
|
method: "POST",
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
organizationSlug: z.string().trim()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
preValidation: passport.authenticate("ldapauth", {
|
||||||
|
session: false
|
||||||
|
// failureFlash: true,
|
||||||
|
// failureRedirect: "/login/provider/error"
|
||||||
|
// this is due to zod type difference
|
||||||
|
}) as any,
|
||||||
|
handler: (req, res) => {
|
||||||
|
let nextUrl;
|
||||||
|
if (req.passportUser.isUserCompleted) {
|
||||||
|
nextUrl = `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`;
|
||||||
|
} else {
|
||||||
|
nextUrl = `${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
nextUrl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/config",
|
||||||
|
method: "GET",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
organization: z.string(),
|
||||||
|
isActive: z.boolean(),
|
||||||
|
url: z.string(),
|
||||||
|
bindDN: z.string(),
|
||||||
|
bindPass: z.string(),
|
||||||
|
searchBase: z.string(),
|
||||||
|
caCert: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const ldap = await server.services.ldap.getLdapCfgWithPermissionCheck({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
orgId: req.query.organizationId,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
return ldap;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/config",
|
||||||
|
method: "POST",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
organizationId: z.string().trim(),
|
||||||
|
isActive: z.boolean(),
|
||||||
|
url: z.string().trim(),
|
||||||
|
bindDN: z.string().trim(),
|
||||||
|
bindPass: z.string().trim(),
|
||||||
|
searchBase: z.string().trim(),
|
||||||
|
caCert: z.string().trim().default("")
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: LdapConfigsSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const ldap = await server.services.ldap.createLdapCfg({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
orgId: req.body.organizationId,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return ldap;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/config",
|
||||||
|
method: "PATCH",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
isActive: z.boolean(),
|
||||||
|
url: z.string().trim(),
|
||||||
|
bindDN: z.string().trim(),
|
||||||
|
bindPass: z.string().trim(),
|
||||||
|
searchBase: z.string().trim(),
|
||||||
|
caCert: z.string().trim()
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
.merge(z.object({ organizationId: z.string() })),
|
||||||
|
response: {
|
||||||
|
200: LdapConfigsSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const ldap = await server.services.ldap.updateLdapCfg({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
orgId: req.body.organizationId,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
return ldap;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||||
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";
|
||||||
|
|
||||||
@ -13,7 +14,17 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z.string().trim(),
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.trim()
|
||||||
|
.refine(
|
||||||
|
(val) => Object.keys(OrgMembershipRole).includes(val),
|
||||||
|
"Please choose a different slug, the slug you have entered is reserved"
|
||||||
|
)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid"
|
||||||
|
}),
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array()
|
permissions: z.any().array()
|
||||||
@ -45,7 +56,17 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
roleId: z.string().trim()
|
roleId: z.string().trim()
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z.string().trim().optional(),
|
slug: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.optional()
|
||||||
|
.refine(
|
||||||
|
(val) => typeof val === "undefined" || Object.keys(OrgMembershipRole).includes(val),
|
||||||
|
"Please choose a different slug, the slug you have entered is reserved."
|
||||||
|
)
|
||||||
|
.refine((val) => typeof val === "undefined" || slugify(val) === val, {
|
||||||
|
message: "Slug must be a valid"
|
||||||
|
}),
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().optional(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array()
|
permissions: z.any().array()
|
||||||
|
@ -2,6 +2,7 @@ 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 { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
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";
|
||||||
@ -19,13 +20,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
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({
|
||||||
@ -89,16 +90,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
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({
|
||||||
|
@ -27,6 +27,7 @@ type TSAMLConfig = {
|
|||||||
cert: string;
|
cert: string;
|
||||||
audience: string;
|
audience: string;
|
||||||
wantAuthnResponseSigned?: boolean;
|
wantAuthnResponseSigned?: boolean;
|
||||||
|
wantAssertionsSigned?: boolean;
|
||||||
disableRequestedAuthnContext?: boolean;
|
disableRequestedAuthnContext?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -82,6 +83,10 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
samlConfig.audience = `spn:${ssoConfig.issuer}`;
|
samlConfig.audience = `spn:${ssoConfig.issuer}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ssoConfig.authProvider === SamlProviders.GOOGLE_SAML) {
|
||||||
|
samlConfig.wantAssertionsSigned = false;
|
||||||
|
}
|
||||||
|
|
||||||
(req as unknown as FastifyRequest).ssoConfig = ssoConfig;
|
(req as unknown as FastifyRequest).ssoConfig = ssoConfig;
|
||||||
done(null, samlConfig);
|
done(null, samlConfig);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -94,14 +99,14 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
async (req, profile, cb) => {
|
async (req, profile, cb) => {
|
||||||
try {
|
try {
|
||||||
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
||||||
const { firstName } = profile;
|
|
||||||
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
|
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
|
||||||
|
|
||||||
if (!email || !firstName) {
|
if (!profile.email || !profile.firstName) {
|
||||||
throw new BadRequestError({ message: "Invalid request. Missing email or first name" });
|
throw new BadRequestError({ message: "Invalid request. Missing email or first name" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
||||||
|
username: profile.nameID ?? email,
|
||||||
email,
|
email,
|
||||||
firstName: profile.firstName as string,
|
firstName: profile.firstName as string,
|
||||||
lastName: profile.lastName as string,
|
lastName: profile.lastName as string,
|
||||||
|
@ -122,7 +122,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
emails: z.array(
|
emails: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
primary: z.boolean(),
|
primary: z.boolean(),
|
||||||
value: z.string().email(),
|
value: z.string(),
|
||||||
type: z.string().trim()
|
type: z.string().trim()
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@ -168,7 +168,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
emails: z.array(
|
emails: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
primary: z.boolean(),
|
primary: z.boolean(),
|
||||||
value: z.string().email(),
|
value: z.string(),
|
||||||
type: z.string().trim()
|
type: z.string().trim()
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@ -198,13 +198,15 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
familyName: z.string().trim(),
|
familyName: z.string().trim(),
|
||||||
givenName: z.string().trim()
|
givenName: z.string().trim()
|
||||||
}),
|
}),
|
||||||
// emails: z.array( // optional?
|
emails: z
|
||||||
// z.object({
|
.array(
|
||||||
// primary: z.boolean(),
|
z.object({
|
||||||
// value: z.string().email(),
|
primary: z.boolean(),
|
||||||
// type: z.string().trim()
|
value: z.string().email(),
|
||||||
// })
|
type: z.string().trim()
|
||||||
// ),
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
// displayName: z.string().trim(),
|
// displayName: z.string().trim(),
|
||||||
active: z.boolean()
|
active: z.boolean()
|
||||||
}),
|
}),
|
||||||
@ -231,8 +233,11 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
|
const primaryEmail = req.body.emails?.find((email) => email.primary)?.value;
|
||||||
|
|
||||||
const user = await req.server.services.scim.createScimUser({
|
const user = await req.server.services.scim.createScimUser({
|
||||||
email: req.body.userName,
|
username: req.body.userName,
|
||||||
|
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 as string
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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 { 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";
|
||||||
|
|
||||||
@ -65,7 +66,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
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({
|
||||||
|
@ -24,7 +24,7 @@ export const auditLogQueueServiceFactory = ({
|
|||||||
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
||||||
await queueService.queue(QueueName.AuditLog, QueueJobs.AuditLog, data, {
|
await queueService.queue(QueueName.AuditLog, QueueJobs.AuditLog, data, {
|
||||||
removeOnFail: {
|
removeOnFail: {
|
||||||
count: 5
|
count: 3
|
||||||
},
|
},
|
||||||
removeOnComplete: true
|
removeOnComplete: true
|
||||||
});
|
});
|
||||||
@ -46,6 +46,7 @@ export const auditLogQueueServiceFactory = ({
|
|||||||
const ttl = plan.auditLogsRetentionDays * MS_IN_DAY;
|
const ttl = plan.auditLogsRetentionDays * MS_IN_DAY;
|
||||||
// skip inserting if audit log retention is 0 meaning its not supported
|
// skip inserting if audit log retention is 0 meaning its not supported
|
||||||
if (ttl === 0) return;
|
if (ttl === 0) return;
|
||||||
|
|
||||||
await auditLogDAL.create({
|
await auditLogDAL.create({
|
||||||
actor: actor.type,
|
actor: actor.type,
|
||||||
actorMetadata: actor.metadata,
|
actorMetadata: actor.metadata,
|
||||||
|
@ -92,7 +92,8 @@ export enum EventType {
|
|||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email?: string | null;
|
||||||
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServiceActorMetadata {
|
interface ServiceActorMetadata {
|
||||||
|
11
backend/src/ee/services/ldap-config/ldap-config-dal.ts
Normal file
11
backend/src/ee/services/ldap-config/ldap-config-dal.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TLdapConfigDALFactory = ReturnType<typeof ldapConfigDALFactory>;
|
||||||
|
|
||||||
|
export const ldapConfigDALFactory = (db: TDbClient) => {
|
||||||
|
const ldapCfgOrm = ormify(db, TableName.LdapConfig);
|
||||||
|
|
||||||
|
return { ...ldapCfgOrm };
|
||||||
|
};
|
429
backend/src/ee/services/ldap-config/ldap-config-service.ts
Normal file
429
backend/src/ee/services/ldap-config/ldap-config-service.ts
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
import { OrgMembershipRole, OrgMembershipStatus, SecretKeyEncoding, TLdapConfigsUpdate } from "@app/db/schemas";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import {
|
||||||
|
decryptSymmetric,
|
||||||
|
encryptSymmetric,
|
||||||
|
generateAsymmetricKeyPair,
|
||||||
|
generateSymmetricKey,
|
||||||
|
infisicalSymmetricDecrypt,
|
||||||
|
infisicalSymmetricEncypt
|
||||||
|
} from "@app/lib/crypto/encryption";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
import { normalizeUsername } from "@app/services/user/user-fns";
|
||||||
|
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { TLdapConfigDALFactory } from "./ldap-config-dal";
|
||||||
|
import { TCreateLdapCfgDTO, TLdapLoginDTO, TUpdateLdapCfgDTO } from "./ldap-config-types";
|
||||||
|
|
||||||
|
type TLdapConfigServiceFactoryDep = {
|
||||||
|
ldapConfigDAL: TLdapConfigDALFactory;
|
||||||
|
orgDAL: Pick<
|
||||||
|
TOrgDALFactory,
|
||||||
|
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||||
|
>;
|
||||||
|
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
|
||||||
|
userDAL: Pick<TUserDALFactory, "create" | "findOne" | "transaction" | "updateById">;
|
||||||
|
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TLdapConfigServiceFactory = ReturnType<typeof ldapConfigServiceFactory>;
|
||||||
|
|
||||||
|
export const ldapConfigServiceFactory = ({
|
||||||
|
ldapConfigDAL,
|
||||||
|
orgDAL,
|
||||||
|
orgBotDAL,
|
||||||
|
userDAL,
|
||||||
|
userAliasDAL,
|
||||||
|
permissionService,
|
||||||
|
licenseService
|
||||||
|
}: TLdapConfigServiceFactoryDep) => {
|
||||||
|
const createLdapCfg = async ({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
orgId,
|
||||||
|
actorOrgId,
|
||||||
|
isActive,
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
caCert
|
||||||
|
}: TCreateLdapCfgDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (!plan.ldap)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to create LDAP configuration due to plan restriction. Upgrade plan to create LDAP configuration."
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgBot = await orgBotDAL.transaction(async (tx) => {
|
||||||
|
const doc = await orgBotDAL.findOne({ orgId }, tx);
|
||||||
|
if (doc) return doc;
|
||||||
|
|
||||||
|
const { privateKey, publicKey } = generateAsymmetricKeyPair();
|
||||||
|
const key = generateSymmetricKey();
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedPrivateKey,
|
||||||
|
iv: privateKeyIV,
|
||||||
|
tag: privateKeyTag,
|
||||||
|
encoding: privateKeyKeyEncoding,
|
||||||
|
algorithm: privateKeyAlgorithm
|
||||||
|
} = infisicalSymmetricEncypt(privateKey);
|
||||||
|
const {
|
||||||
|
ciphertext: encryptedSymmetricKey,
|
||||||
|
iv: symmetricKeyIV,
|
||||||
|
tag: symmetricKeyTag,
|
||||||
|
encoding: symmetricKeyKeyEncoding,
|
||||||
|
algorithm: symmetricKeyAlgorithm
|
||||||
|
} = infisicalSymmetricEncypt(key);
|
||||||
|
|
||||||
|
return orgBotDAL.create(
|
||||||
|
{
|
||||||
|
name: "Infisical org bot",
|
||||||
|
publicKey,
|
||||||
|
privateKeyIV,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
symmetricKeyIV,
|
||||||
|
symmetricKeyTag,
|
||||||
|
encryptedSymmetricKey,
|
||||||
|
symmetricKeyAlgorithm,
|
||||||
|
orgId,
|
||||||
|
privateKeyTag,
|
||||||
|
privateKeyAlgorithm,
|
||||||
|
privateKeyKeyEncoding,
|
||||||
|
symmetricKeyKeyEncoding
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = infisicalSymmetricDecrypt({
|
||||||
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
|
iv: orgBot.symmetricKeyIV,
|
||||||
|
tag: orgBot.symmetricKeyTag,
|
||||||
|
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
||||||
|
});
|
||||||
|
|
||||||
|
const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key);
|
||||||
|
const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key);
|
||||||
|
const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key);
|
||||||
|
|
||||||
|
const ldapConfig = await ldapConfigDAL.create({
|
||||||
|
orgId,
|
||||||
|
isActive,
|
||||||
|
url,
|
||||||
|
encryptedBindDN,
|
||||||
|
bindDNIV,
|
||||||
|
bindDNTag,
|
||||||
|
encryptedBindPass,
|
||||||
|
bindPassIV,
|
||||||
|
bindPassTag,
|
||||||
|
searchBase,
|
||||||
|
encryptedCACert,
|
||||||
|
caCertIV,
|
||||||
|
caCertTag
|
||||||
|
});
|
||||||
|
|
||||||
|
return ldapConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLdapCfg = async ({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
orgId,
|
||||||
|
actorOrgId,
|
||||||
|
isActive,
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
caCert
|
||||||
|
}: TUpdateLdapCfgDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (!plan.ldap)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to update LDAP configuration due to plan restriction. Upgrade plan to update LDAP configuration."
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateQuery: TLdapConfigsUpdate = {
|
||||||
|
isActive,
|
||||||
|
url,
|
||||||
|
searchBase
|
||||||
|
};
|
||||||
|
|
||||||
|
const orgBot = await orgBotDAL.findOne({ orgId });
|
||||||
|
if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
|
||||||
|
const key = infisicalSymmetricDecrypt({
|
||||||
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
|
iv: orgBot.symmetricKeyIV,
|
||||||
|
tag: orgBot.symmetricKeyTag,
|
||||||
|
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bindDN !== undefined) {
|
||||||
|
const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key);
|
||||||
|
updateQuery.encryptedBindDN = encryptedBindDN;
|
||||||
|
updateQuery.bindDNIV = bindDNIV;
|
||||||
|
updateQuery.bindDNTag = bindDNTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bindPass !== undefined) {
|
||||||
|
const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key);
|
||||||
|
updateQuery.encryptedBindPass = encryptedBindPass;
|
||||||
|
updateQuery.bindPassIV = bindPassIV;
|
||||||
|
updateQuery.bindPassTag = bindPassTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (caCert !== undefined) {
|
||||||
|
const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key);
|
||||||
|
updateQuery.encryptedCACert = encryptedCACert;
|
||||||
|
updateQuery.caCertIV = caCertIV;
|
||||||
|
updateQuery.caCertTag = caCertTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ldapConfig] = await ldapConfigDAL.update({ orgId }, updateQuery);
|
||||||
|
|
||||||
|
return ldapConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLdapCfg = async (filter: { orgId: string; isActive?: boolean }) => {
|
||||||
|
const ldapConfig = await ldapConfigDAL.findOne(filter);
|
||||||
|
if (!ldapConfig) throw new BadRequestError({ message: "Failed to find organization LDAP data" });
|
||||||
|
|
||||||
|
const orgBot = await orgBotDAL.findOne({ orgId: ldapConfig.orgId });
|
||||||
|
if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
|
||||||
|
|
||||||
|
const key = infisicalSymmetricDecrypt({
|
||||||
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
|
iv: orgBot.symmetricKeyIV,
|
||||||
|
tag: orgBot.symmetricKeyTag,
|
||||||
|
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
encryptedBindDN,
|
||||||
|
bindDNIV,
|
||||||
|
bindDNTag,
|
||||||
|
encryptedBindPass,
|
||||||
|
bindPassIV,
|
||||||
|
bindPassTag,
|
||||||
|
encryptedCACert,
|
||||||
|
caCertIV,
|
||||||
|
caCertTag
|
||||||
|
} = ldapConfig;
|
||||||
|
|
||||||
|
let bindDN = "";
|
||||||
|
if (encryptedBindDN && bindDNIV && bindDNTag) {
|
||||||
|
bindDN = decryptSymmetric({
|
||||||
|
ciphertext: encryptedBindDN,
|
||||||
|
key,
|
||||||
|
tag: bindDNTag,
|
||||||
|
iv: bindDNIV
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let bindPass = "";
|
||||||
|
if (encryptedBindPass && bindPassIV && bindPassTag) {
|
||||||
|
bindPass = decryptSymmetric({
|
||||||
|
ciphertext: encryptedBindPass,
|
||||||
|
key,
|
||||||
|
tag: bindPassTag,
|
||||||
|
iv: bindPassIV
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let caCert = "";
|
||||||
|
if (encryptedCACert && caCertIV && caCertTag) {
|
||||||
|
caCert = decryptSymmetric({
|
||||||
|
ciphertext: encryptedCACert,
|
||||||
|
key,
|
||||||
|
tag: caCertTag,
|
||||||
|
iv: caCertIV
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: ldapConfig.id,
|
||||||
|
organization: ldapConfig.orgId,
|
||||||
|
isActive: ldapConfig.isActive,
|
||||||
|
url: ldapConfig.url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase: ldapConfig.searchBase,
|
||||||
|
caCert
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLdapCfgWithPermissionCheck = async ({ actor, actorId, orgId, actorOrgId }: TOrgPermission) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
||||||
|
return getLdapCfg({
|
||||||
|
orgId
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const bootLdap = async (organizationSlug: string) => {
|
||||||
|
const organization = await orgDAL.findOne({ slug: organizationSlug });
|
||||||
|
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
||||||
|
|
||||||
|
const ldapConfig = await getLdapCfg({
|
||||||
|
orgId: organization.id,
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
server: {
|
||||||
|
url: ldapConfig.url,
|
||||||
|
bindDN: ldapConfig.bindDN,
|
||||||
|
bindCredentials: ldapConfig.bindPass,
|
||||||
|
searchBase: ldapConfig.searchBase,
|
||||||
|
searchFilter: "(uid={{username}})",
|
||||||
|
searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"],
|
||||||
|
...(ldapConfig.caCert !== ""
|
||||||
|
? {
|
||||||
|
tlsOptions: {
|
||||||
|
ca: [ldapConfig.caCert]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
},
|
||||||
|
passReqToCallback: true
|
||||||
|
};
|
||||||
|
|
||||||
|
return { opts, ldapConfig };
|
||||||
|
};
|
||||||
|
|
||||||
|
const ldapLogin = async ({ externalId, username, firstName, lastName, emails, orgId, relayState }: TLdapLoginDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
let userAlias = await userAliasDAL.findOne({
|
||||||
|
externalId,
|
||||||
|
orgId,
|
||||||
|
aliasType: AuthMethod.LDAP
|
||||||
|
});
|
||||||
|
|
||||||
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
|
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
||||||
|
|
||||||
|
if (userAlias) {
|
||||||
|
await userDAL.transaction(async (tx) => {
|
||||||
|
const [orgMembership] = await orgDAL.findMembership({ userId: userAlias.userId }, { tx });
|
||||||
|
if (!orgMembership) {
|
||||||
|
await orgDAL.createMembership(
|
||||||
|
{
|
||||||
|
userId: userAlias.userId,
|
||||||
|
orgId,
|
||||||
|
role: OrgMembershipRole.Member,
|
||||||
|
status: OrgMembershipStatus.Accepted
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
} else if (orgMembership.status === OrgMembershipStatus.Invited) {
|
||||||
|
await orgDAL.updateMembershipById(
|
||||||
|
orgMembership.id,
|
||||||
|
{
|
||||||
|
status: OrgMembershipStatus.Accepted
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
userAlias = await userDAL.transaction(async (tx) => {
|
||||||
|
const uniqueUsername = await normalizeUsername(username, userDAL);
|
||||||
|
const newUser = await userDAL.create(
|
||||||
|
{
|
||||||
|
username: uniqueUsername,
|
||||||
|
email: emails[0],
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
authMethods: [AuthMethod.LDAP],
|
||||||
|
isGhost: false
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
const newUserAlias = await userAliasDAL.create(
|
||||||
|
{
|
||||||
|
userId: newUser.id,
|
||||||
|
username,
|
||||||
|
aliasType: AuthMethod.LDAP,
|
||||||
|
externalId,
|
||||||
|
emails,
|
||||||
|
orgId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await orgDAL.createMembership(
|
||||||
|
{
|
||||||
|
userId: newUser.id,
|
||||||
|
orgId,
|
||||||
|
role: OrgMembershipRole.Member,
|
||||||
|
status: OrgMembershipStatus.Invited
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
return newUserAlias;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await userDAL.findOne({ id: userAlias.userId });
|
||||||
|
|
||||||
|
const isUserCompleted = Boolean(user.isAccepted);
|
||||||
|
|
||||||
|
const providerAuthToken = jwt.sign(
|
||||||
|
{
|
||||||
|
authTokenType: AuthTokenType.PROVIDER_TOKEN,
|
||||||
|
userId: user.id,
|
||||||
|
username: user.username,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
organizationName: organization.name,
|
||||||
|
organizationId: organization.id,
|
||||||
|
authMethod: AuthMethod.LDAP,
|
||||||
|
isUserCompleted,
|
||||||
|
...(relayState
|
||||||
|
? {
|
||||||
|
callbackPort: (JSON.parse(relayState) as { callbackPort: string }).callbackPort
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
},
|
||||||
|
appCfg.AUTH_SECRET,
|
||||||
|
{
|
||||||
|
expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return { isUserCompleted, providerAuthToken };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createLdapCfg,
|
||||||
|
updateLdapCfg,
|
||||||
|
getLdapCfgWithPermissionCheck,
|
||||||
|
getLdapCfg,
|
||||||
|
// getLdapPassportOpts,
|
||||||
|
ldapLogin,
|
||||||
|
bootLdap
|
||||||
|
};
|
||||||
|
};
|
30
backend/src/ee/services/ldap-config/ldap-config-types.ts
Normal file
30
backend/src/ee/services/ldap-config/ldap-config-types.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
export type TCreateLdapCfgDTO = {
|
||||||
|
isActive: boolean;
|
||||||
|
url: string;
|
||||||
|
bindDN: string;
|
||||||
|
bindPass: string;
|
||||||
|
searchBase: string;
|
||||||
|
caCert: string;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
|
export type TUpdateLdapCfgDTO = Partial<{
|
||||||
|
isActive: boolean;
|
||||||
|
url: string;
|
||||||
|
bindDN: string;
|
||||||
|
bindPass: string;
|
||||||
|
searchBase: string;
|
||||||
|
caCert: string;
|
||||||
|
}> &
|
||||||
|
TOrgPermission;
|
||||||
|
|
||||||
|
export type TLdapLoginDTO = {
|
||||||
|
externalId: string;
|
||||||
|
username: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
emails: string[];
|
||||||
|
orgId: string;
|
||||||
|
relayState?: string;
|
||||||
|
};
|
@ -18,6 +18,8 @@ export const getDefaultOnPremFeatures = () => {
|
|||||||
auditLogs: false,
|
auditLogs: false,
|
||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
|
scim: false,
|
||||||
|
ldap: false,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
|
@ -25,6 +25,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
|
ldap: false,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
has_used_trial: true,
|
has_used_trial: true,
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
// TODO(akhilmhdh): With tony find out the api structure and fill it here
|
// TODO(akhilmhdh): With tony find out the api structure and fill it here
|
||||||
|
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import NodeCache from "node-cache";
|
|
||||||
|
|
||||||
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
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";
|
||||||
@ -39,6 +39,7 @@ type TLicenseServiceFactoryDep = {
|
|||||||
orgDAL: Pick<TOrgDALFactory, "findOrgById">;
|
orgDAL: Pick<TOrgDALFactory, "findOrgById">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
licenseDAL: TLicenseDALFactory;
|
licenseDAL: TLicenseDALFactory;
|
||||||
|
keyStore: Pick<TKeyStoreFactory, "setItemWithExpiry" | "getItem" | "deleteItem">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
|
export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
|
||||||
@ -46,12 +47,18 @@ export type TLicenseServiceFactory = ReturnType<typeof licenseServiceFactory>;
|
|||||||
const LICENSE_SERVER_CLOUD_LOGIN = "/api/auth/v1/license-server-login";
|
const LICENSE_SERVER_CLOUD_LOGIN = "/api/auth/v1/license-server-login";
|
||||||
const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/license-login";
|
const LICENSE_SERVER_ON_PREM_LOGIN = "/api/auth/v1/license-login";
|
||||||
|
|
||||||
const FEATURE_CACHE_KEY = (orgId: string, projectId?: string) => `${orgId}-${projectId || ""}`;
|
const LICENSE_SERVER_CLOUD_PLAN_TTL = 30; // 30 second
|
||||||
export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }: TLicenseServiceFactoryDep) => {
|
const FEATURE_CACHE_KEY = (orgId: string) => `infisical-cloud-plan-${orgId}`;
|
||||||
|
|
||||||
|
export const licenseServiceFactory = ({
|
||||||
|
orgDAL,
|
||||||
|
permissionService,
|
||||||
|
licenseDAL,
|
||||||
|
keyStore
|
||||||
|
}: TLicenseServiceFactoryDep) => {
|
||||||
let isValidLicense = false;
|
let isValidLicense = false;
|
||||||
let instanceType = InstanceType.OnPrem;
|
let instanceType = InstanceType.OnPrem;
|
||||||
let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures();
|
let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures();
|
||||||
const featureStore = new NodeCache({ stdTTL: 60 });
|
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const licenseServerCloudApi = setupLicenceRequestWithStore(
|
const licenseServerCloudApi = setupLicenceRequestWithStore(
|
||||||
@ -75,6 +82,7 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
|
|||||||
isValidLicense = true;
|
isValidLicense = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appCfg.LICENSE_KEY) {
|
if (appCfg.LICENSE_KEY) {
|
||||||
const token = await licenseServerOnPremApi.refreshLicence();
|
const token = await licenseServerOnPremApi.refreshLicence();
|
||||||
if (token) {
|
if (token) {
|
||||||
@ -100,22 +108,21 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
|
|||||||
logger.info(`getPlan: attempting to fetch plan for [orgId=${orgId}] [projectId=${projectId}]`);
|
logger.info(`getPlan: attempting to fetch plan for [orgId=${orgId}] [projectId=${projectId}]`);
|
||||||
try {
|
try {
|
||||||
if (instanceType === InstanceType.Cloud) {
|
if (instanceType === InstanceType.Cloud) {
|
||||||
const cachedPlan = featureStore.get<TFeatureSet>(FEATURE_CACHE_KEY(orgId, projectId));
|
const cachedPlan = await keyStore.getItem(FEATURE_CACHE_KEY(orgId));
|
||||||
if (cachedPlan) return cachedPlan;
|
if (cachedPlan) return JSON.parse(cachedPlan) as TFeatureSet;
|
||||||
|
|
||||||
const org = await orgDAL.findOrgById(orgId);
|
const org = await orgDAL.findOrgById(orgId);
|
||||||
if (!org) throw new BadRequestError({ message: "Org not found" });
|
if (!org) throw new BadRequestError({ message: "Org not found" });
|
||||||
const {
|
const {
|
||||||
data: { currentPlan }
|
data: { currentPlan }
|
||||||
} = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>(
|
} = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>(
|
||||||
`/api/license-server/v1/customers/${org.customerId}/cloud-plan`,
|
`/api/license-server/v1/customers/${org.customerId}/cloud-plan`
|
||||||
{
|
);
|
||||||
params: {
|
await keyStore.setItemWithExpiry(
|
||||||
workspaceId: projectId
|
FEATURE_CACHE_KEY(org.id),
|
||||||
}
|
LICENSE_SERVER_CLOUD_PLAN_TTL,
|
||||||
}
|
JSON.stringify(currentPlan)
|
||||||
);
|
);
|
||||||
featureStore.set(FEATURE_CACHE_KEY(org.id, projectId), currentPlan);
|
|
||||||
return currentPlan;
|
return currentPlan;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -123,26 +130,31 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
|
|||||||
`getPlan: encountered an error when fetching pan [orgId=${orgId}] [projectId=${projectId}] [error]`,
|
`getPlan: encountered an error when fetching pan [orgId=${orgId}] [projectId=${projectId}] [error]`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
|
await keyStore.setItemWithExpiry(
|
||||||
|
FEATURE_CACHE_KEY(orgId),
|
||||||
|
LICENSE_SERVER_CLOUD_PLAN_TTL,
|
||||||
|
JSON.stringify(onPremFeatures)
|
||||||
|
);
|
||||||
return onPremFeatures;
|
return onPremFeatures;
|
||||||
}
|
}
|
||||||
return onPremFeatures;
|
return onPremFeatures;
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshPlan = async (orgId: string, projectId?: string) => {
|
const refreshPlan = async (orgId: string) => {
|
||||||
if (instanceType === InstanceType.Cloud) {
|
if (instanceType === InstanceType.Cloud) {
|
||||||
featureStore.del(FEATURE_CACHE_KEY(orgId, projectId));
|
await keyStore.deleteItem(FEATURE_CACHE_KEY(orgId));
|
||||||
await getPlan(orgId, projectId);
|
await getPlan(orgId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateOrgCustomerId = async (orgName: string, email: string) => {
|
const generateOrgCustomerId = async (orgName: string, email?: string | null) => {
|
||||||
if (instanceType === InstanceType.Cloud) {
|
if (instanceType === InstanceType.Cloud) {
|
||||||
const {
|
const {
|
||||||
data: { customerId }
|
data: { customerId }
|
||||||
} = await licenseServerCloudApi.request.post<{ customerId: string }>(
|
} = await licenseServerCloudApi.request.post<{ customerId: string }>(
|
||||||
"/api/license-server/v1/customers",
|
"/api/license-server/v1/customers",
|
||||||
{
|
{
|
||||||
email,
|
email: email ?? "",
|
||||||
name: orgName
|
name: orgName
|
||||||
},
|
},
|
||||||
{ timeout: 5000, signal: AbortSignal.timeout(5000) }
|
{ timeout: 5000, signal: AbortSignal.timeout(5000) }
|
||||||
@ -166,7 +178,7 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
|
|||||||
quantity: count
|
quantity: count
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
featureStore.del(orgId);
|
await keyStore.deleteItem(FEATURE_CACHE_KEY(orgId));
|
||||||
} else if (instanceType === InstanceType.EnterpriseOnPrem) {
|
} else if (instanceType === InstanceType.EnterpriseOnPrem) {
|
||||||
const usedSeats = await licenseDAL.countOfOrgMembers(null);
|
const usedSeats = await licenseDAL.countOfOrgMembers(null);
|
||||||
await licenseServerOnPremApi.request.patch(`/api/license/v1/license`, { usedSeats });
|
await licenseServerOnPremApi.request.patch(`/api/license/v1/license`, { usedSeats });
|
||||||
@ -215,7 +227,7 @@ export const licenseServiceFactory = ({ orgDAL, permissionService, licenseDAL }:
|
|||||||
`/api/license-server/v1/customers/${organization.customerId}/session/trial`,
|
`/api/license-server/v1/customers/${organization.customerId}/session/trial`,
|
||||||
{ success_url }
|
{ success_url }
|
||||||
);
|
);
|
||||||
featureStore.del(FEATURE_CACHE_KEY(orgId));
|
await keyStore.deleteItem(FEATURE_CACHE_KEY(orgId));
|
||||||
return { url };
|
return { url };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ export type TFeatureSet = {
|
|||||||
auditLogsRetentionDays: 0;
|
auditLogsRetentionDays: 0;
|
||||||
samlSSO: false;
|
samlSSO: false;
|
||||||
scim: false;
|
scim: false;
|
||||||
|
ldap: false;
|
||||||
status: null;
|
status: null;
|
||||||
trial_end: null;
|
trial_end: null;
|
||||||
has_used_trial: true;
|
has_used_trial: true;
|
||||||
|
@ -17,6 +17,7 @@ export enum OrgPermissionSubjects {
|
|||||||
IncidentAccount = "incident-contact",
|
IncidentAccount = "incident-contact",
|
||||||
Sso = "sso",
|
Sso = "sso",
|
||||||
Scim = "scim",
|
Scim = "scim",
|
||||||
|
Ldap = "ldap",
|
||||||
Billing = "billing",
|
Billing = "billing",
|
||||||
SecretScanning = "secret-scanning",
|
SecretScanning = "secret-scanning",
|
||||||
Identity = "identity"
|
Identity = "identity"
|
||||||
@ -31,6 +32,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
||||||
@ -76,6 +78,11 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap);
|
||||||
|
|
||||||
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);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { IdentityProjectMembershipRoleSchema, ProjectUserMembershipRolesSchema, TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { selectAllTableCols } from "@app/lib/knex";
|
import { selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TPermissionDALFactory = ReturnType<typeof permissionDALFactory>;
|
export type TPermissionDALFactory = ReturnType<typeof permissionDALFactory>;
|
||||||
|
|
||||||
@ -43,21 +45,72 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const getProjectPermission = async (userId: string, projectId: string) => {
|
const getProjectPermission = async (userId: string, projectId: string) => {
|
||||||
try {
|
try {
|
||||||
const membership = await db(TableName.ProjectMembership)
|
const docs = await db(TableName.ProjectMembership)
|
||||||
.leftJoin(TableName.ProjectRoles, `${TableName.ProjectMembership}.roleId`, `${TableName.ProjectRoles}.id`)
|
.join(
|
||||||
|
TableName.ProjectUserMembershipRole,
|
||||||
|
`${TableName.ProjectUserMembershipRole}.projectMembershipId`,
|
||||||
|
`${TableName.ProjectMembership}.id`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ProjectRoles,
|
||||||
|
`${TableName.ProjectUserMembershipRole}.customRoleId`,
|
||||||
|
`${TableName.ProjectRoles}.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)
|
||||||
.where(`${TableName.ProjectMembership}.projectId`, projectId)
|
.where(`${TableName.ProjectMembership}.projectId`, projectId)
|
||||||
.select(selectAllTableCols(TableName.ProjectMembership))
|
.select(selectAllTableCols(TableName.ProjectUserMembershipRole))
|
||||||
.select(
|
.select(
|
||||||
|
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("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
|
||||||
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")
|
||||||
)
|
)
|
||||||
.select("permissions")
|
.select("permissions");
|
||||||
.first();
|
|
||||||
|
|
||||||
return membership;
|
const permission = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "membershipId",
|
||||||
|
parentMapper: ({
|
||||||
|
orgId,
|
||||||
|
orgAuthEnforced,
|
||||||
|
membershipId,
|
||||||
|
membershipCreatedAt,
|
||||||
|
membershipUpdatedAt,
|
||||||
|
oldRoleField
|
||||||
|
}) => ({
|
||||||
|
orgId,
|
||||||
|
orgAuthEnforced,
|
||||||
|
userId,
|
||||||
|
role: oldRoleField,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
// when introducting cron mode change it here
|
||||||
|
const activeRoles = permission?.[0]?.roles.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
);
|
||||||
|
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
||||||
}
|
}
|
||||||
@ -65,18 +118,62 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
|
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
|
||||||
try {
|
try {
|
||||||
const membership = await db(TableName.IdentityProjectMembership)
|
const docs = await db(TableName.IdentityProjectMembership)
|
||||||
|
.join(
|
||||||
|
TableName.IdentityProjectMembershipRole,
|
||||||
|
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
|
||||||
|
`${TableName.IdentityProjectMembership}.id`
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.ProjectRoles,
|
TableName.ProjectRoles,
|
||||||
`${TableName.IdentityProjectMembership}.roleId`,
|
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
.where("identityId", identityId)
|
.where("identityId", identityId)
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
.select(selectAllTableCols(TableName.IdentityProjectMembership))
|
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
||||||
.select("permissions")
|
.select(
|
||||||
.first();
|
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
|
||||||
return membership;
|
db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"),
|
||||||
|
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
||||||
|
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
||||||
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
||||||
|
)
|
||||||
|
.select("permissions");
|
||||||
|
|
||||||
|
const permission = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "membershipId",
|
||||||
|
parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, oldRoleField }) => ({
|
||||||
|
id: membershipId,
|
||||||
|
identityId,
|
||||||
|
projectId,
|
||||||
|
role: oldRoleField,
|
||||||
|
createdAt: membershipCreatedAt,
|
||||||
|
updatedAt: membershipUpdatedAt,
|
||||||
|
// just a prefilled value
|
||||||
|
orgAuthEnforced: false,
|
||||||
|
orgId: ""
|
||||||
|
}),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "id",
|
||||||
|
label: "roles" as const,
|
||||||
|
mapper: (data) =>
|
||||||
|
IdentityProjectMembershipRoleSchema.extend({
|
||||||
|
permissions: z.unknown(),
|
||||||
|
customRoleSlug: z.string().optional().nullable()
|
||||||
|
}).parse(data)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// when introducting cron mode change it here
|
||||||
|
const activeRoles = permission?.[0]?.roles.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
);
|
||||||
|
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
|
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok
|
|||||||
|
|
||||||
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 { TBuildProjectPermissionDTO } from "./permission-types";
|
||||||
import {
|
import {
|
||||||
buildServiceTokenProjectPermission,
|
buildServiceTokenProjectPermission,
|
||||||
projectAdminPermissions,
|
projectAdminPermissions,
|
||||||
@ -64,31 +65,35 @@ export const permissionServiceFactory = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildProjectPermission = (role: string, permission?: unknown) => {
|
const buildProjectPermission = (projectUserRoles: TBuildProjectPermissionDTO) => {
|
||||||
switch (role) {
|
const rules = projectUserRoles
|
||||||
case ProjectMembershipRole.Admin:
|
.map(({ role, permissions }) => {
|
||||||
return projectAdminPermissions;
|
switch (role) {
|
||||||
case ProjectMembershipRole.Member:
|
case ProjectMembershipRole.Admin:
|
||||||
return projectMemberPermissions;
|
return projectAdminPermissions;
|
||||||
case ProjectMembershipRole.Viewer:
|
case ProjectMembershipRole.Member:
|
||||||
return projectViewerPermission;
|
return projectMemberPermissions;
|
||||||
case ProjectMembershipRole.NoAccess:
|
case ProjectMembershipRole.Viewer:
|
||||||
return projectNoAccessPermissions;
|
return projectViewerPermission;
|
||||||
case ProjectMembershipRole.Custom:
|
case ProjectMembershipRole.NoAccess:
|
||||||
return createMongoAbility<ProjectPermissionSet>(
|
return projectNoAccessPermissions;
|
||||||
unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
|
case ProjectMembershipRole.Custom: {
|
||||||
permission as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
|
return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
|
||||||
),
|
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
|
||||||
{
|
);
|
||||||
conditionsMatcher
|
|
||||||
}
|
}
|
||||||
);
|
default:
|
||||||
default:
|
throw new BadRequestError({
|
||||||
throw new BadRequestError({
|
name: "ProjectRoleInvalid",
|
||||||
name: "ProjectRoleInvalid",
|
message: "Project role not found"
|
||||||
message: "Project role not found"
|
});
|
||||||
});
|
}
|
||||||
}
|
})
|
||||||
|
.reduce((curr, prev) => prev.concat(curr), []);
|
||||||
|
|
||||||
|
return createMongoAbility<ProjectPermissionSet>(rules, {
|
||||||
|
conditionsMatcher
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -145,33 +150,56 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// user permission for a project in an organization
|
// user permission for a project in an organization
|
||||||
const getUserProjectPermission = async (userId: string, projectId: string, userOrgId?: string) => {
|
const getUserProjectPermission = async (
|
||||||
const membership = await permissionDAL.getProjectPermission(userId, projectId);
|
userId: string,
|
||||||
if (!membership) throw new UnauthorizedError({ name: "User not in project" });
|
projectId: string,
|
||||||
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
|
userOrgId?: string
|
||||||
|
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||||
|
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||||
|
if (!userProjectPermission) throw new UnauthorizedError({ name: "User not in project" });
|
||||||
|
|
||||||
|
if (
|
||||||
|
userProjectPermission.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)
|
||||||
|
) {
|
||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new BadRequestError({ name: "Custom permission not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (membership.orgAuthEnforced && membership.orgId !== userOrgId) {
|
if (userProjectPermission.orgAuthEnforced && userProjectPermission.orgId !== userOrgId) {
|
||||||
throw new BadRequestError({ name: "Cannot access org-scoped resource" });
|
throw new BadRequestError({ name: "Cannot access org-scoped resource" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission(membership.role, membership.permissions),
|
permission: buildProjectPermission(userProjectPermission.roles),
|
||||||
membership
|
membership: userProjectPermission,
|
||||||
|
hasRole: (role: string) =>
|
||||||
|
userProjectPermission.roles.findIndex(
|
||||||
|
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||||
|
) !== -1
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIdentityProjectPermission = async (identityId: string, projectId: string) => {
|
const getIdentityProjectPermission = async (
|
||||||
const membership = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
identityId: string,
|
||||||
if (!membership) throw new UnauthorizedError({ name: "Identity not in project" });
|
projectId: string
|
||||||
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
|
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||||
|
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||||
|
if (!identityProjectPermission) throw new UnauthorizedError({ name: "Identity not in project" });
|
||||||
|
|
||||||
|
if (
|
||||||
|
identityProjectPermission.roles.some(
|
||||||
|
({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions
|
||||||
|
)
|
||||||
|
) {
|
||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new BadRequestError({ name: "Custom permission not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission(membership.role, membership.permissions),
|
permission: buildProjectPermission(identityProjectPermission.roles),
|
||||||
membership
|
membership: identityProjectPermission,
|
||||||
|
hasRole: (role: string) =>
|
||||||
|
identityProjectPermission.roles.findIndex(
|
||||||
|
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||||
|
) !== -1
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -191,14 +219,19 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TProjectPermissionRT<T extends ActorType> = T extends ActorType.SERVICE
|
type TProjectPermissionRT<T extends ActorType> = T extends ActorType.SERVICE
|
||||||
? { permission: MongoAbility<ProjectPermissionSet, MongoQuery>; membership: undefined }
|
? {
|
||||||
|
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
|
membership: undefined;
|
||||||
|
hasRole: (arg: string) => boolean;
|
||||||
|
} // service token doesn't have both membership and roles
|
||||||
: {
|
: {
|
||||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & {
|
membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & {
|
||||||
orgAuthEnforced: boolean;
|
orgAuthEnforced: boolean | null | undefined;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
permissions?: unknown;
|
roles: Array<{ role: string }>;
|
||||||
};
|
};
|
||||||
|
hasRole: (role: string) => boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectPermission = async <T extends ActorType>(
|
const getProjectPermission = async <T extends ActorType>(
|
||||||
@ -228,11 +261,13 @@ export const permissionServiceFactory = ({
|
|||||||
const projectRole = await projectRoleDAL.findOne({ slug: role, projectId });
|
const projectRole = await projectRoleDAL.findOne({ slug: role, projectId });
|
||||||
if (!projectRole) throw new BadRequestError({ message: "Role not found" });
|
if (!projectRole) throw new BadRequestError({ message: "Role not found" });
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission(ProjectMembershipRole.Custom, projectRole.permissions),
|
permission: buildProjectPermission([
|
||||||
|
{ role: ProjectMembershipRole.Custom, permissions: projectRole.permissions }
|
||||||
|
]),
|
||||||
role: projectRole
|
role: projectRole
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { permission: buildProjectPermission(role, []) };
|
return { permission: buildProjectPermission([{ role, permissions: [] }]) };
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
export type TBuildProjectPermissionDTO = {
|
||||||
|
permissions?: unknown;
|
||||||
|
role: string;
|
||||||
|
}[];
|
||||||
|
@ -56,8 +56,8 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
||||||
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback];
|
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback];
|
||||||
|
|
||||||
const buildAdminPermission = () => {
|
const buildAdminPermissionRules = () => {
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
|
||||||
@ -135,13 +135,13 @@ const buildAdminPermission = () => {
|
|||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Project);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Project);
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
||||||
|
|
||||||
return build({ conditionsMatcher });
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const projectAdminPermissions = buildAdminPermission();
|
export const projectAdminPermissions = buildAdminPermissionRules();
|
||||||
|
|
||||||
const buildMemberPermission = () => {
|
const buildMemberPermissionRules = () => {
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
|
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
|
||||||
@ -196,13 +196,13 @@ const buildMemberPermission = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
return build({ conditionsMatcher });
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const projectMemberPermissions = buildMemberPermission();
|
export const projectMemberPermissions = buildMemberPermissionRules();
|
||||||
|
|
||||||
const buildViewerPermission = () => {
|
const buildViewerPermissionRules = () => {
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
@ -220,14 +220,14 @@ const buildViewerPermission = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
return build({ conditionsMatcher });
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const projectViewerPermission = buildViewerPermission();
|
export const projectViewerPermission = buildViewerPermissionRules();
|
||||||
|
|
||||||
const buildNoAccessProjectPermission = () => {
|
const buildNoAccessProjectPermission = () => {
|
||||||
const { build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
return build({ conditionsMatcher });
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildServiceTokenProjectPermission = (
|
export const buildServiceTokenProjectPermission = (
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
OrgMembershipRole,
|
OrgMembershipRole,
|
||||||
OrgMembershipStatus,
|
OrgMembershipStatus,
|
||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
|
TableName,
|
||||||
TSamlConfigs,
|
TSamlConfigs,
|
||||||
TSamlConfigsUpdate
|
TSamlConfigsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
@ -31,7 +32,7 @@ import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } f
|
|||||||
|
|
||||||
type TSamlConfigServiceFactoryDep = {
|
type TSamlConfigServiceFactoryDep = {
|
||||||
samlConfigDAL: TSamlConfigDALFactory;
|
samlConfigDAL: TSamlConfigDALFactory;
|
||||||
userDAL: Pick<TUserDALFactory, "create" | "findUserByEmail" | "transaction" | "updateById">;
|
userDAL: Pick<TUserDALFactory, "create" | "findOne" | "transaction" | "updateById">;
|
||||||
orgDAL: Pick<
|
orgDAL: Pick<
|
||||||
TOrgDALFactory,
|
TOrgDALFactory,
|
||||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||||
@ -69,7 +70,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
if (!plan.samlSSO)
|
if (!plan.samlSSO)
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message:
|
message:
|
||||||
"Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
|
"Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to create SSO configuration."
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgBot = await orgBotDAL.transaction(async (tx) => {
|
const orgBot = await orgBotDAL.transaction(async (tx) => {
|
||||||
@ -122,7 +123,6 @@ export const samlConfigServiceFactory = ({
|
|||||||
|
|
||||||
const { ciphertext: encryptedEntryPoint, iv: entryPointIV, tag: entryPointTag } = encryptSymmetric(entryPoint, key);
|
const { ciphertext: encryptedEntryPoint, iv: entryPointIV, tag: entryPointTag } = encryptSymmetric(entryPoint, key);
|
||||||
const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key);
|
const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key);
|
||||||
|
|
||||||
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
|
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
|
||||||
const samlConfig = await samlConfigDAL.create({
|
const samlConfig = await samlConfigDAL.create({
|
||||||
orgId,
|
orgId,
|
||||||
@ -172,7 +172,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
|
||||||
});
|
});
|
||||||
|
|
||||||
if (entryPoint) {
|
if (entryPoint !== undefined) {
|
||||||
const {
|
const {
|
||||||
ciphertext: encryptedEntryPoint,
|
ciphertext: encryptedEntryPoint,
|
||||||
iv: entryPointIV,
|
iv: entryPointIV,
|
||||||
@ -182,18 +182,19 @@ export const samlConfigServiceFactory = ({
|
|||||||
updateQuery.entryPointIV = entryPointIV;
|
updateQuery.entryPointIV = entryPointIV;
|
||||||
updateQuery.entryPointTag = entryPointTag;
|
updateQuery.entryPointTag = entryPointTag;
|
||||||
}
|
}
|
||||||
if (issuer) {
|
if (issuer !== undefined) {
|
||||||
const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key);
|
const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key);
|
||||||
updateQuery.encryptedIssuer = encryptedIssuer;
|
updateQuery.encryptedIssuer = encryptedIssuer;
|
||||||
updateQuery.issuerIV = issuerIV;
|
updateQuery.issuerIV = issuerIV;
|
||||||
updateQuery.issuerTag = issuerTag;
|
updateQuery.issuerTag = issuerTag;
|
||||||
}
|
}
|
||||||
if (cert) {
|
if (cert !== undefined) {
|
||||||
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
|
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
|
||||||
updateQuery.encryptedCert = encryptedCert;
|
updateQuery.encryptedCert = encryptedCert;
|
||||||
updateQuery.certIV = certIV;
|
updateQuery.certIV = certIV;
|
||||||
updateQuery.certTag = certTag;
|
updateQuery.certTag = certTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery);
|
const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery);
|
||||||
await orgDAL.updateById(orgId, { authEnforced: false, scimEnabled: false });
|
await orgDAL.updateById(orgId, { authEnforced: false, scimEnabled: false });
|
||||||
|
|
||||||
@ -300,16 +301,30 @@ export const samlConfigServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const samlLogin = async ({ firstName, email, lastName, authProvider, orgId, relayState }: TSamlLoginDTO) => {
|
const samlLogin = async ({
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
authProvider,
|
||||||
|
orgId,
|
||||||
|
relayState
|
||||||
|
}: TSamlLoginDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
let user = await userDAL.findUserByEmail(email);
|
let user = await userDAL.findOne({ username });
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
const [orgMembership] = await orgDAL.findMembership({ userId: user.id, orgId }, { tx });
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
|
{
|
||||||
|
userId: user.id,
|
||||||
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
if (!orgMembership) {
|
if (!orgMembership) {
|
||||||
await orgDAL.createMembership(
|
await orgDAL.createMembership(
|
||||||
{
|
{
|
||||||
@ -335,6 +350,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
user = await userDAL.transaction(async (tx) => {
|
user = await userDAL.transaction(async (tx) => {
|
||||||
const newUser = await userDAL.create(
|
const newUser = await userDAL.create(
|
||||||
{
|
{
|
||||||
|
username,
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
@ -357,7 +373,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
{
|
{
|
||||||
authTokenType: AuthTokenType.PROVIDER_TOKEN,
|
authTokenType: AuthTokenType.PROVIDER_TOKEN,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
email: user.email,
|
username: user.username,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
organizationName: organization.name,
|
organizationName: organization.name,
|
||||||
|
@ -4,7 +4,8 @@ import { 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"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCreateSamlCfgDTO = {
|
export type TCreateSamlCfgDTO = {
|
||||||
@ -36,7 +37,8 @@ export type TGetSamlCfgDTO =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TSamlLoginDTO = {
|
export type TSamlLoginDTO = {
|
||||||
email: string;
|
username: string;
|
||||||
|
email?: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
authProvider: string;
|
authProvider: string;
|
||||||
|
@ -20,34 +20,38 @@ export const buildScimUserList = ({
|
|||||||
|
|
||||||
export const buildScimUser = ({
|
export const buildScimUser = ({
|
||||||
userId,
|
userId,
|
||||||
|
username,
|
||||||
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
email,
|
|
||||||
active
|
active
|
||||||
}: {
|
}: {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
username: string;
|
||||||
|
email?: string | null;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
email: string;
|
|
||||||
active: boolean;
|
active: boolean;
|
||||||
}): TScimUser => {
|
}): TScimUser => {
|
||||||
return {
|
const scimUser = {
|
||||||
schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
||||||
id: userId,
|
id: userId,
|
||||||
userName: email,
|
userName: username,
|
||||||
displayName: `${firstName} ${lastName}`,
|
displayName: `${firstName} ${lastName}`,
|
||||||
name: {
|
name: {
|
||||||
givenName: firstName,
|
givenName: firstName,
|
||||||
middleName: null,
|
middleName: null,
|
||||||
familyName: lastName
|
familyName: lastName
|
||||||
},
|
},
|
||||||
emails: [
|
emails: email
|
||||||
{
|
? [
|
||||||
primary: true,
|
{
|
||||||
value: email,
|
primary: true,
|
||||||
type: "work"
|
value: email,
|
||||||
}
|
type: "work"
|
||||||
],
|
}
|
||||||
|
]
|
||||||
|
: [],
|
||||||
active,
|
active,
|
||||||
groups: [],
|
groups: [],
|
||||||
meta: {
|
meta: {
|
||||||
@ -55,4 +59,6 @@ export const buildScimUser = ({
|
|||||||
location: null
|
location: null
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return scimUser;
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipStatus, TableName } from "@app/db/schemas";
|
||||||
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";
|
||||||
@ -146,15 +146,16 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
const users = await orgDAL.findMembership(
|
const users = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
orgId,
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId,
|
||||||
...parseFilter(filter)
|
...parseFilter(filter)
|
||||||
},
|
},
|
||||||
findOpts
|
findOpts
|
||||||
);
|
);
|
||||||
|
|
||||||
const scimUsers = users.map(({ userId, firstName, lastName, email }) =>
|
const scimUsers = users.map(({ userId, username, firstName, lastName, email }) =>
|
||||||
buildScimUser({
|
buildScimUser({
|
||||||
userId: userId ?? "",
|
userId: userId ?? "",
|
||||||
|
username,
|
||||||
firstName: firstName ?? "",
|
firstName: firstName ?? "",
|
||||||
lastName: lastName ?? "",
|
lastName: lastName ?? "",
|
||||||
email,
|
email,
|
||||||
@ -173,7 +174,7 @@ export const scimServiceFactory = ({
|
|||||||
const [membership] = await orgDAL
|
const [membership] = await orgDAL
|
||||||
.findMembership({
|
.findMembership({
|
||||||
userId,
|
userId,
|
||||||
orgId
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
@ -196,14 +197,15 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
return buildScimUser({
|
return buildScimUser({
|
||||||
userId: membership.userId as string,
|
userId: membership.userId as string,
|
||||||
|
username: membership.username,
|
||||||
|
email: membership.email ?? "",
|
||||||
firstName: membership.firstName as string,
|
firstName: membership.firstName as string,
|
||||||
lastName: membership.lastName as string,
|
lastName: membership.lastName as string,
|
||||||
email: membership.email,
|
|
||||||
active: true
|
active: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createScimUser = async ({ firstName, lastName, email, orgId }: TCreateScimUserDTO) => {
|
const createScimUser = async ({ username, email, firstName, lastName, orgId }: TCreateScimUserDTO) => {
|
||||||
const org = await orgDAL.findById(orgId);
|
const org = await orgDAL.findById(orgId);
|
||||||
|
|
||||||
if (!org)
|
if (!org)
|
||||||
@ -219,12 +221,18 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
let user = await userDAL.findOne({
|
let user = await userDAL.findOne({
|
||||||
email
|
username
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
const [orgMembership] = await orgDAL.findMembership({ userId: user.id, orgId }, { tx });
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
|
{
|
||||||
|
userId: user.id,
|
||||||
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
if (orgMembership)
|
if (orgMembership)
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
detail: "User already exists in the database",
|
detail: "User already exists in the database",
|
||||||
@ -248,6 +256,7 @@ export const scimServiceFactory = ({
|
|||||||
user = await userDAL.transaction(async (tx) => {
|
user = await userDAL.transaction(async (tx) => {
|
||||||
const newUser = await userDAL.create(
|
const newUser = await userDAL.create(
|
||||||
{
|
{
|
||||||
|
username,
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
@ -272,21 +281,25 @@ export const scimServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
await smtpService.sendMail({
|
|
||||||
template: SmtpTemplates.ScimUserProvisioned,
|
if (email) {
|
||||||
subjectLine: "Infisical organization invitation",
|
await smtpService.sendMail({
|
||||||
recipients: [email],
|
template: SmtpTemplates.ScimUserProvisioned,
|
||||||
substitutions: {
|
subjectLine: "Infisical organization invitation",
|
||||||
organizationName: org.name,
|
recipients: [email],
|
||||||
callback_url: `${appCfg.SITE_URL}/api/v1/sso/redirect/saml2/organizations/${org.slug}`
|
substitutions: {
|
||||||
}
|
organizationName: org.name,
|
||||||
});
|
callback_url: `${appCfg.SITE_URL}/api/v1/sso/redirect/saml2/organizations/${org.slug}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return buildScimUser({
|
return buildScimUser({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
username: user.username,
|
||||||
firstName: user.firstName as string,
|
firstName: user.firstName as string,
|
||||||
lastName: user.lastName as string,
|
lastName: user.lastName as string,
|
||||||
email: user.email,
|
email: user.email ?? "",
|
||||||
active: true
|
active: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -295,7 +308,7 @@ export const scimServiceFactory = ({
|
|||||||
const [membership] = await orgDAL
|
const [membership] = await orgDAL
|
||||||
.findMembership({
|
.findMembership({
|
||||||
userId,
|
userId,
|
||||||
orgId
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
@ -342,9 +355,10 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
return buildScimUser({
|
return buildScimUser({
|
||||||
userId: membership.userId as string,
|
userId: membership.userId as string,
|
||||||
|
username: membership.username,
|
||||||
|
email: membership.email,
|
||||||
firstName: membership.firstName as string,
|
firstName: membership.firstName as string,
|
||||||
lastName: membership.lastName as string,
|
lastName: membership.lastName as string,
|
||||||
email: membership.email,
|
|
||||||
active
|
active
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -353,7 +367,7 @@ export const scimServiceFactory = ({
|
|||||||
const [membership] = await orgDAL
|
const [membership] = await orgDAL
|
||||||
.findMembership({
|
.findMembership({
|
||||||
userId,
|
userId,
|
||||||
orgId
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
@ -387,9 +401,10 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
return buildScimUser({
|
return buildScimUser({
|
||||||
userId: membership.userId as string,
|
userId: membership.userId as string,
|
||||||
|
username: membership.username,
|
||||||
|
email: membership.email,
|
||||||
firstName: membership.firstName as string,
|
firstName: membership.firstName as string,
|
||||||
lastName: membership.lastName as string,
|
lastName: membership.lastName as string,
|
||||||
email: membership.email,
|
|
||||||
active
|
active
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -32,7 +32,8 @@ export type TGetScimUserDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateScimUserDTO = {
|
export type TCreateScimUserDTO = {
|
||||||
email: string;
|
username: string;
|
||||||
|
email?: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
|
@ -12,9 +12,11 @@ import { groupBy, pick, unique } from "@app/lib/fn";
|
|||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
import { TSecretQueueFactory } from "@app/services/secret/secret-queue";
|
import { TSecretQueueFactory } from "@app/services/secret/secret-queue";
|
||||||
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
||||||
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
||||||
|
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
||||||
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
||||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
||||||
@ -44,10 +46,12 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretApprovalRequestSecretDAL: TSecretApprovalRequestSecretDALFactory;
|
secretApprovalRequestSecretDAL: TSecretApprovalRequestSecretDALFactory;
|
||||||
secretApprovalRequestReviewerDAL: TSecretApprovalRequestReviewerDALFactory;
|
secretApprovalRequestReviewerDAL: TSecretApprovalRequestReviewerDALFactory;
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findById" | "findSecretPathByFolderIds">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findById" | "findSecretPathByFolderIds">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "findManyTagsById">;
|
secretDAL: TSecretDALFactory;
|
||||||
|
secretTagDAL: Pick<TSecretTagDALFactory, "findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret">;
|
||||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
||||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany">;
|
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
||||||
|
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus">;
|
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus">;
|
||||||
secretService: Pick<
|
secretService: Pick<
|
||||||
TSecretServiceFactory,
|
TSecretServiceFactory,
|
||||||
@ -64,8 +68,10 @@ export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretAppro
|
|||||||
|
|
||||||
export const secretApprovalRequestServiceFactory = ({
|
export const secretApprovalRequestServiceFactory = ({
|
||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
|
secretDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
secretApprovalRequestReviewerDAL,
|
secretApprovalRequestReviewerDAL,
|
||||||
secretApprovalRequestSecretDAL,
|
secretApprovalRequestSecretDAL,
|
||||||
secretBlindIndexDAL,
|
secretBlindIndexDAL,
|
||||||
@ -123,14 +129,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
secretApprovalRequest.projectId,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
membership.role !== ProjectMembershipRole.Admin &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerId !== membership.id &&
|
secretApprovalRequest.committerId !== membership.id &&
|
||||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
!policy.approvers.find((approverId) => approverId === membership.id)
|
||||||
) {
|
) {
|
||||||
@ -150,14 +156,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
secretApprovalRequest.projectId,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
membership.role !== ProjectMembershipRole.Admin &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerId !== membership.id &&
|
secretApprovalRequest.committerId !== membership.id &&
|
||||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
!policy.approvers.find((approverId) => approverId === membership.id)
|
||||||
) {
|
) {
|
||||||
@ -192,14 +198,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
secretApprovalRequest.projectId,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
membership.role !== ProjectMembershipRole.Admin &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerId !== membership.id &&
|
secretApprovalRequest.committerId !== membership.id &&
|
||||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
!policy.approvers.find((approverId) => approverId === membership.id)
|
||||||
) {
|
) {
|
||||||
@ -230,9 +236,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const { policy, folderId, projectId } = secretApprovalRequest;
|
const { policy, folderId, projectId } = secretApprovalRequest;
|
||||||
const { membership } = await permissionService.getProjectPermission(ActorType.USER, actorId, projectId, actorOrgId);
|
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.USER,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
membership.role !== ProjectMembershipRole.Admin &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerId !== membership.id &&
|
secretApprovalRequest.committerId !== membership.id &&
|
||||||
!policy.approvers.find((approverId) => approverId === membership.id)
|
!policy.approvers.find((approverId) => approverId === membership.id)
|
||||||
) {
|
) {
|
||||||
@ -335,7 +346,11 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
tags: el?.tags.map(({ id }) => id),
|
tags: el?.tags.map(({ id }) => id),
|
||||||
version: 1,
|
version: 1,
|
||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
}))
|
})),
|
||||||
|
secretDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
const updatedSecrets = secretUpdationCommits.length
|
const updatedSecrets = secretUpdationCommits.length
|
||||||
@ -367,7 +382,11 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
"secretBlindIndex"
|
"secretBlindIndex"
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}))
|
})),
|
||||||
|
secretDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
const deletedSecret = secretDeletionCommits.length
|
const deletedSecret = secretDeletionCommits.length
|
||||||
@ -455,7 +474,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
inputSecrets: createdSecrets,
|
inputSecrets: createdSecrets,
|
||||||
folderId,
|
folderId,
|
||||||
isNew: true,
|
isNew: true,
|
||||||
blindIndexCfg
|
blindIndexCfg,
|
||||||
|
secretDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
commits.push(
|
commits.push(
|
||||||
@ -482,7 +502,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
inputSecrets: updatedSecrets,
|
inputSecrets: updatedSecrets,
|
||||||
folderId,
|
folderId,
|
||||||
isNew: false,
|
isNew: false,
|
||||||
blindIndexCfg
|
blindIndexCfg,
|
||||||
|
secretDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
// now find any secret that needs to update its name
|
// now find any secret that needs to update its name
|
||||||
@ -492,7 +513,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
inputSecrets: nameUpdatedSecrets,
|
inputSecrets: nameUpdatedSecrets,
|
||||||
folderId,
|
folderId,
|
||||||
isNew: true,
|
isNew: true,
|
||||||
blindIndexCfg
|
blindIndexCfg,
|
||||||
|
secretDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const secsGroupedByBlindIndex = groupBy(secretsToBeUpdated, (el) => el.secretBlindIndex as string);
|
const secsGroupedByBlindIndex = groupBy(secretsToBeUpdated, (el) => el.secretBlindIndex as string);
|
||||||
@ -531,7 +553,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
inputSecrets: deletedSecrets,
|
inputSecrets: deletedSecrets,
|
||||||
folderId,
|
folderId,
|
||||||
isNew: false,
|
isNew: false,
|
||||||
blindIndexCfg
|
blindIndexCfg,
|
||||||
|
secretDAL
|
||||||
});
|
});
|
||||||
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => {
|
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => {
|
||||||
if (!i.secretBlindIndex) throw new BadRequestError({ message: "Missing secret blind index" });
|
if (!i.secretBlindIndex) throw new BadRequestError({ message: "Missing secret blind index" });
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
import {
|
||||||
|
CreateAccessKeyCommand,
|
||||||
|
DeleteAccessKeyCommand,
|
||||||
|
GetAccessKeyLastUsedCommand,
|
||||||
|
IAMClient
|
||||||
|
} from "@aws-sdk/client-iam";
|
||||||
|
|
||||||
import { SecretKeyEncoding, SecretType } from "@app/db/schemas";
|
import { SecretKeyEncoding, SecretType } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import {
|
import {
|
||||||
@ -18,7 +25,12 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
|||||||
|
|
||||||
import { TSecretRotationDALFactory } from "../secret-rotation-dal";
|
import { TSecretRotationDALFactory } from "../secret-rotation-dal";
|
||||||
import { rotationTemplates } from "../templates";
|
import { rotationTemplates } from "../templates";
|
||||||
import { TDbProviderClients, TProviderFunctionTypes, TSecretRotationProviderTemplate } from "../templates/types";
|
import {
|
||||||
|
TAwsProviderSystems,
|
||||||
|
TDbProviderClients,
|
||||||
|
TProviderFunctionTypes,
|
||||||
|
TSecretRotationProviderTemplate
|
||||||
|
} from "../templates/types";
|
||||||
import {
|
import {
|
||||||
getDbSetQuery,
|
getDbSetQuery,
|
||||||
secretRotationDbFn,
|
secretRotationDbFn,
|
||||||
@ -127,7 +139,10 @@ export const secretRotationQueueFactory = ({
|
|||||||
internal: {}
|
internal: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// when its a database we keep cycling the variables accordingly
|
/* Rotation Function For Database
|
||||||
|
* A database like sql cannot have multiple password for a user
|
||||||
|
* thus we ask users to create two users with required permission and then we keep cycling between these two db users
|
||||||
|
*/
|
||||||
if (provider.template.type === TProviderFunctionTypes.DB) {
|
if (provider.template.type === TProviderFunctionTypes.DB) {
|
||||||
const lastCred = variables.creds.at(-1);
|
const lastCred = variables.creds.at(-1);
|
||||||
if (lastCred && variables.creds.length === 1) {
|
if (lastCred && variables.creds.length === 1) {
|
||||||
@ -170,6 +185,65 @@ export const secretRotationQueueFactory = ({
|
|||||||
if (variables.creds.length === 2) variables.creds.pop();
|
if (variables.creds.length === 2) variables.creds.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rotation Function For AWS Services
|
||||||
|
* Due to complexity in AWS Authorization hashing signature process we keep it as seperate entity instead of http template mode
|
||||||
|
* We first delete old key before creating a new one because aws iam has a quota limit of 2 keys
|
||||||
|
* */
|
||||||
|
if (provider.template.type === TProviderFunctionTypes.AWS) {
|
||||||
|
if (provider.template.client === TAwsProviderSystems.IAM) {
|
||||||
|
const client = new IAMClient({
|
||||||
|
region: newCredential.inputs.manager_user_aws_region as string,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: newCredential.inputs.manager_user_access_key as string,
|
||||||
|
secretAccessKey: newCredential.inputs.manager_user_secret_key as string
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const iamUserName = newCredential.inputs.iam_username as string;
|
||||||
|
|
||||||
|
if (variables.creds.length === 2) {
|
||||||
|
const deleteCycleCredential = variables.creds.pop();
|
||||||
|
if (deleteCycleCredential) {
|
||||||
|
const deletedIamAccessKey = await client.send(
|
||||||
|
new DeleteAccessKeyCommand({
|
||||||
|
UserName: iamUserName,
|
||||||
|
AccessKeyId: deleteCycleCredential.outputs.iam_user_access_key as string
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!deletedIamAccessKey?.$metadata?.httpStatusCode ||
|
||||||
|
deletedIamAccessKey?.$metadata?.httpStatusCode > 300
|
||||||
|
) {
|
||||||
|
throw new DisableRotationErrors({
|
||||||
|
message: "Failed to delete aws iam access key. Check managed iam user policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newIamAccessKey = await client.send(new CreateAccessKeyCommand({ UserName: iamUserName }));
|
||||||
|
if (!newIamAccessKey.AccessKey)
|
||||||
|
throw new DisableRotationErrors({ message: "Failed to create access key. Check managed iam user policy" });
|
||||||
|
|
||||||
|
// test
|
||||||
|
const testAccessKey = await client.send(
|
||||||
|
new GetAccessKeyLastUsedCommand({ AccessKeyId: newIamAccessKey.AccessKey.AccessKeyId })
|
||||||
|
);
|
||||||
|
if (testAccessKey?.UserName !== iamUserName)
|
||||||
|
throw new DisableRotationErrors({ message: "Failed to create access key. Check managed iam user policy" });
|
||||||
|
|
||||||
|
newCredential.outputs.iam_user_access_key = newIamAccessKey.AccessKey.AccessKeyId;
|
||||||
|
newCredential.outputs.iam_user_secret_key = newIamAccessKey.AccessKey.SecretAccessKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rotation function of HTTP infisical template
|
||||||
|
* This is a generic http based template system for rotation
|
||||||
|
* we use this for sendgrid and for custom secret rotation
|
||||||
|
* This will ensure user provided rotation is easier to make
|
||||||
|
* */
|
||||||
if (provider.template.type === TProviderFunctionTypes.HTTP) {
|
if (provider.template.type === TProviderFunctionTypes.HTTP) {
|
||||||
if (provider.template.functions.set?.pre) {
|
if (provider.template.functions.set?.pre) {
|
||||||
secretRotationPreSetFn(provider.template.functions.set.pre, newCredential);
|
secretRotationPreSetFn(provider.template.functions.set.pre, newCredential);
|
||||||
@ -185,6 +259,9 @@ export const secretRotationQueueFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// insert the new variables to start
|
||||||
|
// encrypt the data - save it
|
||||||
variables.creds.unshift({
|
variables.creds.unshift({
|
||||||
outputs: newCredential.outputs,
|
outputs: newCredential.outputs,
|
||||||
internal: newCredential.internal
|
internal: newCredential.internal
|
||||||
@ -200,6 +277,7 @@ export const secretRotationQueueFactory = ({
|
|||||||
key
|
key
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
// map the final values to output keys in the board
|
||||||
await secretRotationDAL.transaction(async (tx) => {
|
await secretRotationDAL.transaction(async (tx) => {
|
||||||
await secretRotationDAL.updateById(
|
await secretRotationDAL.updateById(
|
||||||
rotationId,
|
rotationId,
|
||||||
|
21
backend/src/ee/services/secret-rotation/templates/aws-iam.ts
Normal file
21
backend/src/ee/services/secret-rotation/templates/aws-iam.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { TAwsProviderSystems, TProviderFunctionTypes } from "./types";
|
||||||
|
|
||||||
|
export const AWS_IAM_TEMPLATE = {
|
||||||
|
type: TProviderFunctionTypes.AWS as const,
|
||||||
|
client: TAwsProviderSystems.IAM,
|
||||||
|
inputs: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
manager_user_access_key: { type: "string" as const },
|
||||||
|
manager_user_secret_key: { type: "string" as const },
|
||||||
|
manager_user_aws_region: { type: "string" as const },
|
||||||
|
iam_username: { type: "string" as const }
|
||||||
|
},
|
||||||
|
required: ["manager_user_access_key", "manager_user_secret_key", "manager_user_aws_region", "iam_username"],
|
||||||
|
additionalProperties: false
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
iam_user_access_key: { type: "string" },
|
||||||
|
iam_user_secret_key: { type: "string" }
|
||||||
|
}
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
import { AWS_IAM_TEMPLATE } from "./aws-iam";
|
||||||
import { MYSQL_TEMPLATE } from "./mysql";
|
import { MYSQL_TEMPLATE } from "./mysql";
|
||||||
import { POSTGRES_TEMPLATE } from "./postgres";
|
import { POSTGRES_TEMPLATE } from "./postgres";
|
||||||
import { SENDGRID_TEMPLATE } from "./sendgrid";
|
import { SENDGRID_TEMPLATE } from "./sendgrid";
|
||||||
@ -24,5 +25,12 @@ export const rotationTemplates: TSecretRotationProviderTemplate[] = [
|
|||||||
image: "mysql.png",
|
image: "mysql.png",
|
||||||
description: "Rotate MySQL@7/MariaDB user credentials",
|
description: "Rotate MySQL@7/MariaDB user credentials",
|
||||||
template: MYSQL_TEMPLATE
|
template: MYSQL_TEMPLATE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-iam",
|
||||||
|
title: "AWS IAM",
|
||||||
|
image: "aws-iam.svg",
|
||||||
|
description: "Rotate AWS IAM User credentials",
|
||||||
|
template: AWS_IAM_TEMPLATE
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export enum TProviderFunctionTypes {
|
export enum TProviderFunctionTypes {
|
||||||
HTTP = "http",
|
HTTP = "http",
|
||||||
DB = "database"
|
DB = "database",
|
||||||
|
AWS = "aws"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TDbProviderClients {
|
export enum TDbProviderClients {
|
||||||
@ -10,6 +11,10 @@ export enum TDbProviderClients {
|
|||||||
MySql = "mysql"
|
MySql = "mysql"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TAwsProviderSystems {
|
||||||
|
IAM = "iam"
|
||||||
|
}
|
||||||
|
|
||||||
export enum TAssignOp {
|
export enum TAssignOp {
|
||||||
Direct = "direct",
|
Direct = "direct",
|
||||||
JmesPath = "jmesopath"
|
JmesPath = "jmesopath"
|
||||||
@ -42,7 +47,7 @@ export type TSecretRotationProviderTemplate = {
|
|||||||
title: string;
|
title: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
template: THttpProviderTemplate | TDbProviderTemplate;
|
template: THttpProviderTemplate | TDbProviderTemplate | TAwsProviderTemplate;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type THttpProviderTemplate = {
|
export type THttpProviderTemplate = {
|
||||||
@ -70,3 +75,14 @@ export type TDbProviderTemplate = {
|
|||||||
};
|
};
|
||||||
outputs: Record<string, unknown>;
|
outputs: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TAwsProviderTemplate = {
|
||||||
|
type: TProviderFunctionTypes.AWS;
|
||||||
|
client: TAwsProviderSystems;
|
||||||
|
inputs: {
|
||||||
|
type: "object";
|
||||||
|
properties: Record<string, { type: string; [x: string]: unknown; desc?: string }>;
|
||||||
|
required?: string[];
|
||||||
|
};
|
||||||
|
outputs: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
@ -64,7 +64,7 @@ export const secretScanningQueueFactory = ({
|
|||||||
orgId: organizationId,
|
orgId: organizationId,
|
||||||
role: OrgMembershipRole.Admin
|
role: OrgMembershipRole.Admin
|
||||||
});
|
});
|
||||||
return adminsOfWork.map((userObject) => userObject.email);
|
return adminsOfWork.filter((userObject) => userObject.email).map((userObject) => userObject.email as string);
|
||||||
};
|
};
|
||||||
|
|
||||||
queueService.start(QueueName.SecretPushEventScan, async (job) => {
|
queueService.start(QueueName.SecretPushEventScan, async (job) => {
|
||||||
@ -149,7 +149,7 @@ export const secretScanningQueueFactory = ({
|
|||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.SecretLeakIncident,
|
template: SmtpTemplates.SecretLeakIncident,
|
||||||
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
|
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
|
||||||
recipients: adminEmails,
|
recipients: adminEmails.filter((email) => email).map((email) => email),
|
||||||
substitutions: {
|
substitutions: {
|
||||||
numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
|
numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
|
||||||
pusher_email: pusher.email,
|
pusher_email: pusher.email,
|
||||||
@ -221,7 +221,7 @@ export const secretScanningQueueFactory = ({
|
|||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.SecretLeakIncident,
|
template: SmtpTemplates.SecretLeakIncident,
|
||||||
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
|
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
|
||||||
recipients: adminEmails,
|
recipients: adminEmails.filter((email) => email).map((email) => email),
|
||||||
substitutions: {
|
substitutions: {
|
||||||
numberOfSecrets: findings.length
|
numberOfSecrets: findings.length
|
||||||
}
|
}
|
||||||
|
286
backend/src/lib/api-docs/constants.ts
Normal file
286
backend/src/lib/api-docs/constants.ts
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
export const IDENTITIES = {
|
||||||
|
CREATE: {
|
||||||
|
name: "The name of the identity to create.",
|
||||||
|
organizationId: "The organization ID to which the identity belongs.",
|
||||||
|
role: "The role of the identity. Possible values are 'no-access', 'member', and 'admin'."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
identityId: "The ID of the identity to update.",
|
||||||
|
name: "The new name of the identity.",
|
||||||
|
role: "The new role of the identity."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
identityId: "The ID of the identity to delete."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const UNIVERSAL_AUTH = {
|
||||||
|
LOGIN: {
|
||||||
|
clientId: "Your Machine Identity Client ID.",
|
||||||
|
clientSecret: "Your Machine Identity Client Secret."
|
||||||
|
},
|
||||||
|
ATTACH: {
|
||||||
|
identityId: "The ID of the identity to attach the configuration onto.",
|
||||||
|
clientSecretTrustedIps:
|
||||||
|
"A list of IPs or CIDR ranges that the Client Secret can be used from together with the Client ID to get back an access token. You can use 0.0.0.0/0, to allow usage from any network address.",
|
||||||
|
accessTokenTrustedIps:
|
||||||
|
"A list of IPs or CIDR ranges that access tokens can be used from. You can use 0.0.0.0/0, to allow usage from any network address.",
|
||||||
|
accessTokenTTL: "The lifetime for an access token in seconds. This value will be referenced at renewal time.",
|
||||||
|
accessTokenMaxTTL:
|
||||||
|
"The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.",
|
||||||
|
accessTokenNumUsesLimit:
|
||||||
|
"The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses."
|
||||||
|
},
|
||||||
|
RETRIEVE: {
|
||||||
|
identityId: "The ID of the identity to retrieve."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
identityId: "The ID of the identity to update.",
|
||||||
|
clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.",
|
||||||
|
accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.",
|
||||||
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
||||||
|
},
|
||||||
|
CREATE_CLIENT_SECRET: {
|
||||||
|
identityId: "The ID of the identity to create a client secret for.",
|
||||||
|
description: "The description of the client secret.",
|
||||||
|
numUsesLimit:
|
||||||
|
"The maximum number of times that the client secret can be used; a value of 0 implies infinite number of uses.",
|
||||||
|
ttl: "The lifetime for the client secret in seconds."
|
||||||
|
},
|
||||||
|
LIST_CLIENT_SECRETS: {
|
||||||
|
identityId: "The ID of the identity to list client secrets for."
|
||||||
|
},
|
||||||
|
REVOKE_CLIENT_SECRET: {
|
||||||
|
identityId: "The ID of the identity to revoke the client secret from.",
|
||||||
|
clientSecretId: "The ID of the client secret to revoke."
|
||||||
|
},
|
||||||
|
RENEW_ACCESS_TOKEN: {
|
||||||
|
accessToken: "The access token to renew."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ORGANIZATIONS = {
|
||||||
|
LIST_USER_MEMBERSHIPS: {
|
||||||
|
organizationId: "The ID of the organization to get memberships from."
|
||||||
|
},
|
||||||
|
UPDATE_USER_MEMBERSHIP: {
|
||||||
|
organizationId: "The ID of the organization to update the membership for.",
|
||||||
|
membershipId: "The ID of the membership to update.",
|
||||||
|
role: "The new role of the membership."
|
||||||
|
},
|
||||||
|
DELETE_USER_MEMBERSHIP: {
|
||||||
|
organizationId: "The ID of the organization to delete the membership from.",
|
||||||
|
membershipId: "The ID of the membership to delete."
|
||||||
|
},
|
||||||
|
LIST_IDENTITY_MEMBERSHIPS: {
|
||||||
|
orgId: "The ID of the organization to get identity memberships from."
|
||||||
|
},
|
||||||
|
GET_PROJECTS: {
|
||||||
|
organizationId: "The ID of the organization to get projects from."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const PROJECTS = {
|
||||||
|
CREATE: {
|
||||||
|
organizationId: "The ID of the organization to create the project in.",
|
||||||
|
projectName: "The name of the project to create.",
|
||||||
|
slug: "An optional slug for the project."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
workspaceId: "The ID of the project to delete."
|
||||||
|
},
|
||||||
|
GET: {
|
||||||
|
workspaceId: "The ID of the project."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
workspaceId: "The ID of the project to update.",
|
||||||
|
name: "The new name of the project.",
|
||||||
|
autoCapitalization: "Disable or enable auto-capitalization for the project."
|
||||||
|
},
|
||||||
|
INVITE_MEMBER: {
|
||||||
|
projectId: "The ID of the project to invite the member to.",
|
||||||
|
emails: "A list of organization member emails to invite to the project.",
|
||||||
|
usernames: "A list of usernames to invite to the project."
|
||||||
|
},
|
||||||
|
REMOVE_MEMBER: {
|
||||||
|
projectId: "The ID of the project to remove the member from.",
|
||||||
|
emails: "A list of organization member emails to remove from the project.",
|
||||||
|
usernames: "A list of usernames to remove from the project."
|
||||||
|
},
|
||||||
|
GET_USER_MEMBERSHIPS: {
|
||||||
|
workspaceId: "The ID of the project to get memberships from."
|
||||||
|
},
|
||||||
|
UPDATE_USER_MEMBERSHIP: {
|
||||||
|
workspaceId: "The ID of the project to update the membership for.",
|
||||||
|
membershipId: "The ID of the membership to update.",
|
||||||
|
roles: "A list of roles to update the membership to."
|
||||||
|
},
|
||||||
|
LIST_IDENTITY_MEMBERSHIPS: {
|
||||||
|
projectId: "The ID of the project to get identity memberships from."
|
||||||
|
},
|
||||||
|
UPDATE_IDENTITY_MEMBERSHIP: {
|
||||||
|
projectId: "The ID of the project to update the identity membership for.",
|
||||||
|
identityId: "The ID of the identity to update the membership for.",
|
||||||
|
roles: "A list of roles to update the membership to."
|
||||||
|
},
|
||||||
|
DELETE_IDENTITY_MEMBERSHIP: {
|
||||||
|
projectId: "The ID of the project to delete the identity membership from.",
|
||||||
|
identityId: "The ID of the identity to delete the membership from."
|
||||||
|
},
|
||||||
|
GET_KEY: {
|
||||||
|
workspaceId: "The ID of the project to get the key from."
|
||||||
|
},
|
||||||
|
GET_SNAPSHOTS: {
|
||||||
|
workspaceId: "The ID of the project to get snapshots from.",
|
||||||
|
environment: "The environment to get snapshots from.",
|
||||||
|
path: "The secret path to get snapshots from.",
|
||||||
|
offset: "The offset to start from. If you enter 10, it will start from the 10th snapshot.",
|
||||||
|
limit: "The number of snapshots to return."
|
||||||
|
},
|
||||||
|
ROLLBACK_TO_SNAPSHOT: {
|
||||||
|
secretSnapshotId: "The ID of the snapshot to rollback to."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const ENVIRONMENTS = {
|
||||||
|
CREATE: {
|
||||||
|
workspaceId: "The ID of the project to create the environment in.",
|
||||||
|
name: "The name of the environment to create.",
|
||||||
|
slug: "The slug of the environment to create."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
workspaceId: "The ID of the project to update the environment in.",
|
||||||
|
id: "The ID of the environment to update.",
|
||||||
|
name: "The new name of the environment.",
|
||||||
|
slug: "The new slug of the environment.",
|
||||||
|
position: "The new position of the environment. The lowest number will be displayed as the first environment."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
workspaceId: "The ID of the project to delete the environment from.",
|
||||||
|
id: "The ID of the environment to delete."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const FOLDERS = {
|
||||||
|
LIST: {
|
||||||
|
workspaceId: "The ID of the project to list folders from.",
|
||||||
|
environment: "The slug of the environment to list folders from.",
|
||||||
|
path: "The path to list folders from.",
|
||||||
|
directory: "The directory to list folders from. (Deprecated in favor of path)"
|
||||||
|
},
|
||||||
|
CREATE: {
|
||||||
|
workspaceId: "The ID of the project to create the folder in.",
|
||||||
|
environment: "The slug of the environment to create the folder in.",
|
||||||
|
name: "The name of the folder to create.",
|
||||||
|
path: "The path of the folder to create.",
|
||||||
|
directory: "The directory of the folder to create. (Deprecated in favor of path)"
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
folderId: "The ID of the folder to update.",
|
||||||
|
environment: "The slug of the environment where the folder is located.",
|
||||||
|
name: "The new name of the folder.",
|
||||||
|
path: "The path of the folder to update.",
|
||||||
|
directory: "The new directory of the folder to update. (Deprecated in favor of path)",
|
||||||
|
workspaceId: "The ID of the project where the folder is located."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
folderIdOrName: "The ID or name of the folder to delete.",
|
||||||
|
workspaceId: "The ID of the project to delete the folder from.",
|
||||||
|
environment: "The slug of the environment where the folder is located.",
|
||||||
|
directory: "The directory of the folder to delete. (Deprecated in favor of path)",
|
||||||
|
path: "The path of the folder to delete."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const RAW_SECRETS = {
|
||||||
|
LIST: {
|
||||||
|
workspaceId: "The ID of the project to list secrets from.",
|
||||||
|
environment: "The slug of the environment to list secrets from.",
|
||||||
|
secretPath: "The secret path to list secrets from.",
|
||||||
|
includeImports: "Weather to include imported secrets or not."
|
||||||
|
},
|
||||||
|
CREATE: {
|
||||||
|
secretName: "The name of the secret to create.",
|
||||||
|
environment: "The slug of the environment to create the secret in.",
|
||||||
|
secretComment: "Attach a comment to the secret.",
|
||||||
|
secretPath: "The path to create the secret in.",
|
||||||
|
secretValue: "The value of the secret to create.",
|
||||||
|
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
|
||||||
|
type: "The type of the secret to create.",
|
||||||
|
workspaceId: "The ID of the project to create the secret in."
|
||||||
|
},
|
||||||
|
GET: {
|
||||||
|
secretName: "The name of the secret to get.",
|
||||||
|
workspaceId: "The ID of the project to get the secret from.",
|
||||||
|
environment: "The slug of the environment to get the secret from.",
|
||||||
|
secretPath: "The path of the secret to get.",
|
||||||
|
version: "The version of the secret to get.",
|
||||||
|
type: "The type of the secret to get.",
|
||||||
|
includeImports: "Weather to include imported secrets or not."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
secretName: "The name of the secret to update.",
|
||||||
|
environment: "The slug of the environment where the secret is located.",
|
||||||
|
secretPath: "The path of the secret to update",
|
||||||
|
secretValue: "The new value of the secret.",
|
||||||
|
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
|
||||||
|
type: "The type of the secret to update.",
|
||||||
|
workspaceId: "The ID of the project to update the secret in."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
secretName: "The name of the secret to delete.",
|
||||||
|
environment: "The slug of the environment where the secret is located.",
|
||||||
|
secretPath: "The path of the secret.",
|
||||||
|
type: "The type of the secret to delete.",
|
||||||
|
workspaceId: "The ID of the project where the secret is located."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const SECRET_IMPORTS = {
|
||||||
|
LIST: {
|
||||||
|
workspaceId: "The ID of the project to list secret imports from.",
|
||||||
|
environment: "The slug of the environment to list secret imports from.",
|
||||||
|
path: "The path to list secret imports from."
|
||||||
|
},
|
||||||
|
CREATE: {
|
||||||
|
environment: "The slug of the environment to import into.",
|
||||||
|
path: "The path to import into.",
|
||||||
|
workspaceId: "The ID of the project you are working in.",
|
||||||
|
import: {
|
||||||
|
environment: "The slug of the environment to import from.",
|
||||||
|
path: "The path to import from."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
secretImportId: "The ID of the secret import to update.",
|
||||||
|
environment: "The slug of the environment where the secret import is located.",
|
||||||
|
import: {
|
||||||
|
environment: "The new environment slug to import from.",
|
||||||
|
path: "The new path to import from.",
|
||||||
|
position: "The new position of the secret import. The lowest number will be displayed as the first import."
|
||||||
|
},
|
||||||
|
path: "The path of the secret import to update.",
|
||||||
|
workspaceId: "The ID of the project where the secret import is located."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
workspaceId: "The ID of the project to delete the secret import from.",
|
||||||
|
secretImportId: "The ID of the secret import to delete.",
|
||||||
|
environment: "The slug of the environment where the secret import is located.",
|
||||||
|
path: "The path of the secret import to delete."
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const AUDIT_LOGS = {
|
||||||
|
EXPORT: {
|
||||||
|
workspaceId: "The ID of the project to export audit logs from.",
|
||||||
|
eventType: "The type of the event to export.",
|
||||||
|
userAgentType: "Choose which consuming application to export audit logs for.",
|
||||||
|
startDate: "The date to start the export from.",
|
||||||
|
endDate: "The date to end the export at.",
|
||||||
|
offset: "The offset to start from. If you enter 10, it will start from the 10th audit log.",
|
||||||
|
limit: "The number of audit logs to return.",
|
||||||
|
actor: "The actor to filter the audit logs by."
|
||||||
|
}
|
||||||
|
} as const;
|
1
backend/src/lib/api-docs/index.ts
Normal file
1
backend/src/lib/api-docs/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./constants";
|
@ -15,8 +15,16 @@ const envSchema = z
|
|||||||
PORT: z.coerce.number().default(4000),
|
PORT: z.coerce.number().default(4000),
|
||||||
REDIS_URL: zpStr(z.string()),
|
REDIS_URL: zpStr(z.string()),
|
||||||
HOST: zpStr(z.string().default("localhost")),
|
HOST: zpStr(z.string().default("localhost")),
|
||||||
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")),
|
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default(
|
||||||
|
`postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`
|
||||||
|
),
|
||||||
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
|
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
|
||||||
|
DB_HOST: zpStr(z.string().describe("Postgres database host").optional()),
|
||||||
|
DB_PORT: zpStr(z.string().describe("Postgres database port").optional()).default("5432"),
|
||||||
|
DB_USER: zpStr(z.string().describe("Postgres database username").optional()),
|
||||||
|
DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()),
|
||||||
|
DB_NAME: zpStr(z.string().describe("Postgres database name").optional()),
|
||||||
|
|
||||||
NODE_ENV: z.enum(["development", "test", "production"]).default("production"),
|
NODE_ENV: z.enum(["development", "test", "production"]).default("production"),
|
||||||
SALT_ROUNDS: z.coerce.number().default(10),
|
SALT_ROUNDS: z.coerce.number().default(10),
|
||||||
INITIAL_ORGANIZATION_NAME: zpStr(z.string().optional()),
|
INITIAL_ORGANIZATION_NAME: zpStr(z.string().optional()),
|
||||||
|
@ -5,7 +5,7 @@ import { ActorType } from "@app/services/auth/auth-type";
|
|||||||
// this is a unique id for sending posthog event
|
// this is a unique id for sending posthog event
|
||||||
export const getTelemetryDistinctId = (req: FastifyRequest) => {
|
export const getTelemetryDistinctId = (req: FastifyRequest) => {
|
||||||
if (req.auth.actor === ActorType.USER) {
|
if (req.auth.actor === ActorType.USER) {
|
||||||
return req.auth.user.email;
|
return req.auth.user.username;
|
||||||
}
|
}
|
||||||
if (req.auth.actor === ActorType.IDENTITY) {
|
if (req.auth.actor === ActorType.IDENTITY) {
|
||||||
return `identity-${req.auth.identityId}`;
|
return `identity-${req.auth.identityId}`;
|
||||||
|
@ -44,6 +44,7 @@ export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
|
|||||||
type: ActorType.USER,
|
type: ActorType.USER,
|
||||||
metadata: {
|
metadata: {
|
||||||
email: req.auth.user.email,
|
email: req.auth.user.email,
|
||||||
|
username: req.auth.user.username,
|
||||||
userId: req.permission.id
|
userId: req.permission.id
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,8 @@ import { registerV1EERoutes } from "@app/ee/routes/v1";
|
|||||||
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
||||||
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
||||||
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
|
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
||||||
|
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||||
import { licenseDALFactory } from "@app/ee/services/license/license-dal";
|
import { licenseDALFactory } from "@app/ee/services/license/license-dal";
|
||||||
import { licenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { licenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { permissionDALFactory } from "@app/ee/services/permission/permission-dal";
|
import { permissionDALFactory } from "@app/ee/services/permission/permission-dal";
|
||||||
@ -51,6 +53,7 @@ import { identityServiceFactory } from "@app/services/identity/identity-service"
|
|||||||
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
|
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
|
||||||
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||||
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
|
import { identityProjectMembershipRoleDALFactory } from "@app/services/identity-project/identity-project-membership-role-dal";
|
||||||
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
|
||||||
import { identityUaClientSecretDALFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal";
|
import { identityUaClientSecretDALFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal";
|
||||||
import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal";
|
import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal";
|
||||||
@ -76,6 +79,7 @@ import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal"
|
|||||||
import { projectKeyServiceFactory } from "@app/services/project-key/project-key-service";
|
import { projectKeyServiceFactory } from "@app/services/project-key/project-key-service";
|
||||||
import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
import { projectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
|
import { projectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
|
||||||
|
import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
|
||||||
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
|
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
|
||||||
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
|
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
|
||||||
import { secretDALFactory } from "@app/services/secret/secret-dal";
|
import { secretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
@ -102,6 +106,7 @@ import { telemetryQueueServiceFactory } from "@app/services/telemetry/telemetry-
|
|||||||
import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
||||||
import { userDALFactory } from "@app/services/user/user-dal";
|
import { userDALFactory } from "@app/services/user/user-dal";
|
||||||
import { userServiceFactory } from "@app/services/user/user-service";
|
import { userServiceFactory } from "@app/services/user/user-service";
|
||||||
|
import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||||
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
||||||
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
|
|
||||||
@ -126,6 +131,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
// db layers
|
// db layers
|
||||||
const userDAL = userDALFactory(db);
|
const userDAL = userDALFactory(db);
|
||||||
|
const userAliasDAL = userAliasDALFactory(db);
|
||||||
const authDAL = authDALFactory(db);
|
const authDAL = authDALFactory(db);
|
||||||
const authTokenDAL = tokenDALFactory(db);
|
const authTokenDAL = tokenDALFactory(db);
|
||||||
const orgDAL = orgDALFactory(db);
|
const orgDAL = orgDALFactory(db);
|
||||||
@ -137,6 +143,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const projectDAL = projectDALFactory(db);
|
const projectDAL = projectDALFactory(db);
|
||||||
const projectMembershipDAL = projectMembershipDALFactory(db);
|
const projectMembershipDAL = projectMembershipDALFactory(db);
|
||||||
|
const projectUserMembershipRoleDAL = projectUserMembershipRoleDALFactory(db);
|
||||||
const projectRoleDAL = projectRoleDALFactory(db);
|
const projectRoleDAL = projectRoleDALFactory(db);
|
||||||
const projectEnvDAL = projectEnvDALFactory(db);
|
const projectEnvDAL = projectEnvDALFactory(db);
|
||||||
const projectKeyDAL = projectKeyDALFactory(db);
|
const projectKeyDAL = projectKeyDALFactory(db);
|
||||||
@ -160,18 +167,20 @@ export const registerRoutes = async (
|
|||||||
const identityAccessTokenDAL = identityAccessTokenDALFactory(db);
|
const identityAccessTokenDAL = identityAccessTokenDALFactory(db);
|
||||||
const identityOrgMembershipDAL = identityOrgDALFactory(db);
|
const identityOrgMembershipDAL = identityOrgDALFactory(db);
|
||||||
const identityProjectDAL = identityProjectDALFactory(db);
|
const identityProjectDAL = identityProjectDALFactory(db);
|
||||||
|
const identityProjectMembershipRoleDAL = identityProjectMembershipRoleDALFactory(db);
|
||||||
|
|
||||||
const identityUaDAL = identityUaDALFactory(db);
|
const identityUaDAL = identityUaDALFactory(db);
|
||||||
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
|
||||||
|
|
||||||
const auditLogDAL = auditLogDALFactory(db);
|
const auditLogDAL = auditLogDALFactory(db);
|
||||||
const trustedIpDAL = trustedIpDALFactory(db);
|
const trustedIpDAL = trustedIpDALFactory(db);
|
||||||
const scimDAL = scimDALFactory(db);
|
|
||||||
const telemetryDAL = telemetryDALFactory(db);
|
const telemetryDAL = telemetryDALFactory(db);
|
||||||
|
|
||||||
// ee db layer ops
|
// ee db layer ops
|
||||||
const permissionDAL = permissionDALFactory(db);
|
const permissionDAL = permissionDALFactory(db);
|
||||||
const samlConfigDAL = samlConfigDALFactory(db);
|
const samlConfigDAL = samlConfigDALFactory(db);
|
||||||
|
const scimDAL = scimDALFactory(db);
|
||||||
|
const ldapConfigDAL = ldapConfigDALFactory(db);
|
||||||
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
|
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
|
||||||
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
|
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
|
||||||
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
|
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
|
||||||
@ -194,7 +203,7 @@ export const registerRoutes = async (
|
|||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
serviceTokenDAL
|
serviceTokenDAL
|
||||||
});
|
});
|
||||||
const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL });
|
const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore });
|
||||||
const trustedIpService = trustedIpServiceFactory({
|
const trustedIpService = trustedIpServiceFactory({
|
||||||
licenseService,
|
licenseService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@ -235,6 +244,16 @@ export const registerRoutes = async (
|
|||||||
smtpService
|
smtpService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ldapService = ldapConfigServiceFactory({
|
||||||
|
ldapConfigDAL,
|
||||||
|
orgDAL,
|
||||||
|
orgBotDAL,
|
||||||
|
userDAL,
|
||||||
|
userAliasDAL,
|
||||||
|
permissionService,
|
||||||
|
licenseService
|
||||||
|
});
|
||||||
|
|
||||||
const telemetryService = telemetryServiceFactory({
|
const telemetryService = telemetryServiceFactory({
|
||||||
keyStore,
|
keyStore,
|
||||||
licenseService
|
licenseService
|
||||||
@ -263,6 +282,8 @@ export const registerRoutes = async (
|
|||||||
incidentContactDAL,
|
incidentContactDAL,
|
||||||
tokenService,
|
tokenService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
projectMembershipDAL,
|
||||||
|
projectKeyDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
userDAL,
|
userDAL,
|
||||||
orgBotDAL
|
orgBotDAL
|
||||||
@ -304,6 +325,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const projectMembershipService = projectMembershipServiceFactory({
|
const projectMembershipService = projectMembershipServiceFactory({
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
|
projectUserMembershipRoleDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
@ -335,7 +357,8 @@ export const registerRoutes = async (
|
|||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
secretApprovalSecretDAL: sarSecretDAL
|
secretApprovalSecretDAL: sarSecretDAL,
|
||||||
|
projectUserMembershipRoleDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectService = projectServiceFactory({
|
const projectService = projectServiceFactory({
|
||||||
@ -352,8 +375,11 @@ export const registerRoutes = async (
|
|||||||
orgService,
|
orgService,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
licenseService
|
licenseService,
|
||||||
|
projectUserMembershipRoleDAL,
|
||||||
|
identityProjectMembershipRoleDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectEnvService = projectEnvServiceFactory({
|
const projectEnvService = projectEnvServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
@ -419,7 +445,12 @@ export const registerRoutes = async (
|
|||||||
orgDAL,
|
orgDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
projectBotDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretBlindIndexDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL
|
||||||
});
|
});
|
||||||
const secretBlindIndexService = secretBlindIndexServiceFactory({
|
const secretBlindIndexService = secretBlindIndexServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
@ -443,6 +474,7 @@ export const registerRoutes = async (
|
|||||||
const sarService = secretApprovalRequestServiceFactory({
|
const sarService = secretApprovalRequestServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
|
secretDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretApprovalRequestSecretDAL: sarSecretDAL,
|
secretApprovalRequestSecretDAL: sarSecretDAL,
|
||||||
secretApprovalRequestReviewerDAL: sarReviewerDAL,
|
secretApprovalRequestReviewerDAL: sarReviewerDAL,
|
||||||
@ -452,6 +484,7 @@ export const registerRoutes = async (
|
|||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
secretService,
|
secretService,
|
||||||
snapshotService,
|
snapshotService,
|
||||||
|
secretVersionTagDAL,
|
||||||
secretQueueService
|
secretQueueService
|
||||||
});
|
});
|
||||||
const secretRotationQueue = secretRotationQueueFactory({
|
const secretRotationQueue = secretRotationQueueFactory({
|
||||||
@ -497,7 +530,9 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
identityProjectDAL,
|
identityProjectDAL,
|
||||||
identityOrgMembershipDAL
|
identityOrgMembershipDAL,
|
||||||
|
identityProjectMembershipRoleDAL,
|
||||||
|
projectRoleDAL
|
||||||
});
|
});
|
||||||
const identityUaService = identityUaServiceFactory({
|
const identityUaService = identityUaServiceFactory({
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
@ -552,6 +587,7 @@ export const registerRoutes = async (
|
|||||||
secretRotation: secretRotationService,
|
secretRotation: secretRotationService,
|
||||||
snapshot: snapshotService,
|
snapshot: snapshotService,
|
||||||
saml: samlService,
|
saml: samlService,
|
||||||
|
ldap: ldapService,
|
||||||
auditLog: auditLogService,
|
auditLog: auditLogService,
|
||||||
secretScanning: secretScanningService,
|
secretScanning: secretScanningService,
|
||||||
license: licenseService,
|
license: licenseService,
|
||||||
|
@ -92,9 +92,10 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
await server.services.telemetry.sendPostHogEvents({
|
await server.services.telemetry.sendPostHogEvents({
|
||||||
event: PostHogEventTypes.AdminInit,
|
event: PostHogEventTypes.AdminInit,
|
||||||
distinctId: user.user.email,
|
distinctId: user.user.username ?? "",
|
||||||
properties: {
|
properties: {
|
||||||
email: user.user.email,
|
username: user.user.username,
|
||||||
|
email: user.user.email ?? "",
|
||||||
lastName: user.user.lastName || "",
|
lastName: user.user.lastName || "",
|
||||||
firstName: user.user.firstName || ""
|
firstName: user.user.firstName || ""
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
|
||||||
|
|
||||||
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/token/renew",
|
url: "/token/renew",
|
||||||
@ -7,7 +9,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Renew access token",
|
description: "Renew access token",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
accessToken: z.string().trim()
|
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.RENEW_ACCESS_TOKEN.accessToken)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
|
import { IdentitiesSchema, OrgMembershipRole } 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 { IDENTITIES } from "@app/lib/api-docs";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
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,9 +21,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim(),
|
name: z.string().trim().describe(IDENTITIES.CREATE.name),
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim().describe(IDENTITIES.CREATE.organizationId),
|
||||||
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess)
|
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(IDENTITIES.CREATE.role)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -78,11 +79,11 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string()
|
identityId: z.string().describe(IDENTITIES.UPDATE.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().optional().describe(IDENTITIES.UPDATE.name),
|
||||||
role: z.string().trim().min(1).optional()
|
role: z.string().trim().min(1).optional().describe(IDENTITIES.UPDATE.role)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -127,7 +128,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string()
|
identityId: z.string().describe(IDENTITIES.DELETE.identityId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } from "@app/db/schemas";
|
import { IdentityUaClientSecretsSchema, IdentityUniversalAuthsSchema } 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 { UNIVERSAL_AUTH } from "@app/lib/api-docs";
|
||||||
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 { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
@ -26,8 +27,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Login with Universal Auth",
|
description: "Login with Universal Auth",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
clientId: z.string().trim(),
|
clientId: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientId),
|
||||||
clientSecret: z.string().trim()
|
clientSecret: z.string().trim().describe(UNIVERSAL_AUTH.LOGIN.clientSecret)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -39,11 +40,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { identityUa, accessToken, identityAccessToken, validClientSecretInfo } =
|
const { identityUa, accessToken, identityAccessToken, validClientSecretInfo, identityMembershipOrg } =
|
||||||
await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp);
|
await server.services.identityUa.login(req.body.clientId, req.body.clientSecret, req.realIp);
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
|
orgId: identityMembershipOrg?.orgId,
|
||||||
event: {
|
event: {
|
||||||
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH,
|
type: EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH,
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -75,7 +77,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim()
|
identityId: z.string().trim().describe(UNIVERSAL_AUTH.ATTACH.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
clientSecretTrustedIps: z
|
clientSecretTrustedIps: z
|
||||||
@ -84,14 +86,16 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.min(1)
|
.min(1)
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
|
.describe(UNIVERSAL_AUTH.ATTACH.clientSecretTrustedIps),
|
||||||
accessTokenTrustedIps: z
|
accessTokenTrustedIps: z
|
||||||
.object({
|
.object({
|
||||||
ipAddress: z.string().trim()
|
ipAddress: z.string().trim()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.min(1)
|
.min(1)
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
|
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
accessTokenTTL: z
|
accessTokenTTL: z
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
@ -99,15 +103,22 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((value) => value !== 0, {
|
.refine((value) => value !== 0, {
|
||||||
message: "accessTokenTTL must have a non zero number"
|
message: "accessTokenTTL must have a non zero number"
|
||||||
})
|
})
|
||||||
.default(2592000),
|
.default(2592000)
|
||||||
|
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTTL), // 30 days
|
||||||
accessTokenMaxTTL: z
|
accessTokenMaxTTL: z
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
.refine((value) => value !== 0, {
|
.refine((value) => value !== 0, {
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
message: "accessTokenMaxTTL must have a non zero number"
|
||||||
})
|
})
|
||||||
.default(2592000), // 30 days
|
.default(2592000)
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
|
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenMaxTTL), // 30 days
|
||||||
|
accessTokenNumUsesLimit: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.default(0)
|
||||||
|
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -155,7 +166,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string()
|
identityId: z.string().describe(UNIVERSAL_AUTH.UPDATE.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
clientSecretTrustedIps: z
|
clientSecretTrustedIps: z
|
||||||
@ -164,16 +175,23 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.min(1)
|
.min(1)
|
||||||
.optional(),
|
.optional()
|
||||||
|
.describe(UNIVERSAL_AUTH.UPDATE.clientSecretTrustedIps),
|
||||||
accessTokenTrustedIps: z
|
accessTokenTrustedIps: z
|
||||||
.object({
|
.object({
|
||||||
ipAddress: z.string().trim()
|
ipAddress: z.string().trim()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.min(1)
|
.min(1)
|
||||||
.optional(),
|
.optional()
|
||||||
accessTokenTTL: z.number().int().min(0).optional(),
|
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
|
accessTokenTTL: z.number().int().min(0).optional().describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
|
||||||
|
accessTokenNumUsesLimit: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.optional()
|
||||||
|
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||||
accessTokenMaxTTL: z
|
accessTokenMaxTTL: z
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
@ -181,6 +199,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
message: "accessTokenMaxTTL must have a non zero number"
|
message: "accessTokenMaxTTL must have a non zero number"
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
|
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -229,7 +248,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string()
|
identityId: z.string().describe(UNIVERSAL_AUTH.RETRIEVE.identityId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -272,12 +291,12 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string()
|
identityId: z.string().describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
description: z.string().trim().default(""),
|
description: z.string().trim().default("").describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.description),
|
||||||
numUsesLimit: z.number().min(0).default(0),
|
numUsesLimit: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.numUsesLimit),
|
||||||
ttl: z.number().min(0).default(0)
|
ttl: z.number().min(0).default(0).describe(UNIVERSAL_AUTH.CREATE_CLIENT_SECRET.ttl)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -323,7 +342,7 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string()
|
identityId: z.string().describe(UNIVERSAL_AUTH.LIST_CLIENT_SECRETS.identityId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -365,8 +384,8 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string(),
|
identityId: z.string().describe(UNIVERSAL_AUTH.REVOKE_CLIENT_SECRET.identityId),
|
||||||
clientSecretId: z.string()
|
clientSecretId: z.string().describe(UNIVERSAL_AUTH.REVOKE_CLIENT_SECRET.clientSecretId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -513,6 +513,37 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:integrationAuthId/heroku/pipelines",
|
||||||
|
method: "GET",
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
integrationAuthId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
pipelines: z
|
||||||
|
.object({
|
||||||
|
app: z.object({ appId: z.string() }),
|
||||||
|
stage: z.string(),
|
||||||
|
pipeline: z.object({ name: z.string(), pipelineId: z.string() })
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const pipelines = await server.services.integrationAuth.getHerokuPipelines({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.integrationAuthId
|
||||||
|
});
|
||||||
|
return { pipelines };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:integrationAuthId/railway/environments",
|
url: "/:integrationAuthId/railway/environments",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -32,6 +32,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
.object({
|
.object({
|
||||||
secretPrefix: z.string().optional(),
|
secretPrefix: z.string().optional(),
|
||||||
secretSuffix: z.string().optional(),
|
secretSuffix: z.string().optional(),
|
||||||
|
initialSyncBehavior: z.string().optional(),
|
||||||
secretGCPLabel: z
|
secretGCPLabel: z
|
||||||
.object({
|
.object({
|
||||||
labelName: z.string(),
|
labelName: z.string(),
|
||||||
|
@ -58,6 +58,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
users: OrgMembershipsSchema.merge(
|
users: OrgMembershipsSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
user: UsersSchema.pick({
|
user: UsersSchema.pick({
|
||||||
|
username: true,
|
||||||
email: true,
|
email: true,
|
||||||
firstName: true,
|
firstName: true,
|
||||||
lastName: true,
|
lastName: true,
|
||||||
@ -87,11 +88,12 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().max(64, { message: "Name must be 64 or fewer characters" }).optional(),
|
||||||
slug: z
|
slug: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.regex(/^[a-zA-Z0-9-]+$/, "Name must only contain alphanumeric characters or hyphens")
|
.max(64, { message: "Slug must be 64 or fewer characters" })
|
||||||
|
.regex(/^[a-zA-Z0-9-]+$/, "Slug must only contain alphanumeric characters or hyphens")
|
||||||
.optional(),
|
.optional(),
|
||||||
authEnforced: z.boolean().optional(),
|
authEnforced: z.boolean().optional(),
|
||||||
scimEnabled: z.boolean().optional()
|
scimEnabled: z.boolean().optional()
|
||||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { ProjectEnvironmentsSchema } from "@app/db/schemas";
|
import { ProjectEnvironmentsSchema } 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 { ENVIRONMENTS } from "@app/lib/api-docs";
|
||||||
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";
|
||||||
|
|
||||||
@ -18,11 +19,11 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim().describe(ENVIRONMENTS.CREATE.workspaceId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim(),
|
name: z.string().trim().describe(ENVIRONMENTS.CREATE.name),
|
||||||
slug: z.string().trim()
|
slug: z.string().trim().describe(ENVIRONMENTS.CREATE.slug)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -73,13 +74,13 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(ENVIRONMENTS.UPDATE.workspaceId),
|
||||||
id: z.string().trim()
|
id: z.string().trim().describe(ENVIRONMENTS.UPDATE.id)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z.string().trim().optional(),
|
slug: z.string().trim().optional().describe(ENVIRONMENTS.UPDATE.slug),
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().optional().describe(ENVIRONMENTS.UPDATE.name),
|
||||||
position: z.number().optional()
|
position: z.number().optional().describe(ENVIRONMENTS.UPDATE.position)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -136,8 +137,8 @@ export const registerProjectEnvRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(ENVIRONMENTS.DELETE.workspaceId),
|
||||||
id: z.string().trim()
|
id: z.string().trim().describe(ENVIRONMENTS.DELETE.id)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OrgMembershipsSchema,
|
OrgMembershipsSchema,
|
||||||
ProjectMembershipRole,
|
|
||||||
ProjectMembershipsSchema,
|
ProjectMembershipsSchema,
|
||||||
|
ProjectUserMembershipRolesSchema,
|
||||||
UserEncryptionKeysSchema,
|
UserEncryptionKeysSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} 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 { PROJECTS } from "@app/lib/api-docs";
|
||||||
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 { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
||||||
|
|
||||||
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
|
export const registerProjectMembershipRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@ -24,20 +27,35 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim().describe(PROJECTS.GET_USER_MEMBERSHIPS.workspaceId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
memberships: ProjectMembershipsSchema.merge(
|
memberships: ProjectMembershipsSchema.omit({ role: true })
|
||||||
z.object({
|
.merge(
|
||||||
user: UsersSchema.pick({
|
z.object({
|
||||||
email: true,
|
user: UsersSchema.pick({
|
||||||
firstName: true,
|
email: true,
|
||||||
lastName: true,
|
firstName: true,
|
||||||
id: true
|
lastName: true,
|
||||||
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
|
id: true
|
||||||
})
|
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
|
||||||
)
|
roles: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
customRoleId: z.string().optional().nullable(),
|
||||||
|
customRoleName: z.string().optional().nullable(),
|
||||||
|
customRoleSlug: z.string().optional().nullable(),
|
||||||
|
isTemporary: z.boolean(),
|
||||||
|
temporaryMode: z.string().optional().nullable(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
.omit({ createdAt: true, updatedAt: true })
|
.omit({ createdAt: true, updatedAt: true })
|
||||||
.array()
|
.array()
|
||||||
})
|
})
|
||||||
@ -86,10 +104,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.params.workspaceId,
|
projectId: req.params.workspaceId,
|
||||||
members: req.body.members.map((member) => ({
|
members: req.body.members
|
||||||
...member,
|
|
||||||
projectRole: ProjectMembershipRole.Member
|
|
||||||
}))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@ -120,43 +135,61 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.workspaceId),
|
||||||
membershipId: z.string().trim()
|
membershipId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.membershipId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
role: z.string().trim()
|
roles: z
|
||||||
|
.array(
|
||||||
|
z.union([
|
||||||
|
z.object({
|
||||||
|
role: z.string(),
|
||||||
|
isTemporary: z.literal(false).default(false)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
role: z.string(),
|
||||||
|
isTemporary: z.literal(true),
|
||||||
|
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
|
||||||
|
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
||||||
|
temporaryAccessStartTime: z.string().datetime()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
.min(1)
|
||||||
|
.refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least long lived role is required")
|
||||||
|
.describe(PROJECTS.UPDATE_USER_MEMBERSHIP.roles)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
membership: ProjectMembershipsSchema
|
roles: ProjectUserMembershipRolesSchema.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const membership = await server.services.projectMembership.updateProjectMembership({
|
const roles = await server.services.projectMembership.updateProjectMembership({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.params.workspaceId,
|
projectId: req.params.workspaceId,
|
||||||
membershipId: req.params.membershipId,
|
membershipId: req.params.membershipId,
|
||||||
role: req.body.role
|
roles: req.body.roles
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
// await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
// ...req.auditLogInfo,
|
||||||
projectId: req.params.workspaceId,
|
// projectId: req.params.workspaceId,
|
||||||
event: {
|
// event: {
|
||||||
type: EventType.UPDATE_USER_WORKSPACE_ROLE,
|
// type: EventType.UPDATE_USER_WORKSPACE_ROLE,
|
||||||
metadata: {
|
// metadata: {
|
||||||
userId: membership.userId,
|
// userId: membership.userId,
|
||||||
newRole: req.body.role,
|
// newRole: req.body.role,
|
||||||
oldRole: membership.role,
|
// oldRole: membership.role,
|
||||||
email: ""
|
// email: ""
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
return { membership };
|
return { roles };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
UserEncryptionKeysSchema,
|
UserEncryptionKeysSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import { PROJECTS } from "@app/lib/api-docs";
|
||||||
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";
|
||||||
|
|
||||||
@ -60,16 +61,32 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
users: ProjectMembershipsSchema.merge(
|
users: ProjectMembershipsSchema.omit({ role: true })
|
||||||
z.object({
|
.merge(
|
||||||
user: UsersSchema.pick({
|
z.object({
|
||||||
email: true,
|
user: UsersSchema.pick({
|
||||||
firstName: true,
|
username: true,
|
||||||
lastName: true,
|
email: true,
|
||||||
id: true
|
firstName: true,
|
||||||
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true }))
|
lastName: true,
|
||||||
})
|
id: true
|
||||||
)
|
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
|
||||||
|
roles: z.array(
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
customRoleId: z.string().optional().nullable(),
|
||||||
|
customRoleName: z.string().optional().nullable(),
|
||||||
|
customRoleSlug: z.string().optional().nullable(),
|
||||||
|
isTemporary: z.boolean(),
|
||||||
|
temporaryMode: z.string().optional().nullable(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
.omit({ createdAt: true, updatedAt: true })
|
.omit({ createdAt: true, updatedAt: true })
|
||||||
.array()
|
.array()
|
||||||
})
|
})
|
||||||
@ -109,7 +126,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim().describe(PROJECTS.GET.workspaceId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -161,7 +178,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim().describe(PROJECTS.DELETE.workspaceId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -219,11 +236,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim().describe(PROJECTS.UPDATE.workspaceId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().optional(),
|
name: z
|
||||||
autoCapitalization: z.boolean().optional()
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(64, { message: "Name must be 64 or fewer characters" })
|
||||||
|
.optional()
|
||||||
|
.describe(PROJECTS.UPDATE.name),
|
||||||
|
autoCapitalization: z.boolean().optional().describe(PROJECTS.UPDATE.autoCapitalization)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { SecretFoldersSchema } from "@app/db/schemas";
|
import { SecretFoldersSchema } 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 { FOLDERS } from "@app/lib/api-docs";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
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";
|
||||||
@ -19,12 +20,12 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(FOLDERS.CREATE.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(FOLDERS.CREATE.environment),
|
||||||
name: z.string().trim(),
|
name: z.string().trim().describe(FOLDERS.CREATE.name),
|
||||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.CREATE.path),
|
||||||
// backward compatiability with cli
|
// backward compatiability with cli
|
||||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.CREATE.directory)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -73,15 +74,15 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
// old way this was name
|
// old way this was name
|
||||||
folderId: z.string()
|
folderId: z.string().describe(FOLDERS.UPDATE.folderId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(FOLDERS.UPDATE.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(FOLDERS.UPDATE.environment),
|
||||||
name: z.string().trim(),
|
name: z.string().trim().describe(FOLDERS.UPDATE.name),
|
||||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.path),
|
||||||
// backward compatiability with cli
|
// backward compatiability with cli
|
||||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.directory)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -119,8 +120,9 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO(daniel): Expose this route in api reference and write docs for it.
|
||||||
server.route({
|
server.route({
|
||||||
url: "/:folderId",
|
url: "/:folderIdOrName",
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
schema: {
|
schema: {
|
||||||
description: "Delete a folder",
|
description: "Delete a folder",
|
||||||
@ -131,14 +133,14 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
folderId: z.string()
|
folderIdOrName: z.string().describe(FOLDERS.DELETE.folderIdOrName)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(FOLDERS.DELETE.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(FOLDERS.DELETE.environment),
|
||||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.DELETE.path),
|
||||||
// keep this here as cli need directory
|
// keep this here as cli need directory
|
||||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.DELETE.directory)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -155,7 +157,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
projectId: req.body.workspaceId,
|
projectId: req.body.workspaceId,
|
||||||
id: req.params.folderId,
|
idOrName: req.params.folderIdOrName,
|
||||||
path
|
path
|
||||||
});
|
});
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@ -187,11 +189,11 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(FOLDERS.LIST.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(FOLDERS.LIST.environment),
|
||||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.LIST.path),
|
||||||
// backward compatiability with cli
|
// backward compatiability with cli
|
||||||
directory: z.string().trim().default("/").transform(removeTrailingSlash)
|
directory: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.LIST.directory)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { SecretImportsSchema, SecretsSchema } from "@app/db/schemas";
|
import { SecretImportsSchema, SecretsSchema } 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 { SECRET_IMPORTS } from "@app/lib/api-docs";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
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";
|
||||||
@ -19,12 +20,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(SECRET_IMPORTS.CREATE.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.environment),
|
||||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.path),
|
||||||
import: z.object({
|
import: z.object({
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(SECRET_IMPORTS.CREATE.import.environment),
|
||||||
path: z.string().trim().transform(removeTrailingSlash)
|
path: z.string().trim().transform(removeTrailingSlash).describe(SECRET_IMPORTS.CREATE.import.path)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -80,20 +81,21 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretImportId: z.string().trim()
|
secretImportId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.secretImportId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(SECRET_IMPORTS.UPDATE.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(SECRET_IMPORTS.UPDATE.environment),
|
||||||
path: z.string().trim().default("/").transform(removeTrailingSlash),
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.UPDATE.path),
|
||||||
import: z.object({
|
import: z.object({
|
||||||
environment: z.string().trim().optional(),
|
environment: z.string().trim().optional().describe(SECRET_IMPORTS.UPDATE.import.environment),
|
||||||
path: z
|
path: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.optional()
|
.optional()
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||||
position: z.number().optional()
|
.describe(SECRET_IMPORTS.UPDATE.import.path),
|
||||||
|
position: z.number().optional().describe(SECRET_IMPORTS.UPDATE.import.position)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -150,12 +152,12 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretImportId: z.string().trim()
|
secretImportId: z.string().trim().describe(SECRET_IMPORTS.DELETE.secretImportId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(SECRET_IMPORTS.DELETE.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(SECRET_IMPORTS.DELETE.environment),
|
||||||
path: z.string().trim().default("/").transform(removeTrailingSlash)
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.DELETE.path)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -210,9 +212,9 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(SECRET_IMPORTS.LIST.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(SECRET_IMPORTS.LIST.environment),
|
||||||
path: z.string().trim().default("/").transform(removeTrailingSlash)
|
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(SECRET_IMPORTS.LIST.path)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||||
|
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
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";
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
orgId: z.string().trim()
|
orgId: z.string().trim().describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orgId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
IdentitiesSchema,
|
IdentitiesSchema,
|
||||||
IdentityProjectMembershipsSchema,
|
IdentityProjectMembershipsSchema,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectRolesSchema
|
ProjectUserMembershipRolesSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import { PROJECTS } from "@app/lib/api-docs";
|
||||||
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 { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
||||||
|
|
||||||
export const registerIdentityProjectRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityProjectRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@ -53,28 +56,45 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim(),
|
projectId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.projectId),
|
||||||
identityId: z.string().trim()
|
identityId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
role: z.string().trim().min(1).default(ProjectMembershipRole.NoAccess)
|
roles: z
|
||||||
|
.array(
|
||||||
|
z.union([
|
||||||
|
z.object({
|
||||||
|
role: z.string(),
|
||||||
|
isTemporary: z.literal(false).default(false)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
role: z.string(),
|
||||||
|
isTemporary: z.literal(true),
|
||||||
|
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode),
|
||||||
|
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
||||||
|
temporaryAccessStartTime: z.string().datetime()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
)
|
||||||
|
.min(1)
|
||||||
|
.describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.roles)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityMembership: IdentityProjectMembershipsSchema
|
roles: ProjectUserMembershipRolesSchema.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const identityMembership = await server.services.identityProject.updateProjectIdentity({
|
const roles = await server.services.identityProject.updateProjectIdentity({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
identityId: req.params.identityId,
|
identityId: req.params.identityId,
|
||||||
projectId: req.params.projectId,
|
projectId: req.params.projectId,
|
||||||
role: req.body.role
|
roles: req.body.roles
|
||||||
});
|
});
|
||||||
return { identityMembership };
|
return { roles };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -90,8 +110,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim(),
|
projectId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.projectId),
|
||||||
identityId: z.string().trim()
|
identityId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.identityId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -123,22 +143,33 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim()
|
projectId: z.string().trim().describe(PROJECTS.LIST_IDENTITY_MEMBERSHIPS.projectId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityMemberships: IdentityProjectMembershipsSchema.merge(
|
identityMemberships: z
|
||||||
z.object({
|
.object({
|
||||||
customRole: ProjectRolesSchema.pick({
|
id: z.string(),
|
||||||
id: true,
|
identityId: z.string(),
|
||||||
name: true,
|
createdAt: z.date(),
|
||||||
slug: true,
|
updatedAt: z.date(),
|
||||||
permissions: true,
|
roles: z.array(
|
||||||
description: true
|
z.object({
|
||||||
}).optional(),
|
id: z.string(),
|
||||||
|
role: z.string(),
|
||||||
|
customRoleId: z.string().optional().nullable(),
|
||||||
|
customRoleName: z.string().optional().nullable(),
|
||||||
|
customRoleSlug: z.string().optional().nullable(),
|
||||||
|
isTemporary: z.boolean(),
|
||||||
|
temporaryMode: z.string().optional().nullable(),
|
||||||
|
temporaryRange: z.string().nullable().optional(),
|
||||||
|
temporaryAccessStartTime: z.date().nullable().optional(),
|
||||||
|
temporaryAccessEndTime: z.date().nullable().optional()
|
||||||
|
})
|
||||||
|
),
|
||||||
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
||||||
})
|
})
|
||||||
).array()
|
.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { OrganizationsSchema, OrgMembershipsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
|
import { OrganizationsSchema, OrgMembershipsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
|
||||||
|
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@ -17,13 +18,14 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim().describe(ORGANIZATIONS.LIST_USER_MEMBERSHIPS.organizationId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
users: OrgMembershipsSchema.merge(
|
users: OrgMembershipsSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
user: UsersSchema.pick({
|
user: UsersSchema.pick({
|
||||||
|
username: true,
|
||||||
email: true,
|
email: true,
|
||||||
firstName: true,
|
firstName: true,
|
||||||
lastName: true,
|
lastName: true,
|
||||||
@ -61,7 +63,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim().describe(ORGANIZATIONS.GET_PROJECTS.organizationId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -105,9 +107,12 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
apiKeyAuth: []
|
apiKeyAuth: []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
params: z.object({
|
||||||
|
organizationId: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.organizationId),
|
||||||
|
membershipId: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.membershipId)
|
||||||
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
role: z.string().trim()
|
role: z.string().trim().describe(ORGANIZATIONS.UPDATE_USER_MEMBERSHIP.role)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -141,7 +146,10 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
apiKeyAuth: []
|
apiKeyAuth: []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
params: z.object({
|
||||||
|
organizationId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.organizationId),
|
||||||
|
membershipId: z.string().trim().describe(ORGANIZATIONS.DELETE_USER_MEMBERSHIP.membershipId)
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
membership: OrgMembershipsSchema
|
membership: OrgMembershipsSchema
|
||||||
@ -179,11 +187,12 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
if (req.auth.actor !== ActorType.USER) return;
|
if (req.auth.actor !== ActorType.USER) return;
|
||||||
|
|
||||||
const organization = await server.services.org.createOrganization(
|
const organization = await server.services.org.createOrganization({
|
||||||
req.permission.id,
|
userId: req.permission.id,
|
||||||
req.auth.user.email,
|
userEmail: req.auth.user.email,
|
||||||
req.body.name
|
orgName: req.body.name
|
||||||
);
|
});
|
||||||
|
|
||||||
return { organization };
|
return { organization };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { ProjectMembershipsSchema } from "@app/db/schemas";
|
import { ProjectMembershipsSchema } 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 { PROJECTS } from "@app/lib/api-docs";
|
||||||
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";
|
||||||
|
|
||||||
@ -11,10 +12,11 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
url: "/:projectId/memberships",
|
url: "/:projectId/memberships",
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().describe("The ID of the project.")
|
projectId: z.string().describe(PROJECTS.INVITE_MEMBER.projectId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
emails: z.string().email().array().describe("Emails of the users to add to the project.")
|
emails: z.string().email().array().default([]).describe(PROJECTS.INVITE_MEMBER.emails),
|
||||||
|
usernames: z.string().array().default([]).describe(PROJECTS.INVITE_MEMBER.usernames)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -28,7 +30,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
projectId: req.params.projectId,
|
projectId: req.params.projectId,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
emails: req.body.emails
|
emails: req.body.emails,
|
||||||
|
usernames: req.body.usernames
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@ -53,11 +56,12 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
url: "/:projectId/memberships",
|
url: "/:projectId/memberships",
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().describe("The ID of the project.")
|
projectId: z.string().describe(PROJECTS.REMOVE_MEMBER.projectId)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
body: z.object({
|
body: z.object({
|
||||||
emails: z.string().email().array().describe("Emails of the users to remove from the project.")
|
emails: z.string().email().array().default([]).describe(PROJECTS.REMOVE_MEMBER.emails),
|
||||||
|
usernames: z.string().array().default([]).describe(PROJECTS.REMOVE_MEMBER.usernames)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -72,7 +76,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId: req.params.projectId,
|
projectId: req.params.projectId,
|
||||||
emails: req.body.emails
|
emails: req.body.emails,
|
||||||
|
usernames: req.body.usernames
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const membership of memberships) {
|
for (const membership of memberships) {
|
||||||
|
@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { ProjectKeysSchema, ProjectsSchema } from "@app/db/schemas";
|
import { ProjectKeysSchema, ProjectsSchema } 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 { PROJECTS } from "@app/lib/api-docs";
|
||||||
import { authRateLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@ -29,7 +30,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
workspaceId: z.string().trim()
|
workspaceId: z.string().trim().describe(PROJECTS.GET_KEY.workspaceId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: ProjectKeysSchema.merge(
|
200: ProjectKeysSchema.merge(
|
||||||
@ -127,7 +128,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
projectName: z.string().trim(),
|
projectName: z.string().trim().describe(PROJECTS.CREATE.projectName),
|
||||||
slug: z
|
slug: z
|
||||||
.string()
|
.string()
|
||||||
.min(5)
|
.min(5)
|
||||||
@ -135,8 +136,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((v) => slugify(v) === v, {
|
.refine((v) => slugify(v) === v, {
|
||||||
message: "Slug must be a valid slug"
|
message: "Slug must be a valid slug"
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional()
|
||||||
organizationId: z.string().trim()
|
.describe(PROJECTS.CREATE.slug),
|
||||||
|
organizationId: z.string().trim().describe(PROJECTS.CREATE.organizationId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -12,7 +12,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
email: z.string().email().trim(),
|
email: z.string().trim(),
|
||||||
providerAuthToken: z.string().trim().optional(),
|
providerAuthToken: z.string().trim().optional(),
|
||||||
clientPublicKey: z.string().trim()
|
clientPublicKey: z.string().trim()
|
||||||
}),
|
}),
|
||||||
@ -42,7 +42,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
email: z.string().email().trim(),
|
email: z.string().trim(),
|
||||||
providerAuthToken: z.string().trim().optional(),
|
providerAuthToken: z.string().trim().optional(),
|
||||||
clientProof: z.string().trim()
|
clientProof: z.string().trim()
|
||||||
}),
|
}),
|
||||||
|
@ -10,6 +10,7 @@ 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 { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
import { CommitType } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||||
|
import { RAW_SECRETS } from "@app/lib/api-docs";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
@ -33,13 +34,14 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim().optional(),
|
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceId),
|
||||||
environment: z.string().trim().optional(),
|
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
|
||||||
include_imports: z
|
include_imports: z
|
||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.default("false")
|
.default("false")
|
||||||
.transform((value) => value === "true")
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.LIST.includeImports)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -123,18 +125,19 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretName: z.string().trim()
|
secretName: z.string().trim().describe(RAW_SECRETS.GET.secretName)
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
workspaceId: z.string().trim().optional(),
|
workspaceId: z.string().trim().optional().describe(RAW_SECRETS.GET.workspaceId),
|
||||||
environment: z.string().trim().optional(),
|
environment: z.string().trim().optional().describe(RAW_SECRETS.GET.environment),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
|
||||||
version: z.coerce.number().optional(),
|
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
|
||||||
type: z.nativeEnum(SecretType).default(SecretType.Shared),
|
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
|
||||||
include_imports: z
|
include_imports: z
|
||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.default("false")
|
.default("false")
|
||||||
.transform((value) => value === "true")
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.GET.includeImports)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -213,16 +216,24 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretName: z.string().trim()
|
secretName: z.string().trim().describe(RAW_SECRETS.CREATE.secretName)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(RAW_SECRETS.CREATE.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z
|
||||||
secretValue: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
|
.string()
|
||||||
secretComment: z.string().trim().optional().default(""),
|
.trim()
|
||||||
skipMultilineEncoding: z.boolean().optional(),
|
.default("/")
|
||||||
type: z.nativeEnum(SecretType).default(SecretType.Shared)
|
.transform(removeTrailingSlash)
|
||||||
|
.describe(RAW_SECRETS.CREATE.secretPath),
|
||||||
|
secretValue: z
|
||||||
|
.string()
|
||||||
|
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||||
|
.describe(RAW_SECRETS.CREATE.secretValue),
|
||||||
|
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
|
||||||
|
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
|
||||||
|
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -290,15 +301,23 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretName: z.string().trim()
|
secretName: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(RAW_SECRETS.UPDATE.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment),
|
||||||
secretValue: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
|
secretValue: z
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
.string()
|
||||||
skipMultilineEncoding: z.boolean().optional(),
|
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||||
type: z.nativeEnum(SecretType).default(SecretType.Shared)
|
.describe(RAW_SECRETS.UPDATE.secretValue),
|
||||||
|
secretPath: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.default("/")
|
||||||
|
.transform(removeTrailingSlash)
|
||||||
|
.describe(RAW_SECRETS.UPDATE.secretPath),
|
||||||
|
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding),
|
||||||
|
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -364,13 +383,18 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
params: z.object({
|
params: z.object({
|
||||||
secretName: z.string().trim()
|
secretName: z.string().trim().describe(RAW_SECRETS.DELETE.secretName)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
workspaceId: z.string().trim(),
|
workspaceId: z.string().trim().describe(RAW_SECRETS.DELETE.workspaceId),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z
|
||||||
type: z.nativeEnum(SecretType).default(SecretType.Shared)
|
.string()
|
||||||
|
.trim()
|
||||||
|
.default("/")
|
||||||
|
.transform(removeTrailingSlash)
|
||||||
|
.describe(RAW_SECRETS.DELETE.secretPath),
|
||||||
|
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.DELETE.type)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -88,7 +88,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
email: z.string().email().trim(),
|
email: z.string().trim(),
|
||||||
firstName: z.string().trim(),
|
firstName: z.string().trim(),
|
||||||
lastName: z.string().trim().optional(),
|
lastName: z.string().trim().optional(),
|
||||||
protectedKey: z.string().trim(),
|
protectedKey: z.string().trim(),
|
||||||
@ -131,13 +131,16 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
|||||||
authorization: req.headers.authorization as string
|
authorization: req.headers.authorization as string
|
||||||
});
|
});
|
||||||
|
|
||||||
void server.services.telemetry.sendLoopsEvent(user.email, user.firstName || "", user.lastName || "");
|
if (user.email) {
|
||||||
|
void server.services.telemetry.sendLoopsEvent(user.email, user.firstName || "", user.lastName || "");
|
||||||
|
}
|
||||||
|
|
||||||
void server.services.telemetry.sendPostHogEvents({
|
void server.services.telemetry.sendPostHogEvents({
|
||||||
event: PostHogEventTypes.UserSignedUp,
|
event: PostHogEventTypes.UserSignedUp,
|
||||||
distinctId: user.email,
|
distinctId: user.username ?? "",
|
||||||
properties: {
|
properties: {
|
||||||
email: user.email,
|
username: user.username,
|
||||||
|
email: user.email ?? "",
|
||||||
attributionSource: req.body.attributionSource
|
attributionSource: req.body.attributionSource
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -194,13 +197,16 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
|||||||
authorization: req.headers.authorization as string
|
authorization: req.headers.authorization as string
|
||||||
});
|
});
|
||||||
|
|
||||||
void server.services.telemetry.sendLoopsEvent(user.email, user.firstName || "", user.lastName || "");
|
if (user.email) {
|
||||||
|
void server.services.telemetry.sendLoopsEvent(user.email, user.firstName || "", user.lastName || "");
|
||||||
|
}
|
||||||
|
|
||||||
void server.services.telemetry.sendPostHogEvents({
|
void server.services.telemetry.sendPostHogEvents({
|
||||||
event: PostHogEventTypes.UserSignedUp,
|
event: PostHogEventTypes.UserSignedUp,
|
||||||
distinctId: user.email,
|
distinctId: user.username ?? "",
|
||||||
properties: {
|
properties: {
|
||||||
email: user.email,
|
username: user.username,
|
||||||
|
email: user.email ?? "",
|
||||||
attributionSource: "Team Invite"
|
attributionSource: "Team Invite"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -5,13 +5,14 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
|||||||
|
|
||||||
import { AuthModeProviderJwtTokenPayload, AuthModeProviderSignUpTokenPayload, AuthTokenType } from "./auth-type";
|
import { AuthModeProviderJwtTokenPayload, AuthModeProviderSignUpTokenPayload, AuthTokenType } from "./auth-type";
|
||||||
|
|
||||||
export const validateProviderAuthToken = (providerToken: string, email: string) => {
|
export const validateProviderAuthToken = (providerToken: string, username?: string) => {
|
||||||
if (!providerToken) throw new UnauthorizedError();
|
if (!providerToken) throw new UnauthorizedError();
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const decodedToken = jwt.verify(providerToken, appCfg.AUTH_SECRET) as AuthModeProviderJwtTokenPayload;
|
const decodedToken = jwt.verify(providerToken, appCfg.AUTH_SECRET) as AuthModeProviderJwtTokenPayload;
|
||||||
|
|
||||||
if (decodedToken.authTokenType !== AuthTokenType.PROVIDER_TOKEN) throw new UnauthorizedError();
|
if (decodedToken.authTokenType !== AuthTokenType.PROVIDER_TOKEN) throw new UnauthorizedError();
|
||||||
if (decodedToken.email !== email) throw new Error("Invalid auth credentials");
|
|
||||||
|
if (decodedToken.username !== username) throw new Error("Invalid auth credentials");
|
||||||
|
|
||||||
if (decodedToken.organizationId) {
|
if (decodedToken.organizationId) {
|
||||||
return { orgId: decodedToken.organizationId };
|
return { orgId: decodedToken.organizationId };
|
||||||
|
@ -39,17 +39,19 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
|
|||||||
if (!isDeviceSeen) {
|
if (!isDeviceSeen) {
|
||||||
const newDeviceList = devices.concat([{ ip, userAgent }]);
|
const newDeviceList = devices.concat([{ ip, userAgent }]);
|
||||||
await userDAL.updateById(user.id, { devices: JSON.stringify(newDeviceList) });
|
await userDAL.updateById(user.id, { devices: JSON.stringify(newDeviceList) });
|
||||||
await smtpService.sendMail({
|
if (user.email) {
|
||||||
template: SmtpTemplates.NewDeviceJoin,
|
await smtpService.sendMail({
|
||||||
subjectLine: "Successful login from new device",
|
template: SmtpTemplates.NewDeviceJoin,
|
||||||
recipients: [user.email],
|
subjectLine: "Successful login from new device",
|
||||||
substitutions: {
|
recipients: [user.email],
|
||||||
email: user.email,
|
substitutions: {
|
||||||
timestamp: new Date().toString(),
|
email: user.email,
|
||||||
ip,
|
timestamp: new Date().toString(),
|
||||||
userAgent
|
ip,
|
||||||
}
|
userAgent
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,7 +133,9 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
|
|||||||
providerAuthToken,
|
providerAuthToken,
|
||||||
clientPublicKey
|
clientPublicKey
|
||||||
}: TLoginGenServerPublicKeyDTO) => {
|
}: TLoginGenServerPublicKeyDTO) => {
|
||||||
const userEnc = await userDAL.findUserEncKeyByEmail(email);
|
const userEnc = await userDAL.findUserEncKeyByUsername({
|
||||||
|
username: email
|
||||||
|
});
|
||||||
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
|
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
|
||||||
throw new Error("Failed to find user");
|
throw new Error("Failed to find user");
|
||||||
}
|
}
|
||||||
@ -158,7 +162,9 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
|
|||||||
ip,
|
ip,
|
||||||
userAgent
|
userAgent
|
||||||
}: TLoginClientProofDTO) => {
|
}: TLoginClientProofDTO) => {
|
||||||
const userEnc = await userDAL.findUserEncKeyByEmail(email);
|
const userEnc = await userDAL.findUserEncKeyByUsername({
|
||||||
|
username: email
|
||||||
|
});
|
||||||
if (!userEnc) throw new Error("Failed to find user");
|
if (!userEnc) throw new Error("Failed to find user");
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
|
|
||||||
@ -187,7 +193,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
|
|||||||
clientPublicKey: null
|
clientPublicKey: null
|
||||||
});
|
});
|
||||||
// send multi factor auth token if they it enabled
|
// send multi factor auth token if they it enabled
|
||||||
if (userEnc.isMfaEnabled) {
|
if (userEnc.isMfaEnabled && userEnc.email) {
|
||||||
const mfaToken = jwt.sign(
|
const mfaToken = jwt.sign(
|
||||||
{
|
{
|
||||||
authTokenType: AuthTokenType.MFA_TOKEN,
|
authTokenType: AuthTokenType.MFA_TOKEN,
|
||||||
@ -227,7 +233,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
|
|||||||
*/
|
*/
|
||||||
const resendMfaToken = async (userId: string) => {
|
const resendMfaToken = async (userId: string) => {
|
||||||
const user = await userDAL.findById(userId);
|
const user = await userDAL.findById(userId);
|
||||||
if (!user) return;
|
if (!user || !user.email) return;
|
||||||
await sendUserMfaCode({
|
await sendUserMfaCode({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
email: user.email
|
email: user.email
|
||||||
@ -263,7 +269,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
|
|||||||
* OAuth2 login for google,github, and other oauth2 provider
|
* OAuth2 login for google,github, and other oauth2 provider
|
||||||
* */
|
* */
|
||||||
const oauth2Login = async ({ email, firstName, lastName, authMethod, callbackPort }: TOauthLoginDTO) => {
|
const oauth2Login = async ({ email, firstName, lastName, authMethod, callbackPort }: TOauthLoginDTO) => {
|
||||||
let user = await userDAL.findUserByEmail(email);
|
let user = await userDAL.findUserByUsername(email);
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@ -282,7 +288,14 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
user = await userDAL.create({ email, firstName, lastName, authMethods: [authMethod], isGhost: false });
|
user = await userDAL.create({
|
||||||
|
username: email,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
authMethods: [authMethod],
|
||||||
|
isGhost: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const isLinkingRequired = !user?.authMethods?.includes(authMethod);
|
const isLinkingRequired = !user?.authMethods?.includes(authMethod);
|
||||||
const isUserCompleted = user.isAccepted;
|
const isUserCompleted = user.isAccepted;
|
||||||
@ -290,7 +303,7 @@ export const authLoginServiceFactory = ({ userDAL, tokenService, smtpService }:
|
|||||||
{
|
{
|
||||||
authTokenType: AuthTokenType.PROVIDER_TOKEN,
|
authTokenType: AuthTokenType.PROVIDER_TOKEN,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
email: user.email,
|
username: user.username,
|
||||||
firstName: user.firstName,
|
firstName: user.firstName,
|
||||||
lastName: user.lastName,
|
lastName: user.lastName,
|
||||||
authMethod,
|
authMethod,
|
||||||
|
@ -99,7 +99,7 @@ export const authPaswordServiceFactory = ({
|
|||||||
* Email password reset flow via email. Step 1 send email
|
* Email password reset flow via email. Step 1 send email
|
||||||
*/
|
*/
|
||||||
const sendPasswordResetEmail = async (email: string) => {
|
const sendPasswordResetEmail = async (email: string) => {
|
||||||
const user = await userDAL.findUserByEmail(email);
|
const user = await userDAL.findUserByUsername(email);
|
||||||
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
||||||
if (!user || (user && !user.isAccepted)) return;
|
if (!user || (user && !user.isAccepted)) return;
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ export const authPaswordServiceFactory = ({
|
|||||||
* */
|
* */
|
||||||
const verifyPasswordResetEmail = async (email: string, code: string) => {
|
const verifyPasswordResetEmail = async (email: string, code: string) => {
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
const user = await userDAL.findUserByEmail(email);
|
const user = await userDAL.findUserByUsername(email);
|
||||||
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
||||||
if (!user || (user && !user.isAccepted)) {
|
if (!user || (user && !user.isAccepted)) {
|
||||||
throw new Error("Failed email verification for pass reset");
|
throw new Error("Failed email verification for pass reset");
|
||||||
|
@ -44,13 +44,13 @@ export const authSignupServiceFactory = ({
|
|||||||
throw new Error("Provided a disposable email");
|
throw new Error("Provided a disposable email");
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await userDAL.findUserByEmail(email);
|
let user = await userDAL.findUserByUsername(email);
|
||||||
if (user && user.isAccepted) {
|
if (user && user.isAccepted) {
|
||||||
// TODO(akhilmhdh-pg): copy as old one. this needs to be changed due to security issues
|
// TODO(akhilmhdh-pg): copy as old one. this needs to be changed due to security issues
|
||||||
throw new Error("Failed to send verification code for complete account");
|
throw new Error("Failed to send verification code for complete account");
|
||||||
}
|
}
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await userDAL.create({ authMethods: [AuthMethod.EMAIL], email, isGhost: false });
|
user = await userDAL.create({ authMethods: [AuthMethod.EMAIL], username: email, email, isGhost: false });
|
||||||
}
|
}
|
||||||
if (!user) throw new Error("Failed to create user");
|
if (!user) throw new Error("Failed to create user");
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ export const authSignupServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const verifyEmailSignup = async (email: string, code: string) => {
|
const verifyEmailSignup = async (email: string, code: string) => {
|
||||||
const user = await userDAL.findUserByEmail(email);
|
const user = await userDAL.findUserByUsername(email);
|
||||||
if (!user || (user && user.isAccepted)) {
|
if (!user || (user && user.isAccepted)) {
|
||||||
// TODO(akhilmhdh): copy as old one. this needs to be changed due to security issues
|
// TODO(akhilmhdh): copy as old one. this needs to be changed due to security issues
|
||||||
throw new Error("Failed to send verification code for complete account");
|
throw new Error("Failed to send verification code for complete account");
|
||||||
@ -115,14 +115,14 @@ export const authSignupServiceFactory = ({
|
|||||||
userAgent,
|
userAgent,
|
||||||
authorization
|
authorization
|
||||||
}: TCompleteAccountSignupDTO) => {
|
}: TCompleteAccountSignupDTO) => {
|
||||||
const user = await userDAL.findUserByEmail(email);
|
const user = await userDAL.findOne({ username: email });
|
||||||
if (!user || (user && user.isAccepted)) {
|
if (!user || (user && user.isAccepted)) {
|
||||||
throw new Error("Failed to complete account for complete user");
|
throw new Error("Failed to complete account for complete user");
|
||||||
}
|
}
|
||||||
|
|
||||||
let organizationId;
|
let organizationId;
|
||||||
if (providerAuthToken) {
|
if (providerAuthToken) {
|
||||||
const { orgId } = validateProviderAuthToken(providerAuthToken, user.email);
|
const { orgId } = validateProviderAuthToken(providerAuthToken, user.username);
|
||||||
organizationId = orgId;
|
organizationId = orgId;
|
||||||
} else {
|
} else {
|
||||||
validateSignUpAuthorization(authorization, user.id);
|
validateSignUpAuthorization(authorization, user.id);
|
||||||
@ -150,7 +150,11 @@ export const authSignupServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!organizationId) {
|
if (!organizationId) {
|
||||||
await orgService.createOrganization(user.id, user.email, organizationName);
|
await orgService.createOrganization({
|
||||||
|
userId: user.id,
|
||||||
|
userEmail: user.email ?? user.username,
|
||||||
|
orgName: organizationName
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedMembersips = await orgDAL.updateMembership(
|
const updatedMembersips = await orgDAL.updateMembership(
|
||||||
@ -215,7 +219,7 @@ export const authSignupServiceFactory = ({
|
|||||||
encryptedPrivateKeyTag,
|
encryptedPrivateKeyTag,
|
||||||
authorization
|
authorization
|
||||||
}: TCompleteAccountInviteDTO) => {
|
}: TCompleteAccountInviteDTO) => {
|
||||||
const user = await userDAL.findUserByEmail(email);
|
const user = await userDAL.findUserByUsername(email);
|
||||||
if (!user || (user && user.isAccepted)) {
|
if (!user || (user && user.isAccepted)) {
|
||||||
throw new Error("Failed to complete account for complete user");
|
throw new Error("Failed to complete account for complete user");
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,8 @@ export enum AuthMethod {
|
|||||||
GITLAB = "gitlab",
|
GITLAB = "gitlab",
|
||||||
OKTA_SAML = "okta-saml",
|
OKTA_SAML = "okta-saml",
|
||||||
AZURE_SAML = "azure-saml",
|
AZURE_SAML = "azure-saml",
|
||||||
JUMPCLOUD_SAML = "jumpcloud-saml"
|
JUMPCLOUD_SAML = "jumpcloud-saml",
|
||||||
|
LDAP = "ldap"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AuthTokenType {
|
export enum AuthTokenType {
|
||||||
@ -61,7 +62,7 @@ export type AuthModeRefreshJwtTokenPayload = {
|
|||||||
|
|
||||||
export type AuthModeProviderJwtTokenPayload = {
|
export type AuthModeProviderJwtTokenPayload = {
|
||||||
authTokenType: AuthTokenType.PROVIDER_TOKEN;
|
authTokenType: AuthTokenType.PROVIDER_TOKEN;
|
||||||
email: string;
|
username: string;
|
||||||
organizationId?: string;
|
organizationId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, sqlNestRelationships } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
|
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
|
||||||
|
|
||||||
@ -15,52 +15,81 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
const docs = await (tx || db)(TableName.IdentityProjectMembership)
|
const docs = await (tx || db)(TableName.IdentityProjectMembership)
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
|
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
|
||||||
|
.join(
|
||||||
|
TableName.IdentityProjectMembershipRole,
|
||||||
|
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
|
||||||
|
`${TableName.IdentityProjectMembership}.id`
|
||||||
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.ProjectRoles,
|
TableName.ProjectRoles,
|
||||||
`${TableName.IdentityProjectMembership}.roleId`,
|
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
.select(selectAllTableCols(TableName.IdentityProjectMembership))
|
.select(
|
||||||
// cr stands for custom role
|
db.ref("id").withSchema(TableName.IdentityProjectMembership),
|
||||||
.select(db.ref("id").as("crId").withSchema(TableName.ProjectRoles))
|
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership),
|
||||||
.select(db.ref("name").as("crName").withSchema(TableName.ProjectRoles))
|
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership),
|
||||||
.select(db.ref("slug").as("crSlug").withSchema(TableName.ProjectRoles))
|
db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity),
|
||||||
.select(db.ref("description").as("crDescription").withSchema(TableName.ProjectRoles))
|
db.ref("id").as("identityId").withSchema(TableName.Identity),
|
||||||
.select(db.ref("permissions").as("crPermission").withSchema(TableName.ProjectRoles))
|
db.ref("name").as("identityName").withSchema(TableName.Identity),
|
||||||
.select(db.ref("permissions").as("crPermission").withSchema(TableName.ProjectRoles))
|
db.ref("id").withSchema(TableName.IdentityProjectMembership),
|
||||||
.select(db.ref("id").as("identityId").withSchema(TableName.Identity))
|
db.ref("role").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
.select(db.ref("name").as("identityName").withSchema(TableName.Identity))
|
db.ref("id").withSchema(TableName.IdentityProjectMembershipRole).as("membershipRoleId"),
|
||||||
.select(db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity));
|
db.ref("customRoleId").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
return docs.map(
|
db.ref("name").withSchema(TableName.ProjectRoles).as("customRoleName"),
|
||||||
({
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||||
crId,
|
db.ref("temporaryMode").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
crDescription,
|
db.ref("isTemporary").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
crSlug,
|
db.ref("temporaryRange").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
crPermission,
|
db.ref("temporaryAccessStartTime").withSchema(TableName.IdentityProjectMembershipRole),
|
||||||
crName,
|
db.ref("temporaryAccessEndTime").withSchema(TableName.IdentityProjectMembershipRole)
|
||||||
identityId,
|
);
|
||||||
identityName,
|
|
||||||
identityAuthMethod,
|
const members = sqlNestRelationships({
|
||||||
...el
|
data: docs,
|
||||||
}) => ({
|
parentMapper: ({ identityId, identityName, identityAuthMethod, id, createdAt, updatedAt }) => ({
|
||||||
...el,
|
id,
|
||||||
identityId,
|
identityId,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
identity: {
|
identity: {
|
||||||
id: identityId,
|
id: identityId,
|
||||||
name: identityName,
|
name: identityName,
|
||||||
authMethod: identityAuthMethod
|
authMethod: identityAuthMethod
|
||||||
},
|
}
|
||||||
customRole: el.roleId
|
}),
|
||||||
? {
|
key: "id",
|
||||||
id: crId,
|
childrenMapper: [
|
||||||
name: crName,
|
{
|
||||||
slug: crSlug,
|
label: "roles" as const,
|
||||||
permissions: crPermission,
|
key: "membershipRoleId",
|
||||||
description: crDescription
|
mapper: ({
|
||||||
}
|
role,
|
||||||
: undefined
|
customRoleId,
|
||||||
})
|
customRoleName,
|
||||||
);
|
customRoleSlug,
|
||||||
|
membershipRoleId,
|
||||||
|
temporaryRange,
|
||||||
|
temporaryMode,
|
||||||
|
temporaryAccessEndTime,
|
||||||
|
temporaryAccessStartTime,
|
||||||
|
isTemporary
|
||||||
|
}) => ({
|
||||||
|
id: membershipRoleId,
|
||||||
|
role,
|
||||||
|
customRoleId,
|
||||||
|
customRoleName,
|
||||||
|
customRoleSlug,
|
||||||
|
temporaryRange,
|
||||||
|
temporaryMode,
|
||||||
|
temporaryAccessEndTime,
|
||||||
|
temporaryAccessStartTime,
|
||||||
|
isTemporary
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return members;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindByProjectId" });
|
throw new DatabaseError({ error, name: "FindByProjectId" });
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TIdentityProjectMembershipRoleDALFactory = ReturnType<typeof identityProjectMembershipRoleDALFactory>;
|
||||||
|
|
||||||
|
export const identityProjectMembershipRoleDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.IdentityProjectMembershipRole);
|
||||||
|
return orm;
|
||||||
|
};
|
@ -1,15 +1,20 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
import { ProjectMembershipRole, TProjectRoles } from "@app/db/schemas";
|
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
||||||
|
import { groupBy } from "@app/lib/fn";
|
||||||
|
|
||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
|
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
|
||||||
|
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
||||||
import { TIdentityProjectDALFactory } from "./identity-project-dal";
|
import { TIdentityProjectDALFactory } from "./identity-project-dal";
|
||||||
|
import { TIdentityProjectMembershipRoleDALFactory } from "./identity-project-membership-role-dal";
|
||||||
import {
|
import {
|
||||||
TCreateProjectIdentityDTO,
|
TCreateProjectIdentityDTO,
|
||||||
TDeleteProjectIdentityDTO,
|
TDeleteProjectIdentityDTO,
|
||||||
@ -19,7 +24,12 @@ import {
|
|||||||
|
|
||||||
type TIdentityProjectServiceFactoryDep = {
|
type TIdentityProjectServiceFactoryDep = {
|
||||||
identityProjectDAL: TIdentityProjectDALFactory;
|
identityProjectDAL: TIdentityProjectDALFactory;
|
||||||
|
identityProjectMembershipRoleDAL: Pick<
|
||||||
|
TIdentityProjectMembershipRoleDALFactory,
|
||||||
|
"create" | "transaction" | "insertMany" | "delete"
|
||||||
|
>;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
|
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
||||||
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRole">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRole">;
|
||||||
};
|
};
|
||||||
@ -30,7 +40,9 @@ export const identityProjectServiceFactory = ({
|
|||||||
identityProjectDAL,
|
identityProjectDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
projectDAL
|
identityProjectMembershipRoleDAL,
|
||||||
|
projectDAL,
|
||||||
|
projectRoleDAL
|
||||||
}: TIdentityProjectServiceFactoryDep) => {
|
}: TIdentityProjectServiceFactoryDep) => {
|
||||||
const createProjectIdentity = async ({
|
const createProjectIdentity = async ({
|
||||||
identityId,
|
identityId,
|
||||||
@ -70,11 +82,26 @@ export const identityProjectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
const isCustomRole = Boolean(customRole);
|
const isCustomRole = Boolean(customRole);
|
||||||
|
|
||||||
const projectIdentity = await identityProjectDAL.create({
|
const projectIdentity = await identityProjectDAL.transaction(async (tx) => {
|
||||||
identityId,
|
const identityProjectMembership = await identityProjectDAL.create(
|
||||||
projectId: project.id,
|
{
|
||||||
role: isCustomRole ? ProjectMembershipRole.Custom : role,
|
identityId,
|
||||||
roleId: customRole?.id
|
projectId: project.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : role,
|
||||||
|
roleId: customRole?.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await identityProjectMembershipRoleDAL.create(
|
||||||
|
{
|
||||||
|
projectMembershipId: identityProjectMembership.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : role,
|
||||||
|
customRoleId: customRole?.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return identityProjectMembership;
|
||||||
});
|
});
|
||||||
return projectIdentity;
|
return projectIdentity;
|
||||||
};
|
};
|
||||||
@ -82,7 +109,7 @@ export const identityProjectServiceFactory = ({
|
|||||||
const updateProjectIdentity = async ({
|
const updateProjectIdentity = async ({
|
||||||
projectId,
|
projectId,
|
||||||
identityId,
|
identityId,
|
||||||
role,
|
roles,
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
@ -106,28 +133,51 @@ export const identityProjectServiceFactory = ({
|
|||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
|
||||||
|
|
||||||
let customRole: TProjectRoles | undefined;
|
// validate custom roles input
|
||||||
if (role) {
|
const customInputRoles = roles.filter(
|
||||||
const { permission: rolePermission, role: customOrgRole } = await permissionService.getProjectPermissionByRole(
|
({ role }) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
|
||||||
role,
|
|
||||||
projectIdentity.projectId
|
|
||||||
);
|
|
||||||
|
|
||||||
const isCustomRole = Boolean(customOrgRole);
|
|
||||||
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
|
|
||||||
if (!hasRequiredNewRolePermission)
|
|
||||||
throw new BadRequestError({ message: "Failed to create a more privileged identity" });
|
|
||||||
if (isCustomRole) customRole = customOrgRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [updatedProjectIdentity] = await identityProjectDAL.update(
|
|
||||||
{ projectId, identityId: projectIdentity.identityId },
|
|
||||||
{
|
|
||||||
role: customRole ? ProjectMembershipRole.Custom : role,
|
|
||||||
roleId: customRole ? customRole.id : null
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
return updatedProjectIdentity;
|
const hasCustomRole = Boolean(customInputRoles.length);
|
||||||
|
const customRoles = hasCustomRole
|
||||||
|
? await projectRoleDAL.find({
|
||||||
|
projectId,
|
||||||
|
$in: { slug: customInputRoles.map(({ role }) => role) }
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
if (customRoles.length !== customInputRoles.length) throw new BadRequestError({ message: "Custom role not found" });
|
||||||
|
|
||||||
|
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
||||||
|
|
||||||
|
const santiziedProjectMembershipRoles = roles.map((inputRole) => {
|
||||||
|
const isCustomRole = Boolean(customRolesGroupBySlug?.[inputRole.role]?.[0]);
|
||||||
|
if (!inputRole.isTemporary) {
|
||||||
|
return {
|
||||||
|
projectMembershipId: projectIdentity.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
||||||
|
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// check cron or relative here later for now its just relative
|
||||||
|
const relativeTimeInMs = ms(inputRole.temporaryRange);
|
||||||
|
return {
|
||||||
|
projectMembershipId: projectIdentity.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : inputRole.role,
|
||||||
|
customRoleId: customRolesGroupBySlug[inputRole.role] ? customRolesGroupBySlug[inputRole.role][0].id : null,
|
||||||
|
isTemporary: true,
|
||||||
|
temporaryMode: ProjectUserMembershipTemporaryMode.Relative,
|
||||||
|
temporaryRange: inputRole.temporaryRange,
|
||||||
|
temporaryAccessStartTime: new Date(inputRole.temporaryAccessStartTime),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(inputRole.temporaryAccessStartTime).getTime() + relativeTimeInMs)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedRoles = await identityProjectMembershipRoleDAL.transaction(async (tx) => {
|
||||||
|
await identityProjectMembershipRoleDAL.delete({ projectMembershipId: projectIdentity.id }, tx);
|
||||||
|
return identityProjectMembershipRoleDAL.insertMany(santiziedProjectMembershipRoles, tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedRoles;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteProjectIdentity = async ({
|
const deleteProjectIdentity = async ({
|
||||||
|
@ -1,12 +1,26 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
|
||||||
|
|
||||||
export type TCreateProjectIdentityDTO = {
|
export type TCreateProjectIdentityDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
role: string;
|
role: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TUpdateProjectIdentityDTO = {
|
export type TUpdateProjectIdentityDTO = {
|
||||||
role: string;
|
roles: (
|
||||||
|
| {
|
||||||
|
role: string;
|
||||||
|
isTemporary?: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
role: string;
|
||||||
|
isTemporary: true;
|
||||||
|
temporaryMode: ProjectUserMembershipTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}
|
||||||
|
)[];
|
||||||
identityId: string;
|
identityId: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
@ -54,6 +54,8 @@ export const identityUaServiceFactory = ({
|
|||||||
const identityUa = await identityUaDAL.findOne({ clientId });
|
const identityUa = await identityUaDAL.findOne({ clientId });
|
||||||
if (!identityUa) throw new UnauthorizedError();
|
if (!identityUa) throw new UnauthorizedError();
|
||||||
|
|
||||||
|
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityUa.identityId });
|
||||||
|
|
||||||
checkIPAgainstBlocklist({
|
checkIPAgainstBlocklist({
|
||||||
ipAddress: ip,
|
ipAddress: ip,
|
||||||
trustedIps: identityUa.clientSecretTrustedIps as TIp[]
|
trustedIps: identityUa.clientSecretTrustedIps as TIp[]
|
||||||
@ -131,7 +133,7 @@ export const identityUaServiceFactory = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return { accessToken, identityUa, validClientSecretInfo, identityAccessToken };
|
return { accessToken, identityUa, validClientSecretInfo, identityAccessToken, identityMembershipOrg };
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachUa = async ({
|
const attachUa = async ({
|
||||||
|
@ -109,7 +109,7 @@ const getAppsGCPSecretManager = async ({ accessToken }: { accessToken: string })
|
|||||||
*/
|
*/
|
||||||
const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
|
const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
|
||||||
const res = (
|
const res = (
|
||||||
await request.get<{ name: string }[]>(`${IntegrationUrls.HEROKU_API_URL}/apps`, {
|
await request.get<{ name: string; id: string }[]>(`${IntegrationUrls.HEROKU_API_URL}/apps`, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/vnd.heroku+json; version=3",
|
Accept: "application/vnd.heroku+json; version=3",
|
||||||
Authorization: `Bearer ${accessToken}`
|
Authorization: `Bearer ${accessToken}`
|
||||||
@ -118,7 +118,8 @@ const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
).data;
|
).data;
|
||||||
|
|
||||||
const apps = res.map((a) => ({
|
const apps = res.map((a) => ({
|
||||||
name: a.name
|
name: a.name,
|
||||||
|
appId: a.id
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
|
@ -20,9 +20,11 @@ import {
|
|||||||
TDeleteIntegrationAuthsDTO,
|
TDeleteIntegrationAuthsDTO,
|
||||||
TGetIntegrationAuthDTO,
|
TGetIntegrationAuthDTO,
|
||||||
TGetIntegrationAuthTeamCityBuildConfigDTO,
|
TGetIntegrationAuthTeamCityBuildConfigDTO,
|
||||||
|
THerokuPipelineCoupling,
|
||||||
TIntegrationAuthAppsDTO,
|
TIntegrationAuthAppsDTO,
|
||||||
TIntegrationAuthBitbucketWorkspaceDTO,
|
TIntegrationAuthBitbucketWorkspaceDTO,
|
||||||
TIntegrationAuthChecklyGroupsDTO,
|
TIntegrationAuthChecklyGroupsDTO,
|
||||||
|
TIntegrationAuthHerokuPipelinesDTO,
|
||||||
TIntegrationAuthNorthflankSecretGroupDTO,
|
TIntegrationAuthNorthflankSecretGroupDTO,
|
||||||
TIntegrationAuthQoveryEnvironmentsDTO,
|
TIntegrationAuthQoveryEnvironmentsDTO,
|
||||||
TIntegrationAuthQoveryOrgsDTO,
|
TIntegrationAuthQoveryOrgsDTO,
|
||||||
@ -576,6 +578,38 @@ export const integrationAuthServiceFactory = ({
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getHerokuPipelines = async ({ id, actor, actorId, actorOrgId }: TIntegrationAuthHerokuPipelinesDTO) => {
|
||||||
|
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||||
|
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
integrationAuth.projectId,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
|
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
|
||||||
|
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
|
||||||
|
|
||||||
|
const { data } = await request.get<THerokuPipelineCoupling[]>(
|
||||||
|
`${IntegrationUrls.HEROKU_API_URL}/pipeline-couplings`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: "application/vnd.heroku+json; version=3",
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.map(({ app: { id: appId }, stage, pipeline: { id: pipelineId, name } }) => ({
|
||||||
|
app: { appId },
|
||||||
|
stage,
|
||||||
|
pipeline: { pipelineId, name }
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const getRailwayEnvironments = async ({ id, actor, actorId, actorOrgId, appId }: TIntegrationAuthRailwayEnvDTO) => {
|
const getRailwayEnvironments = async ({ id, actor, actorId, actorOrgId, appId }: TIntegrationAuthRailwayEnvDTO) => {
|
||||||
const integrationAuth = await integrationAuthDAL.findById(id);
|
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||||
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
|
if (!integrationAuth) throw new BadRequestError({ message: "Failed to find integration" });
|
||||||
@ -649,33 +683,21 @@ export const integrationAuthServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
|
const botKey = await projectBotService.getBotKey(integrationAuth.projectId);
|
||||||
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
|
const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey);
|
||||||
if (appId) {
|
|
||||||
|
if (appId && appId !== "") {
|
||||||
const query = `
|
const query = `
|
||||||
query project($id: String!) {
|
query project($id: String!) {
|
||||||
project(id: $id) {
|
project(id: $id) {
|
||||||
createdAt
|
services {
|
||||||
deletedAt
|
edges {
|
||||||
id
|
node {
|
||||||
description
|
id
|
||||||
expiredAt
|
name
|
||||||
isPublic
|
}
|
||||||
isTempProject
|
}
|
||||||
isUpdatable
|
}
|
||||||
name
|
}
|
||||||
prDeploys
|
|
||||||
teamId
|
|
||||||
updatedAt
|
|
||||||
upstreamUrl
|
|
||||||
services {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const variables = {
|
const variables = {
|
||||||
@ -711,6 +733,7 @@ export const integrationAuthServiceFactory = ({
|
|||||||
);
|
);
|
||||||
return edges.map(({ node: { name, id: serviceId } }) => ({ name, serviceId }));
|
return edges.map(({ node: { name, id: serviceId } }) => ({ name, serviceId }));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -915,6 +938,7 @@ export const integrationAuthServiceFactory = ({
|
|||||||
getQoveryApps,
|
getQoveryApps,
|
||||||
getQoveryEnvs,
|
getQoveryEnvs,
|
||||||
getQoveryJobs,
|
getQoveryJobs,
|
||||||
|
getHerokuPipelines,
|
||||||
getQoveryOrgs,
|
getQoveryOrgs,
|
||||||
getQoveryProjects,
|
getQoveryProjects,
|
||||||
getQoveryContainers,
|
getQoveryContainers,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user