diff --git a/backend/bdd/.env.example b/backend/bdd/.env.example new file mode 100644 index 000000000..8e59c033d --- /dev/null +++ b/backend/bdd/.env.example @@ -0,0 +1,14 @@ +# API URL to the Infisical server +INFISICAL_API_URL="http://localhost:8080" +# JWT token with admin permission for the cert projects +INFISICAL_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRoTWV0aG9kIjoiZW1haWwiLCJhdXRoVG9rZW5UeXBlIjoiYWNjZXNzVG9rZW4iLCJ1c2VySWQiOiJkOWZlMzMwZi00OTQwLTQ3ZmYtYmE4Yy0zZGUxYTVlYjYzNGEiLCJ0b2tlblZlcnNpb25JZCI6ImM4MWZhODY1LTFjYTAtNGNmZS1iNjM5LThlMDI3M2E2N2JjYyIsImFjY2Vzc1ZlcnNpb24iOjEsIm9yZ2FuaXphdGlvbklkIjoiM2I5OTRkNTktMjE5Ny00MjcwLWE3MGMtOTczMzdmZjlmYTRkIiwiaWF0IjoxNzYyMjAzMjQwLCJleHAiOjE3NjMwNjcyNDB9.KFYeMYAv3Ceis0hp-pTa8fsLLWbT-JcqhuWyIY0DWU0" +# PKI project id +PROJECT_ID="c051e74c-48a7-4724-832c-d5b496698546" +# Certificate CA id +CERT_CA_ID="2f0d9820-e5a8-48bb-aac8-deed9d868a1e" +# Certificate template id +CERT_TEMPLATE_ID="4dbf6bb0-6e86-4ee6-8550-9171428c8e82" +# ACME profile ID +PROFILE_ID="108c6303-ab8c-4986-ab88-eefe11bb5553" +# ACME profile EAB secret +EAB_SECRET="JHYxJDEwJFJldE9tb3dkUU9XVnJLZWFia3IxVC94L1pIbHRoQnJsNVRKZWFoV1hpNTczVHpwMFNGZzU4OGtuU3NVK1crVGM" diff --git a/backend/bdd/.python-version b/backend/bdd/.python-version new file mode 100644 index 000000000..e4fba2183 --- /dev/null +++ b/backend/bdd/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/backend/bdd/README.md b/backend/bdd/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/backend/bdd/features/environment.py b/backend/bdd/features/environment.py new file mode 100644 index 000000000..b355f98ae --- /dev/null +++ b/backend/bdd/features/environment.py @@ -0,0 +1,26 @@ +import os + +import httpx +from behave.runner import Context +from dotenv import load_dotenv + +load_dotenv() + +BASE_URL = os.environ.get("INFISICAL_API_URL", "http://localhost:8080") +PROJECT_ID = os.environ.get("PROJECT_ID") +CERT_CA_ID = os.environ.get("CERT_CA_ID") +CERT_TEMPLATE_ID = os.environ.get("CERT_TEMPLATE_ID") +AUTH_TOKEN = os.environ.get("INFISICAL_TOKEN") + + +def before_all(context: Context): + context.vars = { + "BASE_URL": BASE_URL, + "PROJECT_ID": PROJECT_ID, + "CERT_CA_ID": CERT_CA_ID, + "CERT_TEMPLATE_ID": CERT_TEMPLATE_ID, + "AUTH_TOKEN": AUTH_TOKEN, + } + context.http_client = httpx.Client( + base_url=BASE_URL, # headers={"Authorization": f"Bearer {AUTH_TOKEN}"} + ) diff --git a/backend/bdd/features/pki/acme/account.feature b/backend/bdd/features/pki/acme/account.feature new file mode 100644 index 000000000..7e1d67a93 --- /dev/null +++ b/backend/bdd/features/pki/acme/account.feature @@ -0,0 +1,6 @@ +Feature: Account + + Scenario: Create a new account + Given I have an ACME cert profile as "acme_profile" + When I have an ACME client connecting to {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory + Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account diff --git a/backend/bdd/features/pki/acme/auth.feature b/backend/bdd/features/pki/acme/auth.feature new file mode 100644 index 000000000..4605e2eef --- /dev/null +++ b/backend/bdd/features/pki/acme/auth.feature @@ -0,0 +1,36 @@ +Feature: Authorization + + Scenario: Get authorization + Given I have an ACME cert profile as "acme_profile" + When I have an ACME client connecting to {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory +# # TODO: make it I have an account already instead? + Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account + When I create certificate signing request as csr + Then I add names to certificate signing request csr + """ + { + "COMMON_NAME": "localhost" + } + """ + Then I create a RSA private key pair as cert_key + Then I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format + Then I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order + Then the value order.authorizations[0].uri with jq "." should match pattern {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/authorizations/(.+) + Then the value order.authorizations[0].body with jq ".status" should be equal to "pending" + Then the value order.authorizations[0].body with jq ".challenges | map(pick(.type, .status)) | sort_by(.type)" should be equal to json + """ + [ + { + "type": "http-01", + "status": "pending" + } + ] + """ + Then the value order.authorizations[0].body with jq ".challenges | map(.status) | sort" should be equal to ["pending"] + Then the value order.authorizations[0].body with jq ".identifier" should be equal to json + """ + { + "type": "dns", + "value": "localhost" + } + """ diff --git a/backend/bdd/features/pki/acme/cert-profile.feature b/backend/bdd/features/pki/acme/cert-profile.feature new file mode 100644 index 000000000..7ce1ecc8c --- /dev/null +++ b/backend/bdd/features/pki/acme/cert-profile.feature @@ -0,0 +1,49 @@ +Feature: ACME Cert Profile + + Scenario: Create a cert profile + Given I make a random slug as profile_slug + Given I use AUTH_TOKEN for authentication + When I send a POST request to "/api/v1/pki/certificate-profiles" with JSON payload + """ + { + "projectId": "{PROJECT_ID}", + "slug": "{profile_slug}", + "description": "", + "enrollmentType": "acme", + "caId": "{CERT_CA_ID}", + "certificateTemplateId": "{CERT_TEMPLATE_ID}", + "acmeConfig": {} + } + """ + Then the value response.status_code should be equal to 200 + Then the value response with jq ".certificateProfile.id" should be present + Then the value response with jq ".certificateProfile.slug" should be equal to "{profile_slug}" + Then the value response with jq ".certificateProfile.caId" should be equal to "{CERT_CA_ID}" + Then the value response with jq ".certificateProfile.certificateTemplateId" should be equal to "{CERT_TEMPLATE_ID}" + Then the value response with jq ".certificateProfile.enrollmentType" should be equal to "acme" + + Scenario: Reveal EAB secret + Given I make a random slug as profile_slug + Given I use AUTH_TOKEN for authentication + When I send a POST request to "/api/v1/pki/certificate-profiles" with JSON payload + """ + { + "projectId": "{PROJECT_ID}", + "slug": "{profile_slug}", + "description": "", + "enrollmentType": "acme", + "caId": "{CERT_CA_ID}", + "certificateTemplateId": "{CERT_TEMPLATE_ID}", + "acmeConfig": {} + } + """ + Then the value response.status_code should be equal to 200 + And I memorize response with jq ".certificateProfile.id" as profile_id + When I send a GET request to "/api/v1/pki/certificate-profiles/{profile_id}/acme/eab-secret/reveal" + Then the value response.status_code should be equal to 200 + Then the value response with jq ".eabKid" should be equal to "{profile_id}" + Then the value response with jq ".eabSecret" should be present + And I memorize response with jq ".eabKid" as eab_kid + And I memorize response with jq ".eabSecret" as eab_secret + When I have an ACME client connecting to {BASE_URL}/api/v1/pki/acme/profiles/{profile_id}/directory + Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{eab_kid}" with secret "{eab_secret}" as acme_account diff --git a/backend/bdd/features/pki/acme/challenge.feature b/backend/bdd/features/pki/acme/challenge.feature new file mode 100644 index 000000000..ba9970e43 --- /dev/null +++ b/backend/bdd/features/pki/acme/challenge.feature @@ -0,0 +1,23 @@ +Feature: Challenge + + Scenario: Validate challenge + Given I have an ACME cert profile as "acme_profile" + When I have an ACME client connecting to {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory +# # TODO: make it I have an account already instead? + Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account + When I create certificate signing request as csr + Then I add names to certificate signing request csr + """ + { + "COMMON_NAME": "localhost" + } + """ + Then I create a RSA private key pair as cert_key + Then I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format + Then I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order + Then I select challenge with type http-01 for domain localhost from order at order as challenge + Then I serve challenge response for challenge at localhost + Then I tell ACME server that challenge is ready to be verified + Then I poll and finalize the ACME order order as finalized_order + Then the value finalized_order.body with jq ".status" should be equal to "valid" + # TODO: check the fullchain pem content of the order diff --git a/backend/bdd/features/pki/acme/dicrectory.feature b/backend/bdd/features/pki/acme/dicrectory.feature new file mode 100644 index 000000000..481a3337a --- /dev/null +++ b/backend/bdd/features/pki/acme/dicrectory.feature @@ -0,0 +1,14 @@ +Feature: Directory + + Scenario: Get the directory of ACME service urls + Given I have an ACME cert profile as "acme_profile" + When I send a GET request to "/api/v1/pki/acme/profiles/{acme_profile.id}/directory" + Then the response status code should be "200" + Then the response body should match JSON value + """ + { + "newNonce": "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/new-nonce", + "newAccount": "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/new-account", + "newOrder": "{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/new-order" + } + """ diff --git a/backend/bdd/features/pki/acme/nonce.feature b/backend/bdd/features/pki/acme/nonce.feature new file mode 100644 index 000000000..7bbeb3b9d --- /dev/null +++ b/backend/bdd/features/pki/acme/nonce.feature @@ -0,0 +1,7 @@ +Feature: Nonce + + Scenario: Generate a new nonce + Given I have an ACME cert profile as "acme_profile" + When I send a HEAD request to "/api/v1/pki/acme/profiles/{acme_profile.id}/new-nonce" + Then the response status code should be "200" + Then the response header "Replay-Nonce" should contains non-empty value diff --git a/backend/bdd/features/pki/acme/order.feature b/backend/bdd/features/pki/acme/order.feature new file mode 100644 index 000000000..046dcda55 --- /dev/null +++ b/backend/bdd/features/pki/acme/order.feature @@ -0,0 +1,74 @@ +Feature: Order + + Scenario: Create a new order + Given I have an ACME cert profile as "acme_profile" + When I have an ACME client connecting to {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory +# # TODO: make it I have an account already instead? + Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account + When I create certificate signing request as csr + Then I add names to certificate signing request csr + """ + { + "COMMON_NAME": "localhost" + } + """ + Then I create a RSA private key pair as cert_key + Then I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format + Then I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order + Then the value order.uri with jq "." should match pattern {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/orders/(.+) + Then the value order.body with jq ".status" should be equal to "pending" + Then the value order.body with jq ".identifiers" should be equal to [{"type": "dns", "value": "localhost"}] + Then the value order.body with jq ".finalize" should match pattern {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/orders/(.+)/finalize + Then the value order.body with jq "all(.authorizations[]; startswith("{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/authorizations/"))" should be equal to true + + Scenario: Create a new order with SANs + Given I have an ACME cert profile as "acme_profile" + When I have an ACME client connecting to {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory +# # TODO: make it I have an account already instead? + Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account + When I create certificate signing request as csr + Then I add names to certificate signing request csr + """ + { + "COMMON_NAME": "localhost" + } + """ + Then I add subject alternative name to certificate signing request csr + """ + [ + "example.com", + "infisical.com" + ] + """ + Then I create a RSA private key pair as cert_key + Then I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format + Then I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order + Then the value order.body with jq ".identifiers | sort_by(.value)" should be equal to json + """ + [ + {"type": "dns", "value": "example.com"}, + {"type": "dns", "value": "infisical.com"}, + {"type": "dns", "value": "localhost"} + ] + """ + + Scenario: Fetch an order + Given I have an ACME cert profile as "acme_profile" + When I have an ACME client connecting to {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/directory +# # TODO: make it I have an account already instead? + Then I register a new ACME account with email fangpen@infisical.com and EAB key id "{acme_profile.eab_kid}" with secret "{acme_profile.eab_secret}" as acme_account + When I create certificate signing request as csr + Then I add names to certificate signing request csr + """ + { + "COMMON_NAME": "localhost" + } + """ + Then I create a RSA private key pair as cert_key + Then I sign the certificate signing request csr with private key cert_key and output it as csr_pem in PEM format + Then I submit the certificate signing request PEM csr_pem certificate order to the ACME server as order + Then I send an ACME post-as-get to order.uri as fetched_order + Then the value fetched_order with jq ".status" should be equal to "pending" + Then the value fetched_order with jq ".identifiers" should be equal to [{"type": "dns", "value": "localhost"}] + Then the value fetched_order with jq ".finalize" should match pattern {BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/orders/(.+)/finalize + Then the value fetched_order with jq "all(.authorizations[]; startswith("{BASE_URL}/api/v1/pki/acme/profiles/{acme_profile.id}/authorizations/"))" should be equal to true diff --git a/backend/bdd/features/steps/pki_acme.py b/backend/bdd/features/steps/pki_acme.py new file mode 100644 index 000000000..d0fa627cd --- /dev/null +++ b/backend/bdd/features/steps/pki_acme.py @@ -0,0 +1,486 @@ +import json +import logging +import os +import re +import threading + +import httpx +import jq +import requests +import glom +from faker import Faker +from acme import client +from acme import messages +from acme import standalone +from behave.runner import Context +from behave import given +from behave import when +from behave import then +from josepy.jwk import JWKRSA +from josepy import JSONObjectWithFields +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography import x509 +from cryptography.x509.oid import NameOID +from cryptography.hazmat.primitives import hashes + +ACC_KEY_BITS = 2048 +ACC_KEY_PUBLIC_EXPONENT = 65537 +logger = logging.getLogger(__name__) +faker = Faker() + + +class AcmeProfile: + def __init__(self, id: str, eab_kid: str, eab_secret: str): + self.id = id + self.eab_kid = eab_kid + self.eab_secret = eab_secret + + +def replace_vars(payload: dict | list | int | float | str, vars: dict): + if isinstance(payload, dict): + return { + replace_vars(key, vars): replace_vars(value, vars) + for key, value in payload.items() + } + elif isinstance(payload, list): + return [replace_vars(item, vars) for item in payload] + elif isinstance(payload, str): + return payload.format(**vars) + else: + return payload + + +def parse_glom_path(path_str: str) -> glom.Path: + """ + Parse a glom path string with 'attr[index]' syntax into a Path object. + + Examples: + >>> parse_glom_path('authorizations[0]') == Path('authorizations', 0) + True + >>> parse_glom_path('data.items[1].name') == Path('data', 'items', 1, 'name') + True + >>> parse_glom_path('user.addresses[0].street') == Path('user', 'addresses', 0, 'street') + True + """ + parts = [] + + # Split by dots, but preserve bracketed content + tokens = re.split(r"(? dict | None: + headers = {} + auth_token = getattr(context, "auth_token", None) + if auth_token is not None: + headers["authorization"] = "Bearer {}".format(auth_token) + if not headers: + return None + return headers + + +@given("I make a random {faker_type} as {var_name}") +def step_impl(context: Context, faker_type: str, var_name: str): + context.vars[var_name] = getattr(faker, faker_type)() + + +@given('I have an ACME cert profile as "{profile_var}"') +def step_impl(context: Context, profile_var: str): + # TODO: Fixed value for now, just to make test much easier, + # we should call infisical API to create such profile instead + # in the future + profile_id = os.getenv("PROFILE_ID") + kid = profile_id + secret = os.getenv("EAB_SECRET") + context.vars[profile_var] = AcmeProfile( + profile_id, + eab_kid=kid, + eab_secret=secret, + ) + + +@given("I use {token_var} for authentication") +def step_impl(context: Context, token_var: str): + context.auth_token = eval_var(context, token_var) + + +@when('I send a {method} request to "{url}"') +def step_impl(context: Context, method: str, url: str): + logger.debug("Sending %s request to %s", method, url) + response = context.http_client.request( + method, url.format(**context.vars), headers=prepare_headers(context) + ) + context.vars["response"] = response + logger.debug("Response status: %r", response.status_code) + try: + logger.debug("Response JSON payload: %r", response.json()) + except json.decoder.JSONDecodeError: + pass + + +@when('I send a {method} request to "{url}" with JSON payload') +def step_impl(context: Context, method: str, url: str): + json_payload = json.loads(context.text) + json_payload = replace_vars(json_payload, context.vars) + logger.debug( + "Sending %s request to %s with JSON payload: %s", + method, + url, + json.dumps(json_payload), + ) + response = context.http_client.request( + method, + url.format(**context.vars), + headers=prepare_headers(context), + json=json_payload, + ) + context.vars["response"] = response + logger.debug("Response status: %r", response.status_code) + logger.debug("Response JSON payload: %r", response.json()) + + +@when("I have an ACME client connecting to {url}") +def step_impl(context: Context, url: str): + private_key = rsa.generate_private_key( + public_exponent=ACC_KEY_PUBLIC_EXPONENT, key_size=ACC_KEY_BITS + ) + pem_bytes = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), + ) + acc_jwk = JWKRSA.load(pem_bytes) + net = client.ClientNetwork(acc_jwk) + directory_url = url.format(**context.vars) + directory = client.ClientV2.get_directory(directory_url, net) + context.acme_client = client.ClientV2(directory, net=net) + + +@then('the response status code should be "{expected_status_code:d}"') +def step_impl(context: Context, expected_status_code: int): + assert context.vars["response"].status_code == expected_status_code, ( + f"{context.vars['response'].status_code} != {expected_status_code}" + ) + + +@then('the response header "{header}" should contains non-empty value') +def step_impl(context: Context, header: str): + header_value = context.vars["response"].headers.get(header) + assert header_value is not None, f"Header {header} not found in response" + assert header_value, ( + f"Header {header} found in response, but value {header_value:!r} is empty" + ) + + +@then("the response body should match JSON value") +def step_impl(context: Context): + payload = context.vars["response"].json() + expected = json.loads(context.text) + replaced = replace_vars(expected, context.vars) + assert payload == replaced, f"{payload} != {replaced}" + + +@then( + 'I register a new ACME account with email {email} and EAB key id "{kid}" with secret "{secret}" as {account_var}' +) +def step_impl(context: Context, email: str, kid: str, secret: str, account_var: str): + acme_client = context.acme_client + account_public_key = acme_client.net.key.public_key() + eab = messages.ExternalAccountBinding.from_data( + account_public_key=account_public_key, + kid=replace_vars(kid, context.vars), + hmac_key=replace_vars(secret, context.vars), + directory=acme_client.directory, + hmac_alg="HS256", + ) + registration = messages.NewRegistration.from_data( + email=email, + external_account_binding=eab, + ) + context.vars[account_var] = acme_client.new_account(registration) + + +@then( + "I submit the certificate signing request PEM {pem_var} certificate order to the ACME server as {order_var}" +) +def step_impl(context: Context, pem_var: str, order_var: str): + context.vars[order_var] = context.acme_client.new_order(context.vars[pem_var]) + + +@then("I send an ACME post-as-get to {uri_path} as {res_var}") +def step_impl(context: Context, uri_path: str, res_var: str): + uri_value = eval_var(context, uri_path) + context.vars[res_var] = context.acme_client._post_as_get(uri_value) + + +@when("I create certificate signing request as {csr_var}") +def step_impl(context: Context, csr_var: str): + context.vars[csr_var] = x509.CertificateSigningRequestBuilder() + + +@then("I add names to certificate signing request {csr_var}") +def step_impl(context: Context, csr_var: str): + names = json.loads(context.text) + builder: x509.CertificateSigningRequestBuilder = context.vars[csr_var] + context.vars[csr_var] = builder.subject_name( + x509.Name( + [ + x509.NameAttribute(getattr(NameOID, name), value) + for name, value in names.items() + ] + ) + ) + + +@then("I add subject alternative name to certificate signing request {csr_var}") +def step_impl(context: Context, csr_var: str): + names = json.loads(context.text) + builder: x509.CertificateSigningRequestBuilder = context.vars[csr_var] + context.vars[csr_var] = builder.add_extension( + x509.SubjectAlternativeName([x509.DNSName(name) for name in names]), + critical=False, + ) + + +@then("I create a RSA private key pair as {rsa_key_var}") +def step_impl(context: Context, rsa_key_var: str): + context.vars[rsa_key_var] = rsa.generate_private_key( + # TODO: make them configurable if we need to + public_exponent=65537, + key_size=2048, + ) + + +@then( + "I sign the certificate signing request {csr_var} with private key {pk_var} and output it as {pem_var} in PEM format" +) +def step_impl(context: Context, csr_var: str, pk_var: str, pem_var: str): + context.vars[pem_var] = ( + context.vars[csr_var] + .sign(context.vars[pk_var], hashes.SHA256()) + .public_bytes(serialization.Encoding.PEM) + ) + + +@then("the value {var_path} should be true for jq {query}") +def step_impl(context: Context, var_path: str, query: str): + value = eval_var(context, var_path) + result = jq.compile(replace_vars(query, context.vars)).input_value(value).first() + assert result, f"{value} does not match {query}" + + +def apply_value_with_jq(context: Context, var_path: str, jq_query: str): + value = eval_var(context, var_path) + return value, jq.compile(replace_vars(jq_query, context.vars)).input_value( + value + ).first() + + +@then('the value {var_path} with jq "{jq_query}" should be equal to json') +def step_impl(context: Context, var_path: str, jq_query: str): + value, result = apply_value_with_jq( + context=context, + var_path=var_path, + jq_query=jq_query, + ) + expected_value = json.loads(context.text) + assert result == expected_value, ( + f"{json.dumps(value)!r} with jq {jq_query!r}, the result {json.dumps(result)!r} does not match {json.dumps(expected_value)!r}" + ) + + +@then('the value {var_path} with jq "{jq_query}" should be present') +def step_impl(context: Context, var_path: str, jq_query: str): + value, result = apply_value_with_jq( + context=context, + var_path=var_path, + jq_query=jq_query, + ) + assert result, ( + f"{json.dumps(value)!r} with jq {jq_query!r}, the result {json.dumps(result)!r} is not present" + ) + + +@then('the value {var_path} with jq "{jq_query}" should be equal to {expected}') +def step_impl(context: Context, var_path: str, jq_query: str, expected: str): + value, result = apply_value_with_jq( + context=context, + var_path=var_path, + jq_query=jq_query, + ) + expected_value = replace_vars(json.loads(expected), context.vars) + assert result == expected_value, ( + f"{json.dumps(value)!r} with jq {jq_query!r}, the result {json.dumps(result)!r} does not match {json.dumps(expected_value)!r}" + ) + + +@then('the value {var_path} with jq "{jq_query}" should match pattern {regex}') +def step_impl(context: Context, var_path: str, jq_query: str, regex: str): + value, result = apply_value_with_jq( + context=context, + var_path=var_path, + jq_query=jq_query, + ) + assert re.match(replace_vars(regex, context.vars), result), ( + f"{json.dumps(value)!r} with jq {jq_query!r}, the result {json.dumps(result)!r} does not match {regex!r}" + ) + + +@then("the value {var_path} should be equal to json") +def step_impl(context: Context, var_path: str): + value = eval_var(context, var_path) + expected_value = json.loads(context.text) + assert value == expected_value, f"{value!r} does not match {expected_value!r}" + + +@then("the value {var_path} should be equal to {expected}") +def step_impl(context: Context, var_path: str, expected: str): + value = eval_var(context, var_path) + expected_value = json.loads(expected) + assert value == expected_value, f"{value!r} does not match {expected_value!r}" + + +@then('I memorize {var_path} with jq "{jq_query}" as {var_name}') +def step_impl(context: Context, var_path: str, jq_query, var_name: str): + _, value = apply_value_with_jq( + context=context, + var_path=var_path, + jq_query=jq_query, + ) + context.vars[var_name] = value + + +@then("I memorize {var_path} as {var_name}") +def step_impl(context: Context, var_path: str, var_name: str): + value = eval_var(context, var_path) + context.vars[var_name] = value + + +@then("I print the value {var_path}") +def step_impl(context: Context, var_path: str): + value = eval_var(context, var_path) + print(json.dumps(value.json(), indent=2)) + + +@then( + "I select challenge with type {challenge_type} for domain {domain} from order at {var_path} as {challenge_var}" +) +def step_impl( + context: Context, + challenge_type: str, + domain: str, + var_path: str, + challenge_var: str, +): + order = eval_var(context, var_path, as_json=False) + if not isinstance(order, messages.OrderResource): + raise ValueError( + f"Expected OrderResource but got {type(order)!r} at {var_path!r}" + ) + auths = list( + filter(lambda o: o.body.identifier.value == domain, order.authorizations) + ) + if not auths: + raise ValueError( + f"Authorization for domain {domain!r} not found in {var_path!r}" + ) + if len(auths) > 1: + raise ValueError( + f"More than one order for domain {domain!r} found in {var_path!r}" + ) + auth = auths[0] + + challenges = list(filter(lambda a: a.typ == challenge_type, auth.body.challenges)) + if not challenges: + raise ValueError( + f"Authorization type {challenge_type!r} not found in {var_path!r}" + ) + if len(challenges) > 1: + raise ValueError( + f"More than one authorization for type {challenge_type!r} found in {var_path!r}" + ) + context.vars[challenge_var] = challenges[0] + + +@then("I serve challenge response for {var_path} at {hostname}") +def step_impl(context: Context, var_path: str, hostname: str): + if hostname != "localhost": + raise ValueError("Currently only localhost is supported") + challenge = eval_var(context, var_path, as_json=False) + response, validation = challenge.response_and_validation( + context.acme_client.net.key + ) + resource = standalone.HTTP01RequestHandler.HTTP01Resource( + chall=challenge.chall, response=response, validation=validation + ) + # TODO: make port configurable + servers = standalone.HTTP01DualNetworkedServers(("0.0.0.0", 8087), {resource}) + # Start client standalone web server. + web_server = threading.Thread(name="web_server", target=servers.serve_forever) + web_server.daemon = True + web_server.start() + context.web_server = web_server + + +@then("I tell ACME server that {var_path} is ready to be verified") +def step_impl(context: Context, var_path: str): + challenge = eval_var(context, var_path, as_json=False) + acme_client = context.acme_client + response, validation = challenge.response_and_validation(acme_client.net.key) + acme_client.answer_challenge(challenge, response) + + +@then("I poll and finalize the ACME order {var_path} as {finalized_var}") +def step_impl(context: Context, var_path: str, finalized_var: str): + order = eval_var(context, var_path, as_json=False) + acme_client = context.acme_client + finalized_order = acme_client.poll_and_finalize(order) + context.vars[finalized_var] = finalized_order diff --git a/backend/bdd/pyproject.toml b/backend/bdd/pyproject.toml new file mode 100644 index 000000000..4f19cb0de --- /dev/null +++ b/backend/bdd/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "bdd" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "acme>=5.1.0", + "behave>=1.3.3", + "dotenv>=0.9.9", + "faker>=37.12.0", + "glom>=24.11.0", + "httpx>=0.28.1", + "josepy>=2.2.0", + "jq>=1.10.0", +] diff --git a/backend/bdd/uv.lock b/backend/bdd/uv.lock new file mode 100644 index 000000000..8ae5b6c01 --- /dev/null +++ b/backend/bdd/uv.lock @@ -0,0 +1,558 @@ +version = 1 +revision = 2 +requires-python = ">=3.12" + +[[package]] +name = "acme" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "josepy" }, + { name = "pyopenssl" }, + { name = "pyrfc3339" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/f6/897be0abeb0e64f0e6136a8a6369a54d2a603a44cb7a411f6d77dbafb4ac/acme-5.1.0.tar.gz", hash = "sha256:7b97820857d9baffed98bca50ab82bb6a636e447865d7a013a7bdd7972f03cda", size = 89982, upload-time = "2025-10-07T17:30:38.579Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/0b/4d0421412bb063f4393ae7ebf3a9a6fde621aed187a1140ccf7f9e22b823/acme-5.1.0-py3-none-any.whl", hash = "sha256:80e9c315d82302bb97279f4516ff31230d29195ab9d4a6c9411ceec20481b61e", size = 94151, upload-time = "2025-10-07T17:30:15.994Z" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "bdd" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "acme" }, + { name = "behave" }, + { name = "dotenv" }, + { name = "faker" }, + { name = "glom" }, + { name = "httpx" }, + { name = "josepy" }, + { name = "jq" }, +] + +[package.metadata] +requires-dist = [ + { name = "acme", specifier = ">=5.1.0" }, + { name = "behave", specifier = ">=1.3.3" }, + { name = "dotenv", specifier = ">=0.9.9" }, + { name = "faker", specifier = ">=37.12.0" }, + { name = "glom", specifier = ">=24.11.0" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "josepy", specifier = ">=2.2.0" }, + { name = "jq", specifier = ">=1.10.0" }, +] + +[[package]] +name = "behave" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "cucumber-expressions" }, + { name = "cucumber-tag-expressions" }, + { name = "parse" }, + { name = "parse-type" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/51/f37442fe648b3e35ecf69bee803fa6db3f74c5b46d6c882d0bc5654185a2/behave-1.3.3.tar.gz", hash = "sha256:2b8f4b64ed2ea756a5a2a73e23defc1c4631e9e724c499e46661778453ebaf51", size = 892639, upload-time = "2025-09-04T12:12:02.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/71/06f74ffed6d74525c5cd6677c97bd2df0b7649e47a249cf6a0c2038083b2/behave-1.3.3-py2.py3-none-any.whl", hash = "sha256:89bdb62af8fb9f147ce245736a5de69f025e5edfb66f1fbe16c5007493f842c0", size = 223594, upload-time = "2025-09-04T12:12:00.3Z" }, +] + +[[package]] +name = "boltons" +version = "25.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/54/71a94d8e02da9a865587fb3fff100cb0fc7aa9f4d5ed9ed3a591216ddcc7/boltons-25.0.0.tar.gz", hash = "sha256:e110fbdc30b7b9868cb604e3f71d4722dd8f4dcb4a5ddd06028ba8f1ab0b5ace", size = 246294, upload-time = "2025-02-03T05:57:59.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/7f/0e961cf3908bc4c1c3e027de2794f867c6c89fb4916fc7dba295a0e80a2d/boltons-25.0.0-py3-none-any.whl", hash = "sha256:dc9fb38bf28985715497d1b54d00b62ea866eca3938938ea9043e254a3a6ca62", size = 194210, upload-time = "2025-02-03T05:57:56.705Z" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, + { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, + { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, + { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, + { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, + { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, + { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, + { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, + { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, + { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, + { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, + { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, + { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, + { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, + { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, + { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, + { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, + { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, +] + +[[package]] +name = "cucumber-expressions" +version = "18.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/7d/f4e231167b23b3d7348aa1c90117ce8854fae186d6984ad66d705df24061/cucumber_expressions-18.0.1.tar.gz", hash = "sha256:86ce41bf28ee520408416f38022e5a083d815edf04a0bd1dae46d474ca597c60", size = 22232, upload-time = "2024-10-28T11:38:48.672Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/e0/31ce90dad5234c3d52432bfce7562aa11cda4848aea90936a4be6c67d7ab/cucumber_expressions-18.0.1-py3-none-any.whl", hash = "sha256:86230d503cdda7ef35a1f2072a882d7d57c740aa4c163c82b07f039b6bc60c42", size = 20211, upload-time = "2024-10-28T11:38:47.101Z" }, +] + +[[package]] +name = "cucumber-tag-expressions" +version = "8.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/77/b8868653e9c7d432433d4d4d5e99d5923b309b89c8b08bc7f0cb5657ba0b/cucumber_tag_expressions-8.0.0.tar.gz", hash = "sha256:4af80282ff0349918c332428176089094019af6e2a381a2fd8f1c62a7a6bb7e8", size = 8427, upload-time = "2025-10-14T17:01:27.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/51/51ae3ab3b8553ec61f6558e9a0a9e8c500a9db844f9cf00a732b19c9a6ea/cucumber_tag_expressions-8.0.0-py3-none-any.whl", hash = "sha256:bfe552226f62a4462ee91c9643582f524af84ac84952643fb09057580cbb110a", size = 9726, upload-time = "2025-10-14T17:01:26.098Z" }, +] + +[[package]] +name = "dotenv" +version = "0.9.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dotenv" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, +] + +[[package]] +name = "face" +version = "24.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boltons" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/79/2484075a8549cd64beae697a8f664dee69a5ccf3a7439ee40c8f93c1978a/face-24.0.0.tar.gz", hash = "sha256:611e29a01ac5970f0077f9c577e746d48c082588b411b33a0dd55c4d872949f6", size = 62732, upload-time = "2024-11-02T05:24:26.095Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/47/21867c2e5fd006c8d36a560df9e32cb4f1f566b20c5dd41f5f8a2124f7de/face-24.0.0-py3-none-any.whl", hash = "sha256:0e2c17b426fa4639a4e77d1de9580f74a98f4869ba4c7c8c175b810611622cd3", size = 54742, upload-time = "2024-11-02T05:24:24.939Z" }, +] + +[[package]] +name = "faker" +version = "37.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/84/e95acaa848b855e15c83331d0401ee5f84b2f60889255c2e055cb4fb6bdf/faker-37.12.0.tar.gz", hash = "sha256:7505e59a7e02fa9010f06c3e1e92f8250d4cfbb30632296140c2d6dbef09b0fa", size = 1935741, upload-time = "2025-10-24T15:19:58.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/98/2c050dec90e295a524c9b65c4cb9e7c302386a296b2938710448cbd267d5/faker-37.12.0-py3-none-any.whl", hash = "sha256:afe7ccc038da92f2fbae30d8e16d19d91e92e242f8401ce9caf44de892bab4c4", size = 1975461, upload-time = "2025-10-24T15:19:55.739Z" }, +] + +[[package]] +name = "glom" +version = "24.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "boltons" }, + { name = "face" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/89/b57cfbc448189426f2e01b244fbe9226b059ef5423a9d49c1d335a1f1026/glom-24.11.0.tar.gz", hash = "sha256:4325f96759a912044af7b6c6bd0dba44ad8c1eb6038aab057329661d2021bb27", size = 195120, upload-time = "2024-11-02T23:17:50.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/a2/75fd80784ec33da8d39cf885e8811a4fbc045a90db5e336b8e345e66dbb2/glom-24.11.0-py3-none-any.whl", hash = "sha256:991db7fcb4bfa9687010aa519b7b541bbe21111e70e58fdd2d7e34bbaa2c1fbd", size = 102690, upload-time = "2024-11-02T23:17:46.468Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "josepy" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/ad/6f520aee9cc9618d33430380741e9ef859b2c560b1e7915e755c084f6bc0/josepy-2.2.0.tar.gz", hash = "sha256:74c033151337c854f83efe5305a291686cef723b4b970c43cfe7270cf4a677a9", size = 56500, upload-time = "2025-10-14T14:54:42.108Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/b2/b5caed897fbb1cc286c62c01feca977e08d99a17230ff3055b9a98eccf1d/josepy-2.2.0-py3-none-any.whl", hash = "sha256:63e9dd116d4078778c25ca88f880cc5d95f1cab0099bebe3a34c2e299f65d10b", size = 29211, upload-time = "2025-10-14T14:54:41.144Z" }, +] + +[[package]] +name = "jq" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/86/6935afb6c1789d4c6ba5343607e2d2f473069eaac29fac555dbbd154c2d7/jq-1.10.0.tar.gz", hash = "sha256:fc38803075dbf1867e1b4ed268fef501feecb0c50f3555985a500faedfa70f08", size = 2031308, upload-time = "2025-07-14T18:54:53.679Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/d9/b9e2b7004a2cb646507c082ea5e975ac37e6265353ec4c24779a1701c54a/jq-1.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe636cfa95b7027e7b43da83ecfd61431c0de80c3e0aa4946534b087149dcb4c", size = 420103, upload-time = "2025-07-14T18:52:39.016Z" }, + { url = "https://files.pythonhosted.org/packages/75/ad/d6780c218040789ed3ddbfa3b1743aaf824f80be5ebd7d5f885224c5bb08/jq-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:947fc7e1baaa7e95833b950e5a66b3e13a5cff028bff2d009b8c320124d9e69b", size = 426325, upload-time = "2025-07-14T18:52:40.654Z" }, + { url = "https://files.pythonhosted.org/packages/e9/42/5cfc8de34e976112e1b835a83264c7a0bab2cf8f20dc703f1257aa9e07ea/jq-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9382f85a347623afa521c43f8f09439e68906fd5b3492016f969a29219796bb9", size = 738212, upload-time = "2025-07-14T18:52:42.637Z" }, + { url = "https://files.pythonhosted.org/packages/84/0a/eff78a2329967bda38a98580c6fb77c59696b2b7d589e97db232ca42f5c4/jq-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c376aab525d0a1debe403d3bc2f19fda9473696a1eda56bafc88248fc4ae6e7e", size = 757068, upload-time = "2025-07-14T18:52:44.709Z" }, + { url = "https://files.pythonhosted.org/packages/f3/62/353d4c0a9f363ccb2a9b5ea205f079a4ee43642622c25250d95c0fafb7ca/jq-1.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:206f230c67a46776f848858c66b9c377a8e40c2b16195552edd96fd7b45f9a52", size = 744259, upload-time = "2025-07-14T18:52:47.308Z" }, + { url = "https://files.pythonhosted.org/packages/4f/46/0faead425cc3a720c7cd999146f4b5f50aaf394800457efb27746c10832c/jq-1.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:06986456ebc95ccb9e9c2a1f0e842bc9d441225a554a9f9d4370ad95a19ac000", size = 740075, upload-time = "2025-07-14T18:52:50.038Z" }, + { url = "https://files.pythonhosted.org/packages/10/0c/8e0823c5a329d735cff9f3746e0f7d74e7eea4ed9b0e75f90f942d1c455a/jq-1.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d02c0be958ddb4d9254ff251b045df2f8ee5995137702eeab4ffa81158bcdbe0", size = 766475, upload-time = "2025-07-14T18:52:53.047Z" }, + { url = "https://files.pythonhosted.org/packages/06/0c/9b5aae9081fe6620915aa0e0ca76fd016e5b9d399b80c8615852413f4404/jq-1.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cf6fd2ebd2453e75ceef207d5a95a39fcbda371a9b8916db0bd42e8737a621", size = 770416, upload-time = "2025-07-14T18:52:55.858Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e7/8f4e1cc3102de31d71e6298bcbdb15d1439e2bc466f4dcf18bc3694ba61d/jq-1.10.0-cp312-cp312-win32.whl", hash = "sha256:655d75d54a343944a9b011f568156cdc29ae0b35d2fdeefb001f459a4e4fc313", size = 410113, upload-time = "2025-07-14T18:52:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/6efe0a2b69910643b80d7da39fbded8225749dee4b79ebe23d522109a310/jq-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d67c2653ae41eab48f8888c213c9e1807b43167f26ac623c9f3e00989d3edee", size = 422316, upload-time = "2025-07-14T18:52:59.605Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fe/eeede83103e90e8f5fd9b610514a4c714957d6575e03987ebeb77aafeafa/jq-1.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b11d6e115ebad15d738d49932c3a8b9bb302b928e0fb79acc80987598d147a43", size = 419325, upload-time = "2025-07-14T18:53:01.854Z" }, + { url = "https://files.pythonhosted.org/packages/09/12/8b39293715d7721b2999facd4a05ca3328fe4a68cf1c094667789867aac1/jq-1.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df278904c5727dfe5bc678131a0636d731cd944879d890adf2fc6de35214b19b", size = 425344, upload-time = "2025-07-14T18:53:03.528Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f4/ace0c853d4462f1d28798d5696619d2fb68c8e1db228ef5517365a0f3c1c/jq-1.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab4c1ec69fd7719fb1356e2ade7bd2b5a63d6f0eaf5a90fdc5c9f6145f0474ce", size = 735874, upload-time = "2025-07-14T18:53:05.406Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b0/7882035062771686bd7e62db019fa0900fd9a3720b7ad8f7af65ee628484/jq-1.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd24dc21c8afcbe5aa812878251cfafa6f1dc6e1126c35d460cc7e67eb331018", size = 754355, upload-time = "2025-07-14T18:53:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/df/7d/b759a764c5d05c6829e95733a8b26f7e9b14df245ec2a325c0de049393ca/jq-1.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c0d3e89cd239c340c3a54e145ddf52fe63de31866cb73368d22a66bfe7e823f", size = 742546, upload-time = "2025-07-14T18:53:11.756Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6b/483ddb82939d4f2f9b0486887666c67a966434cc8bc72acd851fc8063f50/jq-1.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76710b280e4c464395c3d8e656b849e2704bd06e950a4ebd767860572bbf67df", size = 738777, upload-time = "2025-07-14T18:53:14.856Z" }, + { url = "https://files.pythonhosted.org/packages/0c/72/4d0fc965a8e57f55291763bb236a5aee91430f97c844ee328667b34af19e/jq-1.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b11a56f1fb6e2985fd3627dbd8a0637f62b1a704f7b19705733d461dafa26429", size = 765307, upload-time = "2025-07-14T18:53:17.611Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a6/aca82622d8d20ea02bbcac8aaa92daaadd55a18c2a3ca54b2e63d98336d2/jq-1.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ac05ae44d9aa1e462329e1510e0b5139ac4446de650c7bdfdab226aafdc978ec", size = 769830, upload-time = "2025-07-14T18:53:19.937Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e3/a19aeada32dde0839e3a4d77f2f0d63f2764c579b57f405ff4b91a58a8db/jq-1.10.0-cp313-cp313-win32.whl", hash = "sha256:0bad90f5734e2fc9d09c4116ae9102c357a4d75efa60a85758b0ba633774eddb", size = 410285, upload-time = "2025-07-14T18:53:21.631Z" }, + { url = "https://files.pythonhosted.org/packages/d6/32/df4eb81cf371654d91b6779d3f0005e86519977e19068638c266a9c88af7/jq-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4ec3fbca80a9dfb5349cdc2531faf14dd832e1847499513cf1fc477bcf46a479", size = 423094, upload-time = "2025-07-14T18:53:23.687Z" }, +] + +[[package]] +name = "parse" +version = "1.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391, upload-time = "2024-06-11T04:41:57.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126, upload-time = "2024-06-11T04:41:55.057Z" }, +] + +[[package]] +name = "parse-type" +version = "0.6.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parse" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/ea/42ba6ce0abba04ab6e0b997dcb9b528a4661b62af1fe1b0d498120d5ea78/parse_type-0.6.6.tar.gz", hash = "sha256:513a3784104839770d690e04339a8b4d33439fcd5dd99f2e4580f9fc1097bfb2", size = 98012, upload-time = "2025-08-11T22:53:48.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/8d/eef3d8cdccc32abdd91b1286884c99b8c3a6d3b135affcc2a7a0f383bb32/parse_type-0.6.6-py2.py3-none-any.whl", hash = "sha256:3ca79bbe71e170dfccc8ec6c341edfd1c2a0fc1e5cfd18330f93af938de2348c", size = 27085, upload-time = "2025-08-11T22:53:46.396Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pyopenssl" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" }, +] + +[[package]] +name = "pyrfc3339" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/7f/3c194647ecb80ada6937c38a162ab3edba85a8b6a58fa2919405f4de2509/pyrfc3339-2.1.0.tar.gz", hash = "sha256:c569a9714faf115cdb20b51e830e798c1f4de8dabb07f6ff25d221b5d09d8d7f", size = 12589, upload-time = "2025-08-23T16:40:31.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/90/0200184d2124484f918054751ef997ed6409cb05b7e8dcbf5a22da4c4748/pyrfc3339-2.1.0-py3-none-any.whl", hash = "sha256:560f3f972e339f579513fe1396974352fd575ef27caff160a38b312252fcddf3", size = 6758, upload-time = "2025-08-23T16:40:30.49Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] diff --git a/backend/package-lock.json b/backend/package-lock.json index a6cef3888..9cdaa764b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -83,6 +83,7 @@ "ioredis": "^5.3.2", "isomorphic-dompurify": "^2.22.0", "jmespath": "^0.16.0", + "jose": "^6.1.0", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.2", "jsrp": "^0.2.4", @@ -23236,9 +23237,10 @@ } }, "node_modules/jose": { - "version": "4.15.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", - "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", + "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -23638,6 +23640,15 @@ } } }, + "node_modules/jwks-rsa/node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/jwks-rsa/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -27590,6 +27601,15 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/openid-client/node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/openssl-wrapper": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/openssl-wrapper/-/openssl-wrapper-0.3.4.tgz", diff --git a/backend/package.json b/backend/package.json index 9bcd63cf1..fa4ee2f5f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -210,6 +210,7 @@ "ioredis": "^5.3.2", "isomorphic-dompurify": "^2.22.0", "jmespath": "^0.16.0", + "jose": "^6.1.0", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.2", "jsrp": "^0.2.4", diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 153a94648..30bdc7112 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -106,6 +106,7 @@ import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-c import { TPkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service"; import { TPkiSyncServiceFactory } from "@app/services/pki-sync/pki-sync-service"; import { TPkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service"; +import { TPkiAcmeServiceFactory } from "@app/ee/services/pki-acme/pki-acme-types"; import { TProjectServiceFactory } from "@app/services/project/project-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service"; @@ -295,6 +296,7 @@ declare module "fastify" { certificateAuthority: TCertificateAuthorityServiceFactory; certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory; certificateEst: TCertificateEstServiceFactory; + pkiAcme: TPkiAcmeServiceFactory; certificateEstV3: TCertificateEstV3ServiceFactory; pkiCollection: TPkiCollectionServiceFactory; pkiSubscriber: TPkiSubscriberServiceFactory; diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index f228f439c..603df5f6c 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -266,6 +266,24 @@ import { TOrgRoles, TOrgRolesInsert, TOrgRolesUpdate, + TPkiAcmeAccounts, + TPkiAcmeAccountsInsert, + TPkiAcmeAccountsUpdate, + TPkiAcmeAuths, + TPkiAcmeAuthsInsert, + TPkiAcmeAuthsUpdate, + TPkiAcmeChallenges, + TPkiAcmeChallengesInsert, + TPkiAcmeChallengesUpdate, + TPkiAcmeEnrollmentConfigs, + TPkiAcmeEnrollmentConfigsInsert, + TPkiAcmeEnrollmentConfigsUpdate, + TPkiAcmeOrderAuths, + TPkiAcmeOrderAuthsInsert, + TPkiAcmeOrderAuthsUpdate, + TPkiAcmeOrders, + TPkiAcmeOrdersInsert, + TPkiAcmeOrdersUpdate, TPkiAlertChannels, TPkiAlertChannelsInsert, TPkiAlertChannelsUpdate, @@ -721,6 +739,32 @@ declare module "knex/types/tables" { TPkiApiEnrollmentConfigsInsert, TPkiApiEnrollmentConfigsUpdate >; + [TableName.PkiAcmeEnrollmentConfig]: KnexOriginal.CompositeTableType< + TPkiAcmeEnrollmentConfigs, + TPkiAcmeEnrollmentConfigsInsert, + TPkiAcmeEnrollmentConfigsUpdate + >; + [TableName.PkiAcmeAccount]: KnexOriginal.CompositeTableType< + TPkiAcmeAccounts, + TPkiAcmeAccountsInsert, + TPkiAcmeAccountsUpdate + >; + [TableName.PkiAcmeOrder]: KnexOriginal.CompositeTableType< + TPkiAcmeOrders, + TPkiAcmeOrdersInsert, + TPkiAcmeOrdersUpdate + >; + [TableName.PkiAcmeAuth]: KnexOriginal.CompositeTableType; + [TableName.PkiAcmeOrderAuth]: KnexOriginal.CompositeTableType< + TPkiAcmeOrderAuths, + TPkiAcmeOrderAuthsInsert, + TPkiAcmeOrderAuthsUpdate + >; + [TableName.PkiAcmeChallenge]: KnexOriginal.CompositeTableType< + TPkiAcmeChallenges, + TPkiAcmeChallengesInsert, + TPkiAcmeChallengesUpdate + >; [TableName.CertificateTemplateEstConfig]: KnexOriginal.CompositeTableType< TCertificateTemplateEstConfigs, TCertificateTemplateEstConfigsInsert, diff --git a/backend/src/db/migrations/20251024184713_feature-slack-secret-sync-error-notification.ts b/backend/src/db/migrations/20251024184713_feature-slack-secret-sync-error-notification.ts new file mode 100644 index 000000000..203333fed --- /dev/null +++ b/backend/src/db/migrations/20251024184713_feature-slack-secret-sync-error-notification.ts @@ -0,0 +1,31 @@ +import { Knex } from "knex"; + +import { TableName } from "@app/db/schemas"; + +export async function up(knex: Knex): Promise { + if (!(await knex.schema.hasColumn(TableName.ProjectSlackConfigs, "isSecretSyncErrorNotificationEnabled"))) { + await knex.schema.alterTable(TableName.ProjectSlackConfigs, (table) => { + table.boolean("isSecretSyncErrorNotificationEnabled").notNullable().defaultTo(false); + }); + } + + if (!(await knex.schema.hasColumn(TableName.ProjectSlackConfigs, "secretSyncErrorChannels"))) { + await knex.schema.alterTable(TableName.ProjectSlackConfigs, (table) => { + table.text("secretSyncErrorChannels").notNullable().defaultTo(""); + }); + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasColumn(TableName.ProjectSlackConfigs, "isSecretSyncErrorNotificationEnabled")) { + await knex.schema.alterTable(TableName.ProjectSlackConfigs, (table) => { + table.dropColumn("isSecretSyncErrorNotificationEnabled"); + }); + } + + if (await knex.schema.hasColumn(TableName.ProjectSlackConfigs, "secretSyncErrorChannels")) { + await knex.schema.alterTable(TableName.ProjectSlackConfigs, (table) => { + table.dropColumn("secretSyncErrorChannels"); + }); + } +} diff --git a/backend/src/db/migrations/20251104234547_add-pki-acme.ts b/backend/src/db/migrations/20251104234547_add-pki-acme.ts new file mode 100644 index 000000000..c6b95009b --- /dev/null +++ b/backend/src/db/migrations/20251104234547_add-pki-acme.ts @@ -0,0 +1,245 @@ +import { Knex } from "knex"; +import { TableName } from "../schemas"; +import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; +import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists"; + +// Notice: the old constraint name is "enrollmentType_check" instead of "enrollment_type_check" +// with psql, if there's no quote around an identifier, it will be lowercased. +// this may cause issues in migrations as Knex sometimes generates identifiers without quotes. +// to avoid this, we use a new constraint name that contains only lowercase letters and underscores. +const OLD_ENROLLMENT_TYPE_CHECK_CONSTRAINT = "pki_certificate_profiles_enrollmentType_check"; +const NEW_ENROLLMENT_TYPE_CHECK_CONSTRAINT = "pki_certificate_profiles_enrollment_type_check"; + +const PUBLIC_KEY_THUMBPRINT_ALG_INDEX = "pki_acme_accounts_publicKey_thumbprint_alg_index"; + +export async function up(knex: Knex): Promise { + // Create PkiAcmeEnrollmentConfig table + if (!(await knex.schema.hasTable(TableName.PkiAcmeEnrollmentConfig))) { + await knex.schema.createTable(TableName.PkiAcmeEnrollmentConfig, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + t.binary("encryptedEabSecret").notNullable(); + + t.timestamps(true, true, true); + }); + + await createOnUpdateTrigger(knex, TableName.PkiAcmeEnrollmentConfig); + } + + if (!(await knex.schema.hasColumn(TableName.PkiCertificateProfile, "acmeConfigId"))) { + await knex.schema.alterTable(TableName.PkiCertificateProfile, (t) => { + t.uuid("acmeConfigId"); + t.foreign("acmeConfigId").references("id").inTable(TableName.PkiAcmeEnrollmentConfig).onDelete("SET NULL"); + t.index("acmeConfigId"); + }); + } + + await dropConstraintIfExists(TableName.PkiCertificateProfile, OLD_ENROLLMENT_TYPE_CHECK_CONSTRAINT, knex); + if (await knex.schema.hasColumn(TableName.PkiCertificateProfile, "enrollmentType")) { + // Notice: it's okay to use `.checkIn(...).alter();` here because the constraint name is all lowercase. + await knex.schema.alterTable(TableName.PkiCertificateProfile, (t) => { + t.string("enrollmentType") + .notNullable() + .checkIn(["api", "est", "acme"], NEW_ENROLLMENT_TYPE_CHECK_CONSTRAINT) + .alter(); + }); + } + + // Create PkiAcmeAccount table + if (!(await knex.schema.hasTable(TableName.PkiAcmeAccount))) { + await knex.schema.createTable(TableName.PkiAcmeAccount, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + // Foreign key to PkiCertificateProfile + t.uuid("profileId").notNullable(); + t.foreign("profileId").references("id").inTable(TableName.PkiCertificateProfile).onDelete("CASCADE"); + + // Multi-value emails array + t.specificType("emails", "text[]").notNullable(); + + // Public key (JWK format) + t.jsonb("publicKey").notNullable(); + // Public key thumbprint + t.string("publicKeyThumbprint").notNullable(); + // The JWS algorithm used to sign the public key when creating the account, e.g. "RS256", "ES256", "PS256", etc. + t.string("alg").notNullable(); + // We may need to look up existing accounts by public key thumbprint and algorithm, so we index on both of them. + t.index(["publicKeyThumbprint", "alg"], PUBLIC_KEY_THUMBPRINT_ALG_INDEX); + + t.timestamps(true, true, true); + }); + + await createOnUpdateTrigger(knex, TableName.PkiAcmeAccount); + } + + // Create PkiAcmeOrder table + if (!(await knex.schema.hasTable(TableName.PkiAcmeOrder))) { + await knex.schema.createTable(TableName.PkiAcmeOrder, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + // Foreign key to PkiAcmeAccount + t.uuid("accountId").notNullable(); + t.foreign("accountId").references("id").inTable(TableName.PkiAcmeAccount).onDelete("CASCADE"); + + // Foreign key to certificate + t.uuid("certificateId").nullable(); + t.foreign("certificateId").references("id").inTable(TableName.Certificate).onDelete("CASCADE"); + + t.timestamp("notBefore").nullable(); + t.timestamp("notAfter").nullable(); + + t.timestamp("expiresAt").notNullable(); + + t.text("csr").nullable(); + t.text("error").nullable(); + // Order status + t.string("status").notNullable(); // pending, ready, processing, valid, invalid + + t.timestamps(true, true, true); + }); + + await createOnUpdateTrigger(knex, TableName.PkiAcmeOrder); + } + + // Create PkiAcmeAuth table + if (!(await knex.schema.hasTable(TableName.PkiAcmeAuth))) { + await knex.schema.createTable(TableName.PkiAcmeAuth, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + // Foreign key to PkiAcmeAccount + t.uuid("accountId").notNullable(); + t.foreign("accountId").references("id").inTable(TableName.PkiAcmeAccount).onDelete("CASCADE"); + + // Authorization status + t.string("status").notNullable(); // pending, valid, invalid, deactivated, expired, revoked + + // Token used to validate the authorization through ACME challenge + t.string("token").nullable(); + + // Identifier type and value + t.string("identifierType").notNullable(); // dns + t.string("identifierValue").notNullable(); // domain name + + // Expiration timestamp + t.timestamp("expiresAt").notNullable(); + + // Optional link to issued certificate + t.uuid("certificateId").nullable(); + t.foreign("certificateId").references("id").inTable(TableName.Certificate).onDelete("SET NULL"); + + t.timestamps(true, true, true); + }); + + await createOnUpdateTrigger(knex, TableName.PkiAcmeAuth); + } + + // Create PkiAcmeOrderAuth table + if (!(await knex.schema.hasTable(TableName.PkiAcmeOrderAuth))) { + await knex.schema.createTable(TableName.PkiAcmeOrderAuth, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + // Foreign key to PkiAcmeOrder + t.uuid("orderId").notNullable(); + t.foreign("orderId").references("id").inTable(TableName.PkiAcmeOrder).onDelete("CASCADE"); + + // Foreign key to PkiAcmeAuth + t.uuid("authId").notNullable(); + t.foreign("authId").references("id").inTable(TableName.PkiAcmeAuth).onDelete("CASCADE"); + + t.timestamps(true, true, true); + }); + + await createOnUpdateTrigger(knex, TableName.PkiAcmeOrderAuth); + } + + // Create PkiAcmeChallenge table + if (!(await knex.schema.hasTable(TableName.PkiAcmeChallenge))) { + await knex.schema.createTable(TableName.PkiAcmeChallenge, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + + // Foreign key to PkiAcmeAuth + t.uuid("authId").notNullable(); + t.foreign("authId").references("id").inTable(TableName.PkiAcmeAuth).onDelete("CASCADE"); + + // Challenge type + t.string("type").notNullable(); // http-01, dns-01, tls-alpn-01 + + // Challenge status + t.string("status").notNullable(); // pending, processing, valid, invalid + + // Error message when the challenge fails + t.string("error").nullable(); + + // Validation timestamp + t.timestamp("validatedAt").nullable(); + + t.timestamps(true, true, true); + }); + + await createOnUpdateTrigger(knex, TableName.PkiAcmeChallenge); + } +} + +export async function down(knex: Knex): Promise { + // Drop tables in reverse dependency order + + // Drop PkiAcmeChallenge first (depends on PkiAcmeAuth) + if (await knex.schema.hasTable(TableName.PkiAcmeChallenge)) { + await knex.schema.dropTable(TableName.PkiAcmeChallenge); + await dropOnUpdateTrigger(knex, TableName.PkiAcmeChallenge); + } + + // Drop PkiAcmeOrderAuth (depends on PkiAcmeOrder and PkiAcmeAuth) + if (await knex.schema.hasTable(TableName.PkiAcmeOrderAuth)) { + await knex.schema.dropTable(TableName.PkiAcmeOrderAuth); + await dropOnUpdateTrigger(knex, TableName.PkiAcmeOrderAuth); + } + + // Drop PkiAcmeAuth (depends on PkiAcmeAccount and Certificate) + if (await knex.schema.hasTable(TableName.PkiAcmeAuth)) { + await knex.schema.dropTable(TableName.PkiAcmeAuth); + await dropOnUpdateTrigger(knex, TableName.PkiAcmeAuth); + } + + // Drop PkiAcmeOrder (depends on PkiAcmeAccount) + if (await knex.schema.hasTable(TableName.PkiAcmeOrder)) { + await knex.schema.dropTable(TableName.PkiAcmeOrder); + await dropOnUpdateTrigger(knex, TableName.PkiAcmeOrder); + } + + // Drop PkiAcmeAccount (depends on PkiCertificateProfile) + if (await knex.schema.hasTable(TableName.PkiAcmeAccount)) { + await knex.schema.dropTable(TableName.PkiAcmeAccount); + await dropOnUpdateTrigger(knex, TableName.PkiAcmeAccount); + } + + // Change enrollmentType check constraint to allow acme + await dropConstraintIfExists(TableName.PkiCertificateProfile, NEW_ENROLLMENT_TYPE_CHECK_CONSTRAINT, knex); + if (await knex.schema.hasColumn(TableName.PkiCertificateProfile, "enrollmentType")) { + // Notice: DO NOT USE + // + // `t.string("enrollmentType").checkIn(["api", "est"], OLD_ENROLLMENT_TYPE_CHECK_CONSTRAINT).alter();` + // + // here because knex will generate a constraint name without quotes, and it will be treated as lowercased and causing problems. + await knex.raw( + `ALTER TABLE ?? + ADD CONSTRAINT ?? CHECK (?? IN ('api', 'est')); + `, + [TableName.PkiCertificateProfile, OLD_ENROLLMENT_TYPE_CHECK_CONSTRAINT, "enrollmentType"] + ); + } + + // Drop acmeConfigId column + if (await knex.schema.hasColumn(TableName.PkiCertificateProfile, "acmeConfigId")) { + await knex.schema.alterTable(TableName.PkiCertificateProfile, (t) => { + t.dropForeign(["acmeConfigId"]); + t.dropIndex("acmeConfigId"); + t.dropColumn("acmeConfigId"); + }); + } + + // Drop PkiAcmeEnrollmentConfig + if (await knex.schema.hasTable(TableName.PkiAcmeEnrollmentConfig)) { + await knex.schema.dropTable(TableName.PkiAcmeEnrollmentConfig); + await dropOnUpdateTrigger(knex, TableName.PkiAcmeEnrollmentConfig); + } +} diff --git a/backend/src/db/migrations/20251106172316_delete-pki-templates-on-project-removal.ts b/backend/src/db/migrations/20251106172316_delete-pki-templates-on-project-removal.ts new file mode 100644 index 000000000..4d98f8af4 --- /dev/null +++ b/backend/src/db/migrations/20251106172316_delete-pki-templates-on-project-removal.ts @@ -0,0 +1,21 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + if (await knex.schema.hasTable(TableName.PkiCertificateTemplateV2)) { + await knex.schema.alterTable(TableName.PkiCertificateTemplateV2, (t) => { + t.dropForeign(["projectId"]); + t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); + }); + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasTable(TableName.PkiCertificateTemplateV2)) { + await knex.schema.alterTable(TableName.PkiCertificateTemplateV2, (t) => { + t.dropForeign(["projectId"]); + t.foreign("projectId").references("id").inTable(TableName.Project); + }); + } +} diff --git a/backend/src/db/migrations/utils/dropConstraintIfExists.ts b/backend/src/db/migrations/utils/dropConstraintIfExists.ts index bfe487d49..93985ca76 100644 --- a/backend/src/db/migrations/utils/dropConstraintIfExists.ts +++ b/backend/src/db/migrations/utils/dropConstraintIfExists.ts @@ -3,4 +3,4 @@ import { Knex } from "knex"; import { TableName } from "@app/db/schemas"; export const dropConstraintIfExists = (tableName: TableName, constraintName: string, knex: Knex) => - knex.raw(`ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS ${constraintName};`); + knex.raw("ALTER TABLE ?? DROP CONSTRAINT IF EXISTS ??;", [tableName, constraintName]); diff --git a/backend/src/db/schemas/index.ts b/backend/src/db/schemas/index.ts index e5a6e8c20..78dcb1980 100644 --- a/backend/src/db/schemas/index.ts +++ b/backend/src/db/schemas/index.ts @@ -92,6 +92,12 @@ export * from "./pam-accounts"; export * from "./pam-folders"; export * from "./pam-resources"; export * from "./pam-sessions"; +export * from "./pki-acme-accounts"; +export * from "./pki-acme-auths"; +export * from "./pki-acme-challenges"; +export * from "./pki-acme-enrollment-configs"; +export * from "./pki-acme-order-auths"; +export * from "./pki-acme-orders"; export * from "./pki-alert-channels"; export * from "./pki-alert-history"; export * from "./pki-alert-history-certificate"; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index f70463cc8..444a6bd97 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -27,6 +27,7 @@ export enum TableName { PkiCertificateProfile = "pki_certificate_profiles", PkiEstEnrollmentConfig = "pki_est_enrollment_configs", PkiApiEnrollmentConfig = "pki_api_enrollment_configs", + PkiAcmeEnrollmentConfig = "pki_acme_enrollment_configs", PkiSubscriber = "pki_subscribers", PkiAlert = "pki_alerts", PkiAlertsV2 = "pki_alerts_v2", @@ -214,7 +215,14 @@ export enum TableName { PamAccount = "pam_accounts", PamSession = "pam_sessions", - VaultExternalMigrationConfig = "vault_external_migration_configs" + VaultExternalMigrationConfig = "vault_external_migration_configs", + + // PKI ACME + PkiAcmeAccount = "pki_acme_accounts", + PkiAcmeOrder = "pki_acme_orders", + PkiAcmeOrderAuth = "pki_acme_order_auths", + PkiAcmeAuth = "pki_acme_auths", + PkiAcmeChallenge = "pki_acme_challenges" } export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId"; diff --git a/backend/src/db/schemas/pki-acme-accounts.ts b/backend/src/db/schemas/pki-acme-accounts.ts new file mode 100644 index 000000000..69b1ffe49 --- /dev/null +++ b/backend/src/db/schemas/pki-acme-accounts.ts @@ -0,0 +1,23 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const PkiAcmeAccountsSchema = z.object({ + id: z.string().uuid(), + profileId: z.string().uuid(), + emails: z.string().array(), + publicKey: z.unknown(), + publicKeyThumbprint: z.string(), + alg: z.string(), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TPkiAcmeAccounts = z.infer; +export type TPkiAcmeAccountsInsert = Omit, TImmutableDBKeys>; +export type TPkiAcmeAccountsUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/pki-acme-auths.ts b/backend/src/db/schemas/pki-acme-auths.ts new file mode 100644 index 000000000..7f20e0f24 --- /dev/null +++ b/backend/src/db/schemas/pki-acme-auths.ts @@ -0,0 +1,25 @@ +// 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 PkiAcmeAuthsSchema = z.object({ + id: z.string().uuid(), + accountId: z.string().uuid(), + status: z.string(), + token: z.string().nullable().optional(), + identifierType: z.string(), + identifierValue: z.string(), + expiresAt: z.date(), + certificateId: z.string().uuid().nullable().optional(), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TPkiAcmeAuths = z.infer; +export type TPkiAcmeAuthsInsert = Omit, TImmutableDBKeys>; +export type TPkiAcmeAuthsUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/pki-acme-challenges.ts b/backend/src/db/schemas/pki-acme-challenges.ts new file mode 100644 index 000000000..18245bb76 --- /dev/null +++ b/backend/src/db/schemas/pki-acme-challenges.ts @@ -0,0 +1,23 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const PkiAcmeChallengesSchema = z.object({ + id: z.string().uuid(), + authId: z.string().uuid(), + type: z.string(), + status: z.string(), + error: z.string().nullable().optional(), + validatedAt: z.date().nullable().optional(), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TPkiAcmeChallenges = z.infer; +export type TPkiAcmeChallengesInsert = Omit, TImmutableDBKeys>; +export type TPkiAcmeChallengesUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/pki-acme-enrollment-configs.ts b/backend/src/db/schemas/pki-acme-enrollment-configs.ts new file mode 100644 index 000000000..f0592319b --- /dev/null +++ b/backend/src/db/schemas/pki-acme-enrollment-configs.ts @@ -0,0 +1,23 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { zodBuffer } from "@app/lib/zod"; + +import { TImmutableDBKeys } from "./models"; + +export const PkiAcmeEnrollmentConfigsSchema = z.object({ + id: z.string().uuid(), + encryptedEabSecret: zodBuffer, + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TPkiAcmeEnrollmentConfigs = z.infer; +export type TPkiAcmeEnrollmentConfigsInsert = Omit, TImmutableDBKeys>; +export type TPkiAcmeEnrollmentConfigsUpdate = Partial< + Omit, TImmutableDBKeys> +>; diff --git a/backend/src/db/schemas/pki-acme-order-auths.ts b/backend/src/db/schemas/pki-acme-order-auths.ts new file mode 100644 index 000000000..66f8704f2 --- /dev/null +++ b/backend/src/db/schemas/pki-acme-order-auths.ts @@ -0,0 +1,20 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const PkiAcmeOrderAuthsSchema = z.object({ + id: z.string().uuid(), + orderId: z.string().uuid(), + authId: z.string().uuid(), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TPkiAcmeOrderAuths = z.infer; +export type TPkiAcmeOrderAuthsInsert = Omit, TImmutableDBKeys>; +export type TPkiAcmeOrderAuthsUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/pki-acme-orders.ts b/backend/src/db/schemas/pki-acme-orders.ts new file mode 100644 index 000000000..928753d8c --- /dev/null +++ b/backend/src/db/schemas/pki-acme-orders.ts @@ -0,0 +1,26 @@ +// 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 PkiAcmeOrdersSchema = z.object({ + id: z.string().uuid(), + accountId: z.string().uuid(), + certificateId: z.string().uuid().nullable().optional(), + notBefore: z.date().nullable().optional(), + notAfter: z.date().nullable().optional(), + expiresAt: z.date(), + csr: z.string().nullable().optional(), + error: z.string().nullable().optional(), + status: z.string(), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TPkiAcmeOrders = z.infer; +export type TPkiAcmeOrdersInsert = Omit, TImmutableDBKeys>; +export type TPkiAcmeOrdersUpdate = Partial, TImmutableDBKeys>>; diff --git a/backend/src/db/schemas/pki-certificate-profiles.ts b/backend/src/db/schemas/pki-certificate-profiles.ts index 368770c3e..04560bec6 100644 --- a/backend/src/db/schemas/pki-certificate-profiles.ts +++ b/backend/src/db/schemas/pki-certificate-profiles.ts @@ -18,7 +18,8 @@ export const PkiCertificateProfilesSchema = z.object({ estConfigId: z.string().uuid().nullable().optional(), apiConfigId: z.string().uuid().nullable().optional(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + acmeConfigId: z.string().uuid().nullable().optional() }); export type TPkiCertificateProfiles = z.infer; diff --git a/backend/src/db/schemas/project-slack-configs.ts b/backend/src/db/schemas/project-slack-configs.ts index 0a46e5aae..48674ec8f 100644 --- a/backend/src/db/schemas/project-slack-configs.ts +++ b/backend/src/db/schemas/project-slack-configs.ts @@ -16,7 +16,9 @@ export const ProjectSlackConfigsSchema = z.object({ isSecretRequestNotificationEnabled: z.boolean().default(false), secretRequestChannels: z.string().default(""), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + isSecretSyncErrorNotificationEnabled: z.boolean().default(false), + secretSyncErrorChannels: z.string().default("") }); export type TProjectSlackConfigs = z.infer; diff --git a/backend/src/server/routes/v1/app-connection-routers/chef-connection-router.ts b/backend/src/ee/routes/v1/app-connection-routers/chef-connection-router.ts similarity index 92% rename from backend/src/server/routes/v1/app-connection-routers/chef-connection-router.ts rename to backend/src/ee/routes/v1/app-connection-routers/chef-connection-router.ts index 6bfd83391..2855d8b37 100644 --- a/backend/src/server/routes/v1/app-connection-routers/chef-connection-router.ts +++ b/backend/src/ee/routes/v1/app-connection-routers/chef-connection-router.ts @@ -1,17 +1,16 @@ import z from "zod"; -import { readLimit } from "@app/server/config/rateLimiter"; -import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { CreateChefConnectionSchema, SanitizedChefConnectionSchema, UpdateChefConnectionSchema -} from "@app/services/app-connection/chef"; +} from "@app/ee/services/app-connections/chef"; +import { readLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { registerAppConnectionEndpoints } from "@app/server/routes/v1/app-connection-routers/app-connection-endpoints"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AuthMode } from "@app/services/auth/auth-type"; -import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; - export const registerChefConnectionRouter = async (server: FastifyZodProvider) => { registerAppConnectionEndpoints({ app: AppConnection.Chef, diff --git a/backend/src/ee/routes/v1/index.ts b/backend/src/ee/routes/v1/index.ts index 31847b503..e104a8355 100644 --- a/backend/src/ee/routes/v1/index.ts +++ b/backend/src/ee/routes/v1/index.ts @@ -1,5 +1,6 @@ import { registerProjectTemplateRouter } from "@app/ee/routes/v1/project-template-router"; +import { getConfig } from "@app/lib/config/env"; import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router"; import { registerAccessApprovalRequestRouter } from "./access-approval-request-router"; import { registerAssumePrivilegeRouter } from "./assume-privilege-router"; @@ -30,6 +31,7 @@ import { PAM_RESOURCE_REGISTER_ROUTER_MAP } from "./pam-resource-routers"; import { registerPamResourceRouter } from "./pam-resource-routers/pam-resource-router"; import { registerPamSessionRouter } from "./pam-session-router"; import { registerPITRouter } from "./pit-router"; +import { registerPkiAcmeRouter } from "./pki-acme-router"; import { registerProjectRoleRouter } from "./project-role-router"; import { registerProjectRouter } from "./project-router"; import { registerRateLimitRouter } from "./rate-limit-router"; @@ -107,6 +109,10 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => { await server.register( async (pkiRouter) => { await pkiRouter.register(registerCaCrlRouter, { prefix: "/crl" }); + // Notice: current this feature is still in development and is not yet ready for production. + if (getConfig().isAcmeFeatureEnabled === true) { + await pkiRouter.register(registerPkiAcmeRouter, { prefix: "/acme" }); + } }, { prefix: "/pki" } ); diff --git a/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts b/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts index d2e0183ff..286e0896f 100644 --- a/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts +++ b/backend/src/ee/routes/v1/pam-account-routers/pam-account-router.ts @@ -92,7 +92,8 @@ export const registerPamAccountRouter = async (server: FastifyZodProvider) => { gatewayClientCertificate: z.string(), gatewayClientPrivateKey: z.string(), gatewayServerCertificateChain: z.string(), - relayHost: z.string() + relayHost: z.string(), + metadata: z.record(z.string(), z.string()).optional() }) } }, diff --git a/backend/src/ee/routes/v1/pit-router.ts b/backend/src/ee/routes/v1/pit-router.ts index 14a82bce4..26909d294 100644 --- a/backend/src/ee/routes/v1/pit-router.ts +++ b/backend/src/ee/routes/v1/pit-router.ts @@ -468,7 +468,10 @@ export const registerPITRouter = async (server: FastifyZodProvider) => { .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())) .optional(), secretComment: z.string().trim().optional().default(""), - skipMultilineEncoding: z.boolean().optional(), + skipMultilineEncoding: z + .boolean() + .nullish() + .transform((val) => (val === null ? false : val)), metadata: z.record(z.string()).optional(), secretMetadata: ResourceMetadataSchema.optional(), tagIds: z.string().array().optional() diff --git a/backend/src/ee/routes/v1/pki-acme-router.ts b/backend/src/ee/routes/v1/pki-acme-router.ts new file mode 100644 index 000000000..b1419c086 --- /dev/null +++ b/backend/src/ee/routes/v1/pki-acme-router.ts @@ -0,0 +1,461 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import type { TAcmeResponse, TAuthenciatedJwsPayload, TRawJwsPayload } from "@app/ee/services/pki-acme/pki-acme-types"; +import { FastifyReply, FastifyRequest } from "fastify"; +import { z } from "zod"; + +import { AcmeMalformedError } from "@app/ee/services/pki-acme/pki-acme-errors"; +import { + AcmeOrderResourceSchema, + CreateAcmeAccountResponseSchema, + CreateAcmeOrderBodySchema, + DeactivateAcmeAccountBodySchema, + DeactivateAcmeAccountResponseSchema, + FinalizeAcmeOrderBodySchema, + GetAcmeAuthorizationResponseSchema, + GetAcmeDirectoryResponseSchema, + ListAcmeOrdersPayloadSchema, + ListAcmeOrdersResponseSchema, + RawJwsPayloadSchema, + RespondToAcmeChallengeBodySchema, + RespondToAcmeChallengeResponseSchema +} from "@app/ee/services/pki-acme/pki-acme-schemas"; +import { ApiDocsTags } from "@app/lib/api-docs"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; + +const SharedParamsSchema = z.object({ + profileId: z.string().uuid() +}); + +export interface MyRequestInterface { + Params: { profileId: string; accountId?: string }; + Body: TRawJwsPayload; +} + +export const registerPkiAcmeRouter = async (server: FastifyZodProvider) => { + const validateExistingAccount = async < + // eslint-disable-next-line @typescript-eslint/no-explicit-any + R extends FastifyRequest, + TSchema extends z.ZodSchema | undefined = undefined, + T = TSchema extends z.ZodSchema ? U : string + >({ + req, + schema + }: { + req: R; + schema?: TSchema; + }): Promise> => { + return server.services.pkiAcme.validateExistingAccountJwsPayload({ + url: new URL(req.url, `${req.protocol}://${req.hostname}`), + profileId: (req.params as { profileId: string }).profileId, + rawJwsPayload: req.body as TRawJwsPayload, + schema, + expectedAccountId: (req.params as { accountId?: string }).accountId + }); + }; + + const sendAcmeResponse = async (res: FastifyReply, profileId: string, response: TAcmeResponse): Promise => { + res.code(response.status); + for (const [key, value] of Object.entries(response.headers)) { + res.header(key, value); + } + + const nonce = await server.services.pkiAcme.getAcmeNewNonce(profileId); + res.header("Replay-Nonce", nonce); + res.header("Cache-Control", "no-store"); + return response.body; + }; + + server.addContentTypeParser("application/jose+json", { parseAs: "string" }, (_, body, done) => { + try { + const strBody = body instanceof Buffer ? body.toString() : body; + if (!strBody) { + done(null, undefined); + } + const json: unknown = JSON.parse(strBody); + done(null, json); + } catch (err) { + const error = err as Error; + done(error, undefined); + } + }); + // GET /api/v1/pki/acme/profiles//directory + // Directory (RFC 8555 Section 7.1.1) + server.route({ + method: "GET", + url: "/profiles/:profileId/directory", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME Directory - provides URLs for the client to make API calls to", + params: z.object({ + profileId: z.string().uuid() + }), + response: { + 200: GetAcmeDirectoryResponseSchema + } + }, + handler: async (req) => server.services.pkiAcme.getAcmeDirectory(req.params.profileId) + }); + + // HEAD /api/v1/pki/acme/profiles//new-nonce + // New Nonce (RFC 8555 Section 7.2) + server.route({ + method: "HEAD", + url: "/profiles/:profileId/new-nonce", + config: { + // TODO: probably a different rate limit for nonce creation? + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME New Nonce - generate a new nonce and return in Replay-Nonce header", + params: z.object({ + profileId: z.string().uuid() + }), + response: { + 200: z.string().length(0) + } + }, + handler: async (req, res) => { + const nonce = await server.services.pkiAcme.getAcmeNewNonce(req.params.profileId); + res.header("Replay-Nonce", nonce); + return ""; + } + }); + + // POST /api/v1/pki/acme/profiles//new-account + // New Account (RFC 8555 Section 7.3) + server.route({ + method: "POST", + url: "/profiles/:profileId/new-account", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME New Account - register a new account or find existing one", + params: SharedParamsSchema, + body: RawJwsPayloadSchema, + response: { + 201: CreateAcmeAccountResponseSchema + } + }, + handler: async (req, res) => { + const { payload, protectedHeader } = await server.services.pkiAcme.validateNewAccountJwsPayload({ + url: new URL(req.url, `${req.protocol}://${req.hostname}`), + rawJwsPayload: req.body + }); + const { alg, jwk } = protectedHeader; + return sendAcmeResponse( + res, + req.params.profileId, + await server.services.pkiAcme.createAcmeAccount({ + profileId: req.params.profileId, + alg, + jwk: jwk!, + payload + }) + ); + } + }); + + // POST /api/v1/pki/acme/profiles//accounts/ + // Account Deactivation (RFC 8555 Section 7.3.6) + server.route({ + method: "POST", + url: "/profiles/:profileId/accounts/:accountId", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME Account Deactivation", + params: SharedParamsSchema.extend({ + accountId: z.string() + }), + body: RawJwsPayloadSchema, + response: { + 200: DeactivateAcmeAccountResponseSchema + } + }, + handler: async (req, res) => { + const { payload, profileId, accountId } = await validateExistingAccount({ + req, + schema: DeactivateAcmeAccountBodySchema + }); + return sendAcmeResponse( + res, + profileId, + await server.services.pkiAcme.deactivateAcmeAccount({ + profileId, + accountId, + payload + }) + ); + } + }); + + // POST /api/v1/pki/acme/profiles//new-order + // New Certificate Order (RFC 8555 Section 7.4) + server.route({ + method: "POST", + url: "/profiles/:profileId/new-order", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME New Order - apply for a new certificate", + params: SharedParamsSchema, + body: RawJwsPayloadSchema, + response: { + 201: AcmeOrderResourceSchema + } + }, + handler: async (req, res) => { + const { profileId, accountId, payload } = await validateExistingAccount({ + req, + schema: CreateAcmeOrderBodySchema + }); + return sendAcmeResponse( + res, + profileId, + await server.services.pkiAcme.createAcmeOrder({ + profileId, + accountId, + payload + }) + ); + } + }); + + // POST /api/v1/pki/acme/profiles//orders/ + // Get Order (RFC 8555 Section 7.1.3) + server.route({ + method: "POST", + url: "/profiles/:profileId/orders/:orderId", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME Get Order - return status and details of the order", + params: SharedParamsSchema.extend({ + orderId: z.string().uuid() + }), + body: RawJwsPayloadSchema, + response: { + 200: AcmeOrderResourceSchema + } + }, + handler: async (req, res) => { + const { profileId, accountId, payload } = await validateExistingAccount({ + req + }); + if (payload !== "") { + throw new AcmeMalformedError({ detail: "Payload should be empty" }); + } + return sendAcmeResponse( + res, + profileId, + await server.services.pkiAcme.getAcmeOrder({ + profileId, + accountId, + orderId: req.params.orderId + }) + ); + } + }); + + // POST /api/v1/pki/acme/profiles//orders//finalize + // Applying for Certificate Issuance (RFC 8555 Section 7.4) + server.route({ + method: "POST", + url: "/profiles/:profileId/orders/:orderId/finalize", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME Finalize Order - finalize cert order by providing CSR", + params: SharedParamsSchema.extend({ + orderId: z.string().uuid() + }), + body: RawJwsPayloadSchema, + response: { + 200: AcmeOrderResourceSchema + } + }, + handler: async (req, res) => { + const { profileId, accountId, payload } = await validateExistingAccount({ + req, + schema: FinalizeAcmeOrderBodySchema + }); + return sendAcmeResponse( + res, + profileId, + await server.services.pkiAcme.finalizeAcmeOrder({ + profileId, + accountId, + orderId: req.params.orderId, + payload + }) + ); + } + }); + // POST /api/v1/pki/acme/profiles//accounts//orders + // List Orders (RFC 8555 Section 7.1.2.1) + server.route({ + method: "POST", + url: "/profiles/:profileId/accounts/:accountId/orders", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME List Orders - get existing orders from current account", + params: SharedParamsSchema.extend({ + accountId: z.string() + }), + body: RawJwsPayloadSchema, + response: { + 200: ListAcmeOrdersResponseSchema + } + }, + handler: async (req, res) => { + const { profileId, accountId } = await validateExistingAccount({ + req, + schema: ListAcmeOrdersPayloadSchema + }); + return sendAcmeResponse( + res, + profileId, + await server.services.pkiAcme.listAcmeOrders({ + profileId, + accountId + }) + ); + } + }); + + // POST /api/v1/pki/acme/profiles//orders//certificate + // Download Certificate (RFC 8555 Section 7.4.2) + server.route({ + method: "POST", + url: "/profiles/:profileId/orders/:orderId/certificate", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME Download Certificate - download certificate when ready", + params: SharedParamsSchema.extend({ + orderId: z.string().uuid() + }), + body: RawJwsPayloadSchema, + response: { + 200: z.string() + } + }, + handler: async (req, res) => { + const { profileId, accountId, payload } = await validateExistingAccount({ + req + }); + if (payload !== "") { + throw new AcmeMalformedError({ detail: "Payload should be empty" }); + } + res.type("application/pem-certificate-chain"); + return sendAcmeResponse( + res, + profileId, + await server.services.pkiAcme.downloadAcmeCertificate({ profileId, accountId, orderId: req.params.orderId }) + ); + } + }); + + // POST /api/v1/pki/acme/profiles//authorizations/ + // Identifier Authorization (RFC 8555 Section 7.5) + server.route({ + method: "POST", + url: "/profiles/:profileId/authorizations/:authzId", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME Identifier Authorization - get authorization info (challenges)", + params: SharedParamsSchema.extend({ + authzId: z.string().uuid() + }), + body: RawJwsPayloadSchema, + response: { + 200: GetAcmeAuthorizationResponseSchema + } + }, + handler: async (req, res) => { + const { profileId, accountId, payload } = await validateExistingAccount({ req }); + if (payload !== "") { + throw new AcmeMalformedError({ detail: "Payload should be empty" }); + } + return sendAcmeResponse( + res, + profileId, + await server.services.pkiAcme.getAcmeAuthorization({ + profileId, + accountId, + authzId: req.params.authzId + }) + ); + } + }); + + // POST /api/v1/pki/acme/profiles//authorizations//challenges/ + // Respond to Challenge (RFC 8555 Section 7.5.1) + server.route({ + method: "POST", + url: "/profiles/:profileId/authorizations/:authzId/challenges/:challengeId", + config: { + rateLimit: writeLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiAcme], + description: "ACME Respond to Challenge - let ACME server know challenge is ready", + params: SharedParamsSchema.extend({ + authzId: z.string().uuid(), + challengeId: z.string().uuid() + }), + response: { + 200: RespondToAcmeChallengeResponseSchema + } + }, + handler: async (req, res) => { + const { profileId, accountId } = await validateExistingAccount({ + req, + schema: RespondToAcmeChallengeBodySchema + }); + return sendAcmeResponse( + res, + profileId, + await server.services.pkiAcme.respondToAcmeChallenge({ + profileId, + accountId, + authzId: req.params.authzId, + challengeId: req.params.challengeId + }) + ); + } + }); +}; diff --git a/backend/src/server/routes/v1/secret-sync-routers/chef-sync-router.ts b/backend/src/ee/routes/v1/secret-sync-routers/chef-sync-router.ts similarity index 72% rename from backend/src/server/routes/v1/secret-sync-routers/chef-sync-router.ts rename to backend/src/ee/routes/v1/secret-sync-routers/chef-sync-router.ts index 6972a9b70..3bdd5bce6 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/chef-sync-router.ts +++ b/backend/src/ee/routes/v1/secret-sync-routers/chef-sync-router.ts @@ -1,8 +1,7 @@ -import { ChefSyncSchema, CreateChefSyncSchema, UpdateChefSyncSchema } from "@app/services/secret-sync/chef"; +import { ChefSyncSchema, CreateChefSyncSchema, UpdateChefSyncSchema } from "@app/ee/services/secret-sync/chef"; +import { registerSyncSecretsEndpoints } from "@app/server/routes/v1/secret-sync-routers/secret-sync-endpoints"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; -import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints"; - export const registerChefSyncRouter = async (server: FastifyZodProvider) => registerSyncSecretsEndpoints({ destination: SecretSync.Chef, diff --git a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts index 1027995a7..4b2608c24 100644 --- a/backend/src/ee/services/access-approval-request/access-approval-request-service.ts +++ b/backend/src/ee/services/access-approval-request/access-approval-request-service.ts @@ -243,7 +243,8 @@ export const accessApprovalRequestServiceFactory = ({ ); const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`; - const approvalPath = `/projects/secret-management/${project.id}/approval`; + const projectPath = `/projects/secret-management/${project.id}`; + const approvalPath = `${projectPath}/approval`; const approvalUrl = `${cfg.SITE_URL}${approvalPath}`; await triggerWorkflowIntegrationNotification({ @@ -252,6 +253,7 @@ export const accessApprovalRequestServiceFactory = ({ type: TriggerFeature.ACCESS_REQUEST, payload: { projectName: project.name, + projectPath, requesterFullName, isTemporary, requesterEmail: requestedByUser.email as string, @@ -397,7 +399,8 @@ export const accessApprovalRequestServiceFactory = ({ const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`; const editorFullName = `${editedByUser.firstName} ${editedByUser.lastName}`; - const approvalPath = `/projects/secret-management/${project.id}/approval`; + const projectPath = `/projects/secret-management/${project.id}`; + const approvalPath = `${projectPath}/approval`; const approvalUrl = `${cfg.SITE_URL}${approvalPath}`; await triggerWorkflowIntegrationNotification({ @@ -415,7 +418,8 @@ export const accessApprovalRequestServiceFactory = ({ approvalUrl, editNote, editorEmail: editedByUser.email as string, - editorFullName + editorFullName, + projectPath } }, projectId: project.id diff --git a/backend/src/services/app-connection/chef/chef-connection-enums.ts b/backend/src/ee/services/app-connections/chef/chef-connection-enums.ts similarity index 100% rename from backend/src/services/app-connection/chef/chef-connection-enums.ts rename to backend/src/ee/services/app-connections/chef/chef-connection-enums.ts diff --git a/backend/src/services/app-connection/chef/chef-connection-fns.ts b/backend/src/ee/services/app-connections/chef/chef-connection-fns.ts similarity index 99% rename from backend/src/services/app-connection/chef/chef-connection-fns.ts rename to backend/src/ee/services/app-connections/chef/chef-connection-fns.ts index 1b35628bc..6cef8373f 100644 --- a/backend/src/services/app-connection/chef/chef-connection-fns.ts +++ b/backend/src/ee/services/app-connections/chef/chef-connection-fns.ts @@ -5,10 +5,10 @@ import { request } from "@app/lib/config/request"; import { BadRequestError } from "@app/lib/errors"; import { removeTrailingSlash } from "@app/lib/fn"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { TChefDataBagItemContent } from "../../secret-sync/chef/chef-sync-types"; -import { AppConnection } from "../app-connection-enums"; import { ChefConnectionMethod } from "./chef-connection-enums"; import { TChefConnection, diff --git a/backend/src/services/app-connection/chef/chef-connection-schemas.ts b/backend/src/ee/services/app-connections/chef/chef-connection-schemas.ts similarity index 100% rename from backend/src/services/app-connection/chef/chef-connection-schemas.ts rename to backend/src/ee/services/app-connections/chef/chef-connection-schemas.ts diff --git a/backend/src/services/app-connection/chef/chef-connection-service.ts b/backend/src/ee/services/app-connections/chef/chef-connection-service.ts similarity index 55% rename from backend/src/services/app-connection/chef/chef-connection-service.ts rename to backend/src/ee/services/app-connections/chef/chef-connection-service.ts index c989e7eaf..242b1fbf9 100644 --- a/backend/src/services/app-connection/chef/chef-connection-service.ts +++ b/backend/src/ee/services/app-connections/chef/chef-connection-service.ts @@ -1,7 +1,8 @@ -import { ForbiddenRequestError } from "@app/lib/errors"; +import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; -import { AppConnection } from "../app-connection-enums"; +import { AppConnection } from "../../../../services/app-connection/app-connection-enums"; +import { TLicenseServiceFactory } from "../../license/license-service"; import { listChefDataBagItems, listChefDataBags } from "./chef-connection-fns"; import { TChefConnection } from "./chef-connection-types"; @@ -11,8 +12,23 @@ type TGetAppConnectionFunc = ( actor: OrgServiceActor ) => Promise; -export const chefConnectionService = (getAppConnection: TGetAppConnectionFunc) => { +// Enterprise check +export const checkPlan = async (licenseService: Pick, orgId: string) => { + const plan = await licenseService.getPlan(orgId); + if (!plan.enterpriseAppConnections) + throw new BadRequestError({ + message: + "Failed to use app connection due to plan restriction. Upgrade plan to access enterprise app connections." + }); +}; + +export const chefConnectionService = ( + getAppConnection: TGetAppConnectionFunc, + licenseService: Pick +) => { const listDataBags = async (appConnectionId: string, actor: OrgServiceActor) => { + await checkPlan(licenseService, actor.orgId); + const appConnection = await getAppConnection(AppConnection.Chef, appConnectionId, actor); if (!appConnection) { @@ -23,6 +39,8 @@ export const chefConnectionService = (getAppConnection: TGetAppConnectionFunc) = }; const listDataBagItems = async (appConnectionId: string, dataBagName: string, actor: OrgServiceActor) => { + await checkPlan(licenseService, actor.orgId); + const appConnection = await getAppConnection(AppConnection.Chef, appConnectionId, actor); if (!appConnection) { diff --git a/backend/src/services/app-connection/chef/chef-connection-types.ts b/backend/src/ee/services/app-connections/chef/chef-connection-types.ts similarity index 87% rename from backend/src/services/app-connection/chef/chef-connection-types.ts rename to backend/src/ee/services/app-connections/chef/chef-connection-types.ts index a2da80d3d..5673614e9 100644 --- a/backend/src/services/app-connection/chef/chef-connection-types.ts +++ b/backend/src/ee/services/app-connections/chef/chef-connection-types.ts @@ -1,9 +1,9 @@ import z from "zod"; +import { TChefDataBagItemContent } from "@app/ee/services/secret-sync/chef"; import { DiscriminativePick } from "@app/lib/types"; -import { TChefDataBagItemContent } from "@app/services/secret-sync/chef"; -import { AppConnection } from "../app-connection-enums"; +import { AppConnection } from "../../../../services/app-connection/app-connection-enums"; import { ChefConnectionSchema, CreateChefConnectionSchema, diff --git a/backend/src/services/app-connection/chef/index.ts b/backend/src/ee/services/app-connections/chef/index.ts similarity index 100% rename from backend/src/services/app-connection/chef/index.ts rename to backend/src/ee/services/app-connections/chef/index.ts diff --git a/backend/src/ee/services/gateway-v2/gateway-v2-service.ts b/backend/src/ee/services/gateway-v2/gateway-v2-service.ts index eaec31e50..22daa1cd1 100644 --- a/backend/src/ee/services/gateway-v2/gateway-v2-service.ts +++ b/backend/src/ee/services/gateway-v2/gateway-v2-service.ts @@ -3,7 +3,7 @@ import net from "node:net"; import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; -import { OrganizationActionScope, OrgMembershipRole, TRelays } from "@app/db/schemas"; +import { OrganizationActionScope, OrgMembershipRole, OrgMembershipStatus, TRelays } from "@app/db/schemas"; import { PgSqlLock } from "@app/keystore/keystore"; import { crypto } from "@app/lib/crypto"; import { DatabaseErrorCode } from "@app/lib/error-codes"; @@ -909,7 +909,9 @@ export const gatewayV2ServiceFactory = ({ for await (const [orgId, gateways] of Object.entries(gatewaysByOrg)) { try { - const admins = await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin); + const admins = (await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin)).filter( + (admin) => admin.status !== OrgMembershipStatus.Invited + ); if (admins.length === 0) { logger.warn({ orgId }, "Organization has no admins to notify about unhealthy gateway."); // eslint-disable-next-line no-continue diff --git a/backend/src/ee/services/pam-account/pam-account-service.ts b/backend/src/ee/services/pam-account/pam-account-service.ts index 00b84943f..2f66d28d7 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -480,6 +480,36 @@ export const pamAccountServiceFactory = ({ throw new NotFoundError({ message: `Gateway connection details for gateway '${gatewayId}' not found.` }); } + let metadata; + + switch (resourceType) { + case PamResource.Postgres: + case PamResource.MySQL: + { + const connectionCredentials = await decryptResourceConnectionDetails({ + encryptedConnectionDetails: resource.encryptedConnectionDetails, + kmsService, + projectId: account.projectId + }); + + const credentials = await decryptAccountCredentials({ + encryptedCredentials: account.encryptedCredentials, + kmsService, + projectId: account.projectId + }); + + metadata = { + username: credentials.username, + database: connectionCredentials.database, + accountName: account.name, + accountPath + }; + } + break; + default: + break; + } + return { sessionId: session.id, resourceType, @@ -491,7 +521,8 @@ export const pamAccountServiceFactory = ({ gatewayServerCertificateChain: gatewayConnectionDetails.gateway.serverCertificateChain, relayHost: gatewayConnectionDetails.relayHost, projectId: account.projectId, - account + account, + metadata }; }; diff --git a/backend/src/ee/services/permission/default-roles.ts b/backend/src/ee/services/permission/default-roles.ts index 34876f739..5e7025f05 100644 --- a/backend/src/ee/services/permission/default-roles.ts +++ b/backend/src/ee/services/permission/default-roles.ts @@ -106,7 +106,9 @@ const buildAdminPermissionRules = () => { ProjectPermissionCertificateProfileActions.Edit, ProjectPermissionCertificateProfileActions.Create, ProjectPermissionCertificateProfileActions.Delete, - ProjectPermissionCertificateProfileActions.IssueCert + ProjectPermissionCertificateProfileActions.IssueCert, + ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret, + ProjectPermissionCertificateProfileActions.RotateAcmeEabSecret ], ProjectPermissionSub.CertificateProfiles ); diff --git a/backend/src/ee/services/permission/permission-dal.ts b/backend/src/ee/services/permission/permission-dal.ts index 95480a54a..88b52be22 100644 --- a/backend/src/ee/services/permission/permission-dal.ts +++ b/backend/src/ee/services/permission/permission-dal.ts @@ -201,11 +201,11 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { if (actorType === ActorType.USER) { void queryBuilder - .on(`${TableName.Membership}.actorUserId`, `${TableName.IdentityMetadata}.userId`) + .on(`${TableName.IdentityMetadata}.userId`, db.raw("?", [actorId])) .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); } else if (actorType === ActorType.IDENTITY) { void queryBuilder - .on(`${TableName.Membership}.actorIdentityId`, `${TableName.IdentityMetadata}.identityId`) + .on(`${TableName.IdentityMetadata}.identityId`, db.raw("?", [actorId])) .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); } }) @@ -488,7 +488,7 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => { }) .leftJoin(TableName.IdentityMetadata, (queryBuilder) => { void queryBuilder - .on(`${TableName.Membership}.actorUserId`, `${TableName.IdentityMetadata}.userId`) + .on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`) .andOn(`${TableName.Membership}.scopeOrgId`, `${TableName.IdentityMetadata}.orgId`); }) .where(`${TableName.Membership}.scopeOrgId`, orgId) diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index bb62440c1..74e4554ed 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -116,7 +116,9 @@ export enum ProjectPermissionCertificateProfileActions { Create = "create", Edit = "edit", Delete = "delete", - IssueCert = "issue-cert" + IssueCert = "issue-cert", + RevealAcmeEabSecret = "reveal-acme-eab-secret", + RotateAcmeEabSecret = "rotate-acme-eab-secret" } export enum ProjectPermissionSecretSyncActions { diff --git a/backend/src/ee/services/pki-acme/pki-acme-account-dal.ts b/backend/src/ee/services/pki-acme/pki-acme-account-dal.ts new file mode 100644 index 000000000..685d07f2b --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-account-dal.ts @@ -0,0 +1,42 @@ +import { Knex } from "knex"; + +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; +import { ormify } from "@app/lib/knex"; + +export type TPkiAcmeAccountDALFactory = ReturnType; + +export const pkiAcmeAccountDALFactory = (db: TDbClient) => { + const pkiAcmeAccountOrm = ormify(db, TableName.PkiAcmeAccount); + + const findByProjectIdAndAccountId = async (profileId: string, id: string, tx?: Knex) => { + try { + const account = await (tx || db)(TableName.PkiAcmeAccount).where({ profileId, id }).first(); + + return account || null; + } catch (error) { + throw new DatabaseError({ error, name: "Find PKI ACME account by id" }); + } + }; + + const findByProfileIdAndPublicKeyThumbprintAndAlg = async ( + profileId: string, + alg: string, + publicKeyThumbprint: string, + tx?: Knex + ) => { + try { + const account = await (tx || db)(TableName.PkiAcmeAccount).where({ profileId, alg, publicKeyThumbprint }).first(); + return account || null; + } catch (error) { + throw new DatabaseError({ error, name: "Find PKI ACME account by profile id, public key thumbprint and alg" }); + } + }; + + return { + ...pkiAcmeAccountOrm, + findByProjectIdAndAccountId, + findByProfileIdAndPublicKeyThumbprintAndAlg + }; +}; diff --git a/backend/src/ee/services/pki-acme/pki-acme-auth-dal.ts b/backend/src/ee/services/pki-acme/pki-acme-auth-dal.ts new file mode 100644 index 000000000..cef4d619f --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-auth-dal.ts @@ -0,0 +1,54 @@ +import { Knex } from "knex"; + +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; +import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; + +export type TPkiAcmeAuthDALFactory = ReturnType; + +export const pkiAcmeAuthDALFactory = (db: TDbClient) => { + const pkiAcmeAuthOrm = ormify(db, TableName.PkiAcmeAuth); + + const findByAccountIdAndAuthIdWithChallenges = async (accountId: string, authId: string, tx?: Knex) => { + try { + const rows = await (tx || db)(TableName.PkiAcmeAuth) + .leftJoin(TableName.PkiAcmeChallenge, `${TableName.PkiAcmeChallenge}.authId`, `${TableName.PkiAcmeAuth}.id`) + .select( + selectAllTableCols(TableName.PkiAcmeAuth), + db.ref("id").withSchema(TableName.PkiAcmeChallenge).as("challengeId"), + db.ref("type").withSchema(TableName.PkiAcmeChallenge).as("challengeType"), + db.ref("status").withSchema(TableName.PkiAcmeChallenge).as("challengeStatus") + ) + .where(`${TableName.PkiAcmeAuth}.accountId`, accountId) + .where(`${TableName.PkiAcmeAuth}.id`, authId); + + if (rows.length === 0) { + return null; + } + return sqlNestRelationships({ + data: rows, + key: "id", + parentMapper: (row) => row, + childrenMapper: [ + { + key: "challengeId", + label: "challenges" as const, + mapper: ({ challengeId, challengeType, challengeStatus }) => ({ + id: challengeId, + type: challengeType, + status: challengeStatus + }) + } + ] + })?.[0]; + } catch (error) { + throw new DatabaseError({ error, name: "Find PKI ACME auth by account id and auth id with challenges" }); + } + }; + + return { + ...pkiAcmeAuthOrm, + findByAccountIdAndAuthIdWithChallenges + }; +}; diff --git a/backend/src/ee/services/pki-acme/pki-acme-challenge-dal.ts b/backend/src/ee/services/pki-acme/pki-acme-challenge-dal.ts new file mode 100644 index 000000000..948f4af6e --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-challenge-dal.ts @@ -0,0 +1,175 @@ +import { TDbClient } from "@app/db"; +import { TableName, TPkiAcmeChallenges } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; +import { ormify, selectAllTableCols } from "@app/lib/knex"; +import { Knex } from "knex"; +import { AcmeAuthStatus, AcmeChallengeStatus, AcmeOrderStatus } from "./pki-acme-schemas"; + +export type TPkiAcmeChallengeDALFactory = ReturnType; + +export const pkiAcmeChallengeDALFactory = (db: TDbClient) => { + const pkiAcmeChallengeOrm = ormify(db, TableName.PkiAcmeChallenge); + + const markAsValidCascadeById = async (id: string, tx?: Knex): Promise => { + try { + const [challenge] = (await (tx || db)(TableName.PkiAcmeChallenge) + .where({ id }) + .update({ status: AcmeChallengeStatus.Valid, validatedAt: new Date() }) + .returning("*")) as [TPkiAcmeChallenges]; + + // Update pending auth to valid as well + const updatedAuths = await (tx || db)(TableName.PkiAcmeAuth) + .where({ id: challenge.authId, status: AcmeAuthStatus.Pending }) + .update({ status: AcmeAuthStatus.Valid }) + .returning("id"); + + if (updatedAuths.length > 0) { + // Find all the orders that are involved in the challenge validation + const involvedOrderIds = (tx || db)({ o: TableName.PkiAcmeOrder }) + .distinct("o.id") + .join({ oa: TableName.PkiAcmeOrderAuth }, "o.id", "oa.orderId") + .join({ a: TableName.PkiAcmeAuth }, "oa.authId", `a.id`) + .whereIn( + "a.id", + updatedAuths.map((auth) => auth.id) + ); + // Update status for pending orders that have all auths valid + await (tx || db)(TableName.PkiAcmeOrder) + .whereIn("id", (qb) => { + void qb + .select("o2.id") + .from({ o2: TableName.PkiAcmeOrder }) + .join({ oa2: TableName.PkiAcmeOrderAuth }, "o2.id", "oa2.orderId") + .join({ a2: TableName.PkiAcmeAuth }, "oa2.authId", "a2.id") + .groupBy("o2.id") + // All auths should be valid for the order to be ready + .havingRaw("SUM(CASE WHEN a2.status = ? THEN 1 ELSE 0 END) = COUNT(DISTINCT a2.id)", [ + AcmeAuthStatus.Valid + ]) + // Only update orders that are pending + .where("o2.status", AcmeOrderStatus.Pending) + .whereIn("o2.id", involvedOrderIds); + }) + .update({ status: AcmeOrderStatus.Ready }); + } + + return challenge; + } catch (error) { + throw new DatabaseError({ error, name: "Update certificate profile" }); + } + }; + + const markAsInvalidCascadeById = async (id: string, tx?: Knex): Promise => { + try { + const [challenge] = (await (tx || db)(TableName.PkiAcmeChallenge) + .where({ id }) + .update({ status: AcmeChallengeStatus.Invalid }) + .returning("*")) as [TPkiAcmeChallenges]; + + // Update pending auth to valid as well + const updatedAuths = await (tx || db)(TableName.PkiAcmeAuth) + .where({ id: challenge.authId, status: AcmeAuthStatus.Pending }) + .update({ status: AcmeAuthStatus.Invalid }) + .returning("id"); + + if (updatedAuths.length > 0) { + // Update status for pending orders that have all auths valid + await (tx || db)(TableName.PkiAcmeOrder) + .whereIn("id", (qb) => { + void qb + .select("o.id") + .from({ o: TableName.PkiAcmeOrder }) + .join(TableName.PkiAcmeOrderAuth, "o.id", `${TableName.PkiAcmeOrderAuth}.orderId`) + .join(TableName.PkiAcmeAuth, `${TableName.PkiAcmeOrderAuth}.authId`, `${TableName.PkiAcmeAuth}.id`) + // We only update orders that are pending + .where("o.status", AcmeOrderStatus.Pending) + .whereIn( + `${TableName.PkiAcmeAuth}.id`, + updatedAuths.map((auth) => auth.id) + ); + }) + .update({ status: AcmeOrderStatus.Invalid }); + } + + // TODO: update order status to invalid as well + return challenge; + } catch (error) { + throw new DatabaseError({ error, name: "Update certificate profile" }); + } + }; + + const findByAccountAuthAndChallengeId = async (accountId: string, authId: string, challengeId: string, tx?: Knex) => { + try { + const challenge = await (tx || db)(TableName.PkiAcmeChallenge) + .join(TableName.PkiAcmeAuth, `${TableName.PkiAcmeChallenge}.authId`, `${TableName.PkiAcmeAuth}.id`) + .select(selectAllTableCols(TableName.PkiAcmeChallenge)) + .where(`${TableName.PkiAcmeChallenge}.id`, challengeId) + .where(`${TableName.PkiAcmeChallenge}.authId`, authId) + .where(`${TableName.PkiAcmeAuth}.accountId`, accountId) + .first(); + if (!challenge) { + return null; + } + return challenge; + } catch (error) { + throw new DatabaseError({ error, name: "Find PKI ACME challenge by account id, auth id and challenge id" }); + } + }; + + const findByIdForChallengeValidation = async (id: string, tx?: Knex) => { + const result = await (tx || db)(TableName.PkiAcmeChallenge) + .join(TableName.PkiAcmeAuth, `${TableName.PkiAcmeChallenge}.authId`, `${TableName.PkiAcmeAuth}.id`) + .join(TableName.PkiAcmeAccount, `${TableName.PkiAcmeAuth}.accountId`, `${TableName.PkiAcmeAccount}.id`) + .select( + selectAllTableCols(TableName.PkiAcmeChallenge), + db.ref("id").withSchema(TableName.PkiAcmeAuth).as("authId"), + db.ref("token").withSchema(TableName.PkiAcmeAuth).as("authToken"), + db.ref("status").withSchema(TableName.PkiAcmeAuth).as("authStatus"), + db.ref("identifierType").withSchema(TableName.PkiAcmeAuth).as("authIdentifierType"), + db.ref("identifierValue").withSchema(TableName.PkiAcmeAuth).as("authIdentifierValue"), + db.ref("expiresAt").withSchema(TableName.PkiAcmeAuth).as("authExpiresAt"), + db.ref("id").withSchema(TableName.PkiAcmeAccount).as("accountId"), + db.ref("publicKeyThumbprint").withSchema(TableName.PkiAcmeAccount).as("accountPublicKeyThumbprint") + ) + // For all challenges, acquire update lock on the auth to avoid race conditions + .forUpdate(TableName.PkiAcmeAuth) + .where(`${TableName.PkiAcmeChallenge}.id`, id) + .first(); + if (!result) { + return null; + } + const { + authId, + authToken, + authStatus, + authIdentifierType, + authIdentifierValue, + authExpiresAt, + accountId, + accountPublicKeyThumbprint, + ...challenge + } = result; + return { + ...challenge, + auth: { + token: authToken, + status: authStatus, + identifierType: authIdentifierType, + identifierValue: authIdentifierValue, + expiresAt: authExpiresAt, + account: { + id: accountId, + publicKeyThumbprint: accountPublicKeyThumbprint + } + } + }; + }; + + return { + ...pkiAcmeChallengeOrm, + markAsValidCascadeById, + markAsInvalidCascadeById, + findByAccountAuthAndChallengeId, + findByIdForChallengeValidation + }; +}; diff --git a/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts b/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts new file mode 100644 index 000000000..a058241c4 --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-challenge-service.ts @@ -0,0 +1,133 @@ +import { getConfig } from "@app/lib/config/env"; +import { BadRequestError, NotFoundError } from "@app/lib/errors"; +import { isPrivateIp } from "@app/lib/ip/ipRange"; +import { logger } from "@app/lib/logger"; +import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal"; +import { + AcmeConnectionError, + AcmeDnsFailureError, + AcmeIncorrectResponseError, + AcmeServerInternalError +} from "./pki-acme-errors"; +import { AcmeAuthStatus, AcmeChallengeStatus, AcmeChallengeType } from "./pki-acme-schemas"; +import { TPkiAcmeChallengeServiceFactory } from "./pki-acme-types"; + +type FetchError = Error & { + code?: string; +}; + +type TPkiAcmeChallengeServiceFactoryDep = { + acmeChallengeDAL: Pick< + TPkiAcmeChallengeDALFactory, + "transaction" | "findByIdForChallengeValidation" | "markAsValidCascadeById" | "markAsInvalidCascadeById" + >; +}; + +export const pkiAcmeChallengeServiceFactory = ({ + acmeChallengeDAL +}: TPkiAcmeChallengeServiceFactoryDep): TPkiAcmeChallengeServiceFactory => { + const appCfg = getConfig(); + + const validateChallengeResponse = async (challengeId: string): Promise => { + const error: Error | undefined = await acmeChallengeDAL.transaction(async (tx) => { + logger.info({ challengeId }, "Validating ACME challenge response"); + const challenge = await acmeChallengeDAL.findByIdForChallengeValidation(challengeId, tx); + if (!challenge) { + throw new NotFoundError({ message: "ACME challenge not found" }); + } + if (challenge.status !== AcmeChallengeStatus.Pending) { + throw new BadRequestError({ + message: `ACME challenge is ${challenge.status} instead of ${AcmeChallengeStatus.Pending}` + }); + } + if (challenge.auth.expiresAt < new Date()) { + throw new BadRequestError({ message: "ACME auth has expired" }); + } + if (challenge.auth.status !== AcmeAuthStatus.Pending) { + throw new BadRequestError({ + message: `ACME auth status is ${challenge.auth.status} instead of ${AcmeAuthStatus.Pending}` + }); + } + + // TODO: support other challenge types here. Currently only HTTP-01 is supported + if (challenge.type !== AcmeChallengeType.HTTP_01) { + throw new BadRequestError({ message: "Only HTTP-01 challenges are supported for now" }); + } + let host = challenge.auth.identifierValue; + // check if host is a private ip address + if (isPrivateIp(host)) { + throw new BadRequestError({ message: "Private IP addresses are not allowed" }); + } + if (appCfg.isAcmeDevelopmentMode && appCfg.ACME_DEVELOPMENT_HTTP01_CHALLENGE_HOST_OVERRIDES[host]) { + host = appCfg.ACME_DEVELOPMENT_HTTP01_CHALLENGE_HOST_OVERRIDES[host]; + logger.warn( + { srcHost: challenge.auth.identifierValue, dstHost: host }, + "Using ACME development HTTP-01 challenge host override" + ); + } + const challengeUrl = new URL(`/.well-known/acme-challenge/${challenge.auth.token}`, `http://${host}`); + logger.info({ challengeUrl }, "Performing ACME HTTP-01 challenge validation"); + try { + // TODO: read config from the profile to get the timeout instead + const timeoutMs = 10 * 1000; // 10 seconds + // Notice: well, we are in a transaction, ideally we should not hold transaction and perform + // a long running operation for long time. But assuming we are not performing a tons of + // challenge validation at the same time, it should be fine. + const challengeResponse = await fetch(challengeUrl, { signal: AbortSignal.timeout(timeoutMs) }); + if (challengeResponse.status !== 200) { + throw new BadRequestError({ message: "ACME challenge response is not 200" }); + } + const challengeResponseBody = await challengeResponse.text(); + const thumbprint = challenge.auth.account.publicKeyThumbprint; + const expectedChallengeResponseBody = `${challenge.auth.token}.${thumbprint}`; + if (challengeResponseBody.trimEnd() !== expectedChallengeResponseBody) { + throw new AcmeIncorrectResponseError({ message: "ACME challenge response is not correct" }); + } + await acmeChallengeDAL.markAsValidCascadeById(challengeId, tx); + } catch (exp) { + // TODO: we should retry the challenge validation a few times, but let's keep it simple for now + await acmeChallengeDAL.markAsInvalidCascadeById(challengeId, tx); + // Properly type and inspect the error + if (exp instanceof TypeError && exp.message.includes("fetch failed")) { + const { cause } = exp; + let errors: Error[] = []; + if (cause instanceof AggregateError) { + errors = cause.errors as Error[]; + } else if (cause instanceof Error) { + errors = [cause]; + } + // eslint-disable-next-line no-unreachable-loop + for (const err of errors) { + // TODO: handle multiple errors, return a compound error instead of just the first error + const fetchError = err as FetchError; + if (fetchError.code === "ECONNREFUSED" || fetchError.message.includes("ECONNREFUSED")) { + return new AcmeConnectionError({ message: "Connection refused" }); + } + if (fetchError.code === "ENOTFOUND" || fetchError.message.includes("ENOTFOUND")) { + return new AcmeDnsFailureError({ message: "Hostname could not be resolved (DNS failure)" }); + } + return new AcmeServerInternalError({ message: "Unknown error validating ACME challenge response" }); + } + } else if (exp instanceof DOMException) { + if (exp.name === "TimeoutError") { + logger.error(exp, "Connection timed out while validating ACME challenge response"); + return new AcmeConnectionError({ message: "Connection timed out" }); + } + logger.error(exp, "Unknown error validating ACME challenge response"); + return new AcmeServerInternalError({ message: "Unknown error validating ACME challenge response" }); + } else if (exp instanceof Error) { + logger.error(exp, "Error validating ACME challenge response"); + } else { + logger.error(exp, "Unknown error validating ACME challenge response"); + return new AcmeServerInternalError({ message: "Unknown error validating ACME challenge response" }); + } + return exp; + } + }); + if (error) { + throw error; + } + }; + + return { validateChallengeResponse }; +}; diff --git a/backend/src/ee/services/pki-acme/pki-acme-errors.ts b/backend/src/ee/services/pki-acme/pki-acme-errors.ts new file mode 100644 index 000000000..febce5e81 --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-errors.ts @@ -0,0 +1,574 @@ +/** + * ACME Error Classes based on RFC 8555 Section 6.7 + * https://datatracker.ietf.org/doc/html/rfc8555#section-6.7 + */ + +/* eslint-disable max-classes-per-file */ + +// RFC 8555 Section 6.7 - Error Types +export enum AcmeErrorType { + AccountDoesNotExist = "accountDoesNotExist", + AlreadyRevoked = "alreadyRevoked", + BadCsr = "badCSR", + BadNonce = "badNonce", + BadPublicKey = "badPublicKey", + BadRevocationReason = "badRevocationReason", + BadSignatureAlgorithm = "badSignatureAlgorithm", + CAA = "caa", + Compound = "compound", + Connection = "connection", + DNS = "dns", + ExternalAccountRequired = "externalAccountRequired", + IncorrectResponse = "incorrectResponse", + InvalidContact = "invalidContact", + Malformed = "malformed", + OrderNotReady = "orderNotReady", + RateLimited = "rateLimited", + RejectedIdentifier = "rejectedIdentifier", + ServerInternal = "serverInternal", + TLS = "tls", + Unauthorized = "unauthorized", + UnsupportedContact = "unsupportedContact", + UnsupportedIdentifier = "unsupportedIdentifier", + UserActionRequired = "userActionRequired" +} + +export interface IAcmeError { + type: AcmeErrorType; + detail: string; + status: number; + subproblems?: Array<{ type: string; detail: string; identifier?: { type: string; value: string } }>; +} + +export class AcmeError extends Error implements IAcmeError { + type: AcmeErrorType; + + detail: string; + + status: number; + + subproblems?: Array<{ type: string; detail: string; identifier?: { type: string; value: string } }>; + + error?: unknown; + + constructor({ + type, + detail, + status, + subproblems, + error, + message + }: { + type: AcmeErrorType; + detail: string; + status: number; + subproblems?: Array<{ type: string; detail: string; identifier?: { type: string; value: string } }>; + error?: unknown; + message?: string; + }) { + super(message || detail); + this.type = type; + this.detail = detail; + this.status = status; + this.subproblems = subproblems; + this.error = error; + this.name = "AcmeError"; + } + + toAcmeResponse(): IAcmeError { + return { + type: this.type, + detail: this.detail, + status: this.status, + subproblems: this.subproblems + }; + } +} + +/** + * malformed - The request message was malformed (RFC 8555 Section 6.7.1) + */ +export class AcmeMalformedError extends AcmeError { + constructor({ + detail = "The request message was malformed", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.Malformed, + detail, + status: 400, + error, + message + }); + this.name = "AcmeMalformedError"; + } +} + +/** + * unauthorized - The client lacks sufficient authorization (RFC 8555 Section 6.7.2) + */ +export class AcmeUnauthorizedError extends AcmeError { + constructor({ + detail = "The client lacks sufficient authorization", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.Unauthorized, + detail, + status: 403, + error, + message + }); + this.name = "AcmeUnauthorizedError"; + } +} + +/** + * accountDoesNotExist - The request specified an account that does not exist + * (RFC 8555 Section 6.7.3) + */ +export class AcmeAccountDoesNotExistError extends AcmeError { + constructor({ + detail = "The request specified an account that does not exist", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.AccountDoesNotExist, + detail, + status: 400, + error, + message + }); + this.name = "AcmeAccountDoesNotExistError"; + } +} + +/** + * badNonce - The client sent an unacceptable anti-replay nonce (RFC 8555 Section 6.7.4) + */ +export class AcmeBadNonceError extends AcmeError { + constructor({ + detail = "The client sent an unacceptable anti-replay nonce", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.BadNonce, + detail, + status: 400, + error, + message + }); + this.name = "AcmeBadNonceError"; + } +} + +/** + * badSignatureAlgorithm - The signature algorithm is invalid (RFC 8555 Section 6.7.5) + */ +export class AcmeBadSignatureAlgorithmError extends AcmeError { + constructor({ + detail = "The signature algorithm is invalid", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.BadSignatureAlgorithm, + detail, + status: 401, + error, + message + }); + this.name = "AcmeBadSignatureAlgorithmError"; + } +} + +/** + * badPublicKey - The public key is not acceptable (RFC 8555 Section 6.7.6) + */ +export class AcmeBadPublicKeyError extends AcmeError { + constructor({ + detail = "The public key is not acceptable", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.BadPublicKey, + detail, + status: 400, + error, + message + }); + this.name = "AcmeBadPublicKeyError"; + } +} + +/** + * badCSR - The CSR is unacceptable (RFC 8555 Section 6.7.7) + */ +export class AcmeBadCsrError extends AcmeError { + constructor({ + detail = "The CSR is unacceptable", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.BadCsr, + detail, + status: 400, + error, + message + }); + this.name = "AcmeBadCsrError"; + } +} + +/** + * badRevocationReason - The revocation reason provided is not allowed + * (RFC 8555 Section 6.7.8) + */ +export class AcmeBadRevocationReasonError extends AcmeError { + constructor({ + detail = "The revocation reason provided is not allowed", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.BadRevocationReason, + detail, + status: 400, + error, + message + }); + this.name = "AcmeBadRevocationReasonError"; + } +} + +/** + * rateLimited - The client has exceeded a rate limit (RFC 8555 Section 6.7.9) + */ +export class AcmeRateLimitedError extends AcmeError { + constructor({ + detail = "The client has exceeded a rate limit", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.RateLimited, + detail, + status: 429, + error, + message + }); + this.name = "AcmeRateLimitedError"; + } +} + +/** + * rejectedIdentifier - The server will not issue certificates for the identifier + * (RFC 8555 Section 6.7.10) + */ +export class AcmeRejectedIdentifierError extends AcmeError { + constructor({ + detail = "The server will not issue certificates for the identifier", + subproblems, + error, + message + }: { + detail?: string; + subproblems?: Array<{ type: string; detail: string; identifier?: { type: string; value: string } }>; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.RejectedIdentifier, + detail, + status: 400, + subproblems, + error, + message + }); + this.name = "AcmeRejectedIdentifierError"; + } +} + +/** + * serverInternal - An internal error occurred (RFC 8555 Section 6.7.11) + */ +export class AcmeServerInternalError extends AcmeError { + constructor({ + detail = "An internal error occurred", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.ServerInternal, + detail, + status: 500, + error, + message + }); + this.name = "AcmeServerInternalError"; + } +} + +/** + * unsupportedContact - A contact URL is of an unsupported type (RFC 8555 Section 6.7.13) + */ +export class AcmeUnsupportedContactError extends AcmeError { + constructor({ + detail = "A contact URL is of an unsupported type", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.UnsupportedContact, + detail, + status: 400, + error, + message + }); + this.name = "AcmeUnsupportedContactError"; + } +} + +/** + * unsupportedIdentifier - An identifier is of an unsupported type + * (RFC 8555 Section 6.7.14) + */ +export class AcmeUnsupportedIdentifierError extends AcmeError { + constructor({ + detail = "An identifier is of an unsupported type", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.UnsupportedIdentifier, + detail, + status: 400, + error, + message + }); + this.name = "AcmeUnsupportedIdentifierError"; + } +} + +/** + * userActionRequired - Visit the "instance" URL and take actions specified there + * (RFC 8555 Section 6.7.15) + */ +export class AcmeUserActionRequiredError extends AcmeError { + instance?: string; + + constructor({ + detail = "Visit the instance URL and take actions specified there", + instance, + error, + message + }: { + detail?: string; + instance?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.UserActionRequired, + detail, + status: 403, + error, + message + }); + this.instance = instance; + this.name = "AcmeUserActionRequiredError"; + } + + toAcmeResponse(): IAcmeError & { instance?: string } { + return { + ...super.toAcmeResponse(), + instance: this.instance + }; + } +} + +/** + * incorrectResponse - The response is incorrect (RFC 8555 Section 6.7.16) + */ +export class AcmeIncorrectResponseError extends AcmeError { + constructor({ + detail = "The response is incorrect", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.IncorrectResponse, + detail, + status: 400, + error, + message + }); + this.name = "AcmeIncorrectResponseError"; + } +} + +/** + * connectionError - A connection error occurred (RFC 8555 Section 6.7.17) + */ +export class AcmeConnectionError extends AcmeError { + constructor({ + detail = "A connection error occurred", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.Connection, + detail, + status: 400, + error, + message + }); + this.name = "AcmeConnectionError"; + } +} + +export class AcmeDnsFailureError extends AcmeError { + constructor({ + detail = "Hostname could not be resolved (DNS failure)", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.DNS, + detail, + status: 400, + error, + message + }); + this.name = "AcmeDnsFailureError"; + } +} + +export class AcmeOrderNotReadyError extends AcmeError { + constructor({ + detail = "The order is not ready", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.OrderNotReady, + detail, + status: 403, + error, + message + }); + this.name = "AcmeOrderNotReadyError"; + } +} + +export class AcmeBadCSRError extends AcmeError { + constructor({ + detail = "The CSR is unacceptable", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.BadCsr, + detail, + status: 400, + error, + message + }); + this.name = "AcmeBadCSRError"; + } +} + +export class AcmeExternalAccountRequiredError extends AcmeError { + constructor({ + detail = "External account binding is required", + error, + message + }: { + detail?: string; + error?: unknown; + message?: string; + } = {}) { + super({ + type: AcmeErrorType.ExternalAccountRequired, + detail, + status: 400, + error, + message + }); + this.name = "AcmeExternalAccountRequiredError"; + } +} diff --git a/backend/src/ee/services/pki-acme/pki-acme-fns.ts b/backend/src/ee/services/pki-acme/pki-acme-fns.ts new file mode 100644 index 000000000..0763a8ed0 --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-fns.ts @@ -0,0 +1,17 @@ +import { getConfig } from "@app/lib/config/env"; +import { z } from "zod"; +import { AcmeMalformedError } from "./pki-acme-errors"; + +export const buildUrl = (profileId: string, path: string): string => { + const appCfg = getConfig(); + const baseUrl = appCfg.SITE_URL ?? ""; + return `${baseUrl}/api/v1/pki/acme/profiles/${profileId}${path}`; +}; + +export const extractAccountIdFromKid = (kid: string, profileId: string): string => { + const kidPrefix = buildUrl(profileId, "/accounts/"); + if (!kid.startsWith(kidPrefix)) { + throw new AcmeMalformedError({ detail: "KID must start with the profile account URL" }); + } + return z.string().uuid().parse(kid.slice(kidPrefix.length)); +}; diff --git a/backend/src/ee/services/pki-acme/pki-acme-order-auth-dal.ts b/backend/src/ee/services/pki-acme/pki-acme-order-auth-dal.ts new file mode 100644 index 000000000..5a91e6fea --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-order-auth-dal.ts @@ -0,0 +1,13 @@ +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { ormify } from "@app/lib/knex"; + +export type TPkiAcmeOrderAuthDALFactory = ReturnType; + +export const pkiAcmeOrderAuthDALFactory = (db: TDbClient) => { + const pkiAcmeOrderAuthOrm = ormify(db, TableName.PkiAcmeOrderAuth); + + return { + ...pkiAcmeOrderAuthOrm + }; +}; diff --git a/backend/src/ee/services/pki-acme/pki-acme-order-dal.ts b/backend/src/ee/services/pki-acme/pki-acme-order-dal.ts new file mode 100644 index 000000000..5aab0be63 --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-order-dal.ts @@ -0,0 +1,78 @@ +import { Knex } from "knex"; + +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; +import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; + +export type TPkiAcmeOrderDALFactory = ReturnType; + +export const pkiAcmeOrderDALFactory = (db: TDbClient) => { + const pkiAcmeOrderOrm = ormify(db, TableName.PkiAcmeOrder); + + const findByIdForFinalization = async (id: string, tx?: Knex) => { + try { + const order = await (tx || db)(TableName.PkiAcmeOrder).forUpdate().where({ id }).first(); + return order || null; + } catch (error) { + throw new DatabaseError({ error, name: "Find PKI ACME order by id for finalization" }); + } + }; + + const findByAccountAndOrderIdWithAuthorizations = async (accountId: string, orderId: string, tx?: Knex) => { + try { + const rows = await (tx || db)(TableName.PkiAcmeOrder) + .join(TableName.PkiAcmeOrderAuth, `${TableName.PkiAcmeOrderAuth}.orderId`, `${TableName.PkiAcmeOrder}.id`) + .join(TableName.PkiAcmeAuth, `${TableName.PkiAcmeOrderAuth}.authId`, `${TableName.PkiAcmeAuth}.id`) + .select( + selectAllTableCols(TableName.PkiAcmeOrder), + db.ref("id").withSchema(TableName.PkiAcmeAuth).as("authId"), + db.ref("identifierType").withSchema(TableName.PkiAcmeAuth).as("identifierType"), + db.ref("identifierValue").withSchema(TableName.PkiAcmeAuth).as("identifierValue"), + db.ref("expiresAt").withSchema(TableName.PkiAcmeAuth).as("authExpiresAt") + ) + .where(`${TableName.PkiAcmeOrder}.id`, orderId) + .where(`${TableName.PkiAcmeOrder}.accountId`, accountId) + .orderBy(`${TableName.PkiAcmeAuth}.identifierValue`, "asc"); + + if (rows.length === 0) { + return null; + } + return sqlNestRelationships({ + data: rows, + key: "id", + parentMapper: (row) => row, + childrenMapper: [ + { + key: "authId", + label: "authorizations" as const, + mapper: ({ authId, identifierType, identifierValue, authExpiresAt }) => ({ + id: authId, + identifierType, + identifierValue, + expiresAt: authExpiresAt + }) + } + ] + })?.[0]; + } catch (error) { + throw new DatabaseError({ error, name: "Find PKI ACME order by id" }); + } + }; + + const listByAccountId = async (accountId: string, tx?: Knex) => { + try { + const orders = await (tx || db)(TableName.PkiAcmeOrder).where({ accountId }).orderBy("createdAt", "desc"); + return orders; + } catch (error) { + throw new DatabaseError({ error, name: "List PKI ACME orders by account id" }); + } + }; + + return { + ...pkiAcmeOrderOrm, + findByIdForFinalization, + findByAccountAndOrderIdWithAuthorizations, + listByAccountId + }; +}; diff --git a/backend/src/ee/services/pki-acme/pki-acme-schemas.ts b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts new file mode 100644 index 000000000..4c7d6c3c1 --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-schemas.ts @@ -0,0 +1,174 @@ +import RE2 from "re2"; +import { z } from "zod"; + +export enum AcmeIdentifierType { + DNS = "dns" +} + +export enum AcmeOrderStatus { + Pending = "pending", + Processing = "processing", + Ready = "ready", + Valid = "valid", + Invalid = "invalid" +} + +export enum AcmeAuthStatus { + Pending = "pending", + Valid = "valid", + Invalid = "invalid", + Deactivated = "deactivated", + Expired = "expired", + Revoked = "revoked" +} + +export enum AcmeChallengeStatus { + Pending = "pending", + Processing = "processing", + Valid = "valid", + Invalid = "invalid" +} + +export enum AcmeChallengeType { + HTTP_01 = "http-01", + DNS_01 = "dns-01", + TLS_ALPN_01 = "tls-alpn-01" +} + +export const ProtectedHeaderSchema = z + .object({ + alg: z.string(), + nonce: z.string(), + url: z.string(), + kid: z.string().optional(), + jwk: z.record(z.string(), z.string()).optional() + }) + .refine((data) => data.kid || data.jwk, { + message: "Either kid or jwk must be provided", + path: ["kid", "jwk"] + }); + +// Raw JWS payload schema before parsing and verification +export const RawJwsPayloadSchema = z.object({ + protected: z.string(), + payload: z.string(), + signature: z.string() +}); + +export const GetAcmeDirectoryResponseSchema = z.object({ + newNonce: z.string(), + newAccount: z.string(), + newOrder: z.string(), + revokeCert: z.string().optional() +}); + +// New Account payload schema +export const CreateAcmeAccountBodySchema = z.object({ + contact: z.array(z.string()).optional(), + termsOfServiceAgreed: z.boolean().optional(), + onlyReturnExisting: z.boolean().optional(), + externalAccountBinding: RawJwsPayloadSchema.optional() +}); + +// New Account endpoint +export const CreateAcmeAccountSchema = z.object({ + params: z.object({ + profileId: z.string().uuid() + }), + body: CreateAcmeAccountBodySchema +}); + +export const CreateAcmeAccountResponseSchema = z.object({ + status: z.string(), + contact: z.array(z.string()).optional(), + orders: z.string().optional() +}); + +// New Order payload schema +export const CreateAcmeOrderBodySchema = z.object({ + identifiers: z.array( + z.object({ + type: z.enum(Object.values(AcmeIdentifierType) as [string, ...string[]]), + value: z.string().refine((val) => { + // DNS label pattern: 1-63 chars, alphanumeric or hyphen, but not starting or ending with hyphen + const labelPattern = new RE2(/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/); + const labels = val.split("."); + return labels.every((label) => label.length >= 1 && label.length <= 63 && labelPattern.test(label)); + }, "Invalid DNS identifier") + }) + ), + notBefore: z.string().optional(), + notAfter: z.string().optional() +}); + +export const AcmeOrderResourceSchema = z.object({ + status: z.enum(Object.values(AcmeOrderStatus) as [string, ...string[]]), + expires: z.string().optional(), + notBefore: z.string().optional(), + notAfter: z.string().optional(), + identifiers: z.array( + z.object({ + type: z.string(), + value: z.string() + }) + ), + authorizations: z.array(z.string()), + finalize: z.string(), + certificate: z.string().optional() +}); + +// Account Deactivation payload schema +export const DeactivateAcmeAccountBodySchema = z.object({ + status: z.literal("deactivated") +}); + +export const DeactivateAcmeAccountResponseSchema = z.object({ + status: z.string() +}); + +// List Orders endpoint +export const ListAcmeOrdersPayloadSchema = z.object({}).strict(); + +export const ListAcmeOrdersResponseSchema = z.object({ + orders: z.array(z.string()) +}); + +// Finalize Order payload schema +export const FinalizeAcmeOrderBodySchema = z.object({ + csr: z.string() +}); + +export const GetAcmeAuthorizationResponseSchema = z.object({ + status: z.enum(Object.values(AcmeAuthStatus) as [string, ...string[]]), + expires: z.string().optional(), + identifier: z.object({ + type: z.string(), + value: z.string() + }), + challenges: z.array( + z.object({ + type: z.enum(Object.values(AcmeChallengeType) as [string, ...string[]]), + url: z.string(), + status: z.string(), + token: z.string(), + validated: z.string().optional() + }) + ) +}); + +export const RespondToAcmeChallengeBodySchema = z.object({}).strict(); + +export const RespondToAcmeChallengeResponseSchema = z.object({ + type: z.enum(Object.values(AcmeChallengeType) as [string, ...string[]]), + url: z.string(), + status: z.string(), + token: z.string(), + validated: z.string().optional(), + error: z + .object({ + type: z.string(), + detail: z.string(), + status: z.number() + }) + .optional() +}); diff --git a/backend/src/ee/services/pki-acme/pki-acme-service.ts b/backend/src/ee/services/pki-acme/pki-acme-service.ts new file mode 100644 index 000000000..e3a82000d --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -0,0 +1,840 @@ +import { TPkiAcmeAccounts } from "@app/db/schemas/pki-acme-accounts"; +import { TPkiAcmeAuths } from "@app/db/schemas/pki-acme-auths"; +import { crypto } from "@app/lib/crypto/cryptography"; +import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; +import { logger } from "@app/lib/logger"; +import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; +import * as x509 from "@peculiar/x509"; + +import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; +import { isPrivateIp } from "@app/lib/ip/ipRange"; +import { ActorType } from "@app/services/auth/auth-type"; +import { + EnrollmentType, + TCertificateProfileWithConfigs +} from "@app/services/certificate-profile/certificate-profile-types"; +import { TCertificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; +import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { TProjectDALFactory } from "@app/services/project/project-dal"; +import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; +import { + calculateJwkThumbprint, + errors, + flattenedVerify, + FlattenedVerifyResult, + importJWK, + JWSHeaderParameters +} from "jose"; +import { z, ZodError } from "zod"; +import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal"; +import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal"; +import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal"; +import { + AcmeAccountDoesNotExistError, + AcmeBadCSRError, + AcmeBadNonceError, + AcmeBadPublicKeyError, + AcmeError, + AcmeExternalAccountRequiredError, + AcmeMalformedError, + AcmeOrderNotReadyError, + AcmeServerInternalError, + AcmeUnauthorizedError, + AcmeUnsupportedIdentifierError +} from "./pki-acme-errors"; +import { buildUrl, extractAccountIdFromKid } from "./pki-acme-fns"; +import { TPkiAcmeOrderAuthDALFactory } from "./pki-acme-order-auth-dal"; +import { TPkiAcmeOrderDALFactory } from "./pki-acme-order-dal"; +import { + AcmeAuthStatus, + AcmeChallengeStatus, + AcmeChallengeType, + AcmeIdentifierType, + AcmeOrderStatus, + CreateAcmeAccountBodySchema, + ProtectedHeaderSchema +} from "./pki-acme-schemas"; +import { + TAcmeOrderResource, + TAcmeResponse, + TAuthenciatedJwsPayload, + TCreateAcmeAccountPayload, + TCreateAcmeAccountResponse, + TCreateAcmeOrderPayload, + TDeactivateAcmeAccountPayload, + TDeactivateAcmeAccountResponse, + TFinalizeAcmeOrderPayload, + TGetAcmeAuthorizationResponse, + TGetAcmeDirectoryResponse, + TJwsPayload, + TListAcmeOrdersResponse, + TPkiAcmeChallengeServiceFactory, + TPkiAcmeServiceFactory, + TRawJwsPayload, + TRespondToAcmeChallengeResponse +} from "./pki-acme-types"; + +type TPkiAcmeServiceFactoryDep = { + projectDAL: Pick; + certificateProfileDAL: Pick; + certificateBodyDAL: Pick; + acmeAccountDAL: Pick< + TPkiAcmeAccountDALFactory, + "findByProjectIdAndAccountId" | "findByProfileIdAndPublicKeyThumbprintAndAlg" | "create" + >; + acmeOrderDAL: Pick< + TPkiAcmeOrderDALFactory, + | "create" + | "transaction" + | "updateById" + | "findByAccountAndOrderIdWithAuthorizations" + | "findByIdForFinalization" + | "listByAccountId" + >; + acmeAuthDAL: Pick; + acmeOrderAuthDAL: Pick; + acmeChallengeDAL: Pick< + TPkiAcmeChallengeDALFactory, + "create" | "transaction" | "updateById" | "findByAccountAuthAndChallengeId" | "findByIdForChallengeValidation" + >; + keyStore: Pick; + kmsService: Pick; + certificateV3Service: Pick; + acmeChallengeService: TPkiAcmeChallengeServiceFactory; +}; + +export const pkiAcmeServiceFactory = ({ + projectDAL, + certificateProfileDAL, + certificateBodyDAL, + acmeAccountDAL, + acmeOrderDAL, + acmeAuthDAL, + acmeOrderAuthDAL, + acmeChallengeDAL, + keyStore, + kmsService, + certificateV3Service, + acmeChallengeService +}: TPkiAcmeServiceFactoryDep): TPkiAcmeServiceFactory => { + const validateAcmeProfile = async (profileId: string): Promise => { + const profile = await certificateProfileDAL.findByIdWithConfigs(profileId); + if (!profile) { + throw new NotFoundError({ message: "Certificate profile not found" }); + } + if (profile.enrollmentType !== EnrollmentType.ACME) { + throw new NotFoundError({ message: "Certificate profile is not configured for ACME enrollment" }); + } + return profile; + }; + + const validateJwsPayload = async < + TSchema extends z.ZodSchema | undefined = undefined, + T = TSchema extends z.ZodSchema ? R : string + >({ + url, + rawJwsPayload, + getJWK, + schema + }: { + url: URL; + rawJwsPayload: TRawJwsPayload; + getJWK: (protectedHeader: JWSHeaderParameters) => Promise; + schema?: TSchema; + }): Promise> => { + let result: FlattenedVerifyResult; + try { + result = await flattenedVerify(rawJwsPayload, async (protectedHeader: JWSHeaderParameters | undefined) => { + if (protectedHeader === undefined) { + throw new AcmeMalformedError({ detail: "Protected header is required" }); + } + const jwk = await getJWK(protectedHeader); + const key = await importJWK(jwk, protectedHeader.alg); + return key; + }); + } catch (error) { + if (error instanceof AcmeError) { + throw error; + } + if (error instanceof ZodError) { + throw new AcmeMalformedError({ detail: `Invalid JWS payload: ${error.message}` }); + } + if (error instanceof errors.JWSSignatureVerificationFailed) { + throw new AcmeBadPublicKeyError({ detail: "Invalid JWS payload" }); + } + logger.error(error, "Unexpected error while verifying JWS payload"); + throw new AcmeServerInternalError({ detail: "Failed to verify JWS payload" }); + } + const { protectedHeader: rawProtectedHeader, payload: rawPayload } = result; + try { + const protectedHeader = ProtectedHeaderSchema.parse(rawProtectedHeader); + // Validate the URL + if (new URL(protectedHeader.url).href !== url.href) { + throw new AcmeUnauthorizedError({ detail: "URL mismatch in the protected header" }); + } + // Consume the nonce + if (!protectedHeader.nonce) { + throw new AcmeMalformedError({ detail: "Nonce is required in the protected header" }); + } + const deleted = await keyStore.deleteItem(KeyStorePrefixes.PkiAcmeNonce(protectedHeader.nonce)); + if (deleted !== 1) { + throw new AcmeBadNonceError({ detail: "Invalid nonce" }); + } + + // Parse the payload + const decoder = new TextDecoder(); + const textPayload = decoder.decode(rawPayload); + const payload = schema ? schema.parse(JSON.parse(textPayload)) : textPayload; + return { + protectedHeader, + payload: payload as T + }; + } catch (error) { + if (error instanceof AcmeError) { + throw error; + } + if (error instanceof ZodError) { + throw new AcmeMalformedError({ detail: `Invalid JWS payload: ${error.message}` }); + } + logger.error(error, "Unexpected error while parsing JWS payload"); + throw new AcmeServerInternalError({ detail: "Failed to verify JWS payload" }); + } + }; + + const validateNewAccountJwsPayload = ({ + url, + rawJwsPayload + }: { + url: URL; + rawJwsPayload: TRawJwsPayload; + }): Promise> => { + return validateJwsPayload({ + url, + rawJwsPayload, + getJWK: async (protectedHeader) => { + if (!protectedHeader.jwk) { + throw new AcmeMalformedError({ detail: "JWK is required in the protected header" }); + } + return protectedHeader.jwk as unknown as JsonWebKey; + }, + schema: CreateAcmeAccountBodySchema + }); + }; + + const validateExistingAccountJwsPayload = async < + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TSchema extends z.ZodSchema | undefined = undefined, + T = TSchema extends z.ZodSchema ? R : string + >({ + url, + profileId, + rawJwsPayload, + schema, + expectedAccountId + }: { + url: URL; + profileId: string; + rawJwsPayload: TRawJwsPayload; + schema?: TSchema; + expectedAccountId?: string; + }): Promise> => { + const profile = await validateAcmeProfile(profileId); + const result = await validateJwsPayload({ + url, + rawJwsPayload, + getJWK: async (protectedHeader) => { + if (!protectedHeader.kid) { + throw new AcmeMalformedError({ detail: "KID is required in the protected header" }); + } + const accountId = extractAccountIdFromKid(protectedHeader.kid, profileId); + if (expectedAccountId && accountId !== expectedAccountId) { + throw new NotFoundError({ message: "ACME resource not found" }); + } + const account = await acmeAccountDAL.findByProjectIdAndAccountId(profile.id, accountId); + if (!account) { + throw new AcmeAccountDoesNotExistError({ message: "ACME account not found" }); + } + if (account.alg !== protectedHeader.alg) { + throw new AcmeMalformedError({ detail: "ACME account algorithm mismatch" }); + } + return account.publicKey as JsonWebKey; + }, + schema + }); + return { + ...result, + accountId: extractAccountIdFromKid(result.protectedHeader.kid!, profileId), + profileId + }; + }; + + const buildAcmeOrderResource = ({ + profileId, + order + }: { + order: { + id: string; + status: string; + expiresAt: Date; + notBefore?: Date | null; + notAfter?: Date | null; + authorizations: { + id: string; + identifierType: string; + identifierValue: string; + expiresAt: Date; + }[]; + }; + profileId: string; + }): TAcmeOrderResource => { + return { + status: order.status, + expires: order.expiresAt.toISOString(), + notBefore: order.notBefore?.toISOString(), + notAfter: order.notAfter?.toISOString(), + identifiers: order.authorizations.map((auth) => ({ + type: auth.identifierType, + value: auth.identifierValue + })), + authorizations: order.authorizations.map((auth) => buildUrl(profileId, `/authorizations/${auth.id}`)), + finalize: buildUrl(profileId, `/orders/${order.id}/finalize`), + certificate: + order.status === AcmeOrderStatus.Valid ? buildUrl(profileId, `/orders/${order.id}/certificate`) : undefined + }; + }; + + const getAcmeDirectory = async (profileId: string): Promise => { + const profile = await validateAcmeProfile(profileId); + return { + newNonce: buildUrl(profile.id, "/new-nonce"), + newAccount: buildUrl(profile.id, "/new-account"), + newOrder: buildUrl(profile.id, "/new-order") + }; + }; + + const getAcmeNewNonce = async (profileId: string): Promise => { + await validateAcmeProfile(profileId); + const nonce = crypto.randomBytes(32).toString("base64url"); + const nonceKey = KeyStorePrefixes.PkiAcmeNonce(nonce); + await keyStore.setItemWithExpiry( + nonceKey, + // Expire in 5 minutes. + // TODO: read config from the profile to get the expiration time instead + 60 * 5, + nonce + ); + return nonce; + }; + + /** -------------------------------------------------------------- + * ACME Account + * -------------------------------------------------------------- */ + const createAcmeAccount = async ({ + profileId, + alg, + jwk, + payload: { onlyReturnExisting, contact, externalAccountBinding } + }: { + profileId: string; + alg: string; + jwk: JsonWebKey; + payload: TCreateAcmeAccountPayload; + }): Promise> => { + const profile = await validateAcmeProfile(profileId); + if (!externalAccountBinding) { + throw new AcmeExternalAccountRequiredError({ detail: "External account binding is required" }); + } + + const publicKeyThumbprint = await calculateJwkThumbprint(jwk, "sha256"); + const certificateManagerKmsId = await getProjectKmsCertificateKeyId({ + projectId: profile.projectId, + projectDAL, + kmsService + }); + const kmsDecryptor = await kmsService.decryptWithKmsKey({ + kmsId: certificateManagerKmsId + }); + const eabSecret = await kmsDecryptor({ cipherTextBlob: profile.acmeConfig!.encryptedEabSecret! }); + const { eabPayload, eabProtectedHeader } = await (async () => { + try { + const result = await flattenedVerify(externalAccountBinding, eabSecret); + return { eabPayload: result.payload, eabProtectedHeader: result.protectedHeader }; + } catch (error) { + if (error instanceof errors.JWSSignatureVerificationFailed) { + throw new AcmeMalformedError({ detail: "Invalid external account binding JWS signature" }); + } + logger.error(error, "Unexpected error while verifying EAB JWS signature"); + throw new AcmeServerInternalError({ detail: "Failed to verify EAB JWS signature" }); + } + })(); + + const { alg: eabAlg, kid: eabKid } = eabProtectedHeader!; + if (!["HS256", "HS384", "HS512"].includes(eabAlg!)) { + throw new AcmeMalformedError({ detail: "Invalid algorithm for external account binding JWS payload" }); + } + // Make sure the KID in the EAB payload matches the profile ID + if (eabKid !== profile.id) { + throw new UnauthorizedError({ message: "External account binding KID mismatch" }); + } + + // Make sure the URL matches the expected URL + const url = eabProtectedHeader!.url!; + if (url !== buildUrl(profile.id, "/new-account")) { + throw new UnauthorizedError({ message: "External account binding URL mismatch" }); + } + + // Make sure the JWK in the EAB payload matches the one provided in the outer JWS payload + const decoder = new TextDecoder(); + const decodedEabPayload = decoder.decode(eabPayload); + const eabJWK = JSON.parse(decodedEabPayload) as JsonWebKey; + const eabPayloadJwkThumbprint = await calculateJwkThumbprint(eabJWK, "sha256"); + if (eabPayloadJwkThumbprint !== publicKeyThumbprint) { + throw new AcmeBadPublicKeyError({ + message: "External account binding public key thumbprint or algorithm mismatch" + }); + } + + const existingAccount: TPkiAcmeAccounts | null = await acmeAccountDAL.findByProfileIdAndPublicKeyThumbprintAndAlg( + profileId, + alg, + publicKeyThumbprint + ); + if (onlyReturnExisting && !existingAccount) { + throw new AcmeAccountDoesNotExistError({ message: "ACME account not found" }); + } + if (existingAccount) { + // With the same public key, we found an existing account, just return it + return { + status: 200, + body: { + status: "valid", + contact: existingAccount.emails, + orders: buildUrl(profile.id, `/accounts/${existingAccount.id}/orders`) + }, + headers: { + Location: buildUrl(profile.id, `/accounts/${existingAccount.id}`), + Link: `<${buildUrl(profile.id, "/directory")}>;rel="index"` + } + }; + } + + const newAccount = await acmeAccountDAL.create({ + profileId: profile.id, + alg, + publicKey: jwk, + publicKeyThumbprint, + emails: contact ?? [] + }); + // TODO: create audit log here + return { + status: 201, + body: { + status: "valid", + contact: newAccount.emails, + orders: buildUrl(profile.id, `/accounts/${newAccount.id}/orders`) + }, + headers: { + Location: buildUrl(profile.id, `/accounts/${newAccount.id}`), + Link: `<${buildUrl(profile.id, "/directory")}>;rel="index"` + } + }; + }; + + const deactivateAcmeAccount = async ({ + profileId, + accountId + }: { + profileId: string; + accountId: string; + payload?: TDeactivateAcmeAccountPayload; + }): Promise> => { + await validateAcmeProfile(profileId); + // FIXME: Implement ACME account deactivation + return { + status: 200, + body: { + status: "deactivated" + }, + headers: { + Location: buildUrl(profileId, `/accounts/${accountId}`), + Link: `<${buildUrl(profileId, "/directory")}>;rel="index"` + } + }; + }; + + /** -------------------------------------------------------------- + * ACME Order + * -------------------------------------------------------------- */ + const createAcmeOrder = async ({ + profileId, + accountId, + payload + }: { + profileId: string; + accountId: string; + payload: TCreateAcmeOrderPayload; + }): Promise> => { + // TODO: check and see if we have existing orders for this account that meet the criteria + // if we do, return the existing order + // TODO: check the identifiers and see if are they even allowed for this profile. + // if not, we may be able to reject it early with an unsupportedIdentifier error. + + const order = await acmeOrderDAL.transaction(async (tx) => { + const account = (await acmeAccountDAL.findByProjectIdAndAccountId(profileId, accountId))!; + const createdOrder = await acmeOrderDAL.create( + { + accountId: account.id, + status: AcmeOrderStatus.Pending, + notBefore: payload.notBefore ? new Date(payload.notBefore) : undefined, + notAfter: payload.notAfter ? new Date(payload.notAfter) : undefined, + // TODO: read config from the profile to get the expiration time instead + expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) + }, + tx + ); + const authorizations: TPkiAcmeAuths[] = await Promise.all( + payload.identifiers.map(async (identifier) => { + if (identifier.type !== AcmeIdentifierType.DNS) { + throw new AcmeUnsupportedIdentifierError({ detail: "Only DNS identifiers are supported" }); + } + if (isPrivateIp(identifier.value)) { + throw new AcmeUnsupportedIdentifierError({ detail: "Private IP addresses are not allowed" }); + } + const auth = await acmeAuthDAL.create( + { + accountId: account.id, + status: AcmeAuthStatus.Pending, + identifierType: identifier.type, + identifierValue: identifier.value, + // RFC 8555 suggests a token with at least 128 bits of entropy + // We are using 256 bits of entropy here, should be enough for now + // ref: https://datatracker.ietf.org/doc/html/rfc8555#section-11.3 + token: crypto.randomBytes(32).toString("base64url"), + // TODO: read config from the profile to get the expiration time instead + expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) + }, + tx + ); + // TODO: support other challenge types here. Currently only HTTP-01 is supported. + await acmeChallengeDAL.create( + { + authId: auth.id, + status: AcmeChallengeStatus.Pending, + type: AcmeChallengeType.HTTP_01 + }, + tx + ); + return auth; + }) + ); + + await acmeOrderAuthDAL.insertMany( + authorizations.map((auth) => ({ + orderId: createdOrder.id, + authId: auth.id + })), + tx + ); + // TODO: create audit log here + return { ...createdOrder, authorizations, account }; + }); + + return { + status: 201, + body: buildAcmeOrderResource({ + profileId, + order + }), + headers: { + Location: buildUrl(profileId, `/orders/${order.id}`), + Link: `<${buildUrl(profileId, "/directory")}>;rel="index"` + } + }; + }; + + const getAcmeOrder = async ({ + profileId, + accountId, + orderId + }: { + profileId: string; + accountId: string; + orderId: string; + }): Promise> => { + const order = await acmeOrderDAL.findByAccountAndOrderIdWithAuthorizations(accountId, orderId); + if (!order) { + throw new NotFoundError({ message: "ACME order not found" }); + } + return { + status: 200, + body: buildAcmeOrderResource({ profileId, order }), + headers: { + Location: buildUrl(profileId, `/orders/${orderId}`), + Link: `<${buildUrl(profileId, "/directory")}>;rel="index"` + } + }; + }; + + const finalizeAcmeOrder = async ({ + profileId, + accountId, + orderId, + payload + }: { + profileId: string; + accountId: string; + orderId: string; + payload: TFinalizeAcmeOrderPayload; + }): Promise> => { + let order = await acmeOrderDAL.findByAccountAndOrderIdWithAuthorizations(accountId, orderId); + if (!order) { + throw new NotFoundError({ message: "ACME order not found" }); + } + if (order.status === AcmeOrderStatus.Ready) { + const { order: updatedOrder, error } = await acmeOrderDAL.transaction(async (tx) => { + const finalizingOrder = (await acmeOrderDAL.findByIdForFinalization(orderId, tx))!; + // TODO: ideally, this should be doen with onRequest: verifyAuth([AuthMode.ACME_JWS_SIGNATURE]), instead? + const { ownerOrgId: actorOrgId } = (await certificateProfileDAL.findByIdWithOwnerOrgId(profileId, tx))!; + if (finalizingOrder.status !== AcmeOrderStatus.Ready) { + throw new AcmeOrderNotReadyError({ message: "ACME order is not ready" }); + } + if (finalizingOrder.expiresAt < new Date()) { + throw new AcmeOrderNotReadyError({ message: "ACME order has expired" }); + } + const { csr } = payload; + let errorToReturn: Error | undefined; + try { + const { certificateId } = await certificateV3Service.signCertificateFromProfile({ + actor: ActorType.ACME_ACCOUNT, + actorId: accountId, + actorAuthMethod: null, + actorOrgId, + profileId, + csr, + notBefore: finalizingOrder.notBefore ? new Date(finalizingOrder.notBefore) : undefined, + notAfter: finalizingOrder.notAfter ? new Date(finalizingOrder.notAfter) : undefined, + validity: !finalizingOrder.notAfter + ? { + // TODO: read config from the profile to get the expiration time instead + ttl: (24 * 60 * 60 * 1000).toString() + } + : // ttl is not used if notAfter is provided + ({ ttl: "0" } as const), + enrollmentType: EnrollmentType.ACME + }); + // TODO: associate the certificate with the order + await acmeOrderDAL.updateById( + orderId, + { + status: AcmeOrderStatus.Valid, + csr, + certificateId + }, + tx + ); + } catch (exp) { + await acmeOrderDAL.updateById( + orderId, + { + csr, + status: AcmeOrderStatus.Invalid, + error: exp instanceof Error ? exp.message : "Unknown error" + }, + tx + ); + logger.error(exp, "Failed to sign certificate"); + // TODO: audit log the error + if (exp instanceof BadRequestError) { + errorToReturn = new AcmeBadCSRError({ detail: `Invalid CSR: ${exp.message}` }); + } else { + errorToReturn = new AcmeServerInternalError({ detail: "Failed to sign certificate with internal error" }); + } + } + return { + order: (await acmeOrderDAL.findByAccountAndOrderIdWithAuthorizations(accountId, orderId, tx))!, + error: errorToReturn + }; + }); + if (error) { + throw error; + } + order = updatedOrder; + } else if (order.status !== AcmeOrderStatus.Valid) { + throw new AcmeOrderNotReadyError({ message: "ACME order is not ready" }); + } + return { + status: 200, + body: buildAcmeOrderResource({ profileId, order }), + headers: { + Location: buildUrl(profileId, `/orders/${orderId}`), + Link: `<${buildUrl(profileId, "/directory")}>;rel="index"` + } + }; + }; + + const downloadAcmeCertificate = async ({ + profileId, + accountId, + orderId + }: { + profileId: string; + accountId: string; + orderId: string; + }): Promise> => { + const profile = await validateAcmeProfile(profileId); + const order = await acmeOrderDAL.findByAccountAndOrderIdWithAuthorizations(accountId, orderId); + if (!order) { + throw new NotFoundError({ message: "ACME order not found" }); + } + if (order.status !== AcmeOrderStatus.Valid) { + throw new AcmeOrderNotReadyError({ message: "ACME order is not valid" }); + } + if (!order.certificateId) { + throw new NotFoundError({ message: "The certificate for this ACME order no longer exists" }); + } + + const certBody = await certificateBodyDAL.findOne({ certId: order.certificateId }); + const certificateManagerKeyId = await getProjectKmsCertificateKeyId({ + projectId: profile.projectId, + projectDAL, + kmsService + }); + + const kmsDecryptor = await kmsService.decryptWithKmsKey({ + kmsId: certificateManagerKeyId + }); + const decryptedCert = await kmsDecryptor({ + cipherTextBlob: certBody.encryptedCertificate + }); + const certObj = new x509.X509Certificate(decryptedCert); + const decryptedCertChain = await kmsDecryptor({ + cipherTextBlob: certBody.encryptedCertificateChain! + }); + const certificateChain = decryptedCertChain.toString(); + + const certLeaf = certObj.toString("pem").trim().replace("\n", "\r\n"); + const certChain = certificateChain.trim().replace("\n", "\r\n"); + return { + status: 200, + body: + // The final line is needed, otherwise some clients will not parse the certificate chain correctly + // ref: https://github.com/certbot/certbot/blob/4d5d5f7ae8164884c841969e46caed8db1ad34af/certbot/src/certbot/crypto_util.py#L506-L514 + `${certLeaf}\r\n${certChain}\r\n`, + headers: { + Location: buildUrl(profileId, `/orders/${orderId}/certificate`), + Link: `<${buildUrl(profileId, "/directory")}>;rel="index"` + } + }; + }; + + const listAcmeOrders = async ({ + profileId, + accountId + }: { + profileId: string; + accountId: string; + }): Promise> => { + const orders = await acmeOrderDAL.listByAccountId(accountId); + return { + status: 200, + body: { orders: orders.map((order) => buildUrl(profileId, `/orders/${order.id}`)) }, + headers: { + Link: `<${buildUrl(profileId, "/directory")}>;rel="index"` + } + }; + }; + + /** -------------------------------------------------------------- + * ACME Authorization + * -------------------------------------------------------------- */ + const getAcmeAuthorization = async ({ + profileId, + accountId, + authzId + }: { + profileId: string; + accountId: string; + authzId: string; + }): Promise> => { + const auth = await acmeAuthDAL.findByAccountIdAndAuthIdWithChallenges(accountId, authzId); + if (!auth) { + throw new NotFoundError({ message: "ACME authorization not found" }); + } + return { + status: 200, + body: { + status: auth.status, + expires: auth.expiresAt.toISOString(), + identifier: { + type: auth.identifierType, + value: auth.identifierValue + }, + challenges: auth.challenges.map((challenge) => { + return { + type: challenge.type, + url: buildUrl(profileId, `/authorizations/${authzId}/challenges/${challenge.id}`), + status: challenge.status, + token: auth.token! + }; + }) + }, + headers: { + Location: buildUrl(profileId, `/authorizations/${authzId}`), + Link: `<${buildUrl(profileId, "/directory")}>;rel="index"` + } + }; + }; + + const respondToAcmeChallenge = async ({ + profileId, + accountId, + authzId, + challengeId + }: { + profileId: string; + accountId: string; + authzId: string; + challengeId: string; + }): Promise> => { + const result = await acmeChallengeDAL.findByAccountAuthAndChallengeId(accountId, authzId, challengeId); + if (!result) { + throw new NotFoundError({ message: "ACME challenge not found" }); + } + await acmeChallengeService.validateChallengeResponse(challengeId); + const challenge = (await acmeChallengeDAL.findByIdForChallengeValidation(challengeId))!; + return { + status: 200, + body: { + type: challenge.type, + url: buildUrl(profileId, `/authorizations/${authzId}/challenges/${challengeId}`), + status: challenge.status, + token: challenge.auth.token! + }, + headers: { + Location: buildUrl(profileId, `/authorizations/${authzId}/challenges/${challengeId}`), + Link: [ + `<${buildUrl(profileId, `/authorizations/${authzId}`)}>;rel="up"`, + `<${buildUrl(profileId, "/directory")}>;rel="index"` + ] + } + }; + }; + + return { + validateJwsPayload, + validateNewAccountJwsPayload, + validateExistingAccountJwsPayload, + getAcmeDirectory, + getAcmeNewNonce, + createAcmeAccount, + createAcmeOrder, + deactivateAcmeAccount, + listAcmeOrders, + getAcmeOrder, + finalizeAcmeOrder, + downloadAcmeCertificate, + getAcmeAuthorization, + respondToAcmeChallenge + }; +}; diff --git a/backend/src/ee/services/pki-acme/pki-acme-types.ts b/backend/src/ee/services/pki-acme/pki-acme-types.ts new file mode 100644 index 000000000..3132c9812 --- /dev/null +++ b/backend/src/ee/services/pki-acme/pki-acme-types.ts @@ -0,0 +1,180 @@ +import { z } from "zod"; + +import { JWSHeaderParameters } from "jose"; +import { + AcmeOrderResourceSchema, + CreateAcmeAccountBodySchema, + CreateAcmeAccountResponseSchema, + CreateAcmeOrderBodySchema, + DeactivateAcmeAccountBodySchema, + DeactivateAcmeAccountResponseSchema, + FinalizeAcmeOrderBodySchema, + GetAcmeAuthorizationResponseSchema, + GetAcmeDirectoryResponseSchema, + ListAcmeOrdersResponseSchema, + ProtectedHeaderSchema, + RawJwsPayloadSchema, + RespondToAcmeChallengeResponseSchema +} from "./pki-acme-schemas"; + +export type TGetAcmeDirectoryResponse = z.infer; +export type TCreateAcmeAccountResponse = z.infer; +export type TAcmeOrderResource = z.infer; +export type TDeactivateAcmeAccountResponse = z.infer; +export type TListAcmeOrdersResponse = z.infer; +export type TDownloadAcmeCertificateDTO = string; +export type TGetAcmeAuthorizationResponse = z.infer; +export type TRespondToAcmeChallengeResponse = z.infer; + +// Payload types +export type TRawJwsPayload = z.infer; +export type TProtectedHeader = z.infer; +export type TCreateAcmeAccountPayload = z.infer; +export type TCreateAcmeOrderPayload = z.infer; +export type TDeactivateAcmeAccountPayload = z.infer; +export type TFinalizeAcmeOrderPayload = z.infer; + +export type TJwsPayload = { + protectedHeader: TProtectedHeader; + payload: T; +}; +export type TAuthenciatedJwsPayload = TJwsPayload & { + profileId: string; + accountId: string; +}; +export type TAcmeResponse = { + status: number; + headers: Record; + body: TPayload; +}; + +export type TPkiAcmeServiceFactory = { + validateJwsPayload: < + TSchema extends z.ZodSchema | undefined = undefined, + T = TSchema extends z.ZodSchema ? R : string + >({ + url, + rawJwsPayload, + getJWK, + schema + }: { + url: URL; + rawJwsPayload: TRawJwsPayload; + getJWK: (protectedHeader: JWSHeaderParameters) => Promise; + schema?: TSchema; + }) => Promise>; + validateNewAccountJwsPayload: ({ + url, + rawJwsPayload + }: { + url: URL; + rawJwsPayload: TRawJwsPayload; + }) => Promise>; + validateExistingAccountJwsPayload: < + TSchema extends z.ZodSchema | undefined = undefined, + T = TSchema extends z.ZodSchema ? R : string + >({ + url, + profileId, + rawJwsPayload, + schema, + expectedAccountId + }: { + url: URL; + profileId: string; + rawJwsPayload: TRawJwsPayload; + schema?: TSchema; + expectedAccountId?: string; + }) => Promise>; + getAcmeDirectory: (profileId: string) => Promise; + getAcmeNewNonce: (profileId: string) => Promise; + createAcmeAccount: ({ + profileId, + alg, + jwk, + payload + }: { + profileId: string; + alg: string; + jwk: JsonWebKey; + payload: TCreateAcmeAccountPayload; + }) => Promise>; + deactivateAcmeAccount: ({ + profileId, + accountId, + payload + }: { + profileId: string; + accountId: string; + payload?: TDeactivateAcmeAccountPayload; + }) => Promise>; + createAcmeOrder: ({ + profileId, + accountId, + payload + }: { + profileId: string; + accountId: string; + payload: TCreateAcmeOrderPayload; + }) => Promise>; + getAcmeOrder: ({ + profileId, + accountId, + orderId + }: { + profileId: string; + accountId: string; + orderId: string; + }) => Promise>; + finalizeAcmeOrder: ({ + profileId, + accountId, + orderId, + payload + }: { + profileId: string; + accountId: string; + orderId: string; + payload: TFinalizeAcmeOrderPayload; + }) => Promise>; + downloadAcmeCertificate: ({ + profileId, + accountId, + orderId + }: { + profileId: string; + accountId: string; + orderId: string; + }) => Promise>; + listAcmeOrders: ({ + profileId, + accountId + }: { + profileId: string; + accountId: string; + }) => Promise>; + getAcmeAuthorization: ({ + profileId, + accountId, + authzId + }: { + profileId: string; + accountId: string; + authzId: string; + }) => Promise>; + respondToAcmeChallenge: ({ + profileId, + accountId, + authzId, + challengeId + }: { + profileId: string; + accountId: string; + authzId: string; + challengeId: string; + }) => Promise>; +}; + +export type TPkiAcmeChallengeServiceFactory = { + validateChallengeResponse: (challengeId: string) => Promise; +}; diff --git a/backend/src/ee/services/relay/relay-service.ts b/backend/src/ee/services/relay/relay-service.ts index 096011765..ae7758e67 100644 --- a/backend/src/ee/services/relay/relay-service.ts +++ b/backend/src/ee/services/relay/relay-service.ts @@ -3,7 +3,7 @@ import { isIP } from "node:net"; import { ForbiddenError } from "@casl/ability"; import * as x509 from "@peculiar/x509"; -import { OrganizationActionScope, OrgMembershipRole, TRelays } from "@app/db/schemas"; +import { OrganizationActionScope, OrgMembershipRole, OrgMembershipStatus, TRelays } from "@app/db/schemas"; import { PgSqlLock } from "@app/keystore/keystore"; import { crypto } from "@app/lib/crypto"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; @@ -996,7 +996,9 @@ export const relayServiceFactory = ({ ); if (existingRelay && (existingRelay.host !== host || existingRelay.name !== name)) { - return relayDAL.updateById(existingRelay.id, { host, name }, tx); + throw new BadRequestError({ + message: `Machine identity already has an existing relay with the name "${existingRelay.name}" and host "${existingRelay.host}". Delete the existing relay or use a different machine identity.` + }); } if (!existingRelay) { @@ -1248,7 +1250,9 @@ export const relayServiceFactory = ({ }); } } else { - const admins = await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin); + const admins = (await orgDAL.findOrgMembersByRole(orgId, OrgMembershipRole.Admin)).filter( + (admin) => admin.status !== OrgMembershipStatus.Invited + ); if (admins.length === 0) { // eslint-disable-next-line no-continue continue; diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts index 7597dcfd4..db300720f 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts @@ -670,6 +670,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { .select( db.ref("projectId").withSchema(TableName.Environment), db.ref("slug").withSchema(TableName.Environment).as("environment"), + db.ref("name").withSchema(TableName.Environment).as("environmentName"), db.ref("id").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerId"), db.ref("reviewerUserId").withSchema(TableName.SecretApprovalRequestReviewer), db.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"), @@ -699,30 +700,30 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { ) .as("inner"); - const countQuery = (await (tx || db) - .select(db.raw("count(*) OVER() as total_count")) - .from(innerQuery.clone().distinctOn(`${TableName.SecretApprovalRequest}.id`))) as Array<{ - total_count: number; - }>; - const query = (tx || db).select("*").from(innerQuery).orderBy("createdAt", "desc") as typeof innerQuery; if (search) { void query.where((qb) => { void qb .whereRaw(`CONCAT_WS(' ', ??, ??) ilike ?`, [ - db.ref("firstName").withSchema("committerUser"), - db.ref("lastName").withSchema("committerUser"), + db.ref("committerUserFirstName"), + db.ref("committerUserLastName"), `%${search}%` ]) - .orWhereRaw(`?? ilike ?`, [db.ref("username").withSchema("committerUser"), `%${search}%`]) - .orWhereRaw(`?? ilike ?`, [db.ref("email").withSchema("committerUser"), `%${search}%`]) - .orWhereILike(`${TableName.Environment}.name`, `%${search}%`) - .orWhereILike(`${TableName.Environment}.slug`, `%${search}%`) - .orWhereILike(`${TableName.SecretApprovalPolicy}.secretPath`, `%${search}%`); + .orWhereRaw(`?? ilike ?`, [db.ref("committerUserUsername"), `%${search}%`]) + .orWhereRaw(`?? ilike ?`, [db.ref("committerUserEmail"), `%${search}%`]) + .orWhereILike(`environmentName`, `%${search}%`) + .orWhereILike(`environment`, `%${search}%`) + .orWhereILike(`policySecretPath`, `%${search}%`); }); } + const countQuery = (await (tx || db) + .select(db.raw("count(*) OVER() as total_count")) + .from(query.clone().as("outer"))) as Array<{ + total_count: number; + }>; + const rankOffset = offset + 1; const docs = await (tx || db) .with("w", query) diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts index d96fb2e53..8f1c3d060 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-fns.ts @@ -37,7 +37,7 @@ export const sendApprovalEmailsFn = async ({ type: NotificationType.SECRET_CHANGE_REQUEST, title: "Secret Change Request", body: `You have a new secret change request pending your review for the project **${project.name}** in the organization **${project.organization.name}**.`, - link: `/projects/secret-management/${project.id}/approval?requestId=${secretApprovalRequest.id}` + link: `/projects/secret-management/${project.id}/approval` })) ); @@ -51,7 +51,7 @@ export const sendApprovalEmailsFn = async ({ firstName: reviewerUser.firstName, projectName: project.name, organizationName: project.organization.name, - approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval?requestId=${secretApprovalRequest.id}` + approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval}` }, template: SmtpTemplates.SecretApprovalRequestNeedsReview }); diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index a10a3f568..e6455c113 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -1416,6 +1416,11 @@ export const secretApprovalRequestServiceFactory = ({ const env = await projectEnvDAL.findOne({ id: policy.envId }); const user = await userDAL.findById(actorId); + const projectPath = `/projects/secret-management/${projectId}`; + const approvalPath = `${projectPath}/approval`; + const cfg = getConfig(); + const approvalUrl = `${cfg.SITE_URL}${approvalPath}`; + await triggerWorkflowIntegrationNotification({ input: { projectId, @@ -1427,7 +1432,8 @@ export const secretApprovalRequestServiceFactory = ({ secretPath, projectId, requestId: secretApprovalRequest.id, - secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretName) ?? []))] + secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretName) ?? []))], + approvalUrl } } }, @@ -1786,6 +1792,11 @@ export const secretApprovalRequestServiceFactory = ({ const user = await userDAL.findById(actorId); const env = await projectEnvDAL.findOne({ id: policy.envId }); + const projectPath = `/projects/secret-management/${project.id}`; + const approvalPath = `${projectPath}/approval`; + const cfg = getConfig(); + const approvalUrl = `${cfg.SITE_URL}${approvalPath}`; + await triggerWorkflowIntegrationNotification({ input: { projectId, @@ -1797,7 +1808,8 @@ export const secretApprovalRequestServiceFactory = ({ secretPath, projectId, requestId: secretApprovalRequest.id, - secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretKey) ?? []))] + secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretKey) ?? []))], + approvalUrl } } }, diff --git a/backend/src/services/secret-sync/chef/chef-sync-constants.ts b/backend/src/ee/services/secret-sync/chef/chef-sync-constants.ts similarity index 89% rename from backend/src/services/secret-sync/chef/chef-sync-constants.ts rename to backend/src/ee/services/secret-sync/chef/chef-sync-constants.ts index 567b25bbe..8bbf0e12a 100644 --- a/backend/src/services/secret-sync/chef/chef-sync-constants.ts +++ b/backend/src/ee/services/secret-sync/chef/chef-sync-constants.ts @@ -6,5 +6,6 @@ export const CHEF_SYNC_LIST_OPTION: TSecretSyncListItem = { name: "Chef", destination: SecretSync.Chef, connection: AppConnection.Chef, - canImportSecrets: true + canImportSecrets: true, + enterprise: true }; diff --git a/backend/src/services/secret-sync/chef/chef-sync-fns.ts b/backend/src/ee/services/secret-sync/chef/chef-sync-fns.ts similarity index 99% rename from backend/src/services/secret-sync/chef/chef-sync-fns.ts rename to backend/src/ee/services/secret-sync/chef/chef-sync-fns.ts index 7f32b398a..65a4fca9b 100644 --- a/backend/src/services/secret-sync/chef/chef-sync-fns.ts +++ b/backend/src/ee/services/secret-sync/chef/chef-sync-fns.ts @@ -1,4 +1,4 @@ -import { getChefDataBagItem, updateChefDataBagItem } from "@app/services/app-connection/chef"; +import { getChefDataBagItem, updateChefDataBagItem } from "@app/ee/services/app-connections/chef"; import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; diff --git a/backend/src/services/secret-sync/chef/chef-sync-schemas.ts b/backend/src/ee/services/secret-sync/chef/chef-sync-schemas.ts similarity index 96% rename from backend/src/services/secret-sync/chef/chef-sync-schemas.ts rename to backend/src/ee/services/secret-sync/chef/chef-sync-schemas.ts index c2e09011e..9702f97d3 100644 --- a/backend/src/services/secret-sync/chef/chef-sync-schemas.ts +++ b/backend/src/ee/services/secret-sync/chef/chef-sync-schemas.ts @@ -42,5 +42,6 @@ export const ChefSyncListItemSchema = z.object({ name: z.literal("Chef"), connection: z.literal(AppConnection.Chef), destination: z.literal(SecretSync.Chef), - canImportSecrets: z.literal(true) + canImportSecrets: z.literal(true), + enterprise: z.boolean() }); diff --git a/backend/src/services/secret-sync/chef/chef-sync-types.ts b/backend/src/ee/services/secret-sync/chef/chef-sync-types.ts similarity index 92% rename from backend/src/services/secret-sync/chef/chef-sync-types.ts rename to backend/src/ee/services/secret-sync/chef/chef-sync-types.ts index 464e0372d..0f70e7e1a 100644 --- a/backend/src/services/secret-sync/chef/chef-sync-types.ts +++ b/backend/src/ee/services/secret-sync/chef/chef-sync-types.ts @@ -1,6 +1,6 @@ import z from "zod"; -import { TChefConnection } from "@app/services/app-connection/chef"; +import { TChefConnection } from "@app/ee/services/app-connections/chef"; import { ChefSyncListItemSchema, ChefSyncSchema, CreateChefSyncSchema } from "./chef-sync-schemas"; diff --git a/backend/src/services/secret-sync/chef/index.ts b/backend/src/ee/services/secret-sync/chef/index.ts similarity index 100% rename from backend/src/services/secret-sync/chef/index.ts rename to backend/src/ee/services/secret-sync/chef/index.ts diff --git a/backend/src/keystore/keystore.ts b/backend/src/keystore/keystore.ts index 3155fe05c..204952a92 100644 --- a/backend/src/keystore/keystore.ts +++ b/backend/src/keystore/keystore.ts @@ -77,7 +77,9 @@ export const KeyStorePrefixes = { UserProjectPermissionPattern: (userId: string) => `project-permission:*:*:USER:${userId}:*` as const, IdentityProjectPermissionPattern: (identityId: string) => `project-permission:*:*:IDENTITY:${identityId}:*` as const, GroupMemberProjectPermissionPattern: (projectId: string, groupId: string) => - `group-member-project-permission:${projectId}:${groupId}:*` as const + `group-member-project-permission:${projectId}:${groupId}:*` as const, + + PkiAcmeNonce: (nonce: string) => `pki-acme-nonce:${nonce}` as const }; export const KeyStoreTtls = { diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 0cb606cbf..2a8fbadac 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -62,6 +62,7 @@ export enum ApiDocsTags { PkiCertificateCollections = "PKI Certificate Collections", PkiAlerting = "PKI Alerting", PkiSubscribers = "PKI Subscribers", + PkiAcme = "PKI ACME", SshCertificates = "SSH Certificates", SshCertificateAuthorities = "SSH Certificate Authorities", SshCertificateTemplates = "SSH Certificate Templates", diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 9fc4cff92..b60971b1f 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -106,6 +106,21 @@ const envSchema = z HTTPS_ENABLED: zodStrBool, ROTATION_DEVELOPMENT_MODE: zodStrBool.default("false").optional(), DAILY_RESOURCE_CLEAN_UP_DEVELOPMENT_MODE: zodStrBool.default("false").optional(), + // Note: The ACME feature is still in development and is not yet ready for production. + // This is the feature flag to enable/disable the ACME feature. + // It's not intended to be used by users outside of the development team yet. + ACME_FEATURE_ENABLED: zodStrBool.default("false").optional(), + ACME_DEVELOPMENT_MODE: zodStrBool.default("false").optional(), + ACME_DEVELOPMENT_HTTP01_CHALLENGE_HOST_OVERRIDES: zpStr( + z + .string() + .optional() + .transform((val) => { + if (!val) return {}; + return JSON.parse(val) as Record; + }) + .default("{}") + ), // smtp options SMTP_HOST: zpStr(z.string().optional()), SMTP_IGNORE_TLS: zodStrBool.default("false"), @@ -384,6 +399,8 @@ const envSchema = z (data.NODE_ENV === "development" && data.ROTATION_DEVELOPMENT_MODE) || data.NODE_ENV === "test", isDailyResourceCleanUpDevelopmentMode: data.NODE_ENV === "development" && data.DAILY_RESOURCE_CLEAN_UP_DEVELOPMENT_MODE, + isAcmeFeatureEnabled: data.NODE_ENV === "development" && data.ACME_FEATURE_ENABLED === true, + isAcmeDevelopmentMode: data.NODE_ENV === "development" && data.ACME_DEVELOPMENT_MODE, isProductionMode: data.NODE_ENV === "production" || IS_PACKAGED, isRedisSentinelMode: Boolean(data.REDIS_SENTINEL_HOSTS), REDIS_SENTINEL_HOSTS: data.REDIS_SENTINEL_HOSTS?.trim() diff --git a/backend/src/lib/workflow-integrations/notification-handlers/microsoft-teams.ts b/backend/src/lib/workflow-integrations/notification-handlers/microsoft-teams.ts new file mode 100644 index 000000000..0cfe28491 --- /dev/null +++ b/backend/src/lib/workflow-integrations/notification-handlers/microsoft-teams.ts @@ -0,0 +1,92 @@ +import { validateMicrosoftTeamsChannelsSchema } from "@app/services/microsoft-teams/microsoft-teams-fns"; +import { TMicrosoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service"; +import { + TProjectMicrosoftTeamsConfigDALFactory, + TProjectMicrosoftTeamsConfigWithIntegrations +} from "@app/services/microsoft-teams/project-microsoft-teams-config-dal"; + +import { logger } from "../../logger"; +import { TNotification, TriggerFeature } from "../types"; + +const handleMicrosoftTeamsNotification = async ({ + microsoftTeamsConfig, + notification, + orgId, + microsoftTeamsService +}: { + microsoftTeamsConfig: TProjectMicrosoftTeamsConfigWithIntegrations; + notification: TNotification; + orgId: string; + microsoftTeamsService: Pick; +}): Promise => { + let targetChannels: unknown; + let isEnabled = false; + + switch (notification.type) { + case TriggerFeature.ACCESS_REQUEST: + case TriggerFeature.ACCESS_REQUEST_UPDATED: + targetChannels = microsoftTeamsConfig.accessRequestChannels; + isEnabled = microsoftTeamsConfig.isAccessRequestNotificationEnabled; + break; + case TriggerFeature.SECRET_APPROVAL: + targetChannels = microsoftTeamsConfig.secretRequestChannels; + isEnabled = microsoftTeamsConfig.isSecretRequestNotificationEnabled; + break; + default: + return; + } + + if (isEnabled && targetChannels) { + const { success, data, error: validationError } = validateMicrosoftTeamsChannelsSchema.safeParse(targetChannels); + + if (!success) { + logger.error(validationError, "Invalid Microsoft Teams channel configuration"); + return; + } + + if (data) { + await microsoftTeamsService + .sendNotification({ + notification, + target: data, + tenantId: microsoftTeamsConfig.tenantId, + microsoftTeamsIntegrationId: microsoftTeamsConfig.id, + orgId + }) + .catch((error) => { + logger.error( + error, + `Error sending Microsoft Teams notification. Notification type: ${notification.type}, Tenant ID: ${microsoftTeamsConfig.tenantId}, Project ID: ${microsoftTeamsConfig.projectId}` + ); + }); + } + } +}; + +export const triggerMicrosoftTeamsNotification = async ({ + projectId, + notification, + orgId, + projectMicrosoftTeamsConfigDAL, + microsoftTeamsService +}: { + projectId: string; + notification: TNotification; + orgId: string; + projectMicrosoftTeamsConfigDAL: Pick; + microsoftTeamsService: Pick; +}): Promise => { + try { + const config = await projectMicrosoftTeamsConfigDAL.getIntegrationDetailsByProject(projectId); + if (config) { + await handleMicrosoftTeamsNotification({ + microsoftTeamsConfig: config, + notification, + orgId, + microsoftTeamsService + }); + } + } catch (error) { + logger.error(error, `Error handling Microsoft Teams notification. Project ID: ${projectId}`); + } +}; diff --git a/backend/src/lib/workflow-integrations/notification-handlers/slack.ts b/backend/src/lib/workflow-integrations/notification-handlers/slack.ts new file mode 100644 index 000000000..b55f172cb --- /dev/null +++ b/backend/src/lib/workflow-integrations/notification-handlers/slack.ts @@ -0,0 +1,80 @@ +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { + TProjectSlackConfigDALFactory, + TProjectSlackConfigWithIntegrations +} from "@app/services/slack/project-slack-config-dal"; +import { sendSlackNotification } from "@app/services/slack/slack-fns"; + +import { logger } from "../../logger"; +import { TNotification, TriggerFeature } from "../types"; + +const handleSlackNotification = async ({ + slackConfig, + notification, + orgId, + kmsService +}: { + slackConfig: TProjectSlackConfigWithIntegrations; + notification: TNotification; + orgId: string; + kmsService: Pick; +}): Promise => { + let targetChannelIds: string[] = []; + let isEnabled = false; + + switch (notification.type) { + case TriggerFeature.ACCESS_REQUEST: + case TriggerFeature.ACCESS_REQUEST_UPDATED: + targetChannelIds = slackConfig.accessRequestChannels?.split(", ").filter(Boolean) || []; + isEnabled = slackConfig.isAccessRequestNotificationEnabled; + break; + case TriggerFeature.SECRET_APPROVAL: + targetChannelIds = slackConfig.secretRequestChannels?.split(", ").filter(Boolean) || []; + isEnabled = slackConfig.isSecretRequestNotificationEnabled; + break; + case TriggerFeature.SECRET_SYNC_ERROR: + targetChannelIds = slackConfig.secretSyncErrorChannels?.split(", ").filter(Boolean) || []; + isEnabled = slackConfig.isSecretSyncErrorNotificationEnabled; + break; + default: + return; + } + + if (targetChannelIds.length && isEnabled) { + await sendSlackNotification({ + orgId, + notification, + kmsService, + targetChannelIds, + slackIntegration: slackConfig + }).catch((error) => { + logger.error( + error, + `Error sending Slack notification. Notification type: ${notification.type}, Target channel IDs: ${targetChannelIds.join(", ")}, Project ID: ${slackConfig.projectId}` + ); + }); + } +}; + +export const triggerSlackNotification = async ({ + projectId, + notification, + orgId, + projectSlackConfigDAL, + kmsService +}: { + projectId: string; + notification: TNotification; + orgId: string; + projectSlackConfigDAL: Pick; + kmsService: Pick; +}): Promise => { + try { + const config = await projectSlackConfigDAL.getIntegrationDetailsByProject(projectId); + if (config) { + await handleSlackNotification({ slackConfig: config, notification, orgId, kmsService }); + } + } catch (error) { + logger.error(error, `Error handling Slack notification. Project ID: ${projectId}`); + } +}; diff --git a/backend/src/lib/workflow-integrations/trigger-notification.ts b/backend/src/lib/workflow-integrations/trigger-notification.ts index 355761f73..2dc6f61fe 100644 --- a/backend/src/lib/workflow-integrations/trigger-notification.ts +++ b/backend/src/lib/workflow-integrations/trigger-notification.ts @@ -1,8 +1,7 @@ -import { validateMicrosoftTeamsChannelsSchema } from "@app/services/microsoft-teams/microsoft-teams-fns"; -import { sendSlackNotification } from "@app/services/slack/slack-fns"; - import { logger } from "../logger"; -import { TriggerFeature, TTriggerWorkflowNotificationDTO } from "./types"; +import { triggerMicrosoftTeamsNotification } from "./notification-handlers/microsoft-teams"; +import { triggerSlackNotification } from "./notification-handlers/slack"; +import { TTriggerWorkflowNotificationDTO } from "./types"; export const triggerWorkflowIntegrationNotification = async (dto: TTriggerWorkflowNotificationDTO) => { try { @@ -16,88 +15,25 @@ export const triggerWorkflowIntegrationNotification = async (dto: TTriggerWorkfl return; } - const microsoftTeamsConfig = await projectMicrosoftTeamsConfigDAL.getIntegrationDetailsByProject(projectId); - const slackConfig = await projectSlackConfigDAL.getIntegrationDetailsByProject(projectId); + const handlerPromises = [ + triggerSlackNotification({ + projectId, + notification, + orgId: project.orgId, + projectSlackConfigDAL, + kmsService + }), - if (slackConfig) { - if ( - notification.type === TriggerFeature.ACCESS_REQUEST || - notification.type === TriggerFeature.ACCESS_REQUEST_UPDATED - ) { - const targetChannelIds = slackConfig.accessRequestChannels?.split(", ") || []; - if (targetChannelIds.length && slackConfig.isAccessRequestNotificationEnabled) { - await sendSlackNotification({ - orgId: project.orgId, - notification, - kmsService, - targetChannelIds, - slackIntegration: slackConfig - }).catch((error) => { - logger.error(error, "Error sending Slack notification"); - }); - } - } else if (notification.type === TriggerFeature.SECRET_APPROVAL) { - const targetChannelIds = slackConfig.secretRequestChannels?.split(", ") || []; - if (targetChannelIds.length && slackConfig.isSecretRequestNotificationEnabled) { - await sendSlackNotification({ - orgId: project.orgId, - notification, - kmsService, - targetChannelIds, - slackIntegration: slackConfig - }).catch((error) => { - logger.error(error, "Error sending Slack notification"); - }); - } - } - } + triggerMicrosoftTeamsNotification({ + projectId, + notification, + orgId: project.orgId, + projectMicrosoftTeamsConfigDAL, + microsoftTeamsService + }) + ]; - if (microsoftTeamsConfig) { - if ( - notification.type === TriggerFeature.ACCESS_REQUEST || - notification.type === TriggerFeature.ACCESS_REQUEST_UPDATED - ) { - if (microsoftTeamsConfig.isAccessRequestNotificationEnabled && microsoftTeamsConfig.accessRequestChannels) { - const { success, data } = validateMicrosoftTeamsChannelsSchema.safeParse( - microsoftTeamsConfig.accessRequestChannels - ); - - if (success && data) { - await microsoftTeamsService - .sendNotification({ - notification, - target: data, - tenantId: microsoftTeamsConfig.tenantId, - microsoftTeamsIntegrationId: microsoftTeamsConfig.id, - orgId: project.orgId - }) - .catch((error) => { - logger.error(error, "Error sending Microsoft Teams notification"); - }); - } - } - } else if (notification.type === TriggerFeature.SECRET_APPROVAL) { - if (microsoftTeamsConfig.isSecretRequestNotificationEnabled && microsoftTeamsConfig.secretRequestChannels) { - const { success, data } = validateMicrosoftTeamsChannelsSchema.safeParse( - microsoftTeamsConfig.secretRequestChannels - ); - - if (success && data) { - await microsoftTeamsService - .sendNotification({ - notification, - target: data, - tenantId: microsoftTeamsConfig.tenantId, - microsoftTeamsIntegrationId: microsoftTeamsConfig.id, - orgId: project.orgId - }) - .catch((error) => { - logger.error(error, "Error sending Microsoft Teams notification"); - }); - } - } - } - } + await Promise.allSettled(handlerPromises); } catch (error) { logger.error(error, "Error triggering workflow integration notification"); } diff --git a/backend/src/lib/workflow-integrations/types.ts b/backend/src/lib/workflow-integrations/types.ts index f8f55eadd..6d81c9174 100644 --- a/backend/src/lib/workflow-integrations/types.ts +++ b/backend/src/lib/workflow-integrations/types.ts @@ -7,7 +7,8 @@ import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack export enum TriggerFeature { SECRET_APPROVAL = "secret-approval", ACCESS_REQUEST = "access-request", - ACCESS_REQUEST_UPDATED = "access-request-updated" + ACCESS_REQUEST_UPDATED = "access-request-updated", + SECRET_SYNC_ERROR = "secret-sync-error" } export type TNotification = @@ -20,6 +21,7 @@ export type TNotification = requestId: string; projectId: string; secretKeys: string[]; + approvalUrl: string; }; } | { @@ -31,6 +33,7 @@ export type TNotification = secretPath: string; environment: string; projectName: string; + projectPath: string; permissions: string[]; approvalUrl: string; note?: string; @@ -50,6 +53,21 @@ export type TNotification = editNote?: string; editorFullName?: string; editorEmail?: string; + projectPath: string; + }; + } + | { + type: TriggerFeature.SECRET_SYNC_ERROR; + payload: { + syncName: string; + syncActionLabel: string; + syncDestination: string; + failureMessage: string; + syncUrl: string; + environment: string; + secretPath: string; + projectName: string; + projectPath: string; }; }; diff --git a/backend/src/server/plugins/add-errors-to-response-schemas.ts b/backend/src/server/plugins/add-errors-to-response-schemas.ts index 8eb358a1b..6337bae0f 100644 --- a/backend/src/server/plugins/add-errors-to-response-schemas.ts +++ b/backend/src/server/plugins/add-errors-to-response-schemas.ts @@ -6,9 +6,16 @@ import { DefaultResponseErrorsSchema } from "../routes/sanitizedSchemas"; const isScimRoutes = (pathname: string) => pathname.startsWith("/api/v1/scim/Users") || pathname.startsWith("/api/v1/scim/Groups"); +const isAcmeRoutes = (pathname: string) => pathname.startsWith("/api/v1/pki/acme/"); + export const addErrorsToResponseSchemas = fp(async (server) => { server.addHook("onRoute", (routeOptions) => { - if (routeOptions.schema && routeOptions.schema.response && !isScimRoutes(routeOptions.path)) { + if ( + routeOptions.schema && + routeOptions.schema.response && + !isScimRoutes(routeOptions.path) && + !isAcmeRoutes(routeOptions.path) + ) { routeOptions.schema.response = { ...DefaultResponseErrorsSchema, ...routeOptions.schema.response diff --git a/backend/src/server/plugins/error-handler.ts b/backend/src/server/plugins/error-handler.ts index 8d10a8630..4b29f6930 100644 --- a/backend/src/server/plugins/error-handler.ts +++ b/backend/src/server/plugins/error-handler.ts @@ -5,6 +5,7 @@ import fastifyPlugin from "fastify-plugin"; import jwt from "jsonwebtoken"; import { ZodError } from "zod"; +import { AcmeError } from "@app/ee/services/pki-acme/pki-acme-errors"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError, @@ -242,6 +243,19 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider error: "TokenError", message: errorMessage }); + } else if (error instanceof AcmeError) { + void res + .type("application/problem+json") + .status(error.status) + .send({ + reqId: req.id, + error: error.name, + status: error.status, + type: `urn:ietf:params:acme:error:${error.type}`, + detail: error.detail, + message: error.message + // TODO: add subproblems if they exist + }); } else { void res.status(HttpStatusCodes.InternalServerError).send({ reqId: req.id, diff --git a/backend/src/server/plugins/serve-ui.ts b/backend/src/server/plugins/serve-ui.ts index b71451b6e..4330f9397 100644 --- a/backend/src/server/plugins/serve-ui.ts +++ b/backend/src/server/plugins/serve-ui.ts @@ -31,7 +31,10 @@ export const registerServeUI = async ( CAPTCHA_SITE_KEY: appCfg.CAPTCHA_SITE_KEY, POSTHOG_API_KEY: appCfg.POSTHOG_PROJECT_API_KEY, INTERCOM_ID: appCfg.INTERCOM_ID, - TELEMETRY_CAPTURING_ENABLED: appCfg.TELEMETRY_ENABLED + TELEMETRY_CAPTURING_ENABLED: appCfg.TELEMETRY_ENABLED, + // The feature flag to enable/disable the ACME feature. + // Will be removed once the feature is ready for production. + ACME_FEATURE_ENABLED: appCfg.isAcmeFeatureEnabled }; const js = `window.__INFISICAL_RUNTIME_ENV__ = Object.freeze(${JSON.stringify(config)});`; return res.send(js); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 5bcb56238..70146c854 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -17,30 +17,30 @@ import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approva import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal"; import { accessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service"; import { assumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service"; +import { auditLogStreamDALFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-dal"; +import { auditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service"; import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal"; import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue"; import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; -import { auditLogStreamDALFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-dal"; -import { auditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service"; import { certificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { certificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service"; import { certificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service"; -import { dynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal"; -import { dynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service"; -import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/providers"; import { dynamicSecretLeaseDALFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal"; import { dynamicSecretLeaseQueueServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue"; import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service"; +import { dynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal"; +import { dynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service"; +import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/providers"; import { eventBusFactory } from "@app/ee/services/event/event-bus-service"; import { sseServiceFactory } from "@app/ee/services/event/event-sse-service"; import { externalKmsDALFactory } from "@app/ee/services/external-kms/external-kms-dal"; import { externalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service"; -import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; -import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; -import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal"; import { gatewayV2DalFactory } from "@app/ee/services/gateway-v2/gateway-v2-dal"; import { gatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; import { orgGatewayConfigV2DalFactory } from "@app/ee/services/gateway-v2/org-gateway-config-v2-dal"; +import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; +import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; +import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal"; import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal"; import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service"; import { groupDALFactory } from "@app/ee/services/group/group-dal"; @@ -74,6 +74,10 @@ import { pamSessionServiceFactory } from "@app/ee/services/pam-session/pam-sessi import { permissionDALFactory } from "@app/ee/services/permission/permission-dal"; import { permissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { pitServiceFactory } from "@app/ee/services/pit/pit-service"; +import { pkiAcmeAuthDALFactory } from "@app/ee/services/pki-acme/pki-acme-auth-dal"; +import { pkiAcmeChallengeServiceFactory } from "@app/ee/services/pki-acme/pki-acme-challenge-service"; +import { pkiAcmeOrderAuthDALFactory } from "@app/ee/services/pki-acme/pki-acme-order-auth-dal"; +import { pkiAcmeServiceFactory } from "@app/ee/services/pki-acme/pki-acme-service"; import { projectTemplateDALFactory } from "@app/ee/services/project-template/project-template-dal"; import { projectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service"; import { rateLimitDALFactory } from "@app/ee/services/rate-limit/rate-limit-dal"; @@ -98,39 +102,39 @@ import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret import { secretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal"; import { secretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service"; import { secretReplicationServiceFactory } from "@app/ee/services/secret-replication/secret-replication-service"; -import { secretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal"; -import { secretRotationQueueFactory } from "@app/ee/services/secret-rotation/secret-rotation-queue"; -import { secretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service"; import { secretRotationV2DALFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-dal"; import { secretRotationV2QueueServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-queue"; import { secretRotationV2ServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-service"; +import { secretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal"; +import { secretRotationQueueFactory } from "@app/ee/services/secret-rotation/secret-rotation-queue"; +import { secretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service"; +import { secretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; +import { secretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; +import { secretScanningV2ServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-service"; import { gitAppDALFactory } from "@app/ee/services/secret-scanning/git-app-dal"; import { gitAppInstallSessionDALFactory } from "@app/ee/services/secret-scanning/git-app-install-session-dal"; import { secretScanningDALFactory } from "@app/ee/services/secret-scanning/secret-scanning-dal"; import { secretScanningQueueFactory } from "@app/ee/services/secret-scanning/secret-scanning-queue"; import { secretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service"; -import { secretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; -import { secretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; -import { secretScanningV2ServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-service"; import { secretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service"; import { snapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal"; import { snapshotFolderDALFactory } from "@app/ee/services/secret-snapshot/snapshot-folder-dal"; import { snapshotSecretDALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-dal"; import { snapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal"; -import { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; -import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; -import { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service"; -import { sshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-dal"; -import { sshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal"; import { sshCertificateTemplateDALFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-dal"; import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service"; +import { sshCertificateBodyDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-body-dal"; +import { sshCertificateDALFactory } from "@app/ee/services/ssh-certificate/ssh-certificate-dal"; +import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; +import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal"; +import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; import { sshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal"; import { sshHostLoginUserMappingDALFactory } from "@app/ee/services/ssh-host/ssh-host-login-user-mapping-dal"; import { sshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service"; import { sshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-user-dal"; -import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; -import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal"; -import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; +import { sshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; +import { sshCertificateAuthoritySecretDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-secret-dal"; +import { sshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh-certificate-authority-service"; import { subOrgServiceFactory } from "@app/ee/services/sub-org/sub-org-service"; import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"; import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; @@ -150,16 +154,12 @@ import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal"; import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service"; import { appConnectionDALFactory } from "@app/services/app-connection/app-connection-dal"; import { appConnectionServiceFactory } from "@app/services/app-connection/app-connection-service"; +import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal"; +import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { authDALFactory } from "@app/services/auth/auth-dal"; import { authLoginServiceFactory } from "@app/services/auth/auth-login-service"; import { authPaswordServiceFactory } from "@app/services/auth/auth-password-service"; import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service"; -import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal"; -import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; -import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; -import { certificateDALFactory } from "@app/services/certificate/certificate-dal"; -import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; -import { certificateServiceFactory } from "@app/services/certificate/certificate-service"; import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue"; @@ -172,16 +172,21 @@ import { internalCertificateAuthorityServiceFactory } from "@app/services/certif import { certificateEstV3ServiceFactory } from "@app/services/certificate-est-v3/certificate-est-v3-service"; import { certificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import { certificateProfileServiceFactory } from "@app/services/certificate-profile/certificate-profile-service"; +import { certificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal"; +import { certificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { certificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal"; import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal"; import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service"; -import { certificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal"; -import { certificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { certificateV3QueueServiceFactory } from "@app/services/certificate-v3/certificate-v3-queue"; import { certificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service"; +import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; +import { certificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; +import { certificateServiceFactory } from "@app/services/certificate/certificate-service"; import { cmekServiceFactory } from "@app/services/cmek/cmek-service"; import { convertorServiceFactory } from "@app/services/convertor/convertor-service"; +import { acmeEnrollmentConfigDALFactory } from "@app/services/enrollment-config/acme-enrollment-config-dal"; import { apiEnrollmentConfigDALFactory } from "@app/services/enrollment-config/api-enrollment-config-dal"; import { estEnrollmentConfigDALFactory } from "@app/services/enrollment-config/est-enrollment-config-dal"; import { externalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal"; @@ -189,21 +194,17 @@ import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/externa import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue"; import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service"; import { vaultExternalMigrationConfigDALFactory } from "@app/services/external-migration/vault-external-migration-config-dal"; -import { folderCheckpointDALFactory } from "@app/services/folder-checkpoint/folder-checkpoint-dal"; import { folderCheckpointResourcesDALFactory } from "@app/services/folder-checkpoint-resources/folder-checkpoint-resources-dal"; +import { folderCheckpointDALFactory } from "@app/services/folder-checkpoint/folder-checkpoint-dal"; +import { folderCommitChangesDALFactory } from "@app/services/folder-commit-changes/folder-commit-changes-dal"; import { folderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal"; import { folderCommitQueueServiceFactory } from "@app/services/folder-commit/folder-commit-queue"; import { folderCommitServiceFactory } from "@app/services/folder-commit/folder-commit-service"; -import { folderCommitChangesDALFactory } from "@app/services/folder-commit-changes/folder-commit-changes-dal"; -import { folderTreeCheckpointDALFactory } from "@app/services/folder-tree-checkpoint/folder-tree-checkpoint-dal"; import { folderTreeCheckpointResourcesDALFactory } from "@app/services/folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal"; +import { folderTreeCheckpointDALFactory } from "@app/services/folder-tree-checkpoint/folder-tree-checkpoint-dal"; import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service"; import { healthAlertServiceFactory } from "@app/services/health-alert/health-alert-queue"; -import { identityDALFactory } from "@app/services/identity/identity-dal"; -import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; -import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal"; -import { identityServiceFactory } from "@app/services/identity/identity-service"; 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 { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal"; @@ -233,23 +234,27 @@ import { identityTokenAuthServiceFactory } from "@app/services/identity-token-au import { identityUaClientSecretDALFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal"; import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal"; import { identityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; -import { integrationDALFactory } from "@app/services/integration/integration-dal"; -import { integrationServiceFactory } from "@app/services/integration/integration-service"; +import { identityDALFactory } from "@app/services/identity/identity-dal"; +import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; +import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal"; +import { identityServiceFactory } from "@app/services/identity/identity-service"; import { integrationAuthDALFactory } from "@app/services/integration-auth/integration-auth-dal"; import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service"; +import { integrationDALFactory } from "@app/services/integration/integration-dal"; +import { integrationServiceFactory } from "@app/services/integration/integration-service"; import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal"; import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal"; import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; import { kmsServiceFactory } from "@app/services/kms/kms-service"; import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types"; -import { membershipDALFactory } from "@app/services/membership/membership-dal"; -import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { membershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal"; import { membershipGroupServiceFactory } from "@app/services/membership-group/membership-group-service"; import { membershipIdentityDALFactory } from "@app/services/membership-identity/membership-identity-dal"; import { membershipIdentityServiceFactory } from "@app/services/membership-identity/membership-identity-service"; import { membershipUserDALFactory } from "@app/services/membership-user/membership-user-dal"; import { membershipUserServiceFactory } from "@app/services/membership-user/membership-user-service"; +import { membershipDALFactory } from "@app/services/membership/membership-dal"; +import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { microsoftTeamsIntegrationDALFactory } from "@app/services/microsoft-teams/microsoft-teams-integration-dal"; import { microsoftTeamsServiceFactory } from "@app/services/microsoft-teams/microsoft-teams-service"; import { projectMicrosoftTeamsConfigDALFactory } from "@app/services/microsoft-teams/project-microsoft-teams-config-dal"; @@ -258,11 +263,11 @@ import { notificationServiceFactory } from "@app/services/notification/notificat import { userNotificationDALFactory } from "@app/services/notification/user-notification-dal"; import { offlineUsageReportDALFactory } from "@app/services/offline-usage-report/offline-usage-report-dal"; import { offlineUsageReportServiceFactory } from "@app/services/offline-usage-report/offline-usage-report-service"; +import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; +import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal"; import { orgDALFactory } from "@app/services/org/org-dal"; import { orgServiceFactory } from "@app/services/org/org-service"; -import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; -import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { pamAccountRotationServiceFactory } from "@app/services/pam-account-rotation/pam-account-rotation-queue"; import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue"; import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal"; @@ -284,10 +289,6 @@ import { pkiSyncQueueFactory } from "@app/services/pki-sync/pki-sync-queue"; import { pkiSyncServiceFactory } from "@app/services/pki-sync/pki-sync-service"; import { pkiTemplatesDALFactory } from "@app/services/pki-templates/pki-templates-dal"; import { pkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service"; -import { projectDALFactory } from "@app/services/project/project-dal"; -import { projectQueueFactory } from "@app/services/project/project-queue"; -import { projectServiceFactory } from "@app/services/project/project-service"; -import { projectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; import { projectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; import { projectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { projectEnvDALFactory } from "@app/services/project-env/project-env-dal"; @@ -296,19 +297,18 @@ import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal" import { projectKeyServiceFactory } from "@app/services/project-key/project-key-service"; import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { projectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; +import { projectDALFactory } from "@app/services/project/project-dal"; +import { projectQueueFactory } from "@app/services/project/project-queue"; +import { projectServiceFactory } from "@app/services/project/project-service"; +import { projectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; +import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal"; import { reminderDALFactory } from "@app/services/reminder/reminder-dal"; import { dailyReminderQueueServiceFactory } from "@app/services/reminder/reminder-queue"; import { reminderServiceFactory } from "@app/services/reminder/reminder-service"; -import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal"; import { roleDALFactory } from "@app/services/role/role-dal"; import { roleServiceFactory } from "@app/services/role/role-service"; -import { secretDALFactory } from "@app/services/secret/secret-dal"; -import { secretQueueFactory } from "@app/services/secret/secret-queue"; -import { secretServiceFactory } from "@app/services/secret/secret-service"; -import { secretVersionDALFactory } from "@app/services/secret/secret-version-dal"; -import { secretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal"; import { secretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal"; import { secretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { secretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; @@ -328,6 +328,11 @@ import { secretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret- import { secretV2BridgeServiceFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-service"; import { secretVersionV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; import { secretVersionV2TagBridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; +import { secretDALFactory } from "@app/services/secret/secret-dal"; +import { secretQueueFactory } from "@app/services/secret/secret-queue"; +import { secretServiceFactory } from "@app/services/secret/secret-service"; +import { secretVersionDALFactory } from "@app/services/secret/secret-version-dal"; +import { secretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal"; import { serviceTokenDALFactory } from "@app/services/service-token/service-token-dal"; import { serviceTokenServiceFactory } from "@app/services/service-token/service-token-service"; import { projectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal"; @@ -343,15 +348,18 @@ import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-servi import { totpConfigDALFactory } from "@app/services/totp/totp-config-dal"; import { totpServiceFactory } from "@app/services/totp/totp-service"; import { upgradePathServiceFactory } from "@app/services/upgrade-path/upgrade-path-service"; -import { userDALFactory } from "@app/services/user/user-dal"; -import { userServiceFactory } from "@app/services/user/user-service"; import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service"; +import { userDALFactory } from "@app/services/user/user-dal"; +import { userServiceFactory } from "@app/services/user/user-service"; import { webhookDALFactory } from "@app/services/webhook/webhook-dal"; import { webhookServiceFactory } from "@app/services/webhook/webhook-service"; import { workflowIntegrationDALFactory } from "@app/services/workflow-integration/workflow-integration-dal"; import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service"; +import { pkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-account-dal"; +import { pkiAcmeChallengeDALFactory } from "@app/ee/services/pki-acme/pki-acme-challenge-dal"; +import { pkiAcmeOrderDALFactory } from "@app/ee/services/pki-acme/pki-acme-order-dal"; import { injectAuditLogInfo } from "../plugins/audit-log"; import { injectAssumePrivilege } from "../plugins/auth/inject-assume-privilege"; import { injectIdentity } from "../plugins/auth/inject-identity"; @@ -1069,7 +1077,12 @@ export const registerRoutes = async ( const certificateProfileDAL = certificateProfileDALFactory(db); const apiEnrollmentConfigDAL = apiEnrollmentConfigDALFactory(db); const estEnrollmentConfigDAL = estEnrollmentConfigDALFactory(db); - + const acmeEnrollmentConfigDAL = acmeEnrollmentConfigDALFactory(db); + const acmeAccountDAL = pkiAcmeAccountDALFactory(db); + const acmeOrderDAL = pkiAcmeOrderDALFactory(db); + const acmeAuthDAL = pkiAcmeAuthDALFactory(db); + const acmeOrderAuthDAL = pkiAcmeOrderAuthDALFactory(db); + const acmeChallengeDAL = pkiAcmeChallengeDALFactory(db); const certificateDAL = certificateDALFactory(db); const certificateBodyDAL = certificateBodyDALFactory(db); const certificateSecretDAL = certificateSecretDALFactory(db); @@ -1164,6 +1177,7 @@ export const registerRoutes = async ( certificateTemplateV2DAL, apiEnrollmentConfigDAL, estEnrollmentConfigDAL, + acmeEnrollmentConfigDAL, permissionService, kmsService, projectDAL @@ -1259,7 +1273,10 @@ export const registerRoutes = async ( licenseService, gatewayService, gatewayV2Service, - notificationService + notificationService, + projectSlackConfigDAL, + projectMicrosoftTeamsConfigDAL, + microsoftTeamsService }); const secretQueueService = secretQueueFactory({ @@ -2175,6 +2192,7 @@ export const registerRoutes = async ( certificateAuthorityDAL, certificateProfileDAL, certificateTemplateV2Service, + acmeAccountDAL, internalCaService: internalCertificateAuthorityService, permissionService, certificateSyncDAL, @@ -2201,6 +2219,24 @@ export const registerRoutes = async ( estEnrollmentConfigDAL }); + const acmeChallengeService = pkiAcmeChallengeServiceFactory({ + acmeChallengeDAL + }); + const pkiAcmeService = pkiAcmeServiceFactory({ + projectDAL, + certificateProfileDAL, + certificateBodyDAL, + acmeAccountDAL, + acmeOrderDAL, + acmeAuthDAL, + acmeOrderAuthDAL, + acmeChallengeDAL, + keyStore, + kmsService, + certificateV3Service, + acmeChallengeService + }); + const pkiSubscriberService = pkiSubscriberServiceFactory({ pkiSubscriberDAL, certificateAuthorityDAL, @@ -2457,6 +2493,7 @@ export const registerRoutes = async ( certificateProfile: certificateProfileService, certificateAuthorityCrl: certificateAuthorityCrlService, certificateEst: certificateEstService, + pkiAcme: pkiAcmeService, pit: pitService, pkiAlert: pkiAlertService, pkiCollection: pkiCollectionService, diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 47fbb0e07..17cfcb5ce 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -141,7 +141,8 @@ export const secretRawSchema = z.object({ actorId: z.string().nullable().optional(), actorType: z.string().nullable().optional(), name: z.string().nullable().optional(), - membershipId: z.string().nullable().optional() + membershipId: z.string().nullable().optional(), + groupId: z.string().nullable().optional() }) .optional() .nullable(), diff --git a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts index 9a4d7f38b..5a3496750 100644 --- a/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { ProjectType } from "@app/db/schemas"; +import { ChefConnectionListItemSchema, SanitizedChefConnectionSchema } from "@app/ee/services/app-connections/chef"; import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci"; import { OracleDBConnectionListItemSchema, @@ -48,7 +49,6 @@ import { ChecklyConnectionListItemSchema, SanitizedChecklyConnectionSchema } from "@app/services/app-connection/checkly"; -import { ChefConnectionListItemSchema, SanitizedChefConnectionSchema } from "@app/services/app-connection/chef"; import { CloudflareConnectionListItemSchema, SanitizedCloudflareConnectionSchema diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index 32e9efe4c..aa1d671b6 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -1,3 +1,4 @@ +import { registerChefConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/chef-connection-router"; import { registerOCIConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oci-connection-router"; import { registerOracleDBConnectionRouter } from "@app/ee/routes/v1/app-connection-routers/oracledb-connection-router"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; @@ -13,7 +14,6 @@ import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connect import { registerBitbucketConnectionRouter } from "./bitbucket-connection-router"; import { registerCamundaConnectionRouter } from "./camunda-connection-router"; import { registerChecklyConnectionRouter } from "./checkly-connection-router"; -import { registerChefConnectionRouter } from "./chef-connection-router"; import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router"; import { registerDatabricksConnectionRouter } from "./databricks-connection-router"; import { registerDigitalOceanConnectionRouter } from "./digital-ocean-connection-router"; diff --git a/backend/src/server/routes/v1/certificate-profiles-router.ts b/backend/src/server/routes/v1/certificate-profiles-router.ts index 08f532bc4..7d4e04773 100644 --- a/backend/src/server/routes/v1/certificate-profiles-router.ts +++ b/backend/src/server/routes/v1/certificate-profiles-router.ts @@ -7,8 +7,8 @@ import { ApiDocsTags } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -import { CertStatus } from "@app/services/certificate/certificate-types"; import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types"; +import { CertStatus } from "@app/services/certificate/certificate-types"; export const registerCertificateProfilesRouter = async (server: FastifyZodProvider) => { server.route({ @@ -44,7 +44,8 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid autoRenew: z.boolean().default(false), renewBeforeDays: z.number().min(1).max(30).optional() }) - .optional() + .optional(), + acmeConfig: z.object({}).optional() }) .refine( (data) => { @@ -55,6 +56,9 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid if (data.apiConfig) { return false; } + if (data.acmeConfig) { + return false; + } } if (data.enrollmentType === EnrollmentType.API) { if (!data.apiConfig) { @@ -63,12 +67,26 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid if (data.estConfig) { return false; } + if (data.acmeConfig) { + return false; + } + } + if (data.enrollmentType === EnrollmentType.ACME) { + if (!data.acmeConfig) { + return false; + } + if (data.estConfig) { + return false; + } + if (data.apiConfig) { + return false; + } } return true; }, { message: - "EST enrollment type requires EST configuration and cannot have API configuration. API enrollment type requires API configuration and cannot have EST configuration." + "EST enrollment type requires EST configuration and cannot have API or ACME configuration. API enrollment type requires API configuration and cannot have EST or ACME configuration. ACME enrollment type requires ACME configuration and cannot have EST or API configuration." } ), response: { @@ -150,6 +168,12 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid autoRenew: z.boolean(), renewBeforeDays: z.number().optional() }) + .optional(), + acmeConfig: z + .object({ + id: z.string(), + directoryUrl: z.string() + }) .optional() }).array(), totalCount: z.number() @@ -473,4 +497,36 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid return { certificates }; } }); + + server.route({ + method: "GET", + url: "/:id/acme/eab-secret/reveal", + config: { + rateLimit: readLimit + }, + schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificateProfiles], + params: z.object({ + id: z.string().uuid() + }), + response: { + 200: z.object({ + eabKid: z.string(), + eabSecret: z.string() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const { eabKid, eabSecret } = await server.services.certificateProfile.revealAcmeEabSecret({ + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + profileId: req.params.id + }); + return { eabKid, eabSecret }; + } + }); }; diff --git a/backend/src/server/routes/v1/deprecated-project-router.ts b/backend/src/server/routes/v1/deprecated-project-router.ts index 687d95b9b..f6efdb78a 100644 --- a/backend/src/server/routes/v1/deprecated-project-router.ts +++ b/backend/src/server/routes/v1/deprecated-project-router.ts @@ -614,8 +614,10 @@ export const registerDeprecatedProjectRouter = async (server: FastifyZodProvider integrationId: z.string(), accessRequestChannels: validateSlackChannelsField, secretRequestChannels: validateSlackChannelsField, + secretSyncErrorChannels: validateSlackChannelsField, isAccessRequestNotificationEnabled: z.boolean(), - isSecretRequestNotificationEnabled: z.boolean() + isSecretRequestNotificationEnabled: z.boolean(), + isSecretSyncErrorNotificationEnabled: z.boolean() }), z.object({ integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS), @@ -633,7 +635,9 @@ export const registerDeprecatedProjectRouter = async (server: FastifyZodProvider isAccessRequestNotificationEnabled: true, accessRequestChannels: true, isSecretRequestNotificationEnabled: true, - secretRequestChannels: true + secretRequestChannels: true, + isSecretSyncErrorNotificationEnabled: true, + secretSyncErrorChannels: true }).merge( z.object({ integration: z.literal(WorkflowIntegration.SLACK), diff --git a/backend/src/server/routes/v1/project-membership-router.ts b/backend/src/server/routes/v1/project-membership-router.ts index 57fdad031..dc76efa92 100644 --- a/backend/src/server/routes/v1/project-membership-router.ts +++ b/backend/src/server/routes/v1/project-membership-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { AccessScope, + OrgMembershipRole, ProjectMembershipRole, ProjectMembershipsSchema, ProjectUserMembershipRolesSchema, @@ -266,6 +267,19 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]), handler: async (req) => { const usernamesAndEmails = [...req.body.emails, ...req.body.usernames]; + + await server.services.membershipUser.createMembership({ + permission: req.permission, + scopeData: { + scope: AccessScope.Organization, + orgId: req.permission.orgId + }, + data: { + roles: [{ isTemporary: false, role: OrgMembershipRole.NoAccess }], + usernames: usernamesAndEmails + } + }); + const { memberships } = await server.services.membershipUser.createMembership({ permission: req.permission, scopeData: { diff --git a/backend/src/server/routes/v1/project-router.ts b/backend/src/server/routes/v1/project-router.ts index 1054d359b..70548395d 100644 --- a/backend/src/server/routes/v1/project-router.ts +++ b/backend/src/server/routes/v1/project-router.ts @@ -769,7 +769,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { isAccessRequestNotificationEnabled: true, accessRequestChannels: true, isSecretRequestNotificationEnabled: true, - secretRequestChannels: true + secretRequestChannels: true, + isSecretSyncErrorNotificationEnabled: true, + secretSyncErrorChannels: true }).merge( z.object({ integration: z.literal(WorkflowIntegration.SLACK), @@ -873,7 +875,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { accessRequestChannels: validateSlackChannelsField, secretRequestChannels: validateSlackChannelsField, isAccessRequestNotificationEnabled: z.boolean(), - isSecretRequestNotificationEnabled: z.boolean() + isSecretRequestNotificationEnabled: z.boolean(), + secretSyncErrorChannels: validateSlackChannelsField, + isSecretSyncErrorNotificationEnabled: z.boolean() }), z.object({ integration: z.literal(WorkflowIntegration.MICROSOFT_TEAMS), @@ -891,7 +895,9 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { isAccessRequestNotificationEnabled: true, accessRequestChannels: true, isSecretRequestNotificationEnabled: true, - secretRequestChannels: true + secretRequestChannels: true, + isSecretSyncErrorNotificationEnabled: true, + secretSyncErrorChannels: true }).merge( z.object({ integration: z.literal(WorkflowIntegration.SLACK), diff --git a/backend/src/server/routes/v1/secret-sync-routers/index.ts b/backend/src/server/routes/v1/secret-sync-routers/index.ts index d305dc342..810e5b7fa 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/index.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/index.ts @@ -1,3 +1,4 @@ +import { registerChefSyncRouter } from "@app/ee/routes/v1/secret-sync-routers/chef-sync-router"; import { registerOCIVaultSyncRouter } from "@app/ee/routes/v1/secret-sync-routers/oci-vault-sync-router"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; @@ -10,7 +11,6 @@ import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router"; import { registerBitbucketSyncRouter } from "./bitbucket-sync-router"; import { registerCamundaSyncRouter } from "./camunda-sync-router"; import { registerChecklySyncRouter } from "./checkly-sync-router"; -import { registerChefSyncRouter } from "./chef-sync-router"; import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router"; import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router"; import { registerDatabricksSyncRouter } from "./databricks-sync-router"; diff --git a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts index 85adf12f1..61e4ab79d 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { ChefSyncListItemSchema, ChefSyncSchema } from "@app/ee/services/secret-sync/chef"; import { OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "@app/ee/services/secret-sync/oci-vault"; import { ApiDocsTags, SecretSyncs } from "@app/lib/api-docs"; import { readLimit } from "@app/server/config/rateLimiter"; @@ -24,7 +25,6 @@ import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/s import { BitbucketSyncListItemSchema, BitbucketSyncSchema } from "@app/services/secret-sync/bitbucket"; import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda"; import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas"; -import { ChefSyncListItemSchema, ChefSyncSchema } from "@app/services/secret-sync/chef"; import { CloudflarePagesSyncListItemSchema, CloudflarePagesSyncSchema diff --git a/backend/src/server/routes/v1/sso-router.ts b/backend/src/server/routes/v1/sso-router.ts index 32b09b337..63f673d3f 100644 --- a/backend/src/server/routes/v1/sso-router.ts +++ b/backend/src/server/routes/v1/sso-router.ts @@ -550,12 +550,14 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => { providerAuthToken: req.body.providerAuthToken }); - void res.setCookie("jid", data.token.refresh, { - httpOnly: true, - path: "/", - sameSite: "strict", - secure: appCfg.HTTPS_ENABLED - }); + if ([AuthMethod.GOOGLE, AuthMethod.GITHUB, AuthMethod.GITLAB].includes(data.decodedProviderToken.authMethod)) { + void res.setCookie("jid", data.token.refresh, { + httpOnly: true, + path: "/", + sameSite: "strict", + secure: appCfg.HTTPS_ENABLED + }); + } addAuthOriginDomainCookie(res); diff --git a/backend/src/server/routes/v3/certificates-router.ts b/backend/src/server/routes/v3/certificates-router.ts index d2d696596..9aa4b198b 100644 --- a/backend/src/server/routes/v3/certificates-router.ts +++ b/backend/src/server/routes/v3/certificates-router.ts @@ -6,12 +6,6 @@ import { ms } from "@app/lib/ms"; import { writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -import { - ACMESANType, - CertificateOrderStatus, - CertKeyAlgorithm, - CertSignatureAlgorithm -} from "@app/services/certificate/certificate-types"; import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators"; import { CertExtendedKeyUsageType, @@ -20,7 +14,14 @@ import { } from "@app/services/certificate-common/certificate-constants"; import { extractCertificateRequestFromCSR } from "@app/services/certificate-common/certificate-csr-utils"; import { mapEnumsForValidation } from "@app/services/certificate-common/certificate-utils"; +import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types"; import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators"; +import { + ACMESANType, + CertificateOrderStatus, + CertKeyAlgorithm, + CertSignatureAlgorithm +} from "@app/services/certificate/certificate-types"; interface CertificateRequestForService { commonName?: string; @@ -204,7 +205,8 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) => ttl: req.body.ttl }, notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined, - notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined + notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined, + enrollmentType: EnrollmentType.API }); await server.services.auditLog.createAuditLog({ diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index 3c511fd4d..863fa75f9 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -1,5 +1,10 @@ import { ProjectType } from "@app/db/schemas"; import { TAppConnections } from "@app/db/schemas/app-connections"; +import { + ChefConnectionMethod, + getChefConnectionListItem, + validateChefConnectionCredentials +} from "@app/ee/services/app-connections/chef"; import { getOCIConnectionListItem, OCIConnectionMethod, @@ -68,7 +73,6 @@ import { } from "./bitbucket"; import { CamundaConnectionMethod, getCamundaConnectionListItem, validateCamundaConnectionCredentials } from "./camunda"; import { ChecklyConnectionMethod, getChecklyConnectionListItem, validateChecklyConnectionCredentials } from "./checkly"; -import { ChefConnectionMethod, getChefConnectionListItem, validateChefConnectionCredentials } from "./chef"; import { CloudflareConnectionMethod } from "./cloudflare/cloudflare-connection-enum"; import { getCloudflareConnectionListItem, diff --git a/backend/src/services/app-connection/app-connection-maps.ts b/backend/src/services/app-connection/app-connection-maps.ts index 08ca0c681..5b8cc3fc1 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -86,6 +86,6 @@ export const APP_CONNECTION_PLAN_MAP: Record { } }; + const findByIdWithOwnerOrgId = async ( + id: string, + tx?: Knex + ): Promise<(TCertificateProfile & { ownerOrgId: string }) | undefined> => { + try { + const certificateProfile = (await (tx || db)(TableName.PkiCertificateProfile) + .join(TableName.Project, `${TableName.PkiCertificateProfile}.projectId`, `${TableName.Project}.id`) + .select(selectAllTableCols(TableName.PkiCertificateProfile)) + .select(db.ref("orgId").withSchema(TableName.Project).as("ownerOrgId")) + .where(`${TableName.PkiCertificateProfile}.id`, id) + .first()) as (TCertificateProfile & { ownerOrgId: string }) | undefined; + return certificateProfile; + } catch (error) { + throw new DatabaseError({ error, name: "Find certificate profile by id with owner org id" }); + } + }; + const findByIdWithConfigs = async (id: string, tx?: Knex): Promise => { try { const query = (tx || db)(TableName.PkiCertificateProfile) @@ -88,6 +105,11 @@ export const certificateProfileDALFactory = (db: TDbClient) => { `${TableName.PkiCertificateProfile}.apiConfigId`, `${TableName.PkiApiEnrollmentConfig}.id` ) + .leftJoin( + TableName.PkiAcmeEnrollmentConfig, + `${TableName.PkiCertificateProfile}.acmeConfigId`, + `${TableName.PkiAcmeEnrollmentConfig}.id` + ) .select(selectAllTableCols(TableName.PkiCertificateProfile)) .select( db.ref("id").withSchema(TableName.CertificateAuthority).as("caId"), @@ -107,7 +129,9 @@ export const certificateProfileDALFactory = (db: TDbClient) => { db.ref("encryptedCaChain").withSchema(TableName.PkiEstEnrollmentConfig).as("estConfigEncryptedCaChain"), db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigId"), db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigAutoRenew"), - db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigRenewBeforeDays") + db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiConfigRenewBeforeDays"), + db.ref("id").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeConfigId"), + db.ref("encryptedEabSecret").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeConfigEncryptedEabSecret") ) .where(`${TableName.PkiCertificateProfile}.id`, id) .first(); @@ -134,6 +158,13 @@ export const certificateProfileDALFactory = (db: TDbClient) => { } as TCertificateProfileWithConfigs["apiConfig"]) : undefined; + const acmeConfig = result.acmeConfigId + ? ({ + id: result.acmeConfigId, + encryptedEabSecret: result.acmeConfigEncryptedEabSecret + } as TCertificateProfileWithConfigs["acmeConfig"]) + : undefined; + const certificateAuthority = result.caId && result.caProjectId && result.caStatus && result.caName ? ({ @@ -164,10 +195,12 @@ export const certificateProfileDALFactory = (db: TDbClient) => { enrollmentType: result.enrollmentType as EnrollmentType, estConfigId: result.estConfigId, apiConfigId: result.apiConfigId, + acmeConfigId: result.acmeConfigId, createdAt: result.createdAt, updatedAt: result.updatedAt, estConfig, apiConfig, + acmeConfig, certificateAuthority, certificateTemplate }; @@ -241,6 +274,11 @@ export const certificateProfileDALFactory = (db: TDbClient) => { `${TableName.PkiCertificateProfile}.apiConfigId`, `${TableName.PkiApiEnrollmentConfig}.id` ) + .leftJoin( + TableName.PkiAcmeEnrollmentConfig, + `${TableName.PkiCertificateProfile}.acmeConfigId`, + `${TableName.PkiAcmeEnrollmentConfig}.id` + ) .select(selectAllTableCols(TableName.PkiCertificateProfile)) .select( db.ref("id").withSchema(TableName.PkiEstEnrollmentConfig).as("estId"), @@ -252,7 +290,8 @@ export const certificateProfileDALFactory = (db: TDbClient) => { db.ref("encryptedCaChain").withSchema(TableName.PkiEstEnrollmentConfig).as("estEncryptedCaChain"), db.ref("id").withSchema(TableName.PkiApiEnrollmentConfig).as("apiId"), db.ref("autoRenew").withSchema(TableName.PkiApiEnrollmentConfig).as("apiAutoRenew"), - db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays") + db.ref("renewBeforeDays").withSchema(TableName.PkiApiEnrollmentConfig).as("apiRenewBeforeDays"), + db.ref("id").withSchema(TableName.PkiAcmeEnrollmentConfig).as("acmeId") ); const results = (await query @@ -279,6 +318,12 @@ export const certificateProfileDALFactory = (db: TDbClient) => { } : undefined; + const acmeConfig = result.acmeId + ? { + id: result.acmeId as string + } + : undefined; + const baseProfile = { id: result.id, projectId: result.projectId, @@ -292,7 +337,8 @@ export const certificateProfileDALFactory = (db: TDbClient) => { createdAt: result.createdAt, updatedAt: result.updatedAt, estConfig, - apiConfig + apiConfig, + acmeConfig }; return baseProfile as TCertificateProfileWithConfigs; @@ -432,6 +478,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => { updateById, deleteById, findById, + findByIdWithOwnerOrgId, findByIdWithConfigs, findBySlugAndProjectId, findByProjectId, diff --git a/backend/src/services/certificate-profile/certificate-profile-schemas.ts b/backend/src/services/certificate-profile/certificate-profile-schemas.ts index 8ac494fe6..bf88593bd 100644 --- a/backend/src/services/certificate-profile/certificate-profile-schemas.ts +++ b/backend/src/services/certificate-profile/certificate-profile-schemas.ts @@ -27,7 +27,8 @@ export const createCertificateProfileSchema = z autoRenew: z.boolean().default(false), renewBeforeDays: z.number().min(1).max(30).optional() }) - .optional() + .optional(), + acmeConfig: z.object({}).optional() }) .refine( (data) => { @@ -38,6 +39,9 @@ export const createCertificateProfileSchema = z if (data.apiConfig) { return false; } + if (data.acmeConfig) { + return false; + } } if (data.enrollmentType === EnrollmentType.API) { if (!data.apiConfig) { @@ -46,6 +50,20 @@ export const createCertificateProfileSchema = z if (data.estConfig) { return false; } + if (data.acmeConfig) { + return false; + } + } + if (data.enrollmentType === EnrollmentType.ACME) { + if (!data.acmeConfig) { + return false; + } + if (data.estConfig) { + return false; + } + if (data.apiConfig) { + return false; + } } return true; }, diff --git a/backend/src/services/certificate-profile/certificate-profile-service.test.ts b/backend/src/services/certificate-profile/certificate-profile-service.test.ts index bb30b8d5c..ffca3c1cb 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.test.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.test.ts @@ -10,6 +10,7 @@ import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ActorType, AuthMethod } from "../auth/auth-type"; import type { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal"; +import { TAcmeEnrollmentConfigDALFactory } from "../enrollment-config/acme-enrollment-config-dal"; import type { TApiEnrollmentConfigDALFactory } from "../enrollment-config/api-enrollment-config-dal"; import type { TEstEnrollmentConfigDALFactory } from "../enrollment-config/est-enrollment-config-dal"; import type { TKmsServiceFactory } from "../kms/kms-service"; @@ -142,6 +143,17 @@ describe("CertificateProfileService", () => { delete: vi.fn() } as unknown as TEstEnrollmentConfigDALFactory; + const mockAcmeEnrollmentConfigDAL = { + create: vi.fn().mockResolvedValue({ id: "acme-config-123" }), + findById: vi.fn(), + updateById: vi.fn(), + transaction: vi.fn(), + find: vi.fn(), + findOne: vi.fn(), + update: vi.fn(), + delete: vi.fn() + } as unknown as TAcmeEnrollmentConfigDALFactory; + const mockPermissionService = { getProjectPermission: vi.fn().mockResolvedValue({ permission: { @@ -182,6 +194,7 @@ describe("CertificateProfileService", () => { certificateTemplateV2DAL: mockCertificateTemplateV2DAL, apiEnrollmentConfigDAL: mockApiEnrollmentConfigDAL, estEnrollmentConfigDAL: mockEstEnrollmentConfigDAL, + acmeEnrollmentConfigDAL: mockAcmeEnrollmentConfigDAL, permissionService: mockPermissionService, kmsService: mockKmsService, projectDAL: mockProjectDAL @@ -234,6 +247,7 @@ describe("CertificateProfileService", () => { certificateTemplateId: "template-123", apiConfigId: "api-config-123", estConfigId: null, + acmeConfigId: null, projectId: "project-123" }, undefined diff --git a/backend/src/services/certificate-profile/certificate-profile-service.ts b/backend/src/services/certificate-profile/certificate-profile-service.ts index f858a8d4f..8adbf2c45 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.ts @@ -13,10 +13,10 @@ import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ActorAuthMethod, ActorType } from "../auth/auth-type"; -import { isCertChainValid } from "../certificate/certificate-fns"; import { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal"; +import { isCertChainValid } from "../certificate/certificate-fns"; import { TApiEnrollmentConfigDALFactory } from "../enrollment-config/api-enrollment-config-dal"; -import { TApiConfigData, TEstConfigData } from "../enrollment-config/enrollment-config-types"; +import { TAcmeConfigData, TApiConfigData, TEstConfigData } from "../enrollment-config/enrollment-config-types"; import { TEstEnrollmentConfigDALFactory } from "../enrollment-config/est-enrollment-config-dal"; import { TKmsServiceFactory } from "../kms/kms-service"; import { TProjectDALFactory } from "../project/project-dal"; @@ -30,6 +30,38 @@ import { TCertificateProfileUpdate, TCertificateProfileWithConfigs } from "./certificate-profile-types"; +import { TAcmeEnrollmentConfigDALFactory } from "../enrollment-config/acme-enrollment-config-dal"; +import { buildUrl } from "@app/ee/services/pki-acme/pki-acme-fns"; + +const generateAndEncryptAcmeEabSecret = async ( + projectId: string, + kmsService: Pick, + projectDAL: Pick +) => { + try { + const certificateManagerKmsId = await getProjectKmsCertificateKeyId({ + projectId, + projectDAL, + kmsService + }); + + const kmsEncryptor = await kmsService.encryptWithKmsKey({ + kmsId: certificateManagerKmsId + }); + + const appCfg = getConfig(); + const secret = crypto.randomBytes(32).toString("hex"); + const secretHash = await crypto.hashing().createHash(secret, appCfg.SALT_ROUNDS); + + const { cipherTextBlob } = await kmsEncryptor({ + plainText: Buffer.from(secretHash) + }); + + return { encryptedEabSecret: cipherTextBlob }; + } catch (error) { + throw new BadRequestError({ message: `Failed to generate ACME EAB secret: ${(error as Error).message}` }); + } +}; const validateAndEncryptPemCaChain = async ( caChain: string, @@ -95,9 +127,13 @@ const decryptCaChain = async ( } }; -export type TCertificateProfileCreateData = Omit & { +export type TCertificateProfileCreateData = Omit< + TCertificateProfileInsert, + "estConfigId" | "apiConfigId" | "acmeConfigId" +> & { estConfig?: TEstConfigData; apiConfig?: TApiConfigData; + acmeConfig?: TAcmeConfigData; }; type TCertificateProfileServiceFactoryDep = { @@ -105,6 +141,7 @@ type TCertificateProfileServiceFactoryDep = { certificateTemplateV2DAL: TCertificateTemplateV2DALFactory; apiEnrollmentConfigDAL: TApiEnrollmentConfigDALFactory; estEnrollmentConfigDAL: TEstEnrollmentConfigDALFactory; + acmeEnrollmentConfigDAL: TAcmeEnrollmentConfigDALFactory; permissionService: Pick; kmsService: Pick; projectDAL: Pick; @@ -124,6 +161,7 @@ export const certificateProfileServiceFactory = ({ certificateTemplateV2DAL, apiEnrollmentConfigDAL, estEnrollmentConfigDAL, + acmeEnrollmentConfigDAL, permissionService, kmsService, projectDAL @@ -188,11 +226,14 @@ export const certificateProfileServiceFactory = ({ message: "API enrollment requires API configuration" }); } + // TODO: acme type currently doesn't require config obj, but add a check in the future if + // we have options // Create enrollment configs and profile const profile = await certificateProfileDAL.transaction(async (tx) => { let estConfigId: string | null = null; let apiConfigId: string | null = null; + let acmeConfigId: string | null = null; if (data.enrollmentType === EnrollmentType.EST && data.estConfig) { const appCfg = getConfig(); @@ -228,16 +269,21 @@ export const certificateProfileServiceFactory = ({ tx ); apiConfigId = apiConfig.id; + } else if (data.enrollmentType === EnrollmentType.ACME && data.acmeConfig) { + const { encryptedEabSecret } = await generateAndEncryptAcmeEabSecret(projectId, kmsService, projectDAL); + const acmeConfig = await acmeEnrollmentConfigDAL.create({ encryptedEabSecret }, tx); + acmeConfigId = acmeConfig.id; } // Create the profile with the created config IDs - const { estConfig, apiConfig, ...profileData } = data; + const { estConfig, apiConfig, acmeConfig, ...profileData } = data; const profileResult = await certificateProfileDAL.create( { ...profileData, projectId, estConfigId, - apiConfigId + apiConfigId, + acmeConfigId }, tx ); @@ -439,6 +485,12 @@ export const certificateProfileServiceFactory = ({ profile.estConfig.caChain = ""; } } + if (profile.enrollmentType === EnrollmentType.ACME && profile.acmeConfig) { + profile.acmeConfig.directoryUrl = buildUrl(profile.id, "/directory"); + if (profile.acmeConfig.encryptedEabSecret) { + profile.acmeConfig.encryptedEabSecret = undefined; + } + } return { ...profile, @@ -574,7 +626,10 @@ export const certificateProfileServiceFactory = ({ const result: TCertificateProfileWithConfigs = { ...converted, estConfig: decryptedEstConfig, - apiConfig: profileWithConfigs.apiConfig + apiConfig: profileWithConfigs.apiConfig, + acmeConfig: profileWithConfigs.acmeConfig + ? { ...profileWithConfigs.acmeConfig, directoryUrl: buildUrl(profile.id, "/directory") } + : undefined }; return result; @@ -737,6 +792,59 @@ export const certificateProfileServiceFactory = ({ }; }; + const revealAcmeEabSecret = async ({ + actor, + actorId, + actorAuthMethod, + actorOrgId, + profileId + }: { + actor: ActorType; + actorId: string; + actorAuthMethod: ActorAuthMethod; + actorOrgId: string; + profileId: string; + }) => { + const profile = await certificateProfileDAL.findByIdWithConfigs(profileId); + if (!profile) { + throw new NotFoundError({ message: "Certificate profile not found" }); + } + + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: profile.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.CertificateManager + }); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret, + ProjectPermissionSub.CertificateProfiles + ); + + if (profile.enrollmentType !== EnrollmentType.ACME) { + throw new ForbiddenRequestError({ + message: "Profile is not configured for ACME enrollment" + }); + } + if (!profile.acmeConfig) { + throw new NotFoundError({ message: "ACME configuration not found for this profile" }); + } + + const certificateManagerKmsId = await getProjectKmsCertificateKeyId({ + projectId: profile.projectId, + projectDAL, + kmsService + }); + + const kmsDecryptor = await kmsService.decryptWithKmsKey({ + kmsId: certificateManagerKmsId + }); + const eabSecret = await kmsDecryptor({ cipherTextBlob: profile.acmeConfig.encryptedEabSecret! }); + return { eabKid: profile.id, eabSecret: eabSecret.toString("base64url") }; + }; + return { createProfile, updateProfile, @@ -746,6 +854,7 @@ export const certificateProfileServiceFactory = ({ listProfiles, deleteProfile, getProfileCertificates, - getEstConfigurationByProfile + getEstConfigurationByProfile, + revealAcmeEabSecret }; }; diff --git a/backend/src/services/certificate-profile/certificate-profile-types.ts b/backend/src/services/certificate-profile/certificate-profile-types.ts index 5dac470c8..4a3339857 100644 --- a/backend/src/services/certificate-profile/certificate-profile-types.ts +++ b/backend/src/services/certificate-profile/certificate-profile-types.ts @@ -6,7 +6,8 @@ import { export enum EnrollmentType { API = "api", - EST = "est" + EST = "est", + ACME = "acme" } export type TCertificateProfile = Omit & { @@ -28,6 +29,7 @@ export type TCertificateProfileUpdate = Omit ({ extractCertificateRequestFromCSR: vi.fn(), @@ -69,6 +70,10 @@ describe("CertificateV3Service", () => { getTemplateV2ById: vi.fn() }; + const mockAcmeAccountDAL: Pick = { + findById: vi.fn() + }; + const mockInternalCaService: Pick = { signCertFromCa: vi.fn(), @@ -132,6 +137,7 @@ describe("CertificateV3Service", () => { certificateAuthorityDAL: mockCertificateAuthorityDAL, certificateProfileDAL: mockCertificateProfileDAL, certificateTemplateV2Service: mockCertificateTemplateV2Service, + acmeAccountDAL: mockAcmeAccountDAL, internalCaService: mockInternalCaService, permissionService: mockPermissionService, certificateSyncDAL: { @@ -697,6 +703,7 @@ describe("CertificateV3Service", () => { profileId, csr: mockCSR, validity: mockValidity, + enrollmentType: EnrollmentType.API, ...mockActor }); @@ -731,6 +738,7 @@ describe("CertificateV3Service", () => { profileId, csr: mockCSR, validity: mockValidity, + enrollmentType: EnrollmentType.API, ...mockActor }) ).rejects.toThrow(ForbiddenRequestError); @@ -740,6 +748,7 @@ describe("CertificateV3Service", () => { profileId, csr: mockCSR, validity: mockValidity, + enrollmentType: EnrollmentType.API, ...mockActor }) ).rejects.toThrow("Profile is not configured for api enrollment"); diff --git a/backend/src/services/certificate-v3/certificate-v3-service.ts b/backend/src/services/certificate-v3/certificate-v3-service.ts index 51c79f135..7f4c6c637 100644 --- a/backend/src/services/certificate-v3/certificate-v3-service.ts +++ b/backend/src/services/certificate-v3/certificate-v3-service.ts @@ -64,12 +64,14 @@ import { TSignCertificateFromProfileDTO, TUpdateRenewalConfigDTO } from "./certificate-v3-types"; +import { TPkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-account-dal"; type TCertificateV3ServiceFactoryDep = { certificateDAL: Pick; certificateSecretDAL: Pick; certificateAuthorityDAL: Pick; certificateProfileDAL: Pick; + acmeAccountDAL: Pick; certificateTemplateV2Service: Pick< TCertificateTemplateV2ServiceFactory, "validateCertificateRequest" | "getTemplateV2ById" @@ -93,6 +95,7 @@ const validateProfileAndPermissions = async ( actorAuthMethod: ActorAuthMethod, actorOrgId: string, certificateProfileDAL: Pick, + acmeAccountDAL: Pick, permissionService: Pick, requiredEnrollmentType: EnrollmentType ) => { @@ -107,6 +110,19 @@ const validateProfileAndPermissions = async ( }); } + if (actor === ActorType.ACME_ACCOUNT && requiredEnrollmentType === EnrollmentType.ACME) { + const account = await acmeAccountDAL.findById(actorId); + if (!account) { + throw new NotFoundError({ message: "ACME account not found" }); + } + if (account.profileId !== profile.id) { + throw new ForbiddenRequestError({ + message: "ACME account is not associated with this profile" + }); + } + return profile; + } + const { permission } = await permissionService.getProjectPermission({ actor, actorId, @@ -336,6 +352,7 @@ export const certificateV3ServiceFactory = ({ certificateSecretDAL, certificateAuthorityDAL, certificateProfileDAL, + acmeAccountDAL, certificateTemplateV2Service, internalCaService, permissionService, @@ -358,6 +375,7 @@ export const certificateV3ServiceFactory = ({ actorAuthMethod, actorOrgId, certificateProfileDAL, + acmeAccountDAL, permissionService, EnrollmentType.API ); @@ -484,7 +502,8 @@ export const certificateV3ServiceFactory = ({ actor, actorId, actorAuthMethod, - actorOrgId + actorOrgId, + enrollmentType }: TSignCertificateFromProfileDTO): Promise> => { const profile = await validateProfileAndPermissions( profileId, @@ -493,8 +512,9 @@ export const certificateV3ServiceFactory = ({ actorAuthMethod, actorOrgId, certificateProfileDAL, + acmeAccountDAL, permissionService, - EnrollmentType.API + enrollmentType ); const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId); @@ -595,6 +615,7 @@ export const certificateV3ServiceFactory = ({ actorAuthMethod, actorOrgId, certificateProfileDAL, + acmeAccountDAL, permissionService, EnrollmentType.API ); diff --git a/backend/src/services/certificate-v3/certificate-v3-types.ts b/backend/src/services/certificate-v3/certificate-v3-types.ts index a62a25b73..8685d5f30 100644 --- a/backend/src/services/certificate-v3/certificate-v3-types.ts +++ b/backend/src/services/certificate-v3/certificate-v3-types.ts @@ -1,11 +1,12 @@ import { TProjectPermission } from "@app/lib/types"; -import { ACMESANType, CertificateOrderStatus } from "../certificate/certificate-types"; import { CertExtendedKeyUsageType, CertKeyUsageType, CertSubjectAlternativeNameType } from "../certificate-common/certificate-constants"; +import { EnrollmentType } from "../certificate-profile/certificate-profile-types"; +import { ACMESANType, CertificateOrderStatus } from "../certificate/certificate-types"; export type TIssueCertificateFromProfileDTO = { profileId: string; @@ -35,6 +36,7 @@ export type TSignCertificateFromProfileDTO = { }; notBefore?: Date; notAfter?: Date; + enrollmentType: EnrollmentType; } & Omit; export type TOrderCertificateFromProfileDTO = { diff --git a/backend/src/services/enrollment-config/acme-enrollment-config-dal.ts b/backend/src/services/enrollment-config/acme-enrollment-config-dal.ts new file mode 100644 index 000000000..afa8f17ef --- /dev/null +++ b/backend/src/services/enrollment-config/acme-enrollment-config-dal.ts @@ -0,0 +1,61 @@ +import { Knex } from "knex"; + +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; +import { ormify } from "@app/lib/knex"; + +import { TAcmeEnrollmentConfigInsert, TAcmeEnrollmentConfigUpdate } from "./enrollment-config-types"; + +export type TAcmeEnrollmentConfigDALFactory = ReturnType; + +export const acmeEnrollmentConfigDALFactory = (db: TDbClient) => { + const acmeEnrollmentConfigOrm = ormify(db, TableName.PkiAcmeEnrollmentConfig); + + const create = async (data: TAcmeEnrollmentConfigInsert, tx?: Knex) => { + try { + const result = await (tx || db)(TableName.PkiAcmeEnrollmentConfig).insert(data).returning("*"); + const [acmeConfig] = result; + + if (!acmeConfig) { + throw new Error("Failed to create ACME enrollment config"); + } + + return acmeConfig; + } catch (error) { + throw new DatabaseError({ error, name: "Create ACME enrollment config" }); + } + }; + + const updateById = async (id: string, data: TAcmeEnrollmentConfigUpdate, tx?: Knex) => { + try { + const result = await (tx || db)(TableName.PkiAcmeEnrollmentConfig).where({ id }).update(data).returning("*"); + const [acmeConfig] = result; + + if (!acmeConfig) { + return null; + } + + return acmeConfig; + } catch (error) { + throw new DatabaseError({ error, name: "Update ACME enrollment config" }); + } + }; + + const findById = async (id: string, tx?: Knex) => { + try { + const acmeConfig = await (tx || db)(TableName.PkiAcmeEnrollmentConfig).where({ id }).first(); + + return acmeConfig || null; + } catch (error) { + throw new DatabaseError({ error, name: "Find ACME enrollment config by id" }); + } + }; + + return { + ...acmeEnrollmentConfigOrm, + create, + updateById, + findById + }; +}; diff --git a/backend/src/services/enrollment-config/enrollment-config-types.ts b/backend/src/services/enrollment-config/enrollment-config-types.ts index d2e03e4da..ed2f921c4 100644 --- a/backend/src/services/enrollment-config/enrollment-config-types.ts +++ b/backend/src/services/enrollment-config/enrollment-config-types.ts @@ -8,6 +8,11 @@ import { TPkiEstEnrollmentConfigsInsert, TPkiEstEnrollmentConfigsUpdate } from "@app/db/schemas/pki-est-enrollment-configs"; +import { + TPkiAcmeEnrollmentConfigs, + TPkiAcmeEnrollmentConfigsInsert, + TPkiAcmeEnrollmentConfigsUpdate +} from "@app/db/schemas/pki-acme-enrollment-configs"; export type TEstEnrollmentConfig = TPkiEstEnrollmentConfigs; export type TEstEnrollmentConfigInsert = TPkiEstEnrollmentConfigsInsert; @@ -17,6 +22,10 @@ export type TApiEnrollmentConfig = TPkiApiEnrollmentConfigs; export type TApiEnrollmentConfigInsert = TPkiApiEnrollmentConfigsInsert; export type TApiEnrollmentConfigUpdate = TPkiApiEnrollmentConfigsUpdate; +export type TAcmeEnrollmentConfig = TPkiAcmeEnrollmentConfigs; +export type TAcmeEnrollmentConfigInsert = TPkiAcmeEnrollmentConfigsInsert; +export type TAcmeEnrollmentConfigUpdate = TPkiAcmeEnrollmentConfigsUpdate; + export interface TEstConfigData { disableBootstrapCaValidation: boolean; passphrase: string; @@ -27,3 +36,5 @@ export interface TApiConfigData { autoRenew: boolean; renewBeforeDays?: number; } + +export interface TAcmeConfigData {} diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts index a03beeb3b..2464a2af6 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts @@ -112,7 +112,20 @@ export const identityOidcAuthServiceFactory = ({ }); const { kid } = decodedToken.header as { kid: string }; - const oidcSigningKey = await client.getSigningKey(kid); + + let oidcSigningKey; + try { + oidcSigningKey = await client.getSigningKey(kid); + } catch (error) { + if (error instanceof Error && error.name === "SigningKeyNotFoundError") { + throw new UnauthorizedError({ + message: `Access denied: Unable to verify JWT signature. The signing key '${kid}' was not found in the OIDC provider's JWKS endpoint. This may indicate an invalid token or misconfigured OIDC provider.` + }); + } + throw new UnauthorizedError({ + message: `Access denied: Failed to retrieve signing key from OIDC provider: ${error instanceof Error ? error.message : String(error)}` + }); + } let tokenData: Record; try { diff --git a/backend/src/services/identity-token-auth/identity-token-auth-service.ts b/backend/src/services/identity-token-auth/identity-token-auth-service.ts index 2d3e11cd8..e6969da61 100644 --- a/backend/src/services/identity-token-auth/identity-token-auth-service.ts +++ b/backend/src/services/identity-token-auth/identity-token-auth-service.ts @@ -244,8 +244,8 @@ export const identityTokenAuthServiceFactory = ({ } if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.TOKEN_AUTH)) { - throw new BadRequestError({ - message: "The identity does not have Token Auth attached" + throw new NotFoundError({ + message: "Token Auth configuration not found for identity" }); } diff --git a/backend/src/services/microsoft-teams/project-microsoft-teams-config-dal.ts b/backend/src/services/microsoft-teams/project-microsoft-teams-config-dal.ts index 918b96e89..b1f9fec02 100644 --- a/backend/src/services/microsoft-teams/project-microsoft-teams-config-dal.ts +++ b/backend/src/services/microsoft-teams/project-microsoft-teams-config-dal.ts @@ -1,16 +1,20 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName } from "@app/db/schemas"; +import { TableName, TMicrosoftTeamsIntegrations } from "@app/db/schemas"; +import { TProjectMicrosoftTeamsConfigs } from "@app/db/schemas/project-microsoft-teams-configs"; import { ormify, selectAllTableCols } from "@app/lib/knex"; export type TProjectMicrosoftTeamsConfigDALFactory = ReturnType; +export type TProjectMicrosoftTeamsConfigWithIntegrations = TProjectMicrosoftTeamsConfigs & TMicrosoftTeamsIntegrations; export const projectMicrosoftTeamsConfigDALFactory = (db: TDbClient) => { const projectMicrosoftTeamsConfigOrm = ormify(db, TableName.ProjectMicrosoftTeamsConfigs); const getIntegrationDetailsByProject = (projectId: string, tx?: Knex) => { - return (tx || db.replicaNode())(TableName.ProjectMicrosoftTeamsConfigs) + return (tx || db.replicaNode())( + TableName.ProjectMicrosoftTeamsConfigs + ) .join( TableName.MicrosoftTeamsIntegrations, `${TableName.ProjectMicrosoftTeamsConfigs}.microsoftTeamsIntegrationId`, diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index 25052ba9a..727cc9aa9 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -1605,8 +1605,14 @@ export const projectServiceFactory = ({ isAccessRequestNotificationEnabled, accessRequestChannels, isSecretRequestNotificationEnabled, - secretRequestChannels - }: TUpdateProjectWorkflowIntegration) => { + secretRequestChannels, + secretSyncErrorChannels, + isSecretSyncErrorNotificationEnabled + }: TUpdateProjectWorkflowIntegration & { + // workaround intersection type while we don't have the microsoft teams integration for failed secret syncs + isSecretSyncErrorNotificationEnabled?: boolean; + secretSyncErrorChannels?: string; + }) => { const project = await projectDAL.findById(projectId); if (!project) { throw new NotFoundError({ @@ -1628,6 +1634,7 @@ export const projectServiceFactory = ({ const sanitizedAccessRequestChannels = validateSlackChannelsField.parse(accessRequestChannels); const sanitizedSecretRequestChannels = validateSlackChannelsField.parse(secretRequestChannels); + const sanitizedSecretSyncErrorChannels = validateSlackChannelsField.parse(secretSyncErrorChannels); const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(integrationId); @@ -1665,7 +1672,9 @@ export const projectServiceFactory = ({ isAccessRequestNotificationEnabled, accessRequestChannels: sanitizedAccessRequestChannels, isSecretRequestNotificationEnabled, - secretRequestChannels: sanitizedSecretRequestChannels + secretRequestChannels: sanitizedSecretRequestChannels, + isSecretSyncErrorNotificationEnabled, + secretSyncErrorChannels: sanitizedSecretSyncErrorChannels }, tx ); @@ -1678,7 +1687,9 @@ export const projectServiceFactory = ({ isAccessRequestNotificationEnabled, accessRequestChannels: sanitizedAccessRequestChannels, isSecretRequestNotificationEnabled, - secretRequestChannels: sanitizedSecretRequestChannels + secretRequestChannels: sanitizedSecretRequestChannels, + isSecretSyncErrorNotificationEnabled, + secretSyncErrorChannels: sanitizedSecretSyncErrorChannels }, tx ); @@ -1688,6 +1699,7 @@ export const projectServiceFactory = ({ ...updatedWorkflowIntegration, accessRequestChannels: sanitizedAccessRequestChannels, secretRequestChannels: sanitizedSecretRequestChannels, + secretSyncErrorChannels: sanitizedSecretSyncErrorChannels, integrationId: slackIntegration.id, integration: WorkflowIntegration.SLACK } as const; diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 2b75b1bc7..9e0745236 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -185,8 +185,10 @@ export type TUpdateProjectWorkflowIntegration = ( integration: WorkflowIntegration.SLACK; isAccessRequestNotificationEnabled: boolean; isSecretRequestNotificationEnabled: boolean; + isSecretSyncErrorNotificationEnabled: boolean; accessRequestChannels?: string; secretRequestChannels?: string; + secretSyncErrorChannels?: string; } | { integrationId: string; diff --git a/backend/src/services/secret-folder/secret-folder-dal.ts b/backend/src/services/secret-folder/secret-folder-dal.ts index 7dfeaddcf..f2befbe1a 100644 --- a/backend/src/services/secret-folder/secret-folder-dal.ts +++ b/backend/src/services/secret-folder/secret-folder-dal.ts @@ -419,13 +419,14 @@ export const secretFolderDALFactory = (db: TDbClient) => { .select( selectAllTableCols(TableName.SecretFolder), db.raw( - `DENSE_RANK() OVER (ORDER BY ${TableName.SecretFolder}."name" ${ - orderDirection ?? OrderByDirection.ASC - }) as rank` + `DENSE_RANK() OVER (ORDER BY ${TableName.SecretFolder}."name" COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}) as rank` ), db.ref("slug").withSchema(TableName.Environment).as("environment") ) - .orderBy(`${TableName.SecretFolder}.${orderBy}`, orderDirection); + .orderByRaw( + `${TableName.SecretFolder}.?? COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}`, + [orderBy] + ); if (limit) { const rankOffset = offset + 1; // ranks start from 1 @@ -434,7 +435,10 @@ export const secretFolderDALFactory = (db: TDbClient) => { .select("*") .from[number]>("w") .where("w.rank", ">=", rankOffset) - .andWhere("w.rank", "<", rankOffset + limit); + .andWhere("w.rank", "<", rankOffset + limit) + .orderByRaw(`"w".?? COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}`, [ + orderBy + ]); } const folders = await query; @@ -445,7 +449,10 @@ export const secretFolderDALFactory = (db: TDbClient) => { } }; - const findByEnvsDeep = async ({ parentIds }: TFindFoldersDeepByParentIdsDTO, tx?: Knex) => { + const findByEnvsDeep = async ( + { parentIds, orderBy = SecretsOrderBy.Name, orderDirection = OrderByDirection.ASC }: TFindFoldersDeepByParentIdsDTO, + tx?: Knex + ) => { try { const folders = await (tx || db.replicaNode()) .withRecursive("parents", (qb) => @@ -480,7 +487,9 @@ export const secretFolderDALFactory = (db: TDbClient) => { .select<(TSecretFolders & { path: string; depth: number; environment: string })[]>("*") .from("parents") .orderBy("depth") - .orderBy(`name`); + .orderByRaw(`"parents".?? COLLATE "en-x-icu" ${orderDirection === OrderByDirection.ASC ? "ASC" : "DESC"}`, [ + orderBy + ]); return folders; } catch (error) { diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index c216ea3a8..033efdb14 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -14,6 +14,7 @@ import { PgSqlLock } from "@app/keystore/keystore"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrderByDirection, OrgServiceActor } from "@app/lib/types"; import { ActorType } from "@app/services/auth/auth-type"; +import { SecretsOrderBy } from "@app/services/secret/secret-types"; import { buildFolderPath } from "@app/services/secret-folder/secret-folder-fns"; import { @@ -781,7 +782,11 @@ export const secretFolderServiceFactory = ({ if (!parentFolder) return []; if (recursive) { - const recursiveFolders = await folderDAL.findByEnvsDeep({ parentIds: [parentFolder.id] }); + const recursiveFolders = await folderDAL.findByEnvsDeep({ + parentIds: [parentFolder.id], + orderBy: orderBy || SecretsOrderBy.Name, + orderDirection: orderDirection || OrderByDirection.ASC + }); // remove the parent folder return recursiveFolders .filter((folder) => { @@ -800,19 +805,15 @@ export const secretFolderServiceFactory = ({ })); } - const folders = await folderDAL.find( - { - envId: env.id, - parentId: parentFolder.id, - isReserved: false, - $search: search ? { name: `%${search}%` } : undefined - }, - { - sort: orderBy ? [[orderBy, orderDirection ?? OrderByDirection.ASC]] : undefined, - limit, - offset - } - ); + const folders = await folderDAL.findByMultiEnv({ + environmentIds: [env.id], + parentIds: [parentFolder.id], + search, + orderBy: orderBy || SecretsOrderBy.Name, + orderDirection: orderDirection || OrderByDirection.ASC, + limit, + offset + }); if (lastSecretModified) { return folders.filter((el) => el.lastSecretModified ? el.lastSecretModified >= new Date(lastSecretModified) : false diff --git a/backend/src/services/secret-folder/secret-folder-types.ts b/backend/src/services/secret-folder/secret-folder-types.ts index da8be52a0..d220c29d2 100644 --- a/backend/src/services/secret-folder/secret-folder-types.ts +++ b/backend/src/services/secret-folder/secret-folder-types.ts @@ -64,6 +64,8 @@ export type TGetFoldersDeepByEnvsDTO = { export type TFindFoldersDeepByParentIdsDTO = { parentIds: string[]; + orderBy?: SecretsOrderBy; + orderDirection?: OrderByDirection; }; export type TCreateManyFoldersDTO = { diff --git a/backend/src/services/secret-sync/secret-sync-fns.ts b/backend/src/services/secret-sync/secret-sync-fns.ts index 77845535f..6ee9b91d3 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -4,6 +4,7 @@ import handlebars from "handlebars"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; +import { CHEF_SYNC_LIST_OPTION, ChefSyncFns } from "@app/ee/services/secret-sync/chef"; import { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "@app/ee/services/secret-sync/oci-vault"; import { BadRequestError } from "@app/lib/errors"; import { @@ -34,7 +35,6 @@ import { BITBUCKET_SYNC_LIST_OPTION, BitbucketSyncFns } from "./bitbucket"; import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda"; import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants"; import { ChecklySyncFns } from "./checkly/checkly-sync-fns"; -import { CHEF_SYNC_LIST_OPTION, ChefSyncFns } from "./chef"; import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants"; import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns"; import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers"; diff --git a/backend/src/services/secret-sync/secret-sync-maps.ts b/backend/src/services/secret-sync/secret-sync-maps.ts index 86cb189c3..529634a6d 100644 --- a/backend/src/services/secret-sync/secret-sync-maps.ts +++ b/backend/src/services/secret-sync/secret-sync-maps.ts @@ -107,7 +107,7 @@ export const SECRET_SYNC_PLAN_MAP: Record = { [SecretSync.Northflank]: SecretSyncPlanType.Regular, [SecretSync.Bitbucket]: SecretSyncPlanType.Regular, [SecretSync.LaravelForge]: SecretSyncPlanType.Regular, - [SecretSync.Chef]: SecretSyncPlanType.Regular + [SecretSync.Chef]: SecretSyncPlanType.Enterprise }; export const SECRET_SYNC_SKIP_FIELDS_MAP: Record = { diff --git a/backend/src/services/secret-sync/secret-sync-queue.ts b/backend/src/services/secret-sync/secret-sync-queue.ts index e83a0033f..f6e23dded 100644 --- a/backend/src/services/secret-sync/secret-sync-queue.ts +++ b/backend/src/services/secret-sync/secret-sync-queue.ts @@ -10,6 +10,8 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; +import { triggerWorkflowIntegrationNotification } from "@app/lib/workflow-integrations/trigger-notification"; +import { TriggerFeature } from "@app/lib/workflow-integrations/types"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { SecretNameSchema } from "@app/server/lib/schemas"; import { decryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns"; @@ -62,8 +64,11 @@ import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal"; import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service"; +import { TMicrosoftTeamsServiceFactory } from "../microsoft-teams/microsoft-teams-service"; +import { TProjectMicrosoftTeamsConfigDALFactory } from "../microsoft-teams/project-microsoft-teams-config-dal"; import { TNotificationServiceFactory } from "../notification/notification-service"; import { NotificationType } from "../notification/notification-types"; +import { TProjectSlackConfigDALFactory } from "../slack/project-slack-config-dal"; export type TSecretSyncQueueFactory = ReturnType; @@ -104,6 +109,9 @@ type TSecretSyncQueueFactoryDep = { gatewayService: Pick; gatewayV2Service: Pick; notificationService: Pick; + projectSlackConfigDAL: Pick; + projectMicrosoftTeamsConfigDAL: Pick; + microsoftTeamsService: Pick; }; type SecretSyncActionJob = Job< @@ -147,7 +155,10 @@ export const secretSyncQueueFactory = ({ licenseService, gatewayService, gatewayV2Service, - notificationService + notificationService, + projectSlackConfigDAL, + projectMicrosoftTeamsConfigDAL, + microsoftTeamsService }: TSecretSyncQueueFactoryDep) => { const appCfg = getConfig(); @@ -921,34 +932,65 @@ export const secretSyncQueueFactory = ({ break; } - const syncPath = `/projects/secret-management/${projectId}/integrations/secret-syncs/${destination}/${secretSync.id}`; + const baseProjectPath = `/projects/secret-management/${projectId}`; + const overviewPath = `${baseProjectPath}/overview`; + const syncPath = `${baseProjectPath}/integrations/secret-syncs/${destination}/${secretSync.id}`; - await notificationService.createUserNotifications( - projectAdmins.map((admin) => ({ - userId: admin.userId, - orgId: project.orgId, - type: NotificationType.SECRET_SYNC_FAILED, - title: `Secret Sync Failed to ${actionLabel} Secrets`, - body: `Your **${syncDestination}** sync **${name}** failed to complete${failureMessage ? `: \`${failureMessage}\`` : ""}`, - link: syncPath - })) - ); + const notifications = [ + triggerWorkflowIntegrationNotification({ + input: { + notification: { + type: TriggerFeature.SECRET_SYNC_ERROR, + payload: { + syncName: name, + syncDestination, + failureMessage: failureMessage || "An unknown error occurred", + syncUrl: `${appCfg.SITE_URL}${syncPath}`, + syncActionLabel: actionLabel, + environment: environment?.name || "-", + secretPath: folder?.path || "-", + projectName: project.name, + projectPath: overviewPath + } + }, + projectId + }, + dependencies: { + projectDAL, + projectSlackConfigDAL, + kmsService, + microsoftTeamsService, + projectMicrosoftTeamsConfigDAL + } + }), + notificationService.createUserNotifications( + projectAdmins.map((admin) => ({ + userId: admin.userId, + orgId: project.orgId, + type: NotificationType.SECRET_SYNC_FAILED, + title: `Secret Sync Failed to ${actionLabel} Secrets`, + body: `Your **${syncDestination}** sync **${name}** failed to complete${failureMessage ? `: \`${failureMessage}\`` : ""}`, + link: syncPath + })) + ), + smtpService.sendMail({ + recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean), + template: SmtpTemplates.SecretSyncFailed, + subjectLine: `Secret Sync Failed to ${actionLabel} Secrets`, + substitutions: { + syncName: name, + syncDestination, + content: `Your ${syncDestination} Sync named "${name}" failed while attempting to ${action.toLowerCase()} secrets.`, + failureMessage, + secretPath: folder?.path, + environment: environment?.name, + projectName: project.name, + syncUrl: `${appCfg.SITE_URL}${syncPath}` + } + }) + ]; - await smtpService.sendMail({ - recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean), - template: SmtpTemplates.SecretSyncFailed, - subjectLine: `Secret Sync Failed to ${actionLabel} Secrets`, - substitutions: { - syncName: name, - syncDestination, - content: `Your ${syncDestination} Sync named "${name}" failed while attempting to ${action.toLowerCase()} secrets.`, - failureMessage, - secretPath: folder?.path, - environment: environment?.name, - projectName: project.name, - syncUrl: `${appCfg.SITE_URL}${syncPath}` - } - }); + await Promise.allSettled(notifications); }; const queueSecretSyncsSyncSecretsByPath = async ({ diff --git a/backend/src/services/secret-sync/secret-sync-types.ts b/backend/src/services/secret-sync/secret-sync-types.ts index d6ef0b3fc..f1a87a9d2 100644 --- a/backend/src/services/secret-sync/secret-sync-types.ts +++ b/backend/src/services/secret-sync/secret-sync-types.ts @@ -1,6 +1,12 @@ import { Job } from "bullmq"; import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types"; +import { + TChefSync, + TChefSyncInput, + TChefSyncListItem, + TChefSyncWithCredentials +} from "@app/ee/services/secret-sync/chef"; import { TOCIVaultSync, TOCIVaultSyncInput, @@ -21,7 +27,6 @@ import { TCamundaSyncListItem, TCamundaSyncWithCredentials } from "@app/services/secret-sync/camunda"; -import { TChefSync, TChefSyncInput, TChefSyncListItem, TChefSyncWithCredentials } from "@app/services/secret-sync/chef"; import { TDatabricksSync, TDatabricksSyncInput, diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts index 6b284ddee..01a7f6210 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts @@ -793,6 +793,7 @@ export const reshapeBridgeSecret = ( userActorId?: string | null; identityActorId?: string | null; membershipId?: string | null; + groupId?: string | null; actorType?: string | null; tags?: { id: string; @@ -823,7 +824,8 @@ export const reshapeBridgeSecret = ( actorType: secret.actorType, actorId: secret.userActorId || secret.identityActorId, name: secret.identityActorName || secret.userActorName, - membershipId: secret.membershipId + membershipId: secret.membershipId, + groupId: secret.groupId } : undefined, tags: secret.tags, diff --git a/backend/src/services/secret-v2-bridge/secret-version-dal.ts b/backend/src/services/secret-v2-bridge/secret-version-dal.ts index 7d25dac86..a7f0eb565 100644 --- a/backend/src/services/secret-v2-bridge/secret-version-dal.ts +++ b/backend/src/services/secret-v2-bridge/secret-version-dal.ts @@ -182,7 +182,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => { const findVersionsBySecretIdWithActors = async ({ secretId, - projectId, secretVersions, findOpt = {}, tx @@ -196,13 +195,22 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => { try { const { offset, limit, sort = [["createdAt", "desc"]] } = findOpt; const query = (tx || db.replicaNode())(TableName.SecretVersionV2) + .leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.SecretVersionV2}.folderId`) + .leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`) .leftJoin(TableName.Users, `${TableName.Users}.id`, `${TableName.SecretVersionV2}.userActorId`) + .leftJoin(TableName.Identity, `${TableName.Identity}.id`, `${TableName.SecretVersionV2}.identityActorId`) + .leftJoin(TableName.UserGroupMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`) .leftJoin(TableName.Membership, (qb) => { void qb - .on(`${TableName.Membership}.actorUserId`, `${TableName.SecretVersionV2}.userActorId`) - .andOn(`${TableName.Membership}.scope`, db.raw("?", [AccessScope.Project])); + .on(`${TableName.Membership}.scope`, db.raw("?", [AccessScope.Project])) + .andOn(`${TableName.Membership}.scopeProjectId`, `${TableName.Environment}.projectId`) + .andOn((sqb) => { + void sqb + .on(`${TableName.Membership}.actorUserId`, `${TableName.SecretVersionV2}.userActorId`) + .orOn(`${TableName.Membership}.actorIdentityId`, `${TableName.SecretVersionV2}.identityActorId`) + .orOn(`${TableName.Membership}.actorGroupId`, `${TableName.UserGroupMembership}.groupId`); + }); }) - .leftJoin(TableName.Identity, `${TableName.Identity}.id`, `${TableName.SecretVersionV2}.identityActorId`) .leftJoin(TableName.SecretV2, `${TableName.SecretVersionV2}.secretId`, `${TableName.SecretV2}.id`) .leftJoin( TableName.SecretVersionV2Tag, @@ -216,12 +224,6 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => { ) .where((qb) => { void qb.where(`${TableName.SecretVersionV2}.secretId`, secretId); - void qb.where(`${TableName.Membership}.scopeProjectId`, projectId); - if (secretVersions?.length) void qb.whereIn(`${TableName.SecretVersionV2}.version`, secretVersions); - }) - .orWhere((qb) => { - void qb.where(`${TableName.SecretVersionV2}.secretId`, secretId); - void qb.whereNull(`${TableName.Membership}.scopeProjectId`); if (secretVersions?.length) void qb.whereIn(`${TableName.SecretVersionV2}.version`, secretVersions); }) .select( @@ -229,6 +231,7 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => { db.ref("username").withSchema(TableName.Users).as("userActorName"), db.ref("name").withSchema(TableName.Identity).as("identityActorName"), db.ref("id").withSchema(TableName.Membership).as("membershipId"), + db.ref("actorGroupId").withSchema(TableName.Membership).as("groupId"), db.ref("id").withSchema(TableName.SecretTag).as("tagId"), db.ref("color").withSchema(TableName.SecretTag).as("tagColor"), db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug") @@ -256,7 +259,8 @@ export const secretVersionV2BridgeDALFactory = (db: TDbClient) => { ...SecretVersionsV2Schema.parse(el), userActorName: el.userActorName, identityActorName: el.identityActorName, - membershipId: el.membershipId + membershipId: el.membershipId, + groupId: el.groupId }), childrenMapper: [ { diff --git a/backend/src/services/slack/project-slack-config-dal.ts b/backend/src/services/slack/project-slack-config-dal.ts index 276442b1b..4b5b2146e 100644 --- a/backend/src/services/slack/project-slack-config-dal.ts +++ b/backend/src/services/slack/project-slack-config-dal.ts @@ -1,16 +1,17 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName } from "@app/db/schemas"; +import { TableName, TProjectSlackConfigs, TSlackIntegrations } from "@app/db/schemas"; import { ormify, selectAllTableCols } from "@app/lib/knex"; export type TProjectSlackConfigDALFactory = ReturnType; +export type TProjectSlackConfigWithIntegrations = TProjectSlackConfigs & TSlackIntegrations; export const projectSlackConfigDALFactory = (db: TDbClient) => { const projectSlackConfigOrm = ormify(db, TableName.ProjectSlackConfigs); const getIntegrationDetailsByProject = (projectId: string, tx?: Knex) => { - return (tx || db.replicaNode())(TableName.ProjectSlackConfigs) + return (tx || db.replicaNode())(TableName.ProjectSlackConfigs) .join( TableName.SlackIntegrations, `${TableName.ProjectSlackConfigs}.slackIntegrationId`, diff --git a/backend/src/services/slack/slack-fns.ts b/backend/src/services/slack/slack-fns.ts index 88db1a84d..aaeb28916 100644 --- a/backend/src/services/slack/slack-fns.ts +++ b/backend/src/services/slack/slack-fns.ts @@ -8,6 +8,9 @@ import { TNotification, TriggerFeature } from "@app/lib/workflow-integrations/ty import { KmsDataKey } from "../kms/kms-types"; import { TSendSlackNotificationDTO } from "./slack-types"; +const COMPANY_BRAND_COLOR = "#e0ed34"; +const ERROR_COLOR = "#e74c3c"; + export const fetchSlackChannels = async (botKey: string) => { const slackChannels: { name: string; @@ -48,13 +51,9 @@ const buildSlackPayload = (notification: TNotification) => { const messageBody = `A secret approval request has been opened by ${payload.userEmail}. *Environment*: ${payload.environment} *Secret path*: ${payload.secretPath || "/"} -*Secret Key${payload.secretKeys.length > 1 ? "s" : ""}*: ${payload.secretKeys.join(", ")} +*Secret Key${payload.secretKeys.length > 1 ? "s" : ""}*: ${payload.secretKeys.join(", ")}`; -View the complete details <${appCfg.SITE_URL}/projects/secret-management/${payload.projectId}/approval?requestId=${ - payload.requestId - }|here>.`; - - const payloadBlocks = [ + const headerBlocks = [ { type: "header", text: { @@ -62,37 +61,52 @@ View the complete details <${appCfg.SITE_URL}/projects/secret-management/${paylo text: "Secret approval request", emoji: true } - }, + } + ]; + + const payloadBlocks = [ { type: "section", text: { type: "mrkdwn", text: messageBody } + }, + { + type: "actions", + elements: [ + { + type: "button", + text: { + type: "plain_text", + text: "View request", + emoji: true + }, + style: "primary", + url: payload.approvalUrl + } + ] } ]; return { + headerBlocks, payloadMessage: messageBody, - payloadBlocks + payloadBlocks, + color: COMPANY_BRAND_COLOR }; } case TriggerFeature.ACCESS_REQUEST: { const { payload } = notification; - const messageBody = `${payload.requesterFullName} (${payload.requesterEmail}) has requested ${ - payload.isTemporary ? "temporary" : "permanent" - } access to ${payload.secretPath} in the ${payload.environment} environment of ${payload.projectName}. - -The following permissions are requested: ${payload.permissions.join(", ")} + const projectUrl = `${appCfg.SITE_URL}${payload.projectPath}/overview`; + const accessType = payload.isTemporary ? "temporary" : "permanent"; + const permissionsFormatted = payload.permissions.map((p) => `*${p}*`).join(", "); -View the request and approve or deny it <${payload.approvalUrl}|here>.${ - payload.note - ? ` -User Note: ${payload.note}` - : "" + const messageBody = `${payload.requesterFullName} (${payload.requesterEmail}) has requested ${accessType} access to ${payload.secretPath} in the ${payload.environment} environment of ${payload.projectName}.\n\nThe following permissions are requested: ${payload.permissions.join(", ")}${ + payload.note ? `\n\nUser note: ${payload.note}` : "" }`; - const payloadBlocks = [ + const headerBlocks = [ { type: "header", text: { @@ -100,37 +114,54 @@ User Note: ${payload.note}` text: "New access approval request pending for review", emoji: true } - }, + } + ]; + + const payloadBlocks = [ { type: "section", text: { type: "mrkdwn", - text: messageBody + text: `*${payload.requesterFullName}* (${payload.requesterEmail}) has requested *${accessType}* access to *${payload.secretPath}* in the *${payload.environment}* environment of *<${projectUrl}|${payload.projectName}>*.\n\nThe following permissions are requested: ${permissionsFormatted}${ + payload.note ? `\n\n*User note:* ${payload.note}` : "" + }` } + }, + { + type: "actions", + elements: [ + { + type: "button", + text: { + type: "plain_text", + text: "View request", + emoji: true + }, + style: "primary", + url: payload.approvalUrl + } + ] } ]; return { + headerBlocks, payloadMessage: messageBody, - payloadBlocks + payloadBlocks, + color: COMPANY_BRAND_COLOR }; } case TriggerFeature.ACCESS_REQUEST_UPDATED: { const { payload } = notification; - const messageBody = `${payload.editorFullName} (${payload.editorEmail}) has updated the ${ - payload.isTemporary ? "temporary" : "permanent" - } access request from ${payload.requesterFullName} (${payload.requesterEmail}) to ${payload.secretPath} in the ${payload.environment} environment of ${payload.projectName}. - -The following permissions are requested: ${payload.permissions.join(", ")} + const projectUrl = `${appCfg.SITE_URL}${payload.projectPath}/overview`; + const accessType = payload.isTemporary ? "temporary" : "permanent"; + const permissionsFormatted = payload.permissions.map((p) => `*${p}*`).join(", "); -View the request and approve or deny it <${payload.approvalUrl}|here>.${ - payload.editNote - ? ` -Editor Note: ${payload.editNote}` - : "" + const messageBody = `${payload.editorFullName} (${payload.editorEmail}) has updated the ${accessType} access request from ${payload.requesterFullName} (${payload.requesterEmail}) to ${payload.secretPath} in the ${payload.environment} environment of ${payload.projectName}.\n\nThe following permissions are requested: ${payload.permissions.join(", ")}${ + payload.editNote ? `\n\nEditor Note: ${payload.editNote}` : "" }`; - const payloadBlocks = [ + const headerBlocks = [ { type: "header", text: { @@ -138,19 +169,89 @@ Editor Note: ${payload.editNote}` text: "Updated access approval request pending for review", emoji: true } - }, + } + ]; + + const payloadBlocks = [ { type: "section", text: { type: "mrkdwn", - text: messageBody + text: `*${payload.editorFullName}* (${payload.editorEmail}) has updated the *${accessType}* access request from *${payload.requesterFullName}* (${payload.requesterEmail}) to *${payload.secretPath}* in the *${payload.environment}* environment of *<${projectUrl}|${payload.projectName}>*.\n\nThe following permissions are requested: ${permissionsFormatted}${ + payload.editNote ? `\n\n*Editor Note:* ${payload.editNote}` : "" + }` } + }, + { + type: "actions", + elements: [ + { + type: "button", + text: { + type: "plain_text", + text: "View request", + emoji: true + }, + style: "primary", + url: payload.approvalUrl + } + ] + } + ]; + + return { + headerBlocks, + payloadMessage: messageBody, + payloadBlocks, + color: COMPANY_BRAND_COLOR + }; + } + case TriggerFeature.SECRET_SYNC_ERROR: { + const { payload } = notification; + const projectUrl = `${appCfg.SITE_URL}${payload.projectPath}`; + const messageBody = `Secret sync ${payload.syncName} for ${payload.syncDestination} failed on ${payload.syncActionLabel}\n\n\nEnvironment: ${payload.environment}\n\n\nSecret Path: ${payload.secretPath}\n\n\nProject: ${payload.projectName} (${projectUrl})\n\n\nReason:\n${payload.failureMessage}`; + + const headerBlocks = [ + { + type: "header", + text: { + type: "plain_text", + text: `Secret sync ${payload.syncName} for ${payload.syncDestination} failed on ${payload.syncActionLabel}`, + emoji: true + } + } + ]; + + const payloadBlocks = [ + { + type: "section", + text: { + type: "mrkdwn", + text: `*Environment:* ${payload.environment}\n\n*Secret Path:* ${payload.secretPath}\n\n*Project:* <${projectUrl}|${payload.projectName}>\n\n*Reason:* ${payload.failureMessage}` + } + }, + { + type: "actions", + elements: [ + { + type: "button", + text: { + type: "plain_text", + text: "Open secret sync", + emoji: true + }, + style: "primary", + url: payload.syncUrl + } + ] } ]; return { payloadMessage: messageBody, - payloadBlocks + headerBlocks, + payloadBlocks, + color: ERROR_COLOR }; } default: { @@ -177,15 +278,22 @@ export const sendSlackNotification = async ({ }).toString("utf8"); const slackWebClient = new WebClient(botKey); - const { payloadMessage, payloadBlocks } = buildSlackPayload(notification); + const { payloadMessage, payloadBlocks, color, headerBlocks } = buildSlackPayload(notification); for await (const conversationId of targetChannelIds) { // we send both text and blocks for compatibility with barebone clients + await slackWebClient.chat .postMessage({ channel: conversationId, text: payloadMessage, - blocks: payloadBlocks + blocks: headerBlocks, + attachments: [ + { + color, + blocks: payloadBlocks + } + ] }) .catch((err) => logger.error(err)); } diff --git a/docs/docs.json b/docs/docs.json index 6d8eb04cc..3fb6914af 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -784,7 +784,10 @@ "groups": [ { "group": "Infisical PAM", - "pages": ["documentation/platform/pam/overview"] + "pages": [ + "documentation/platform/pam/overview", + "documentation/platform/pam/session-recording" + ] } ] } diff --git a/docs/documentation/guides/organization-structure.mdx b/docs/documentation/guides/organization-structure.mdx index 3cba64678..752c06ccc 100644 --- a/docs/documentation/guides/organization-structure.mdx +++ b/docs/documentation/guides/organization-structure.mdx @@ -24,9 +24,11 @@ Infisical is designed to provide comprehensive, centralized, and efficient manag ### 2. Projects - **Definition and Role**: [Projects](/documentation/platform/project) are the highest-level construct within an [organization](/documentation/platform/organization) in Infisical. They serve as the primary container for all functionalities. -- **Correspondence to Code Repositories**: Projects typically align with specific code repositories. +- **Common Project Mappings**: Projects typically align with applications, services, or code repositories — each being a valid and common approach depending on your organizational structure. - **Functional Capabilities**: Each project encompasses features for managing secrets, certificates, and encryption keys, serving as the central hub for these resources. +Projects are isolated from one another. Secrets, certificates, and other resources cannot be shared or referenced across different projects. Each project maintains its own separate set of resources. + ### 3. Environments - **Purpose**: Environments are designed for organizing and compartmentalizing secrets within projects. @@ -40,8 +42,9 @@ Infisical is designed to provide comprehensive, centralized, and efficient manag ### 5. Imports -- **Purpose and Benefits**: To promote reusability and avoid redundancy, Infisical supports the use of imports. This allows secrets, folders, or entire environments to be referenced across multiple projects as needed. -- **Best Practice**: Utilizing [secret imports](/documentation/platform/secret-reference#secret-imports) or [references](/documentation/platform/secret-reference#secret-referencing) ensures consistency and minimizes manual overhead. +- **Purpose and Benefits**: To promote reusability and avoid redundancy within a project, Infisical supports the use of imports and references. This allows secrets, folders, or entire environments to be referenced within the same project as needed. +- **Project Isolation**: Imports and references only work within a single project. Secrets cannot be imported or referenced across different projects, as projects are isolated from one another. +- **Best Practice**: Utilizing [secret imports](/documentation/platform/secret-reference#secret-imports) or [references](/documentation/platform/secret-reference#secret-referencing) ensures consistency and minimizes manual overhead when managing secrets within a project. ### 6. Approval Workflows diff --git a/docs/documentation/platform/kms/hsm-integration.mdx b/docs/documentation/platform/kms/hsm-integration.mdx index 7a8d15fe5..a2e2dce16 100644 --- a/docs/documentation/platform/kms/hsm-integration.mdx +++ b/docs/documentation/platform/kms/hsm-integration.mdx @@ -30,13 +30,96 @@ Using a hardware security module comes with the added benefit of having a secure Enabling HSM encryption has a set of key benefits: 1. **Root Key Wrapping**: The root KMS encryption key that is used to secure your Infisical instance will be encrypted using the HSM device rather than the standard software-protected key. + #### Caveats - **Performance**: Using an HSM device can have a performance impact on your Infisical instance. This is due to the additional latency introduced by the HSM device. This is however only noticeable when your instance(s) start up or when the encryption strategy is changed. - **Key Recovery**: If the HSM device is lost or destroyed, you will no longer be able to decrypt your data stored within Infisical. Most HSM providers offer recovery options, which you should consider when setting up an HSM device. -### Requirements -- An Infisical instance with a version number that is equal to or greater than `v0.91.0`. -- An HSM device from a provider such as [Thales Luna HSM](https://cpl.thalesgroup.com/encryption/data-protection-on-demand/services/luna-cloud-hsm), [AWS CloudHSM](https://aws.amazon.com/cloudhsm/), [Fortanix HSM](https://www.fortanix.com/platform/data-security-manager), or others. +## Requirements +- An HSM device _(PKCS#11 compatible library)_ from a compatible provider such as [Thales Luna HSM](https://cpl.thalesgroup.com/encryption/data-protection-on-demand/services/luna-cloud-hsm), [AWS CloudHSM](https://aws.amazon.com/cloudhsm/), [Fortanix HSM](https://www.fortanix.com/platform/data-security-manager), or others. + Infisical is validated to work with PKCS#11 2.30 and newer. If your HSM device doesn't follow the >=2.30 PKCS#11 standard you may see degraded performance. + + +## Environment Variable Configuration +To configure your Infisical instance to use an HSM, you must set the required environment variables. Below you'll find an example of the required environment variables. +For further instructions on how to configure the HSM device for your Infisical instance, please see the [Setup Instructions](#setup-instructions) section. + + +```dotenv +HSM_LIB_PATH=/usr/local/lib/cloudhsm/cloudhsm.so +HSM_SLOT=1 +HSM_KEY_LABEL=infisical-key +HSM_PIN=your:pin +``` + +- `HSM_LIB_PATH`: The path to the PKCS#11 library provided by the HSM provider. This usually comes in the form of a `.so` for Linux and MacOS, or a `.dll` file for Windows. For Docker, you need to mount the library path as a volume. Further instructions can be found below. If you are using Docker, make sure to set the HSM_LIB_PATH environment variable to the path where the library is mounted in the container. +- `HSM_PIN`: The PKCS#11 PIN to use for authentication with the HSM device. +- `HSM_SLOT`: The slot number to use for the HSM device. This is typically between `0` and `5` for most HSM devices. +- `HSM_KEY_LABEL`: The label of the key to use for encryption. **Please note that if no key is found with the provided label, the HSM will create a new key with the provided label.** + +You can read more about the [default instance configurations](/self-hosting/configuration/envars) here. + +## PKCS#11 Key Attributes + +If no AES key or HMAC key already exists with the label you defined on the `HSM_KEY_LABEL` environment variable, then Infisical will create one for you automatically using the label specified on `HSM_KEY_LABEL`. +Below you'll find a list of the attributes each key will be created with. + +### AES Key + + + If you bring your own AES key and don't let Infisical create it for you it must have at least the following attributes: + + * `CKA_CLASS`: `CKO_SECRET_KEY` — Defines the key class _(secret key)_. + * `CKA_KEY_TYPE`: `CKO_AES` — Defines the key type _(AES key)_. + * `CKA_VALUE_LEN`: `32` — 256-bit key size. + * `CKA_ENCRYPT`: `true` — Encryption capabilities enabled. + * `CKA_DECRYPT`: `true` — Decryption capabilities enabled. + * `CKA_TOKEN`: `true` — The key material will persist in your HSM so it can be reused. + + + Note that for security reasons it is highly recommended to create an AES key with the full set of key attributes seen below if you're going to bring your own key. + + + +* `CKA_CLASS`: `CKO_SECRET_KEY` — Defines the key class _(secret key)_. +* `CKA_KEY_TYPE`: `CKO_AES` — Defines the key type _(AES key)_. +* `CKA_VALUE_LEN`: `32` — 256-bit key size. +* `CKA_LABEL`: Your specified label in the `HSM_KEY_LABEL` environment variable. +* `CKA_ENCRYPT`: `true` — Encryption capabilities enabled. +* `CKA_DECRYPT`: `true` — Decryption capabilities enabled. +* `CKA_TOKEN`: `true` — The key material will persist in your HSM so it can be reused. +* `CKA_EXTRACTABLE`: `false` — The key material is not extractable from the HSM. +* `CKA_SENSITIVE`: `true` — The key material is marked as sensitive. +* `CKA_PRIVATE`: `true` — The key material is marked as private to the slot and can't be accessed from other slots. + +### HMAC Key + + + If you bring your own HMAC key and don't let Infisical create it for you it must have at least the following attributes: + + * `CKA_CLASS`: `CKO_SECRET_KEY` — Defines the key class _(secret key)_. + * `CKA_KEY_TYPE`: `CKO_GENERIC_SECRET` — Defines the key class _(generic secret key)_. + * `CKA_VALUE_LEN`: `32` — 256-bit key size + * `CKA_SIGN`: `true` — Signing capabilities enabled + * `CKA_VERIFY`: `true` — Verifying capabilities enabled. + * `CKA_TOKEN`: `true` — The key material will persist in your HSM so it can be reused. + + + Note that for security reasons it is highly recommended to create an HMAC key with the full set of key attributes seen below if you're going to bring your own key. + + + +* `CKA_CLASS`: `CKO_SECRET_KEY` — Defines the key class _(secret key)_. +* `CKA_KEY_TYPE`: `CKO_GENERIC_SECRET` — Defines the key class _(generic secret key)_. +* `CKA_VALUE_LEN`: `32` — 256-bit key size. +* `CKA_LABEL`: Your specified label in the `HSM_KEY_LABEL` environment variable, suffixed with `_HMAC`. If you specify `infisical-key-v1`, then the HMAC key label will become `infisical-key-v1_HMAC`. +* `CKA_SIGN`: `true` — Signing capabilities enabled +* `CKA_VERIFY`: `true` — Verifying capabilities enabled. +* `CKA_TOKEN`: `true` — The key material will persist in your HSM so it can be reused. +* `CKA_EXTRACTABLE`: `false` — The key material is not extractable from the HSM. +* `CKA_SENSITIVE`: `true` — The key material is marked as sensitive. +* `CKA_PRIVATE`: `true` — The key material is marked as private to the slot and can't be accessed from other slots. + ## Setup Instructions diff --git a/docs/documentation/platform/pam/session-recording.mdx b/docs/documentation/platform/pam/session-recording.mdx new file mode 100644 index 000000000..7e560d5c2 --- /dev/null +++ b/docs/documentation/platform/pam/session-recording.mdx @@ -0,0 +1,60 @@ +--- +title: "Session Recording" +sidebarTitle: "Session Recording" +description: "Learn how Infisical records and stores session activity for auditing and monitoring." +--- + +Infisical's Privileged Access Management (PAM) provides robust session recording capabilities to help you audit and monitor user activity across your infrastructure. + +## How It Works + +When a user initiates a session through the Infisical Gateway, a recording of the session begins. The gateway securely caches all recording data in temporary encrypted files on its local system. + +Once the session concludes, the gateway transmits the complete recording to the Infisical platform for long-term, centralized storage. This asynchronous process ensures that sessions remain operational even if the connection to the Infisical platform is temporarily lost. After the upload is complete, administrators can search and review the session logs in the Infisical UI. + +## What's Captured + +The content captured during a session depends on the type of resource being accessed. + +### Database Sessions + +For database connections, Infisical captures all queries executed and their corresponding responses. + + +Support for additional resource types like SSH and RDP is coming soon. + + +## Viewing Recordings + +To review session recordings: + +1. Navigate to the **PAM Sessions** page in your project. +2. Click on a session from the list to view its details. + +![PAM Sessions](/images/pam/session-recording/sessions-page.png) + +The session details page provides key information, including the complete session logs, connection status, the user who initiated it, and more. + +![PAM Individual Session](/images/pam/session-recording/individual-session-page.png) + +### Searching Logs + +You can use the search bar to quickly find relevant information: + +- **On the main Sessions page:** Search across all session logs to locate specific queries or outputs. +- **On an individual session page:** Search within that specific session's logs to pinpoint activity. + +![PAM Sessions Search](/images/pam/session-recording/sessions-page-search.png) + +![PAM Individual Session Search](/images/pam/session-recording/individual-session-page-search.png) + +## FAQ + + + + Yes. All session recordings are encrypted at rest by default, ensuring your audit data is always secure. + + + Currently, Infisical uses an asynchronous approach where the gateway records the entire session locally before uploading it. This design makes your PAM sessions more resilient, as they don't depend on a constant, active connection to the Infisical platform. We may introduce live streaming capabilities in a future release. + + diff --git a/docs/images/pam/session-recording/individual-session-page-search.png b/docs/images/pam/session-recording/individual-session-page-search.png new file mode 100644 index 000000000..ce369f515 Binary files /dev/null and b/docs/images/pam/session-recording/individual-session-page-search.png differ diff --git a/docs/images/pam/session-recording/individual-session-page.png b/docs/images/pam/session-recording/individual-session-page.png new file mode 100644 index 000000000..2926caf67 Binary files /dev/null and b/docs/images/pam/session-recording/individual-session-page.png differ diff --git a/docs/images/pam/session-recording/sessions-page-search.png b/docs/images/pam/session-recording/sessions-page-search.png new file mode 100644 index 000000000..a90cda587 Binary files /dev/null and b/docs/images/pam/session-recording/sessions-page-search.png differ diff --git a/docs/images/pam/session-recording/sessions-page.png b/docs/images/pam/session-recording/sessions-page.png new file mode 100644 index 000000000..8faab291d Binary files /dev/null and b/docs/images/pam/session-recording/sessions-page.png differ diff --git a/docs/integrations/app-connections/chef.mdx b/docs/integrations/app-connections/chef.mdx index 60c49fb1f..fdb866557 100644 --- a/docs/integrations/app-connections/chef.mdx +++ b/docs/integrations/app-connections/chef.mdx @@ -3,6 +3,14 @@ title: "Chef Connection" description: "Learn how to configure a Chef Connection for Infisical." --- + + Chef App Connection is a paid feature. + + If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical, + then you should contact team@infisical.com to purchase an enterprise license to use it. + + + Infisical supports the use of User Private Key to connect with Chef Server. Please access your **starter kit** to get all the required information to create a Chef Connection. diff --git a/docs/integrations/platforms/kubernetes-injector.mdx b/docs/integrations/platforms/kubernetes-injector.mdx index b7ecf5815..c8fabbb34 100644 --- a/docs/integrations/platforms/kubernetes-injector.mdx +++ b/docs/integrations/platforms/kubernetes-injector.mdx @@ -51,6 +51,71 @@ $ kubectl logs deployment/infisical-agent-injector 2025/05/19 14:20:06 Successfully updated webhook configuration with CA bundle ``` +## Windows support + +By default the agent injector is built for Linux-based pods, but supports injecting into Windows-based pods. + +**To inject into Windows-based pods, no extra configuration is needed.** The agent injector will automatically detect and handle injections into Windows-based pods. + +However, if you are trying to run the agent injector itself on a Windows-based pod, you'll need to configure your helm values.yaml file to point to a Windows-based image. + + +The Agent Injector will only run on and inject into Windows-based pods that are running on the supported Windows versions: +- **Windows Server 2022** +- **Windows Server 2019** + +We're looking to add support for other Windows versions in the future. If you're using a different Windows version, please let us know by opening [an issue](https://github.com/Infisical/infisical-agent-injector/issues/new), and we'll look into adding support for your desired version as soon as possible. + + +You will need to set the `nodeSelector.kubernetes.io/os` label to `windows` and set the image tag to a Windows-based image. Below are two examples for Windows Server 2019 and Windows Server 2022. + + + + + Create your `values.yaml` file and add the following: + + ```yaml values.yaml + image: + repository: infisical/infisical-agent-injector + tag: "v0.1.4-windows-server-2019" + + nodeSelector: + kubernetes.io/os: windows + ``` + + Install the agent injector using the values.yaml file you created above. + + ```bash + helm install --generate-name infisical-helm-charts/infisical-agent-injector -f values.yaml + ``` + + + + + Create your `values.yaml` file and add the following: + + ```yaml values.yaml + image: + repository: infisical/infisical-agent-injector + tag: "v0.1.4-windows-server-2022" + + nodeSelector: + kubernetes.io/os: windows + ``` + + Install the agent injector using the values.yaml file you created above. + + ```bash + helm install --generate-name infisical-helm-charts/infisical-agent-injector -f values.yaml + ``` + + + + + + Note that Windows support is only supported in version `v0.1.4` and above. If you are using an older version, you will need to upgrade to `v0.1.4` or above to use Windows support. + + ## Supported annotations The Infisical Agent Injector supports the following annotations: diff --git a/docs/integrations/secret-syncs/chef.mdx b/docs/integrations/secret-syncs/chef.mdx index 13f3b3474..423d42b4b 100644 --- a/docs/integrations/secret-syncs/chef.mdx +++ b/docs/integrations/secret-syncs/chef.mdx @@ -3,6 +3,14 @@ title: "Chef Sync" description: "Learn how to configure a Chef Sync for Infisical." --- + + Chef Sync is a paid feature. + + If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical, + then you should contact team@infisical.com to purchase an enterprise license to use it. + + + **Prerequisites:** - Create a [Chef Connection](/integrations/app-connections/chef) diff --git a/docs/self-hosting/guides/monitoring-telemetry.mdx b/docs/self-hosting/guides/monitoring-telemetry.mdx index b23c51b27..763826d4f 100644 --- a/docs/self-hosting/guides/monitoring-telemetry.mdx +++ b/docs/self-hosting/guides/monitoring-telemetry.mdx @@ -27,7 +27,9 @@ Both approaches provide the same metrics data in OTEL format, so you can choose - Access to deploy monitoring services (Prometheus, Grafana, etc.) - Basic understanding of Prometheus and Grafana -## Environment Variables +## Setup + +### Environment Variables Configure the following environment variables in your Infisical backend: @@ -37,287 +39,304 @@ OTEL_TELEMETRY_COLLECTION_ENABLED=true # Choose export type: "prometheus" or "otlp" OTEL_EXPORT_TYPE=prometheus - -# For OTLP push mode, also configure: -# OTEL_EXPORT_OTLP_ENDPOINT=http://otel-collector:4318/v1/metrics -# OTEL_COLLECTOR_BASIC_AUTH_USERNAME=your_collector_username -# OTEL_COLLECTOR_BASIC_AUTH_PASSWORD=your_collector_password -# OTEL_OTLP_PUSH_INTERVAL=30000 ``` -**Note**: The `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` values must match the credentials configured in your OpenTelemetry Collector's `basicauth/server` extension. These are not hardcoded values - you configure them in your collector configuration file. + + + This approach exposes metrics on port 9464 at the `/metrics` endpoint, allowing Prometheus to scrape the data. The metrics are exposed in Prometheus format but originate from OpenTelemetry instrumentation. -## Option 1: Pull-based Monitoring (Prometheus) + ### Configuration -This approach exposes metrics on port 9464 at the `/metrics` endpoint, allowing Prometheus to scrape the data. The metrics are exposed in Prometheus format but originate from OpenTelemetry instrumentation. + + +```bash +OTEL_TELEMETRY_COLLECTION_ENABLED=true +OTEL_EXPORT_TYPE=prometheus +``` + -### Configuration + + Expose the metrics port in your Infisical backend: -1. **Enable Prometheus export in Infisical**: + - **Docker**: Expose port 9464 + - **Kubernetes**: Create a service exposing port 9464 + - **Other**: Ensure port 9464 is accessible to your monitoring stack + - ```bash - OTEL_TELEMETRY_COLLECTION_ENABLED=true - OTEL_EXPORT_TYPE=prometheus - ``` - -2. **Expose the metrics port** in your Infisical backend: - - - **Docker**: Expose port 9464 - - **Kubernetes**: Create a service exposing port 9464 - - **Other**: Ensure port 9464 is accessible to your monitoring stack - -3. **Create Prometheus configuration** (`prometheus.yml`): - - ```yaml - global: - scrape_interval: 30s - evaluation_interval: 30s - - scrape_configs: - - job_name: "infisical" - scrape_interval: 30s - static_configs: - - targets: ["infisical-backend:9464"] # Adjust hostname/port based on your deployment - metrics_path: "/metrics" - ``` - - **Note**: Replace `infisical-backend:9464` with the actual hostname and port where your Infisical backend is running. This could be: - - - **Docker Compose**: `infisical-backend:9464` (service name) - - **Kubernetes**: `infisical-backend.default.svc.cluster.local:9464` (service name) - - **Bare Metal**: `192.168.1.100:9464` (actual IP address) - - **Cloud**: `your-infisical.example.com:9464` (domain name) - -### Deployment Options - -#### Docker Compose + +Create `prometheus.yml`: ```yaml -services: +global: + scrape_interval: 30s + evaluation_interval: 30s + +scrape_configs: + - job_name: "infisical" + scrape_interval: 30s + static_configs: + - targets: ["infisical-backend:9464"] # Adjust hostname/port based on your deployment + metrics_path: "/metrics" +``` + + +Replace `infisical-backend:9464` with the actual hostname and port where your Infisical backend is running. This could be: + +- **Docker Compose**: `infisical-backend:9464` (service name) +- **Kubernetes**: `infisical-backend.default.svc.cluster.local:9464` (service name) +- **Bare Metal**: `192.168.1.100:9464` (actual IP address) +- **Cloud**: `your-infisical.example.com:9464` (domain name) + + + + + ### Deployment Options + + Once you've configured Infisical to expose metrics, you'll need to deploy Prometheus to scrape and store them. Below are examples for different deployment environments. Choose the option that matches your infrastructure. + + + + ```yaml + services: + prometheus: + image: prom/prometheus:latest + ports: + - "9090:9090" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro + command: + - "--config.file=/etc/prometheus/prometheus.yml" + + grafana: + image: grafana/grafana:latest + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin + ``` + + + ```yaml + # prometheus-deployment.yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: prometheus + spec: + replicas: 1 + selector: + matchLabels: + app: prometheus + template: + metadata: + labels: + app: prometheus + spec: + containers: + - name: prometheus + image: prom/prometheus:latest + ports: + - containerPort: 9090 + volumeMounts: + - name: config + mountPath: /etc/prometheus + volumes: + - name: config + configMap: + name: prometheus-config + + --- + # prometheus-service.yaml + apiVersion: v1 + kind: Service + metadata: + name: prometheus + spec: + selector: + app: prometheus + ports: + - port: 9090 + targetPort: 9090 + type: ClusterIP + ``` + + + ```bash + helm repo add prometheus-community https://prometheus-community.github.io/helm-charts + helm install prometheus prometheus-community/prometheus \ + --set server.config.global.scrape_interval=30s \ + --set server.config.scrape_configs[0].job_name=infisical \ + --set server.config.scrape_configs[0].static_configs[0].targets[0]=infisical-backend:9464 + ``` + + + + + + This approach sends metrics directly to an OpenTelemetry Collector via the OTLP protocol. This gives you the most flexibility as you can configure the collector to export to multiple backends simultaneously. + + ### Configuration + + + +```bash +OTEL_TELEMETRY_COLLECTION_ENABLED=true +OTEL_EXPORT_TYPE=otlp +OTEL_EXPORT_OTLP_ENDPOINT=http://otel-collector:4318/v1/metrics +OTEL_COLLECTOR_BASIC_AUTH_USERNAME=infisical +OTEL_COLLECTOR_BASIC_AUTH_PASSWORD=infisical +OTEL_OTLP_PUSH_INTERVAL=30000 +``` + + + +Create `otel-collector-config.yaml`: + +```yaml +extensions: + health_check: + pprof: + zpages: + basicauth/server: + htpasswd: + inline: | + your_username:your_password + +receivers: + otlp: + protocols: + http: + endpoint: 0.0.0.0:4318 + auth: + authenticator: basicauth/server + prometheus: - image: prom/prometheus:latest - ports: - - "9090:9090" - volumes: - - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro - command: - - "--config.file=/etc/prometheus/prometheus.yml" + config: + scrape_configs: + - job_name: otel-collector + scrape_interval: 30s + static_configs: + - targets: [infisical-backend:9464] + metric_relabel_configs: + - action: labeldrop + regex: "service_instance_id|service_name" - grafana: - image: grafana/grafana:latest - ports: - - "3000:3000" - environment: - - GF_SECURITY_ADMIN_USER=admin - - GF_SECURITY_ADMIN_PASSWORD=admin +processors: + batch: + +exporters: + prometheus: + endpoint: "0.0.0.0:8889" + auth: + authenticator: basicauth/server + resource_to_telemetry_conversion: + enabled: true + +service: + extensions: [basicauth/server, health_check, pprof, zpages] + pipelines: + metrics: + receivers: [otlp] + processors: [batch] + exporters: [prometheus] ``` -#### Kubernetes + +Replace `your_username:your_password` with your chosen credentials. These must match the values you set in Infisical's `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` environment variables. + + + + +Create Prometheus configuration for the collector: ```yaml -# prometheus-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: prometheus -spec: - replicas: 1 - selector: - matchLabels: - app: prometheus - template: - metadata: - labels: - app: prometheus - spec: - containers: - - name: prometheus - image: prom/prometheus:latest - ports: - - containerPort: 9090 - volumeMounts: - - name: config - mountPath: /etc/prometheus - volumes: - - name: config - configMap: - name: prometheus-config +global: + scrape_interval: 30s + evaluation_interval: 30s ---- -# prometheus-service.yaml -apiVersion: v1 -kind: Service -metadata: - name: prometheus -spec: - selector: - app: prometheus - ports: - - port: 9090 - targetPort: 9090 - type: ClusterIP +scrape_configs: + - job_name: "otel-collector" + scrape_interval: 30s + static_configs: + - targets: ["otel-collector:8889"] # Adjust hostname/port based on your deployment + metrics_path: "/metrics" ``` -#### Helm + +Replace `otel-collector:8889` with the actual hostname and port where your OpenTelemetry Collector is running. This could be: -```bash -helm repo add prometheus-community https://prometheus-community.github.io/helm-charts -helm install prometheus prometheus-community/prometheus \ - --set server.config.global.scrape_interval=30s \ - --set server.config.scrape_configs[0].job_name=infisical \ - --set server.config.scrape_configs[0].static_configs[0].targets[0]=infisical-backend:9464 -``` +- **Docker Compose**: `otel-collector:8889` (service name) +- **Kubernetes**: `otel-collector.default.svc.cluster.local:8889` (service name) +- **Bare Metal**: `192.168.1.100:8889` (actual IP address) +- **Cloud**: `your-collector.example.com:8889` (domain name) + + + -## Option 2: Push-based Monitoring (OTLP) + ### Deployment Options -This approach sends metrics directly to an OpenTelemetry Collector via the OTLP protocol. This gives you the most flexibility as you can configure the collector to export to multiple backends simultaneously. + After configuring Infisical and the OpenTelemetry Collector, you'll need to deploy the collector to receive metrics from Infisical. Below are examples for different deployment environments. Choose the option that matches your infrastructure. -### Configuration + + + ```yaml + services: + otel-collector: + image: otel/opentelemetry-collector-contrib:latest + ports: + - 4318:4318 # OTLP http receiver + - 8889:8889 # Prometheus exporter metrics + volumes: + - ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro + command: + - "--config=/etc/otelcol-contrib/config.yaml" + ``` + + + ```yaml + # otel-collector-deployment.yaml + apiVersion: apps/v1 + kind: Deployment + metadata: + name: otel-collector + spec: + replicas: 1 + selector: + matchLabels: + app: otel-collector + template: + metadata: + labels: + app: otel-collector + spec: + containers: + - name: otel-collector + image: otel/opentelemetry-collector-contrib:latest + ports: + - containerPort: 4318 + - containerPort: 8889 + volumeMounts: + - name: config + mountPath: /etc/otelcol-contrib + volumes: + - name: config + configMap: + name: otel-collector-config + ``` + + + ```bash + helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts + helm install otel-collector open-telemetry/opentelemetry-collector \ + --set config.receivers.otlp.protocols.http.endpoint=0.0.0.0:4318 \ + --set config.exporters.prometheus.endpoint=0.0.0.0:8889 + ``` + + -1. **Enable OTLP export in Infisical**: - - ```bash - OTEL_TELEMETRY_COLLECTION_ENABLED=true - OTEL_EXPORT_TYPE=otlp - OTEL_EXPORT_OTLP_ENDPOINT=http://otel-collector:4318/v1/metrics - OTEL_COLLECTOR_BASIC_AUTH_USERNAME=infisical - OTEL_COLLECTOR_BASIC_AUTH_PASSWORD=infisical - OTEL_OTLP_PUSH_INTERVAL=30000 - ``` - -2. **Create OpenTelemetry Collector configuration** (`otel-collector-config.yaml`): - - ```yaml - extensions: - health_check: - pprof: - zpages: - basicauth/server: - htpasswd: - inline: | - your_username:your_password - - receivers: - otlp: - protocols: - http: - endpoint: 0.0.0.0:4318 - auth: - authenticator: basicauth/server - - prometheus: - config: - scrape_configs: - - job_name: otel-collector - scrape_interval: 30s - static_configs: - - targets: [infisical-backend:9464] - metric_relabel_configs: - - action: labeldrop - regex: "service_instance_id|service_name" - - processors: - batch: - - exporters: - prometheus: - endpoint: "0.0.0.0:8889" - auth: - authenticator: basicauth/server - resource_to_telemetry_conversion: - enabled: true - - service: - extensions: [basicauth/server, health_check, pprof, zpages] - pipelines: - metrics: - receivers: [otlp] - processors: [batch] - exporters: [prometheus] - ``` - - **Important**: Replace `your_username:your_password` with your chosen credentials. These must match the values you set in Infisical's `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` environment variables. - -3. **Create Prometheus configuration** for the collector: - - ```yaml - global: - scrape_interval: 30s - evaluation_interval: 30s - - scrape_configs: - - job_name: "otel-collector" - scrape_interval: 30s - static_configs: - - targets: ["otel-collector:8889"] # Adjust hostname/port based on your deployment - metrics_path: "/metrics" - ``` - - **Note**: Replace `otel-collector:8889` with the actual hostname and port where your OpenTelemetry Collector is running. This could be: - - - **Docker Compose**: `otel-collector:8889` (service name) - - **Kubernetes**: `otel-collector.default.svc.cluster.local:8889` (service name) - - **Bare Metal**: `192.168.1.100:8889` (actual IP address) - - **Cloud**: `your-collector.example.com:8889` (domain name) - -### Deployment Options - -#### Docker Compose - -```yaml -services: - otel-collector: - image: otel/opentelemetry-collector-contrib:latest - ports: - - 4318:4318 # OTLP http receiver - - 8889:8889 # Prometheus exporter metrics - volumes: - - ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro - command: - - "--config=/etc/otelcol-contrib/config.yaml" -``` - -#### Kubernetes - -```yaml -# otel-collector-deployment.yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: otel-collector -spec: - replicas: 1 - selector: - matchLabels: - app: otel-collector - template: - metadata: - labels: - app: otel-collector - spec: - containers: - - name: otel-collector - image: otel/opentelemetry-collector-contrib:latest - ports: - - containerPort: 4318 - - containerPort: 8889 - volumeMounts: - - name: config - mountPath: /etc/otelcol-contrib - volumes: - - name: config - configMap: - name: otel-collector-config -``` - -#### Helm - -```bash -helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts -helm install otel-collector open-telemetry/opentelemetry-collector \ - --set config.receivers.otlp.protocols.http.endpoint=0.0.0.0:4318 \ - --set config.exporters.prometheus.endpoint=0.0.0.0:8889 -``` + + ## Available Metrics @@ -327,166 +346,211 @@ Infisical exposes the following key metrics in OpenTelemetry format: These metrics track all HTTP API requests to Infisical, including request counts, latency, and errors. Use these to monitor overall API health, identify performance bottlenecks, and track usage patterns across users and machine identities. -#### Total API Requests + + + **Metric Name**: `infisical.http.server.request.count` -- **Metric Name**: `infisical.http.server.request.count` -- **Type**: Counter -- **Unit**: `{request}` -- **Description**: Total number of API requests to Infisical (covers both human users and machine identities) -- **Attributes**: - - `infisical.organization.id` (string): Organization ID - - `infisical.organization.name` (string): Organization name (e.g., "Platform Engineering Team") - - `infisical.user.id` (string, optional): User ID if human user - - `infisical.user.email` (string, optional): User email (e.g., "jane.doe@cisco.com") - - `infisical.identity.id` (string, optional): Machine identity ID - - `infisical.identity.name` (string, optional): Machine identity name (e.g., "prod-k8s-operator") - - `infisical.auth.method` (string, optional): Auth method used - - `http.request.method` (string): HTTP method (GET, POST, PUT, DELETE) - - `http.route` (string): API endpoint route pattern - - `http.response.status_code` (int): HTTP status code - - `infisical.project.id` (string, optional): Project ID - - `infisical.project.name` (string, optional): Project name - - `user_agent.original` (string, optional): User agent string - - `client.address` (string, optional): IP address + **Type**: Counter -#### Request Duration + **Unit**: `{request}` -- **Metric Name**: `infisical.http.server.request.duration` -- **Type**: Histogram -- **Unit**: `s` (seconds) -- **Description**: API request latency -- **Buckets**: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] -- **Attributes**: - - `infisical.organization.id` (string): Organization ID - - `infisical.organization.name` (string): Organization name - - `infisical.user.id` (string, optional): User ID if human user - - `infisical.user.email` (string, optional): User email - - `infisical.identity.id` (string, optional): Machine identity ID - - `infisical.identity.name` (string, optional): Machine identity name - - `http.request.method` (string): HTTP method - - `http.route` (string): API endpoint route pattern - - `http.response.status_code` (int): HTTP status code - - `infisical.project.id` (string, optional): Project ID - - `infisical.project.name` (string, optional): Project name + **Description**: Total number of API requests to Infisical (covers both human users and machine identities) -#### API Errors by Actor + **Attributes**: + - `infisical.organization.id` (string): Organization ID + - `infisical.organization.name` (string): Organization name (e.g., "Platform Engineering Team") + - `infisical.user.id` (string, optional): User ID if human user + - `infisical.user.email` (string, optional): User email (e.g., "jane.doe@cisco.com") + - `infisical.identity.id` (string, optional): Machine identity ID + - `infisical.identity.name` (string, optional): Machine identity name (e.g., "prod-k8s-operator") + - `infisical.auth.method` (string, optional): Auth method used + - `http.request.method` (string): HTTP method (GET, POST, PUT, DELETE) + - `http.route` (string): API endpoint route pattern + - `http.response.status_code` (int): HTTP status code + - `infisical.project.id` (string, optional): Project ID + - `infisical.project.name` (string, optional): Project name + - `user_agent.original` (string, optional): User agent string + - `client.address` (string, optional): IP address + -- **Metric Name**: `infisical.http.server.error.count` -- **Type**: Counter -- **Unit**: `{error}` -- **Description**: API errors grouped by actor (for identifying misconfigured services) -- **Attributes**: - - `infisical.organization.id` (string): Organization ID - - `infisical.organization.name` (string): Organization name - - `infisical.user.id` (string, optional): User ID if human - - `infisical.user.email` (string, optional): User email - - `infisical.identity.id` (string, optional): Identity ID if machine - - `infisical.identity.name` (string, optional): Identity name - - `http.route` (string): API endpoint where error occurred - - `http.request.method` (string): HTTP method - - `error.type` (string): Error category/type (client_error, server_error, auth_error, rate_limit_error, etc.) - - `infisical.project.id` (string, optional): Project ID - - `infisical.project.name` (string, optional): Project name - - `client.address` (string, optional): IP address - - `user_agent.original` (string, optional): User agent information + + **Metric Name**: `infisical.http.server.request.duration` + + **Type**: Histogram + + **Unit**: `s` (seconds) + + **Description**: API request latency + + **Buckets**: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] + + **Attributes**: + - `infisical.organization.id` (string): Organization ID + - `infisical.organization.name` (string): Organization name + - `infisical.user.id` (string, optional): User ID if human user + - `infisical.user.email` (string, optional): User email + - `infisical.identity.id` (string, optional): Machine identity ID + - `infisical.identity.name` (string, optional): Machine identity name + - `http.request.method` (string): HTTP method + - `http.route` (string): API endpoint route pattern + - `http.response.status_code` (int): HTTP status code + - `infisical.project.id` (string, optional): Project ID + - `infisical.project.name` (string, optional): Project name + + + + **Metric Name**: `infisical.http.server.error.count` + + **Type**: Counter + + **Unit**: `{error}` + + **Description**: API errors grouped by actor (for identifying misconfigured services) + + **Attributes**: + - `infisical.organization.id` (string): Organization ID + - `infisical.organization.name` (string): Organization name + - `infisical.user.id` (string, optional): User ID if human + - `infisical.user.email` (string, optional): User email + - `infisical.identity.id` (string, optional): Identity ID if machine + - `infisical.identity.name` (string, optional): Identity name + - `http.route` (string): API endpoint where error occurred + - `http.request.method` (string): HTTP method + - `error.type` (string): Error category/type (client_error, server_error, auth_error, rate_limit_error, etc.) + - `infisical.project.id` (string, optional): Project ID + - `infisical.project.name` (string, optional): Project name + - `client.address` (string, optional): IP address + - `user_agent.original` (string, optional): User agent information + + ### Secret Operations Metrics These metrics provide visibility into secret access patterns, helping you understand which secrets are being accessed, by whom, and from where. Essential for security auditing and access pattern analysis. -#### Secret Read Operations - -- **Metric Name**: `infisical.secret.read.count` -- **Type**: Counter -- **Unit**: `{operation}` -- **Description**: Number of secret read operations -- **Attributes**: - - `infisical.organization.id` (string): Organization ID - - `infisical.organization.name` (string): Organization name - - `infisical.project.id` (string): Project ID - - `infisical.project.name` (string): Project name (e.g., "payment-service-secrets") - - `infisical.environment` (string): Environment (dev, staging, prod) - - `infisical.secret.path` (string): Path to secrets (e.g., "/microservice-a/database") - - `infisical.secret.name` (string, optional): Name of secret - - `infisical.user.id` (string, optional): User ID if human - - `infisical.user.email` (string, optional): User email - - `infisical.identity.id` (string, optional): Machine identity ID - - `infisical.identity.name` (string, optional): Machine identity name - - `user_agent.original` (string, optional): User agent/SDK information - - `client.address` (string, optional): IP address + + + **Metric Name**: `infisical.secret.read.count` + + **Type**: Counter + + **Unit**: `{operation}` + + **Description**: Number of secret read operations + + **Attributes**: + - `infisical.organization.id` (string): Organization ID + - `infisical.organization.name` (string): Organization name + - `infisical.project.id` (string): Project ID + - `infisical.project.name` (string): Project name (e.g., "payment-service-secrets") + - `infisical.environment` (string): Environment (dev, staging, prod) + - `infisical.secret.path` (string): Path to secrets (e.g., "/microservice-a/database") + - `infisical.secret.name` (string, optional): Name of secret + - `infisical.user.id` (string, optional): User ID if human + - `infisical.user.email` (string, optional): User email + - `infisical.identity.id` (string, optional): Machine identity ID + - `infisical.identity.name` (string, optional): Machine identity name + - `user_agent.original` (string, optional): User agent/SDK information + - `client.address` (string, optional): IP address + + ### Authentication Metrics These metrics track authentication attempts and outcomes, enabling you to monitor login success rates, detect potential security threats, and identify authentication issues. -#### Login Attempts - -- **Metric Name**: `infisical.auth.attempt.count` -- **Type**: Counter -- **Unit**: `{attempt}` -- **Description**: Authentication attempts (both successful and failed) -- **Attributes**: - - `infisical.organization.id` (string): Organization ID - - `infisical.organization.name` (string): Organization name - - `infisical.user.id` (string, optional): User ID if human (if identifiable) - - `infisical.user.email` (string, optional): User email (if identifiable) - - `infisical.identity.id` (string, optional): Identity ID if machine (if identifiable) - - `infisical.identity.name` (string, optional): Identity name (if identifiable) - - `infisical.auth.method` (string): Authentication method attempted - - `infisical.auth.result` (string): success or failure - - `error.type` (string, optional): Reason for failure if failed (invalid_credentials, expired_token, invalid_token, etc.) - - `client.address` (string): IP address - - `user_agent.original` (string, optional): User agent/client information - - `infisical.auth.attempt.username` (string, optional): Attempted username/email (if available) - -### Legacy Metrics - -These metrics are from the previous instrumentation and may be deprecated in future versions. Consider migrating to the new Core API Metrics for more comprehensive observability. - -- `API_latency` - API request latency histogram in milliseconds (Labels: `route`, `method`, `statusCode`) -- `API_errors` - API error count histogram (Labels: `route`, `method`, `type`, `name`) + + + **Metric Name**: `infisical.auth.attempt.count` + + **Type**: Counter + + **Unit**: `{attempt}` + + **Description**: Authentication attempts (both successful and failed) + + **Attributes**: + - `infisical.organization.id` (string): Organization ID + - `infisical.organization.name` (string): Organization name + - `infisical.user.id` (string, optional): User ID if human (if identifiable) + - `infisical.user.email` (string, optional): User email (if identifiable) + - `infisical.identity.id` (string, optional): Identity ID if machine (if identifiable) + - `infisical.identity.name` (string, optional): Identity name (if identifiable) + - `infisical.auth.method` (string): Authentication method attempted + - `infisical.auth.result` (string): success or failure + - `error.type` (string, optional): Reason for failure if failed (invalid_credentials, expired_token, invalid_token, etc.) + - `client.address` (string): IP address + - `user_agent.original` (string, optional): User agent/client information + - `infisical.auth.attempt.username` (string, optional): Attempted username/email (if available) + + ### Integration & Secret Sync Metrics These metrics monitor secret synchronization operations between Infisical and external systems, helping you track sync health, identify integration failures, and troubleshoot connectivity issues. -- `integration_secret_sync_errors` - Integration secret sync error count + + + Integration secret sync error count - - **Labels**: `version`, `integration`, `integrationId`, `type`, `status`, `name`, `projectId` - - **Example**: Monitor integration sync failures across different services + - **Labels**: `version`, `integration`, `integrationId`, `type`, `status`, `name`, `projectId` + - **Example**: Monitor integration sync failures across different services + -- `secret_sync_sync_secrets_errors` - Secret sync operation error count + + Secret sync operation error count - - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` - - **Example**: Track secret sync failures to external systems + - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` + - **Example**: Track secret sync failures to external systems + -- `secret_sync_import_secrets_errors` - Secret import operation error count + + Secret import operation error count - - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` - - **Example**: Monitor secret import failures + - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` + - **Example**: Monitor secret import failures + -- `secret_sync_remove_secrets_errors` - Secret removal operation error count - - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` - - **Example**: Track secret removal operation failures + + Secret removal operation error count + + - **Labels**: `version`, `destination`, `syncId`, `projectId`, `type`, `status`, `name` + - **Example**: Track secret removal operation failures + + ### System Metrics These low-level HTTP metrics are automatically collected by OpenTelemetry's instrumentation layer, providing baseline performance data for all HTTP traffic. -- `http_server_duration` - HTTP server request duration metrics (histogram buckets, count, sum) -- `http_client_duration` - HTTP client request duration metrics (histogram buckets, count, sum) + + + HTTP server request duration metrics (histogram buckets, count, sum) + + + + HTTP client request duration metrics (histogram buckets, count, sum) + + ## Troubleshooting -### Common Issues + + If your metrics are not showing up in Prometheus or your monitoring system, check the following: -1. **Metrics not appearing**: + - Verify `OTEL_TELEMETRY_COLLECTION_ENABLED=true` is set in your Infisical environment variables + - Ensure the correct `OTEL_EXPORT_TYPE` is set (`prometheus` or `otlp`) + - Check network connectivity between Infisical and your monitoring services (Prometheus or OTLP collector) + - For pull-based monitoring: Verify port 9464 is exposed and accessible + - For push-based monitoring: Verify the OTLP endpoint URL is correct and reachable + - Check Infisical backend logs for any errors related to metrics export + - - Check if `OTEL_TELEMETRY_COLLECTION_ENABLED=true` - - Verify the correct `OTEL_EXPORT_TYPE` is set - - Check network connectivity between services + + If you're experiencing authentication errors with the OpenTelemetry Collector: -2. **Authentication errors**: - - - Verify basic auth credentials in OTLP configuration - - Check if credentials match between Infisical and collector + - Verify basic auth credentials in your OTLP configuration match between Infisical and the collector + - Check that `OTEL_COLLECTOR_BASIC_AUTH_USERNAME` and `OTEL_COLLECTOR_BASIC_AUTH_PASSWORD` match the credentials in your `otel-collector-config.yaml` + - Ensure the htpasswd format in the collector configuration is correct + - Test the collector endpoint manually using curl with the same credentials to verify they work + diff --git a/frontend/src/components/features/WishForm.tsx b/frontend/src/components/features/WishForm.tsx index 118900bc9..28107809a 100644 --- a/frontend/src/components/features/WishForm.tsx +++ b/frontend/src/components/features/WishForm.tsx @@ -35,23 +35,16 @@ export const WishForm = () => { const [isOpen, setIsOpen] = useToggle(false); const createWish = async (data: TFormData) => { - try { - await mutateAsync({ - text: data.text - }); + await mutateAsync({ + text: data.text + }); - createNotification({ - text: "Your wish has been sent to the Infisical team!", - type: "success" - }); + createNotification({ + text: "Your wish has been sent to the Infisical team!", + type: "success" + }); - setIsOpen.off(); - } catch { - createNotification({ - text: "An error occured while sending your wish to the Infisical team.", - type: "error" - }); - } + setIsOpen.off(); }; return ( diff --git a/frontend/src/components/mfa/TotpRegistration.tsx b/frontend/src/components/mfa/TotpRegistration.tsx index 0b79bfd98..bcb0d3691 100644 --- a/frontend/src/components/mfa/TotpRegistration.tsx +++ b/frontend/src/components/mfa/TotpRegistration.tsx @@ -25,27 +25,20 @@ const TotpRegistration = ({ onComplete, shouldCenterQr }: Props) => { const handleTotpVerify = async (event: React.FormEvent) => { event.preventDefault(); - try { - const result = await verifyUserTotp({ - totp - }); + const result = await verifyUserTotp({ + totp + }); - createNotification({ - text: "Successfully configured mobile authenticator", - type: "success" - }); + createNotification({ + text: "Successfully configured mobile authenticator", + type: "success" + }); - if (result.recoveryCodes && result.recoveryCodes.length > 0) { - setRecoveryCodes(result.recoveryCodes); - setShowRecoveryModal(true); - } else if (onComplete) { - onComplete(); - } - } catch { - createNotification({ - text: "Failed to verify TOTP code", - type: "error" - }); + if (result.recoveryCodes && result.recoveryCodes.length > 0) { + setRecoveryCodes(result.recoveryCodes); + setShowRecoveryModal(true); + } else if (onComplete) { + onComplete(); } }; diff --git a/frontend/src/components/organization/CreateOrgModal/CreateOrgModal.tsx b/frontend/src/components/organization/CreateOrgModal/CreateOrgModal.tsx index 23c3fe6a5..84931f84a 100644 --- a/frontend/src/components/organization/CreateOrgModal/CreateOrgModal.tsx +++ b/frontend/src/components/organization/CreateOrgModal/CreateOrgModal.tsx @@ -43,35 +43,27 @@ export const CreateOrgModal: FC = ({ isOpen, onClose }) => const { mutateAsync: selectOrg } = useSelectOrganization(); const onFormSubmit = async ({ name }: FormData) => { - try { - const organization = await createOrg({ - name - }); + const organization = await createOrg({ + name + }); - await selectOrg({ - organizationId: organization.id - }); + await selectOrg({ + organizationId: organization.id + }); - createNotification({ - text: "Successfully created organization", - type: "success" - }); + createNotification({ + text: "Successfully created organization", + type: "success" + }); - navigate({ - to: "/organization/projects" - }); + navigate({ + to: "/organization/projects" + }); - localStorage.setItem("orgData.id", organization.id); + localStorage.setItem("orgData.id", organization.id); - reset(); - onClose(); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to created organization", - type: "error" - }); - } + reset(); + onClose(); }; return ( diff --git a/frontend/src/components/pki-syncs/DeletePkiSyncModal.tsx b/frontend/src/components/pki-syncs/DeletePkiSyncModal.tsx index e465d3f34..c01efcbf2 100644 --- a/frontend/src/components/pki-syncs/DeletePkiSyncModal.tsx +++ b/frontend/src/components/pki-syncs/DeletePkiSyncModal.tsx @@ -20,28 +20,19 @@ export const DeletePkiSyncModal = ({ isOpen, onOpenChange, pkiSync, onComplete } const handleDeletePkiSync = async () => { const destinationName = PKI_SYNC_MAP[destination].name; - try { - await deleteSync.mutateAsync({ - syncId, - projectId, - destination - }); + await deleteSync.mutateAsync({ + syncId, + projectId, + destination + }); - createNotification({ - text: `Successfully deleted ${destinationName} PKI Sync`, - type: "success" - }); + createNotification({ + text: `Successfully deleted ${destinationName} PKI Sync`, + type: "success" + }); - if (onComplete) onComplete(); - onOpenChange(false); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to delete ${destinationName} PKI Sync`, - type: "error" - }); - } + if (onComplete) onComplete(); + onOpenChange(false); }; return ( diff --git a/frontend/src/components/pki-syncs/PkiSyncImportCertificatesModal.tsx b/frontend/src/components/pki-syncs/PkiSyncImportCertificatesModal.tsx index 33b06dbe4..43192c5e3 100644 --- a/frontend/src/components/pki-syncs/PkiSyncImportCertificatesModal.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncImportCertificatesModal.tsx @@ -21,27 +21,18 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => { const triggerImportCertificates = useTriggerPkiSyncImportCertificates(); const handleTriggerImportCertificates = async () => { - try { - await triggerImportCertificates.mutateAsync({ - syncId, - destination, - projectId - }); + await triggerImportCertificates.mutateAsync({ + syncId, + destination, + projectId + }); - createNotification({ - text: `Successfully triggered certificate import for ${destinationName} Sync`, - type: "success" - }); + createNotification({ + text: `Successfully triggered certificate import for ${destinationName} Sync`, + type: "success" + }); - onComplete(); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to trigger certificate import for ${destinationName} Sync`, - type: "error" - }); - } + onComplete(); }; return ( diff --git a/frontend/src/components/pki-syncs/PkiSyncRemoveCertificatesModal.tsx b/frontend/src/components/pki-syncs/PkiSyncRemoveCertificatesModal.tsx index f17855289..cee845381 100644 --- a/frontend/src/components/pki-syncs/PkiSyncRemoveCertificatesModal.tsx +++ b/frontend/src/components/pki-syncs/PkiSyncRemoveCertificatesModal.tsx @@ -21,27 +21,18 @@ const Content = ({ pkiSync, onComplete }: ContentProps) => { const triggerRemoveCertificates = useTriggerPkiSyncRemoveCertificates(); const handleTriggerRemoveCertificates = async () => { - try { - await triggerRemoveCertificates.mutateAsync({ - syncId, - destination, - projectId - }); + await triggerRemoveCertificates.mutateAsync({ + syncId, + destination, + projectId + }); - createNotification({ - text: `Successfully triggered certificate removal for ${destinationName} Sync`, - type: "success" - }); + createNotification({ + text: `Successfully triggered certificate removal for ${destinationName} Sync`, + type: "success" + }); - onComplete(); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to trigger certificate removal for ${destinationName} Sync`, - type: "error" - }); - } + onComplete(); }; return ( diff --git a/frontend/src/components/pki-syncs/forms/CreatePkiSyncForm.tsx b/frontend/src/components/pki-syncs/forms/CreatePkiSyncForm.tsx index 6ffb2f859..1dee6eaa7 100644 --- a/frontend/src/components/pki-syncs/forms/CreatePkiSyncForm.tsx +++ b/frontend/src/components/pki-syncs/forms/CreatePkiSyncForm.tsx @@ -86,14 +86,8 @@ export const CreatePkiSyncForm = ({ destination, onComplete, onCancel, initialDa type: "success" }); onComplete(pkiSync); - } catch (err: Error | unknown) { - console.error("PKI sync creation failed:", err); + } catch { setShowConfirmation(false); - createNotification({ - title: `Failed to create ${destinationName} Certificate Sync`, - text: err instanceof Error ? err.message : "An unknown error occurred", - type: "error" - }); } }; diff --git a/frontend/src/components/pki-syncs/forms/EditPkiSyncForm.tsx b/frontend/src/components/pki-syncs/forms/EditPkiSyncForm.tsx index 137e0d6de..9041d32f2 100644 --- a/frontend/src/components/pki-syncs/forms/EditPkiSyncForm.tsx +++ b/frontend/src/components/pki-syncs/forms/EditPkiSyncForm.tsx @@ -42,28 +42,19 @@ export const EditPkiSyncForm = ({ pkiSync, fields, onComplete }: Props) => { }); const onSubmit = async ({ connection, ...formData }: TUpdatePkiSyncForm) => { - try { - const updatedPkiSync = await updatePkiSync.mutateAsync({ - syncId: pkiSync.id, - ...formData, - connectionId: connection.id, - projectId: pkiSync.projectId, - destination: pkiSync.destination - }); + const updatedPkiSync = await updatePkiSync.mutateAsync({ + syncId: pkiSync.id, + ...formData, + connectionId: connection.id, + projectId: pkiSync.projectId, + destination: pkiSync.destination + }); - createNotification({ - text: `Successfully updated ${destinationName} PKI Sync`, - type: "success" - }); - onComplete(updatedPkiSync); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${destinationName} PKI Sync`, - text: err.message, - type: "error" - }); - } + createNotification({ + text: `Successfully updated ${destinationName} PKI Sync`, + type: "success" + }); + onComplete(updatedPkiSync); }; let Component: ReactNode; diff --git a/frontend/src/components/project/ProjectOverviewChangeSection.tsx b/frontend/src/components/project/ProjectOverviewChangeSection.tsx index 548fbd1e3..455b0806a 100644 --- a/frontend/src/components/project/ProjectOverviewChangeSection.tsx +++ b/frontend/src/components/project/ProjectOverviewChangeSection.tsx @@ -56,30 +56,22 @@ export const ProjectOverviewChangeSection = ({ showSlugField = false }: Props) = }, [currentProject, showSlugField]); const onFormSubmit = async (data: BaseFormData | FormDataWithSlug) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await mutateAsync({ - projectId: currentProject.id, - newProjectName: data.name, - newProjectDescription: data.description, - ...(showSlugField && - "slug" in data && { - newSlug: data.slug !== currentProject.slug ? data.slug : undefined - }) - }); + await mutateAsync({ + projectId: currentProject.id, + newProjectName: data.name, + newProjectDescription: data.description, + ...(showSlugField && + "slug" in data && { + newSlug: data.slug !== currentProject.slug ? data.slug : undefined + }) + }); - createNotification({ - text: "Successfully updated project overview", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update project overview", - type: "error" - }); - } + createNotification({ + text: "Successfully updated project overview", + type: "success" + }); }; return ( diff --git a/frontend/src/components/projects/NewProjectModal.tsx b/frontend/src/components/projects/NewProjectModal.tsx index d71e7b957..663d6a0b7 100644 --- a/frontend/src/components/projects/NewProjectModal.tsx +++ b/frontend/src/components/projects/NewProjectModal.tsx @@ -141,29 +141,24 @@ const NewProjectForm = ({ onOpenChange }: NewProjectFormProps) => { // type check if (!currentOrg) return; if (!user) return; - try { - const { - data: { project } - } = await createWs.mutateAsync({ - projectName: name, - projectDescription: description, - kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined, - template, - type - }); - await refetchWorkspaces(); + const { + data: { project } + } = await createWs.mutateAsync({ + projectName: name, + projectDescription: description, + kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined, + template, + type + }); + await refetchWorkspaces(); - createNotification({ text: "Project created", type: "success" }); - reset(); - onOpenChange(false); - navigate({ - to: getProjectHomePage(project.type, project.environments), - params: { projectId: project.id } - }); - } catch (err) { - console.error(err); - createNotification({ text: "Failed to create project", type: "error" }); - } + createNotification({ text: "Project created", type: "success" }); + reset(); + onOpenChange(false); + navigate({ + to: getProjectHomePage(project.type, project.environments), + params: { projectId: project.id } + }); }; const onSubmit = handleSubmit((data) => { return onCreateProject(data); diff --git a/frontend/src/components/secret-rotations-v2/DeleteSecretRotationV2Modal.tsx b/frontend/src/components/secret-rotations-v2/DeleteSecretRotationV2Modal.tsx index 20b08eb49..524c66933 100644 --- a/frontend/src/components/secret-rotations-v2/DeleteSecretRotationV2Modal.tsx +++ b/frontend/src/components/secret-rotations-v2/DeleteSecretRotationV2Modal.tsx @@ -37,29 +37,22 @@ export const DeleteSecretRotationV2Modal = ({ const handleDeleteSecretRotation = async () => { const rotationType = SECRET_ROTATION_MAP[type].name; - try { - await deleteSecretRotation.mutateAsync({ - rotationId, - type, - revokeGeneratedCredentials, - deleteSecrets, - projectId, - secretPath: folder.path - }); + await deleteSecretRotation.mutateAsync({ + rotationId, + type, + revokeGeneratedCredentials, + deleteSecrets, + projectId, + secretPath: folder.path + }); - createNotification({ - text: `Successfully deleted ${rotationType} Rotation`, - type: "success" - }); + createNotification({ + text: `Successfully deleted ${rotationType} Rotation`, + type: "success" + }); - if (onComplete) onComplete(); - onOpenChange(false); - } catch { - createNotification({ - text: `Failed to delete ${rotationType} Rotation`, - type: "error" - }); - } + if (onComplete) onComplete(); + onOpenChange(false); }; return ( diff --git a/frontend/src/components/secret-rotations-v2/RotateSecretRotationV2Modal.tsx b/frontend/src/components/secret-rotations-v2/RotateSecretRotationV2Modal.tsx index e2c931d49..7ad9611be 100644 --- a/frontend/src/components/secret-rotations-v2/RotateSecretRotationV2Modal.tsx +++ b/frontend/src/components/secret-rotations-v2/RotateSecretRotationV2Modal.tsx @@ -22,28 +22,19 @@ const Content = ({ secretRotation, onComplete }: ContentProps) => { const rotationType = SECRET_ROTATION_MAP[type].name; const handleRotateSecrets = async () => { - try { - await rotateSecrets.mutateAsync({ - rotationId, - type, - projectId, - secretPath: folder.path - }); + await rotateSecrets.mutateAsync({ + rotationId, + type, + projectId, + secretPath: folder.path + }); - createNotification({ - text: `Successfully rotated ${rotationType} secrets`, - type: "success" - }); + createNotification({ + text: `Successfully rotated ${rotationType} secrets`, + type: "success" + }); - onComplete(); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to rotate ${rotationType} secrets`, - type: "error" - }); - } + onComplete(); }; return ( diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx index 9887da026..320793ed1 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2Form.tsx @@ -120,21 +120,13 @@ export const SecretRotationV2Form = ({ environment: environment.slug, projectId: currentProject.id }); - try { - const rotation = await mutation; + const rotation = await mutation; - createNotification({ - text: `Successfully ${secretRotation ? "updated" : "created"} ${rotationType} Rotation`, - type: "success" - }); - onComplete(rotation); - } catch (err: any) { - createNotification({ - title: `Failed to ${secretRotation ? "update" : "create"} ${rotationType} Rotation`, - text: err.message, - type: "error" - }); - } + createNotification({ + text: `Successfully ${secretRotation ? "updated" : "created"} ${rotationType} Rotation`, + type: "success" + }); + onComplete(rotation); }; const handlePrev = () => { diff --git a/frontend/src/components/secret-scanning/DeleteSecretScanningDataSourceModal.tsx b/frontend/src/components/secret-scanning/DeleteSecretScanningDataSourceModal.tsx index 9cf918dfb..091de4cc0 100644 --- a/frontend/src/components/secret-scanning/DeleteSecretScanningDataSourceModal.tsx +++ b/frontend/src/components/secret-scanning/DeleteSecretScanningDataSourceModal.tsx @@ -28,26 +28,19 @@ export const DeleteSecretScanningDataSourceModal = ({ const handleDeleteDataSource = async () => { const dataSourceType = SECRET_SCANNING_DATA_SOURCE_MAP[type].name; - try { - await deleteDataSource.mutateAsync({ - dataSourceId, - type, - projectId - }); + await deleteDataSource.mutateAsync({ + dataSourceId, + type, + projectId + }); - createNotification({ - text: `Successfully deleted ${dataSourceType} Data Source`, - type: "success" - }); + createNotification({ + text: `Successfully deleted ${dataSourceType} Data Source`, + type: "success" + }); - if (onComplete) onComplete(); - onOpenChange(false); - } catch { - createNotification({ - text: `Failed to delete ${dataSourceType} Data Source`, - type: "error" - }); - } + if (onComplete) onComplete(); + onOpenChange(false); }; return ( diff --git a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx index 4d5ccdc79..563f82d46 100644 --- a/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx +++ b/frontend/src/components/secret-scanning/forms/SecretScanningDataSourceForm.tsx @@ -73,21 +73,13 @@ export const SecretScanningDataSourceForm = ({ connectionId: connection?.id, projectId: currentProject.id }); - try { - const source = await mutation; + const source = await mutation; - createNotification({ - text: `Successfully ${source ? "updated" : "created"} ${sourceType} Data Source`, - type: "success" - }); - onComplete(source); - } catch (err: any) { - createNotification({ - title: `Failed to ${dataSource ? "update" : "create"} ${sourceType} Data Source`, - text: err.message, - type: "error" - }); - } + createNotification({ + text: `Successfully ${source ? "updated" : "created"} ${sourceType} Data Source`, + type: "success" + }); + onComplete(source); }; const handlePrev = () => { diff --git a/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx b/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx index 2b3903a9b..de8b2e79d 100644 --- a/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx +++ b/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx @@ -23,29 +23,20 @@ export const DeleteSecretSyncModal = ({ isOpen, onOpenChange, secretSync, onComp const handleDeleteSecretSync = async () => { const destinationName = SECRET_SYNC_MAP[destination].name; - try { - await deleteSync.mutateAsync({ - syncId, - destination, - removeSecrets, - projectId - }); + await deleteSync.mutateAsync({ + syncId, + destination, + removeSecrets, + projectId + }); - createNotification({ - text: `Successfully removed ${destinationName} Sync`, - type: "success" - }); + createNotification({ + text: `Successfully removed ${destinationName} Sync`, + type: "success" + }); - if (onComplete) onComplete(); - onOpenChange(false); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to remove ${destinationName} Sync`, - type: "error" - }); - } + if (onComplete) onComplete(); + onOpenChange(false); }; return ( diff --git a/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx b/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx index c1b21a771..656faffeb 100644 --- a/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx @@ -51,28 +51,19 @@ const Content = ({ secretSync, onComplete }: ContentProps) => { const triggerImportSecrets = useTriggerSecretSyncImportSecrets(); const handleTriggerImportSecrets = async ({ importBehavior }: TFormData) => { - try { - await triggerImportSecrets.mutateAsync({ - syncId, - destination, - importBehavior, - projectId - }); + await triggerImportSecrets.mutateAsync({ + syncId, + destination, + importBehavior, + projectId + }); - createNotification({ - text: `Successfully triggered secret import for ${destinationName} Sync`, - type: "success" - }); + createNotification({ + text: `Successfully triggered secret import for ${destinationName} Sync`, + type: "success" + }); - onComplete(); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to trigger secret import for ${destinationName} Sync`, - type: "error" - }); - } + onComplete(); }; return ( diff --git a/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx b/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx index 718392b92..9c9c0e659 100644 --- a/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx @@ -21,27 +21,18 @@ const Content = ({ secretSync, onComplete }: ContentProps) => { const triggerSyncImport = useTriggerSecretSyncRemoveSecrets(); const handleTriggerRemoveSecrets = async () => { - try { - await triggerSyncImport.mutateAsync({ - syncId, - destination, - projectId - }); + await triggerSyncImport.mutateAsync({ + syncId, + destination, + projectId + }); - createNotification({ - text: `Successfully triggered secret removal for ${destinationName} Sync`, - type: "success" - }); + createNotification({ + text: `Successfully triggered secret removal for ${destinationName} Sync`, + type: "success" + }); - onComplete(); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to trigger secret removal for ${destinationName} Sync`, - type: "error" - }); - } + onComplete(); }; return ( diff --git a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx index ebad86cbd..21a69b163 100644 --- a/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx +++ b/frontend/src/components/secret-syncs/forms/CreateSecretSyncForm.tsx @@ -88,14 +88,8 @@ export const CreateSecretSyncForm = ({ type: "success" }); onComplete(secretSync); - } catch (err: any) { - console.error(err); + } catch { setShowConfirmation(false); - createNotification({ - title: `Failed to add ${destinationName} Sync`, - text: err.message, - type: "error" - }); } }; diff --git a/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx index 2085afe9c..207b72cd8 100644 --- a/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx +++ b/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx @@ -58,29 +58,20 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) => const performUpdate = useCallback( async (formData: TSecretSyncForm) => { - try { - const { environment, connection, ...updateData } = formData; - const updatedSecretSync = await updateSecretSync.mutateAsync({ - syncId: secretSync.id, - ...updateData, - environment: environment?.slug, - connectionId: connection.id, - projectId: secretSync.projectId - }); + const { environment, connection, ...updateData } = formData; + const updatedSecretSync = await updateSecretSync.mutateAsync({ + syncId: secretSync.id, + ...updateData, + environment: environment?.slug, + connectionId: connection.id, + projectId: secretSync.projectId + }); - createNotification({ - text: `Successfully updated ${destinationName} Sync`, - type: "success" - }); - onComplete(updatedSecretSync); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${destinationName} Sync`, - text: err.message, - type: "error" - }); - } + createNotification({ + text: `Successfully updated ${destinationName} Sync`, + type: "success" + }); + onComplete(updatedSecretSync); }, [updateSecretSync, secretSync.id, secretSync.projectId, destinationName, onComplete] ); diff --git a/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx b/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx index 3c38926af..97e74cae4 100644 --- a/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx +++ b/frontend/src/components/tags/CreateTagModal/CreateTagModal.tsx @@ -130,26 +130,18 @@ export const CreateTagModal = ({ isOpen, onToggle, append, currentSecret }: Prop }, [isOpen]); const onFormSubmit = async ({ slug, color }: FormData) => { - try { - const data = await createWsTag({ - projectId, - tagColor: color, - tagSlug: slug - }); - append(data); - onToggle(false); - reset(); - createNotification({ - text: "Successfully created a tag", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to create a tag", - type: "error" - }); - } + const data = await createWsTag({ + projectId, + tagColor: color, + tagSlug: slug + }); + append(data); + onToggle(false); + reset(); + createNotification({ + text: "Successfully created a tag", + type: "success" + }); }; return ( diff --git a/frontend/src/config/env.ts b/frontend/src/config/env.ts index 63446c95f..c8100bd16 100644 --- a/frontend/src/config/env.ts +++ b/frontend/src/config/env.ts @@ -26,6 +26,9 @@ export const envConfig = { import.meta.env.VITE_TELEMETRY_CAPTURING_ENABLED === true ); }, + get ACME_FEATURE_ENABLED() { + return window?.__INFISICAL_RUNTIME_ENV__?.ACME_FEATURE_ENABLED ?? false; + }, get PLATFORM_VERSION() { return import.meta.env.VITE_INFISICAL_PLATFORM_VERSION; diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index 0236113eb..b6fd85f44 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -129,7 +129,8 @@ export enum ProjectPermissionCertificateProfileActions { Create = "create", Edit = "edit", Delete = "delete", - IssueCert = "issue-cert" + IssueCert = "issue-cert", + RevealAcmeEabSecret = "reveal-acme-eab-secret" } export enum ProjectPermissionSecretRotationActions { diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts index 30b80cb70..a6de7ac8c 100644 --- a/frontend/src/global.d.ts +++ b/frontend/src/global.d.ts @@ -7,6 +7,7 @@ declare global { POSTHOG_API_KEY?: string; INTERCOM_ID?: string; TELEMETRY_CAPTURING_ENABLED: string; + ACME_FEATURE_ENABLED?: boolean; }; } } diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index 1d0d7bec3..b27da2441 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -131,7 +131,7 @@ export const APP_CONNECTION_MAP: Record< image: "Laravel Forge.png", size: 65 }, - [AppConnection.Chef]: { name: "Chef", image: "Chef.png" } + [AppConnection.Chef]: { name: "Chef", image: "Chef.png", enterprise: true } }; export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => { diff --git a/frontend/src/hooks/api/ca/types.ts b/frontend/src/hooks/api/ca/types.ts index 696e72494..31d35e904 100644 --- a/frontend/src/hooks/api/ca/types.ts +++ b/frontend/src/hooks/api/ca/types.ts @@ -159,8 +159,8 @@ export type TCreateCertificateDTO = { ttl: string; // string compatible with ms notBefore?: string; notAfter?: string; - keyUsages: CertKeyUsage[]; - extendedKeyUsages: CertExtendedKeyUsage[]; + keyUsages: string[]; + extendedKeyUsages: string[]; }; export type TCreateCertificateResponse = { diff --git a/frontend/src/hooks/api/certificateProfiles/queries.tsx b/frontend/src/hooks/api/certificateProfiles/queries.tsx index 19859b02f..1e0fe3b9b 100644 --- a/frontend/src/hooks/api/certificateProfiles/queries.tsx +++ b/frontend/src/hooks/api/certificateProfiles/queries.tsx @@ -10,7 +10,8 @@ import { TGetProfileCertificatesDTO, TGetProfileMetricsDTO, TListCertificateProfilesDTO, - TProfileCertificate + TProfileCertificate, + TRevealAcmeEabSecretDTO } from "./types"; export const certificateProfileKeys = { @@ -41,6 +42,11 @@ export const certificateProfileKeys = { "metrics", profileId, params + ], + revealAcmeEabSecret: (profileId: string) => [ + "certificate-profiles", + "reveal-acme-eab-secret", + profileId ] }; @@ -112,6 +118,20 @@ export const useGetCertificateProfileBySlug = ({ }); }; +export const useRevealAcmeEabSecret = ({ profileId }: TRevealAcmeEabSecretDTO) => { + return useQuery({ + queryKey: certificateProfileKeys.revealAcmeEabSecret(profileId), + queryFn: async () => { + const { data } = await apiRequest.get<{ + eabKid: string; + eabSecret: string; + }>(`/api/v1/pki/certificate-profiles/${profileId}/acme/eab-secret/reveal`); + return data; + }, + enabled: Boolean(profileId) + }); +}; + export const useGetProfileCertificates = ({ profileId, offset = 0, diff --git a/frontend/src/hooks/api/certificateProfiles/types.ts b/frontend/src/hooks/api/certificateProfiles/types.ts index f3584b12d..c2b38e8e8 100644 --- a/frontend/src/hooks/api/certificateProfiles/types.ts +++ b/frontend/src/hooks/api/certificateProfiles/types.ts @@ -5,7 +5,7 @@ export type TCertificateProfile = { certificateTemplateId: string; slug: string; description?: string; - enrollmentType: "api" | "est"; + enrollmentType: "api" | "est" | "acme"; estConfigId?: string; apiConfigId?: string; createdAt: string; @@ -36,6 +36,10 @@ export type TCertificateProfileWithDetails = TCertificateProfile & { autoRenew: boolean; renewBeforeDays?: number; }; + acmeConfig?: { + id: string; + directoryUrl: string; + }; }; export type TCreateCertificateProfileDTO = { @@ -44,7 +48,7 @@ export type TCreateCertificateProfileDTO = { certificateTemplateId: string; slug: string; description?: string; - enrollmentType: "api" | "est"; + enrollmentType: "api" | "est" | "acme"; estConfig?: { disableBootstrapCaValidation?: boolean; passphrase: string; @@ -54,6 +58,7 @@ export type TCreateCertificateProfileDTO = { autoRenew?: boolean; renewBeforeDays?: number; }; + acmeConfig?: unknown; }; export type TUpdateCertificateProfileDTO = { @@ -69,6 +74,7 @@ export type TUpdateCertificateProfileDTO = { autoRenew?: boolean; renewBeforeDays?: number; }; + acmeConfig?: unknown; }; export type TDeleteCertificateProfileDTO = { @@ -81,7 +87,7 @@ export type TListCertificateProfilesDTO = { offset?: number; search?: string; includeConfigs?: boolean; - enrollmentType?: "api" | "est"; + enrollmentType?: "api" | "est" | "acme"; }; export type TGetCertificateProfileByIdDTO = { @@ -93,6 +99,10 @@ export type TGetCertificateProfileBySlugDTO = { slug: string; }; +export type TRevealAcmeEabSecretDTO = { + profileId: string; +}; + export type TProfileCertificate = { id: string; serialNumber: string; diff --git a/frontend/src/hooks/api/certificateTemplates/mutations.tsx b/frontend/src/hooks/api/certificateTemplates/mutations.tsx index 0355d9d87..998194ddc 100644 --- a/frontend/src/hooks/api/certificateTemplates/mutations.tsx +++ b/frontend/src/hooks/api/certificateTemplates/mutations.tsx @@ -90,7 +90,12 @@ export const useCreateCertTemplateV2 = () => { return data.certificateTemplate; }, onSuccess: (_, { projectId }) => { - queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) }); + queryClient.invalidateQueries({ + predicate: (query) => { + const [firstKey, queryProjectId] = query.queryKey; + return firstKey === "list-template" && queryProjectId === projectId; + } + }); } }); }; @@ -107,7 +112,12 @@ export const useUpdateCertTemplateV2 = () => { return data.certificateTemplate; }, onSuccess: (_, { projectId }) => { - queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) }); + queryClient.invalidateQueries({ + predicate: (query) => { + const [firstKey, queryProjectId] = query.queryKey; + return firstKey === "list-template" && queryProjectId === projectId; + } + }); } }); }; @@ -127,7 +137,12 @@ export const useDeleteCertTemplateV2 = () => { return data.certificateTemplate; }, onSuccess: (_, { projectId }) => { - queryClient.invalidateQueries({ queryKey: certTemplateKeys.listTemplates({ projectId }) }); + queryClient.invalidateQueries({ + predicate: (query) => { + const [firstKey, queryProjectId] = query.queryKey; + return firstKey === "list-template" && queryProjectId === projectId; + } + }); } }); }; diff --git a/frontend/src/hooks/api/secrets/types.ts b/frontend/src/hooks/api/secrets/types.ts index 4f912fad2..f4bf2da98 100644 --- a/frontend/src/hooks/api/secrets/types.ts +++ b/frontend/src/hooks/api/secrets/types.ts @@ -109,6 +109,7 @@ export type SecretVersions = { actorType?: string | null; name?: string | null; membershipId?: string | null; + groupId?: string | null; } | null; }; diff --git a/frontend/src/hooks/api/workflowIntegrations/types.ts b/frontend/src/hooks/api/workflowIntegrations/types.ts index 668850124..7c51ae259 100644 --- a/frontend/src/hooks/api/workflowIntegrations/types.ts +++ b/frontend/src/hooks/api/workflowIntegrations/types.ts @@ -86,6 +86,8 @@ export type ProjectWorkflowIntegrationConfig = accessRequestChannels: string; isSecretRequestNotificationEnabled: boolean; secretRequestChannels: string; + isSecretSyncErrorNotificationEnabled: boolean; + secretSyncErrorChannels: string; } | { id: string; @@ -112,6 +114,8 @@ export type TUpdateProjectWorkflowIntegrationConfigDTO = accessRequestChannels: string; isSecretRequestNotificationEnabled: boolean; secretRequestChannels: string; + isSecretSyncErrorNotificationEnabled: boolean; + secretSyncErrorChannels: string; } | { integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS; diff --git a/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx b/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx index 75041a69e..bfea2e788 100644 --- a/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/NavBar/NewSubOrganizationForm.tsx @@ -36,28 +36,21 @@ export const NewSubOrganizationForm = ({ onClose }: ContentProps) => { const router = useRouter(); const onSubmit = async ({ name }: FormData) => { - try { - const { organization } = await createSubOrg.mutateAsync({ - name - }); + const { organization } = await createSubOrg.mutateAsync({ + name + }); - createNotification({ - type: "success", - text: "Successfully created sub organization" - }); - onClose(); + createNotification({ + type: "success", + text: "Successfully created sub organization" + }); + onClose(); - navigate({ - to: "/organization/projects", - search: (prev) => ({ ...prev, subOrganization: organization.name }) - }); - await router.invalidate({ sync: true }).catch(() => null); - } catch { - createNotification({ - text: "Failed to create sub organization", - type: "error" - }); - } + navigate({ + to: "/organization/projects", + search: (prev) => ({ ...prev, subOrganization: organization.name }) + }); + await router.invalidate({ sync: true }).catch(() => null); }; return ( diff --git a/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx b/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx index a32cb5093..dcb50b5d2 100644 --- a/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx +++ b/frontend/src/layouts/ProjectLayout/components/ProjectSelect/ProjectSelect.tsx @@ -11,7 +11,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, linkOptions } from "@tanstack/react-router"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { NewProjectModal } from "@app/components/projects"; import { @@ -59,31 +58,17 @@ export const ProjectSelect = () => { const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites(); const addProjectToFavorites = async (projectId: string) => { - try { - await updateUserProjectFavorites({ - orgId: currentOrg!.id, - projectFavorites: [...(projectFavorites || []), projectId] - }); - } catch { - createNotification({ - text: "Failed to add project to favorites.", - type: "error" - }); - } + await updateUserProjectFavorites({ + orgId: currentOrg!.id, + projectFavorites: [...(projectFavorites || []), projectId] + }); }; const removeProjectFromFavorites = async (projectId: string) => { - try { - await updateUserProjectFavorites({ - orgId: currentOrg!.id, - projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] - }); - } catch { - createNotification({ - text: "Failed to remove project from favorites.", - type: "error" - }); - } + await updateUserProjectFavorites({ + orgId: currentOrg!.id, + projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] + }); }; const isAddingProjectsAllowed = subscription?.workspaceLimit diff --git a/frontend/src/pages/admin/AccessManagementPage/components/AddServerAdminModal.tsx b/frontend/src/pages/admin/AccessManagementPage/components/AddServerAdminModal.tsx index 3e7638627..63ec2fa44 100644 --- a/frontend/src/pages/admin/AccessManagementPage/components/AddServerAdminModal.tsx +++ b/frontend/src/pages/admin/AccessManagementPage/components/AddServerAdminModal.tsx @@ -65,20 +65,13 @@ const Content = ({ onClose }: ContentProps) => { const users = usersData.filter((user) => !user.superAdmin); const onSubmit = async ({ user }: FormData) => { - try { - await grantAdmin.mutateAsync(user.id); + await grantAdmin.mutateAsync(user.id); - createNotification({ - type: "success", - text: "Successfully granted server admin status" - }); - onClose(); - } catch { - createNotification({ - text: "Failed to grant server admin status", - type: "error" - }); - } + createNotification({ + type: "success", + text: "Successfully granted server admin status" + }); + onClose(); }; return ( diff --git a/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx b/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx index 0ec5c9e58..58516be39 100644 --- a/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx +++ b/frontend/src/pages/admin/AccessManagementPage/components/ServerAdminsTable.tsx @@ -303,18 +303,11 @@ export const ServerAdminsTable = () => { const handleRemoveUser = async () => { const { id } = popUp?.removeUser?.data as { id: string; username: string }; - try { - await deleteUser(id); - createNotification({ - type: "success", - text: "Successfully deleted user" - }); - } catch { - createNotification({ - type: "error", - text: "Error deleting user" - }); - } + await deleteUser(id); + createNotification({ + type: "success", + text: "Successfully deleted user" + }); handlePopUpClose("removeUser"); }; @@ -322,39 +315,25 @@ export const ServerAdminsTable = () => { const handleRemoveServerAdminAccess = async () => { const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string }; - try { - await removeAdminAccess(id); - createNotification({ - type: "success", - text: "Successfully removed server admin access from user" - }); - } catch { - createNotification({ - type: "error", - text: "Error removing server admin access from user" - }); - } + await removeAdminAccess(id); + createNotification({ + type: "success", + text: "Successfully removed server admin access from user" + }); handlePopUpClose("removeServerAdmin"); }; const handleRemoveUsers = async () => { - try { - await deleteUsers(selectedUsers.map((user) => user.id)); + await deleteUsers(selectedUsers.map((user) => user.id)); - createNotification({ - text: "Successfully removed users", - type: "success" - }); + createNotification({ + text: "Successfully removed users", + type: "success" + }); - setSelectedUsers([]); - handlePopUpClose("removeUsers"); - } catch { - createNotification({ - text: "Failed to remove users", - type: "error" - }); - } + setSelectedUsers([]); + handlePopUpClose("removeUsers"); }; return ( diff --git a/frontend/src/pages/admin/AuthenticationPage/components/AuthenticationPageForm.tsx b/frontend/src/pages/admin/AuthenticationPage/components/AuthenticationPageForm.tsx index 2abb96e60..f8ca30c52 100644 --- a/frontend/src/pages/admin/AuthenticationPage/components/AuthenticationPageForm.tsx +++ b/frontend/src/pages/admin/AuthenticationPage/components/AuthenticationPageForm.tsx @@ -54,59 +54,51 @@ export const AuthenticationPageForm = () => { }); const onAuthFormSubmit = async (formData: TAuthForm) => { - try { - const enabledMethods: LoginMethod[] = []; - if (formData.isEmailEnabled) { - enabledMethods.push(LoginMethod.EMAIL); - } + const enabledMethods: LoginMethod[] = []; + if (formData.isEmailEnabled) { + enabledMethods.push(LoginMethod.EMAIL); + } - if (formData.isGoogleEnabled) { - enabledMethods.push(LoginMethod.GOOGLE); - } + if (formData.isGoogleEnabled) { + enabledMethods.push(LoginMethod.GOOGLE); + } - if (formData.isGithubEnabled) { - enabledMethods.push(LoginMethod.GITHUB); - } + if (formData.isGithubEnabled) { + enabledMethods.push(LoginMethod.GITHUB); + } - if (formData.isGitlabEnabled) { - enabledMethods.push(LoginMethod.GITLAB); - } + if (formData.isGitlabEnabled) { + enabledMethods.push(LoginMethod.GITLAB); + } - if (formData.isSamlEnabled) { - enabledMethods.push(LoginMethod.SAML); - } + if (formData.isSamlEnabled) { + enabledMethods.push(LoginMethod.SAML); + } - if (formData.isLdapEnabled) { - enabledMethods.push(LoginMethod.LDAP); - } + if (formData.isLdapEnabled) { + enabledMethods.push(LoginMethod.LDAP); + } - if (formData.isOidcEnabled) { - enabledMethods.push(LoginMethod.OIDC); - } + if (formData.isOidcEnabled) { + enabledMethods.push(LoginMethod.OIDC); + } - if (!enabledMethods.length) { - createNotification({ - type: "error", - text: "At least one login method should be enabled." - }); - return; - } - - await updateServerConfig({ - enabledLoginMethods: enabledMethods - }); - - createNotification({ - text: "Login methods have been successfully updated.", - type: "success" - }); - } catch (e) { - console.error(e); + if (!enabledMethods.length) { createNotification({ type: "error", - text: "Failed to update login methods." + text: "At least one login method should be enabled." }); + return; } + + await updateServerConfig({ + enabledLoginMethods: enabledMethods + }); + + createNotification({ + text: "Login methods have been successfully updated.", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx b/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx index fbf956b7b..e9bdc3d1e 100644 --- a/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx +++ b/frontend/src/pages/admin/CachingPage/components/CachingPageForm.tsx @@ -31,15 +31,10 @@ export const CachingPageForm = () => { const handleInvalidateCacheSubmit = async () => { if (!type || isInvalidating) return; - try { - await invalidateCache({ type }); - createNotification({ text: `Began invalidating ${type} cache`, type: "success" }); - setShouldPoll(true); - handlePopUpClose("invalidateCache"); - } catch (err) { - console.error(err); - createNotification({ text: `Failed to invalidate ${type} cache`, type: "error" }); - } + await invalidateCache({ type }); + createNotification({ text: `Began invalidating ${type} cache`, type: "success" }); + setShouldPoll(true); + handlePopUpClose("invalidateCache"); }; useEffect(() => { diff --git a/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx b/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx index aa7fdbdbb..5121face1 100644 --- a/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx +++ b/frontend/src/pages/admin/EncryptionPage/components/EncryptionPageForm.tsx @@ -60,19 +60,12 @@ export const EncryptionPageForm = () => { return; } - try { - await updateEncryptionStrategy(formData.encryptionStrategy); + await updateEncryptionStrategy(formData.encryptionStrategy); - createNotification({ - type: "success", - text: "Encryption strategy updated successfully" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update encryption strategy" - }); - } + createNotification({ + type: "success", + text: "Encryption strategy updated successfully" + }); }, []); return ( diff --git a/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx b/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx index 5f74593a3..8361aa944 100644 --- a/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx +++ b/frontend/src/pages/admin/EnvironmentPage/components/EnvironmentPageForm.tsx @@ -176,31 +176,19 @@ export const EnvironmentPageForm = () => { const onSubmit = useCallback( async (formData: TForm) => { - try { - const filteredFormData = Object.fromEntries( - Object.entries(formData).filter(([, value]) => value !== "") - ); - await updateServerConfig({ - envOverrides: filteredFormData - }); + const filteredFormData = Object.fromEntries( + Object.entries(formData).filter(([, value]) => value !== "") + ); + await updateServerConfig({ + envOverrides: filteredFormData + }); - createNotification({ - type: "success", - text: "Environment overrides updated successfully. It can take up to 5 minutes to take effect." - }); + createNotification({ + type: "success", + text: "Environment overrides updated successfully. It can take up to 5 minutes to take effect." + }); - reset(formData); - } catch (error) { - const errorMessage = - (error as any)?.response?.data?.message || - (error as any)?.message || - "An unknown error occurred"; - createNotification({ - type: "error", - title: "Failed to update environment overrides", - text: errorMessage - }); - } + reset(formData); }, [reset, updateServerConfig] ); diff --git a/frontend/src/pages/admin/GeneralPage/components/GeneralPageForm.tsx b/frontend/src/pages/admin/GeneralPage/components/GeneralPageForm.tsx index be92dd679..eff3e3cf8 100644 --- a/frontend/src/pages/admin/GeneralPage/components/GeneralPageForm.tsx +++ b/frontend/src/pages/admin/GeneralPage/components/GeneralPageForm.tsx @@ -68,37 +68,29 @@ export const GeneralPageForm = () => { const organizations = useGetOrganizations(); const onFormSubmit = async (formData: TDashboardForm) => { - try { - const { - allowedSignUpDomain, - trustSamlEmails, - trustLdapEmails, - trustOidcEmails, - authConsentContent, - pageFrameContent - } = formData; + const { + allowedSignUpDomain, + trustSamlEmails, + trustLdapEmails, + trustOidcEmails, + authConsentContent, + pageFrameContent + } = formData; - await updateServerConfig({ - defaultAuthOrgId: defaultAuthOrgId || null, - allowSignUp: signUpMode !== SignUpModes.Disabled, - allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null, - trustSamlEmails, - trustLdapEmails, - trustOidcEmails, - authConsentContent, - pageFrameContent - }); - createNotification({ - text: "Successfully changed sign up setting.", - type: "success" - }); - } catch (e) { - console.error(e); - createNotification({ - type: "error", - text: "Failed to update sign up setting." - }); - } + await updateServerConfig({ + defaultAuthOrgId: defaultAuthOrgId || null, + allowSignUp: signUpMode !== SignUpModes.Disabled, + allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null, + trustSamlEmails, + trustLdapEmails, + trustOidcEmails, + authConsentContent, + pageFrameContent + }); + createNotification({ + text: "Successfully changed sign up setting.", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/admin/GeneralPage/components/UsageReportSection.tsx b/frontend/src/pages/admin/GeneralPage/components/UsageReportSection.tsx index d05800996..663aa18b7 100644 --- a/frontend/src/pages/admin/GeneralPage/components/UsageReportSection.tsx +++ b/frontend/src/pages/admin/GeneralPage/components/UsageReportSection.tsx @@ -10,23 +10,15 @@ export const UsageReportSection = () => { const generateUsageReport = useGenerateUsageReport(); const handleGenerateReport = async () => { - try { - const response = await generateUsageReport.mutateAsync(); - const { csvContent, filename } = response; + const response = await generateUsageReport.mutateAsync(); + const { csvContent, filename } = response; - downloadFile(csvContent, filename, "text/csv"); + downloadFile(csvContent, filename, "text/csv"); - createNotification({ - text: `Usage report downloaded: "${filename}"`, - type: "success" - }); - } catch (error) { - console.error("Failed to generate usage report:", error); - createNotification({ - text: "Failed to generate usage report. Please try again.", - type: "error" - }); - } + createNotification({ + text: `Usage report downloaded: "${filename}"`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/AddOrganizationModal.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/AddOrganizationModal.tsx index d577998b5..48983496f 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/AddOrganizationModal.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/AddOrganizationModal.tsx @@ -81,25 +81,18 @@ const Content = ({ onClose }: ContentProps) => { const { users = [] } = data ?? {}; const onSubmit = async ({ name, invitees }: FormData) => { - try { - await createOrg.mutateAsync({ - name, - inviteAdminEmails: invitees - .filter((user) => Boolean(user.email)) - .map((user) => user.email) as string[] - }); + await createOrg.mutateAsync({ + name, + inviteAdminEmails: invitees + .filter((user) => Boolean(user.email)) + .map((user) => user.email) as string[] + }); - createNotification({ - type: "success", - text: "Successfully created organization" - }); - onClose(); - } catch { - createNotification({ - text: "Failed to create organization", - type: "error" - }); - } + createNotification({ + type: "success", + text: "Successfully created organization" + }); + onClose(); }; const { append } = useFieldArray({ control, name: "invitees" }); diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx index 9929d7500..d17786712 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/MachineIdentitiesTable.tsx @@ -185,18 +185,11 @@ export const MachineIdentitiesTable = () => { const handleRemoveServerAdmin = async () => { const { id } = popUp?.removeServerAdmin?.data as { id: string; name: string }; - try { - await deleteIdentitySuperAdminAccess(id); - createNotification({ - type: "success", - text: "Successfully removed server admin permissions" - }); - } catch { - createNotification({ - type: "error", - text: "Error removing server admin permissions" - }); - } + await deleteIdentitySuperAdminAccess(id); + createNotification({ + type: "success", + text: "Successfully removed server admin permissions" + }); handlePopUpClose("removeServerAdmin"); }; diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx index fbb9462d0..93d0253ff 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/OrganizationsTable.tsx @@ -179,12 +179,6 @@ const ViewMembersModalContent = ({ text: "Successfully resent org invitation", type: "success" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to resend org invitation", - type: "error" - }); } finally { setResendInviteId(null); } @@ -479,26 +473,19 @@ const OrganizationsPanelTable = ({ const { mutateAsync: accessOrganization } = useServerAdminAccessOrg(); const handleAccessOrg = async (orgId: string) => { - try { - await accessOrganization(orgId); + await accessOrganization(orgId); - navigate({ - to: "/login/select-organization", - search: { - org_id: orgId - } - }); + navigate({ + to: "/login/select-organization", + search: { + org_id: orgId + } + }); - createNotification({ - text: "Successfully joined organization", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to join organization", - type: "error" - }); - } + createNotification({ + text: "Successfully joined organization", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx b/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx index d61d428af..c343a58eb 100644 --- a/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx +++ b/frontend/src/pages/admin/ResourceOverviewPage/components/UserIdentitiesTable.tsx @@ -364,18 +364,11 @@ export const UserIdentitiesTable = () => { const handleRemoveUser = async () => { const { id } = popUp?.removeUser?.data as { id: string; username: string }; - try { - await deleteUser(id); - createNotification({ - type: "success", - text: "Successfully deleted user" - }); - } catch { - createNotification({ - type: "error", - text: "Error deleting user" - }); - } + await deleteUser(id); + createNotification({ + type: "success", + text: "Successfully deleted user" + }); handlePopUpClose("removeUser"); }; @@ -383,18 +376,11 @@ export const UserIdentitiesTable = () => { const handleGrantServerAdminAccess = async () => { const { id } = popUp?.upgradeToServerAdmin?.data as { id: string; username: string }; - try { - await grantAdminAccess(id); - createNotification({ - type: "success", - text: "Successfully granted server admin access to user" - }); - } catch { - createNotification({ - type: "error", - text: "Error granting server admin access to user" - }); - } + await grantAdminAccess(id); + createNotification({ + type: "success", + text: "Successfully granted server admin access to user" + }); handlePopUpClose("upgradeToServerAdmin"); }; @@ -402,39 +388,25 @@ export const UserIdentitiesTable = () => { const handleRemoveServerAdminAccess = async () => { const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string }; - try { - await removeAdminAccess(id); - createNotification({ - type: "success", - text: "Successfully removed server admin access from user" - }); - } catch { - createNotification({ - type: "error", - text: "Error removing server admin access from user" - }); - } + await removeAdminAccess(id); + createNotification({ + type: "success", + text: "Successfully removed server admin access from user" + }); handlePopUpClose("removeServerAdmin"); }; const handleRemoveUsers = async () => { - try { - await deleteUsers(selectedUsers.map((user) => user.id)); + await deleteUsers(selectedUsers.map((user) => user.id)); - createNotification({ - text: "Successfully removed users", - type: "success" - }); + createNotification({ + text: "Successfully removed users", + type: "success" + }); - setSelectedUsers([]); - handlePopUpClose("removeUsers"); - } catch { - createNotification({ - text: "Failed to remove users", - type: "error" - }); - } + setSelectedUsers([]); + handlePopUpClose("removeUsers"); }; return ( diff --git a/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx b/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx index 04bd2f55c..81959a647 100644 --- a/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx +++ b/frontend/src/pages/admin/SignUpPage/SignUpPage.tsx @@ -6,7 +6,6 @@ import { useNavigate } from "@tanstack/react-router"; import { AnimatePresence, motion } from "framer-motion"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; // TODO(akhilmhdh): rewrite this into module functions in lib import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button, ContentLoader, FormControl, Input } from "@app/components/v2"; @@ -46,29 +45,21 @@ export const SignUpPage = () => { const handleFormSubmit = async ({ email, password, firstName, lastName }: TFormSchema) => { // avoid multi submission if (isSubmitting) return; - try { - const res = await createAdminUser({ - email, - password, - firstName, - lastName - }); + const res = await createAdminUser({ + email, + password, + firstName, + lastName + }); - SecurityClient.setToken(res.token); - await selectOrganization({ organizationId: res.organization.id }); + SecurityClient.setToken(res.token); + await selectOrganization({ organizationId: res.organization.id }); - // TODO(akhilmhdh): This is such a confusing pattern and too unreliable - // Will be refactored in next iteration to make it url based rather than local storage ones - // Part of migration to nextjs 14 - localStorage.setItem("orgData.id", res.organization.id); - navigate({ to: "/admin" }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to create admin" - }); - } + // TODO(akhilmhdh): This is such a confusing pattern and too unreliable + // Will be refactored in next iteration to make it url based rather than local storage ones + // Part of migration to nextjs 14 + localStorage.setItem("orgData.id", res.organization.id); + navigate({ to: "/admin" }); }; if (config?.initialized) return ; diff --git a/frontend/src/pages/auth/PasswordSetupPage/PasswordSetupPage.tsx b/frontend/src/pages/auth/PasswordSetupPage/PasswordSetupPage.tsx index a1dad3cc5..9626f4cc5 100644 --- a/frontend/src/pages/auth/PasswordSetupPage/PasswordSetupPage.tsx +++ b/frontend/src/pages/auth/PasswordSetupPage/PasswordSetupPage.tsx @@ -75,11 +75,7 @@ export const PasswordSetupPage = () => { setTimeout(() => { window.location.href = "/login"; }, 3000); - } catch (error) { - createNotification({ - type: "error", - text: (error as Error).message ?? "Error setting password" - }); + } catch { navigate({ to: "/personal-settings" }); } } diff --git a/frontend/src/pages/auth/SignUpSsoPage/components/EmailConfirmationStep/EmailConfirmationStep.tsx b/frontend/src/pages/auth/SignUpSsoPage/components/EmailConfirmationStep/EmailConfirmationStep.tsx index efa498749..2240fe263 100644 --- a/frontend/src/pages/auth/SignUpSsoPage/components/EmailConfirmationStep/EmailConfirmationStep.tsx +++ b/frontend/src/pages/auth/SignUpSsoPage/components/EmailConfirmationStep/EmailConfirmationStep.tsx @@ -73,67 +73,53 @@ export const EmailConfirmationStep = ({ const { mutateAsync: verifyEmailVerificationCode } = useVerifyEmailVerificationCode(); const checkCode = async () => { - try { - await verifyEmailVerificationCode({ username, code }); - setCodeError(false); + await verifyEmailVerificationCode({ username, code }); + setCodeError(false); - createNotification({ - text: "Successfully verified code", - type: "success" - }); + createNotification({ + text: "Successfully verified code", + type: "success" + }); - switch (authType) { - case UserAliasType.SAML: { - window.open(`/api/v1/sso/redirect/saml2/organizations/${organizationSlug}`); - window.close(); - break; - } - case UserAliasType.LDAP: { - navigate({ to: "/login/ldap", search: { organizationSlug } }); - break; - } - case UserAliasType.OIDC: { - window.open(`/api/v1/sso/oidc/login?orgSlug=${organizationSlug}`); - window.close(); - break; - } - default: { - setStep(1); - break; - } + switch (authType) { + case UserAliasType.SAML: { + window.open(`/api/v1/sso/redirect/saml2/organizations/${organizationSlug}`); + window.close(); + break; + } + case UserAliasType.LDAP: { + navigate({ to: "/login/ldap", search: { organizationSlug } }); + break; + } + case UserAliasType.OIDC: { + window.open(`/api/v1/sso/oidc/login?orgSlug=${organizationSlug}`); + window.close(); + break; + } + default: { + setStep(1); + break; } - } catch { - createNotification({ - text: "Failed to verify code", - type: "error" - }); } setCode(""); }; const resendCode = async () => { - try { - const queryParams = new URLSearchParams(window.location.search); - const token = queryParams.get("token"); - if (!token) { - createNotification({ - text: "Failed to resend code, no token found", - type: "error" - }); - return; - } - await sendEmailVerificationCode(token); + const queryParams = new URLSearchParams(window.location.search); + const token = queryParams.get("token"); + if (!token) { createNotification({ - text: "Successfully resent code", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to resend code", + text: "Failed to resend code, no token found", type: "error" }); + return; } + await sendEmailVerificationCode(token); + createNotification({ + text: "Successfully resent code", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx index 5601e48db..af5bcb7ae 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertModal.tsx @@ -113,52 +113,44 @@ export const PkiAlertModal = ({ popUp, handlePopUpToggle }: Props) => { alertUnit, emails }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - const emailArray = emails - .split(",") - .map((email) => email.trim()) - .filter((email) => email.length > 0); + const emailArray = emails + .split(",") + .map((email) => email.trim()) + .filter((email) => email.length > 0); - const alertBeforeDays = convertToDays(alertUnit, Number(alertBefore)); + const alertBeforeDays = convertToDays(alertUnit, Number(alertBefore)); - if (alert) { - // update - await updatePkiAlert({ - alertId: alert.id, - pkiCollectionId, - name, - projectId, - alertBeforeDays, - emails: emailArray - }); - } else { - // create - await createPkiAlert({ - name, - projectId, - pkiCollectionId, - alertBeforeDays, - emails: emailArray - }); - } - - handlePopUpToggle("pkiAlert", false); - - reset(); - - createNotification({ - text: `Successfully ${alert ? "updated" : "created"} alert`, - type: "success" + if (alert) { + // update + await updatePkiAlert({ + alertId: alert.id, + pkiCollectionId, + name, + projectId, + alertBeforeDays, + emails: emailArray }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${alert ? "updated" : "created"} alert`, - type: "error" + } else { + // create + await createPkiAlert({ + name, + projectId, + pkiCollectionId, + alertBeforeDays, + emails: emailArray }); } + + handlePopUpToggle("pkiAlert", false); + + reset(); + + createNotification({ + text: `Successfully ${alert ? "updated" : "created"} alert`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx index 329974f65..1529dfc19 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiAlertsSection.tsx @@ -22,27 +22,19 @@ export const PkiAlertsSection = () => { ] as const); const onRemoveAlertSubmit = async (alertId: string) => { - try { - if (!projectId) return; + if (!projectId) return; - await deletePkiAlert({ - alertId, - projectId - }); + await deletePkiAlert({ + alertId, + projectId + }); - createNotification({ - text: "Successfully deleted alert", - type: "success" - }); + createNotification({ + text: "Successfully deleted alert", + type: "success" + }); - handlePopUpClose("deletePkiAlert"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete alert", - type: "error" - }); - } + handlePopUpClose("deletePkiAlert"); }; return ( diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx index 5900a1ba9..a29e9028b 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionModal.tsx @@ -62,49 +62,41 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => { }, [pkiCollection]); const onFormSubmit = async ({ name, description }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - if (pkiCollection) { - // update - await updatePkiCollection({ - collectionId: pkiCollection.id, - name, - description, - projectId - }); - } else { - // create - const { id: collectionId } = await createPkiCollection({ - name, - description, - projectId - }); - - navigate({ - to: "/projects/cert-management/$projectId/pki-collections/$collectionId", - params: { - projectId, - collectionId - } - }); - } - - handlePopUpToggle("pkiCollection", false); - - reset(); - - createNotification({ - text: `Successfully ${pkiCollection ? "updated" : "created"} PKI collection`, - type: "success" + if (pkiCollection) { + // update + await updatePkiCollection({ + collectionId: pkiCollection.id, + name, + description, + projectId }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${pkiCollection ? "updated" : "created"} PKI collection`, - type: "error" + } else { + // create + const { id: collectionId } = await createPkiCollection({ + name, + description, + projectId + }); + + navigate({ + to: "/projects/cert-management/$projectId/pki-collections/$collectionId", + params: { + projectId, + collectionId + } }); } + + handlePopUpToggle("pkiCollection", false); + + reset(); + + createNotification({ + text: `Successfully ${pkiCollection ? "updated" : "created"} PKI collection`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx index eecceaa7d..fa9cf7bea 100644 --- a/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx +++ b/frontend/src/pages/cert-manager/AlertingPage/components/PkiCollectionSection.tsx @@ -22,27 +22,19 @@ export const PkiCollectionSection = () => { ] as const); const onRemovePkiCollectionSubmit = async (collectionId: string) => { - try { - if (!projectId) return; + if (!projectId) return; - await deletePkiCollection({ - collectionId, - projectId - }); + await deletePkiCollection({ + collectionId, + projectId + }); - createNotification({ - text: "Successfully deleted PKI collection", - type: "success" - }); + createNotification({ + text: "Successfully deleted PKI collection", + type: "success" + }); - handlePopUpClose("deletePkiCollection"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete PKI collection", - type: "error" - }); - } + handlePopUpClose("deletePkiCollection"); }; return ( diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx index ca73abd50..b464a2230 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/CertAuthDetailsByIDPage.tsx @@ -57,33 +57,26 @@ const Page = () => { ] as const); const onRemoveCaSubmit = async () => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - await deleteCa({ - caName, - projectId: currentProject.id, - type: CaType.INTERNAL - }); + await deleteCa({ + caName, + projectId: currentProject.id, + type: CaType.INTERNAL + }); - createNotification({ - text: "Successfully deleted CA", - type: "success" - }); + createNotification({ + text: "Successfully deleted CA", + type: "success" + }); - handlePopUpClose("deleteCa"); - navigate({ - to: "/projects/cert-management/$projectId/certificate-authorities", - params: { - projectId - } - }); - } catch { - createNotification({ - text: "Failed to delete CA", - type: "error" - }); - } + handlePopUpClose("deleteCa"); + navigate({ + to: "/projects/cert-management/$projectId/certificate-authorities", + params: { + projectId + } + }); }; return ( diff --git a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx index 857586a58..c6283da37 100644 --- a/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx +++ b/frontend/src/pages/cert-manager/CertAuthDetailsByIDPage/components/CaRenewalModal.tsx @@ -84,27 +84,23 @@ export const CaRenewalModal = ({ popUp, handlePopUpToggle }: Props) => { // }, [ca, parentCa]); const onFormSubmit = async ({ type, notAfter }: FormData) => { - try { - if (!projectSlug || !popUpData.caId) return; + if (!projectSlug || !popUpData.caId) return; - await renewCa({ - projectSlug, - caId: popUpData.caId, - notAfter, - type - }); + await renewCa({ + projectSlug, + caId: popUpData.caId, + notAfter, + type + }); - handlePopUpToggle("renewCa", false); + handlePopUpToggle("renewCa", false); - createNotification({ - text: "Successfully renewed CA", - type: "success" - }); + createNotification({ + text: "Successfully renewed CA", + type: "success" + }); - reset(); - } catch (err) { - console.error(err); - } + reset(); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx index f730322c9..8a7e9690e 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/ExternalCaInstallForm.tsx @@ -48,29 +48,22 @@ export const ExternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => { }, []); const onFormSubmit = async ({ certificate, certificateChain }: FormData) => { - try { - if (!csr || !caId || !currentProject?.slug) return; + if (!csr || !caId || !currentProject?.slug) return; - await importCaCertificate({ - caId, - projectSlug: currentProject?.slug, - certificate, - certificateChain - }); + await importCaCertificate({ + caId, + projectSlug: currentProject?.slug, + certificate, + certificateChain + }); - reset(); + reset(); - createNotification({ - text: "Successfully installed certificate for CA", - type: "success" - }); - handlePopUpToggle("installCaCert", false); - } catch { - createNotification({ - text: "Failed to install certificate for CA", - type: "error" - }); - } + createNotification({ + text: "Successfully installed certificate for CA", + type: "success" + }); + handlePopUpToggle("installCaCert", false); }; const downloadTxtFile = (filename: string, content: string) => { diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx index 5979d9711..f80e21212 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaInstallCertModal/InternalCaInstallForm.tsx @@ -101,37 +101,30 @@ export const InternalCaInstallForm = ({ caId, handlePopUpToggle }: Props) => { }, [parentCa]); const onFormSubmit = async ({ notAfter, maxPathLength }: FormData) => { - try { - if (!csr || !caId || !currentProject?.slug) return; + if (!csr || !caId || !currentProject?.slug) return; - const { certificate, certificateChain } = await signIntermediate({ - caId: parentCaId, - csr, - maxPathLength: Number(maxPathLength), - notAfter, - notBefore: new Date().toISOString() - }); + const { certificate, certificateChain } = await signIntermediate({ + caId: parentCaId, + csr, + maxPathLength: Number(maxPathLength), + notAfter, + notBefore: new Date().toISOString() + }); - await importCaCertificate({ - caId, - projectSlug: currentProject?.slug, - certificate, - certificateChain - }); + await importCaCertificate({ + caId, + projectSlug: currentProject?.slug, + certificate, + certificateChain + }); - reset(); + reset(); - createNotification({ - text: "Successfully installed certificate for CA", - type: "success" - }); - handlePopUpToggle("installCaCert", false); - } catch { - createNotification({ - text: "Failed to install certificate for CA", - type: "error" - }); - } + createNotification({ + text: "Successfully installed certificate for CA", + type: "success" + }); + handlePopUpToggle("installCaCert", false); }; function generatePathLengthOpts(parentCaMaxPathLength: number): number[] { diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx index a19a3a9c7..53434b79d 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaModal.tsx @@ -175,48 +175,40 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => { status, configuration }: FormData) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - if (ca) { - // update - await updateMutateAsync({ - caName: ca.name, - projectId: currentProject.id, - name, - type: CaType.INTERNAL, - status, - enableDirectIssuance - }); - } else { - // create - await createMutateAsync({ - projectId: currentProject.id, - name, - type, - status, - enableDirectIssuance, - configuration: { - ...configuration, - maxPathLength: Number(configuration.maxPathLength) - } - }); - } - - reset(); - handlePopUpToggle("ca", false); - - createNotification({ - text: `Successfully ${ca ? "updated" : "created"} CA`, - type: "success" + if (ca) { + // update + await updateMutateAsync({ + caName: ca.name, + projectId: currentProject.id, + name, + type: CaType.INTERNAL, + status, + enableDirectIssuance }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create CA", - type: "error" + } else { + // create + await createMutateAsync({ + projectId: currentProject.id, + name, + type, + status, + enableDirectIssuance, + configuration: { + ...configuration, + maxPathLength: Number(configuration.maxPathLength) + } }); } + + reset(); + handlePopUpToggle("ca", false); + + createNotification({ + text: `Successfully ${ca ? "updated" : "created"} CA`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx index 1e264dadc..918a844b1 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/CaSection.tsx @@ -27,44 +27,29 @@ export const CaSection = () => { ] as const); const onRemoveCaSubmit = async (caName: string) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - await deleteCa({ caName, projectId: currentProject.id, type: CaType.INTERNAL }); + await deleteCa({ caName, projectId: currentProject.id, type: CaType.INTERNAL }); - createNotification({ - text: "Successfully deleted CA", - type: "success" - }); + createNotification({ + text: "Successfully deleted CA", + type: "success" + }); - handlePopUpClose("deleteCa"); - } catch { - createNotification({ - text: "Failed to delete CA", - type: "error" - }); - } + handlePopUpClose("deleteCa"); }; const onUpdateCaStatus = async ({ caName, status }: { caName: string; status: CaStatus }) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - await updateCa({ caName, projectId: currentProject.id, type: CaType.INTERNAL, status }); + await updateCa({ caName, projectId: currentProject.id, type: CaType.INTERNAL, status }); - createNotification({ - text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, - type: "success" - }); + createNotification({ + text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, + type: "success" + }); - handlePopUpClose("caStatus"); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, - type: "error" - }); - } + handlePopUpClose("caStatus"); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx index a1fdcfc1f..be757e65b 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx @@ -297,63 +297,55 @@ export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => { status, configuration: formConfiguration }: FormData) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - let configPayload: any; + let configPayload: any; - if (type === CaType.ACME && "dnsAppConnection" in formConfiguration) { - configPayload = { - dnsProviderConfig: formConfiguration.dnsProviderConfig, - directoryUrl: formConfiguration.directoryUrl, - accountEmail: formConfiguration.accountEmail, - dnsAppConnectionId: formConfiguration.dnsAppConnection.id, - eabKid: formConfiguration.eabKid, - eabHmacKey: formConfiguration.eabHmacKey - }; - } else if (type === CaType.AZURE_AD_CS && "azureAdcsConnection" in formConfiguration) { - configPayload = { - azureAdcsConnectionId: formConfiguration.azureAdcsConnection.id - }; - } else { - throw new Error("Invalid certificate authority configuration"); - } + if (type === CaType.ACME && "dnsAppConnection" in formConfiguration) { + configPayload = { + dnsProviderConfig: formConfiguration.dnsProviderConfig, + directoryUrl: formConfiguration.directoryUrl, + accountEmail: formConfiguration.accountEmail, + dnsAppConnectionId: formConfiguration.dnsAppConnection.id, + eabKid: formConfiguration.eabKid, + eabHmacKey: formConfiguration.eabHmacKey + }; + } else if (type === CaType.AZURE_AD_CS && "azureAdcsConnection" in formConfiguration) { + configPayload = { + azureAdcsConnectionId: formConfiguration.azureAdcsConnection.id + }; + } else { + throw new Error("Invalid certificate authority configuration"); + } - if (ca) { - await updateMutateAsync({ - caName: ca.name, - projectId: currentProject.id, - name, - type, - status, - enableDirectIssuance: type === CaType.AZURE_AD_CS ? false : enableDirectIssuance, - configuration: configPayload - }); - } else { - await createMutateAsync({ - projectId: currentProject.id, - name, - type, - status, - enableDirectIssuance: type === CaType.AZURE_AD_CS ? false : enableDirectIssuance, - configuration: configPayload - }); - } - - reset(); - handlePopUpToggle("ca", false); - - createNotification({ - text: `Successfully ${ca ? "updated" : "created"} CA`, - type: "success" + if (ca) { + await updateMutateAsync({ + caName: ca.name, + projectId: currentProject.id, + name, + type, + status, + enableDirectIssuance: type === CaType.AZURE_AD_CS ? false : enableDirectIssuance, + configuration: configPayload }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create CA", - type: "error" + } else { + await createMutateAsync({ + projectId: currentProject.id, + name, + type, + status, + enableDirectIssuance: type === CaType.AZURE_AD_CS ? false : enableDirectIssuance, + configuration: configPayload }); } + + reset(); + handlePopUpToggle("ca", false); + + createNotification({ + text: `Successfully ${ca ? "updated" : "created"} CA`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx index 24bfb79ac..31894b855 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaSection.tsx @@ -23,23 +23,16 @@ export const ExternalCaSection = () => { ] as const); const onRemoveCaSubmit = async (caName: string, type: CaType) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await deleteCa({ caName, type, projectId: currentProject.id }); + await deleteCa({ caName, type, projectId: currentProject.id }); - createNotification({ - text: "Successfully deleted CA", - type: "success" - }); + createNotification({ + text: "Successfully deleted CA", + type: "success" + }); - handlePopUpClose("deleteCa"); - } catch { - createNotification({ - text: "Failed to delete CA", - type: "error" - }); - } + handlePopUpClose("deleteCa"); }; const onUpdateCaStatus = async ({ @@ -51,24 +44,16 @@ export const ExternalCaSection = () => { type: CaType; status: CaStatus; }) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - await updateCa({ caName: name, type, status, projectId: currentProject.id }); + await updateCa({ caName: name, type, status, projectId: currentProject.id }); - createNotification({ - text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, - type: "success" - }); + createNotification({ + text: `Successfully ${status === CaStatus.ACTIVE ? "enabled" : "disabled"} CA`, + type: "success" + }); - handlePopUpClose("caStatus"); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${status === CaStatus.ACTIVE ? "enable" : "disable"} CA`, - type: "error" - }); - } + handlePopUpClose("caStatus"); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx index bb0bbda0b..d0434b79a 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateImportModal.tsx @@ -71,38 +71,30 @@ export const CertificateImportModal = ({ popUp, handlePopUpToggle }: Props) => { chainPem, collectionId }: FormData) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - const { serialNumber, certificate, certificateChain, privateKey } = await importCertificate({ - projectSlug: currentProject.slug, + const { serialNumber, certificate, certificateChain, privateKey } = await importCertificate({ + projectSlug: currentProject.slug, - certificatePem, - privateKeyPem, - chainPem, - pkiCollectionId: collectionId - }); + certificatePem, + privateKeyPem, + chainPem, + pkiCollectionId: collectionId + }); - reset(); + reset(); - setCertificateDetails({ - serialNumber, - certificate, - certificateChain, - privateKey - }); + setCertificateDetails({ + serialNumber, + certificate, + certificateChain, + privateKey + }); - createNotification({ - text: "Successfully imported certificate", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to import certificate", - type: "error" - }); - } + createNotification({ + text: "Successfully imported certificate", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal.tsx index 98ee930d5..81bc5461f 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateIssuanceModal.tsx @@ -94,9 +94,9 @@ const createSchema = (shouldShowSubjectSection: boolean) => { export type FormData = z.infer>; type Props = { - popUp: UsePopUpState<["certificateIssuance"]>; + popUp: UsePopUpState<["issueCertificate"]>; handlePopUpToggle: ( - popUpName: keyof UsePopUpState<["certificateIssuance"]>, + popUpName: keyof UsePopUpState<["issueCertificate"]>, state?: boolean ) => void; profileId?: string; @@ -115,7 +115,7 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId } const { currentProject } = useProject(); const inputSerialNumber = - (popUp?.certificateIssuance?.data as { serialNumber: string })?.serialNumber || ""; + (popUp?.issueCertificate?.data as { serialNumber: string })?.serialNumber || ""; const sanitizedSerialNumber = inputSerialNumber.replace(/[^a-fA-F0-9:]/g, ""); const { data: cert } = useGetCert(sanitizedSerialNumber); @@ -181,7 +181,7 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId } } = useCertificateTemplate( templateData, actualSelectedProfile, - popUp?.certificateIssuance?.isOpen || false, + popUp?.issueCertificate?.isOpen || false, setValue, watch ); @@ -227,10 +227,10 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId } }, [cert, reset]); useEffect(() => { - if (popUp?.certificateIssuance?.isOpen && profileId && !cert) { + if (popUp?.issueCertificate?.isOpen && profileId && !cert) { setValue("profileId", profileId); } - }, [popUp?.certificateIssuance?.isOpen, profileId, cert, setValue]); + }, [popUp?.issueCertificate?.isOpen, profileId, cert, setValue]); const onFormSubmit = useCallback( async ({ @@ -243,84 +243,72 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId } keyUsages, extendedKeyUsages }: FormData) => { - try { - if (!currentProject?.slug) { - createNotification({ - text: "Project not found. Please refresh and try again.", - type: "error" - }); - return; - } - - if (!formProfileId) { - createNotification({ - text: "Please select a certificate profile.", - type: "error" - }); - return; - } - - let commonName = ""; - if ( - constraints.shouldShowSubjectSection && - subjectAttributes && - subjectAttributes.length > 0 - ) { - commonName = getAttributeValue(subjectAttributes, "common_name"); - if (!commonName.trim()) { - createNotification({ - text: "Common name is required.", - type: "error" - }); - return; - } - } - - const certificateRequest: any = { - profileId: formProfileId, - projectSlug: currentProject.slug, - ttl, - signatureAlgorithm, - keyAlgorithm, - keyUsages: filterUsages(keyUsages) as CertKeyUsage[], - extendedKeyUsages: filterUsages(extendedKeyUsages) as CertExtendedKeyUsage[] - }; - - if (constraints.shouldShowSubjectSection && commonName) { - certificateRequest.commonName = commonName; - } - if (constraints.shouldShowSanSection && subjectAltNames && subjectAltNames.length > 0) { - const formattedSans = formatSubjectAltNames(subjectAltNames); - if (formattedSans && formattedSans.length > 0) { - certificateRequest.altNames = formattedSans; - } - } - - const { serialNumber, certificate, certificateChain, privateKey } = - await createCertificate(certificateRequest); - - setCertificateDetails({ - serialNumber, - certificate, - certificateChain, - privateKey - }); - + if (!currentProject?.slug) { createNotification({ - text: "Successfully created certificate", - type: "success" - }); - } catch (err) { - console.error("Certificate creation failed:", err); - const errorMessage = - err instanceof Error - ? err.message - : "An unexpected error occurred while creating the certificate"; - createNotification({ - text: `Failed to create certificate: ${errorMessage}`, + text: "Project not found. Please refresh and try again.", type: "error" }); + return; } + + if (!formProfileId) { + createNotification({ + text: "Please select a certificate profile.", + type: "error" + }); + return; + } + + let commonName = ""; + if ( + constraints.shouldShowSubjectSection && + subjectAttributes && + subjectAttributes.length > 0 + ) { + commonName = getAttributeValue(subjectAttributes, "common_name"); + if (!commonName.trim()) { + createNotification({ + text: "Common name is required.", + type: "error" + }); + return; + } + } + + const certificateRequest: any = { + profileId: formProfileId, + projectSlug: currentProject.slug, + ttl, + signatureAlgorithm, + keyAlgorithm, + keyUsages: filterUsages(keyUsages) as CertKeyUsage[], + extendedKeyUsages: filterUsages(extendedKeyUsages) as CertExtendedKeyUsage[] + }; + + if (constraints.shouldShowSubjectSection && commonName) { + certificateRequest.commonName = commonName; + } + if (constraints.shouldShowSanSection && subjectAltNames && subjectAltNames.length > 0) { + const formattedSans = formatSubjectAltNames(subjectAltNames); + if (formattedSans && formattedSans.length > 0) { + certificateRequest.altNames = formattedSans; + } + } + + const { serialNumber, certificate, certificateChain, privateKey } = + await createCertificate(certificateRequest); + + setCertificateDetails({ + serialNumber, + certificate, + certificateChain, + privateKey + }); + + createNotification({ + text: "Successfully created certificate", + type: "success" + }); }, [ currentProject?.slug, @@ -344,9 +332,9 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId } return ( { - handlePopUpToggle("certificateIssuance", isOpen); + handlePopUpToggle("issueCertificate", isOpen); if (!isOpen) { resetAllState(); } @@ -515,7 +503,7 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId } colorSchema="secondary" variant="plain" onClick={() => { - handlePopUpToggle("certificateIssuance", false); + handlePopUpToggle("issueCertificate", false); }} > Cancel diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx index d6199678a..96eb2654a 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateManageRenewalModal.tsx @@ -163,38 +163,28 @@ export const CertificateManageRenewalModal = ({ popUp, handlePopUpToggle }: Prop }, [popUp.manageRenewal.isOpen, defaultRenewalDays, reset]); const onUpdateRenewal = async (data: FormData) => { - try { - if (!currentProject?.slug) { - createNotification({ - text: "Unable to update auto-renewal: Project not found. Please refresh the page and try again.", - type: "error" - }); - return; - } - - await updateRenewalConfig({ - certificateId: certificateData.certificateId, - renewBeforeDays: data.renewBeforeDays, - projectSlug: currentProject.slug - }); - + if (!currentProject?.slug) { createNotification({ - text: isAutoRenewalEnabled - ? "Auto-renewal configuration updated successfully" - : "Auto-renewal enabled successfully", - type: "success" - }); - - handlePopUpToggle("manageRenewal", false); - } catch (err) { - console.error(err); - createNotification({ - text: isAutoRenewalEnabled - ? "Failed to update auto-renewal configuration. Please check your inputs and try again." - : "Failed to enable auto-renewal. Please check your inputs and try again.", + text: "Unable to update auto-renewal: Project not found. Please refresh the page and try again.", type: "error" }); + return; } + + await updateRenewalConfig({ + certificateId: certificateData.certificateId, + renewBeforeDays: data.renewBeforeDays, + projectSlug: currentProject.slug + }); + + createNotification({ + text: isAutoRenewalEnabled + ? "Auto-renewal configuration updated successfully" + : "Auto-renewal enabled successfully", + type: "success" + }); + + handlePopUpToggle("manageRenewal", false); }; const getModalTitle = () => { diff --git a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx index 22718222a..75ae0e848 100644 --- a/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx +++ b/frontend/src/pages/cert-manager/CertificatesPage/components/CertificateModal.tsx @@ -75,6 +75,7 @@ export type FormData = z.infer; type Props = { popUp: UsePopUpState<["certificate"]>; handlePopUpToggle: (popUpName: keyof UsePopUpState<["certificate"]>, state?: boolean) => void; + preselectedTemplate?: { id: string; name: string }; }; type TCertificateDetails = { @@ -86,7 +87,7 @@ type TCertificateDetails = { const CERT_TEMPLATE_NONE_VALUE = "none"; -export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { +export const CertificateModal = ({ popUp, handlePopUpToggle, preselectedTemplate }: Props) => { const [certificateDetails, setCertificateDetails] = useState(null); const { currentProject } = useProject(); const { data: cert } = useGetCert( @@ -147,13 +148,15 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { (cert.extendedKeyUsages || []).map((name) => [name, true]) ) }); - } else { + } else if (popUp?.certificate?.isOpen) { + const templateId = preselectedTemplate?.id || CERT_TEMPLATE_NONE_VALUE; + reset({ caId: "", commonName: "", subjectAltNames: "", ttl: "", - certificateTemplateId: CERT_TEMPLATE_NONE_VALUE, + certificateTemplateId: templateId, keyUsages: { [CertKeyUsage.DIGITAL_SIGNATURE]: true, [CertKeyUsage.KEY_ENCIPHERMENT]: true @@ -161,7 +164,7 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { extendedKeyUsages: {} }); } - }, [cert]); + }, [cert, preselectedTemplate, popUp?.certificate?.isOpen]); useEffect(() => { if (!cert && selectedCertTemplate) { @@ -186,45 +189,41 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { keyUsages, extendedKeyUsages }: FormData) => { - try { - if (!currentProject?.slug) return; + if (!currentProject?.slug) return; - const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({ - caId: !selectedCertTemplate ? caId : undefined, - certificateTemplateId: selectedCertTemplate ? selectedCertTemplateId : undefined, - projectSlug: currentProject.slug, - pkiCollectionId: collectionId, - commonName, - subjectAltNames, - ttl, - keyUsages: Object.entries(keyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertKeyUsage), - extendedKeyUsages: Object.entries(extendedKeyUsages) - .filter(([, value]) => value) - .map(([key]) => key as CertExtendedKeyUsage) - }); + const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({ + caId: !selectedCertTemplate ? caId : undefined, + certificateTemplateId: selectedCertTemplate ? selectedCertTemplateId : undefined, + projectSlug: currentProject.slug, + pkiCollectionId: collectionId, + commonName, + subjectAltNames, + ttl, + keyUsages: Object.entries(keyUsages) + .filter(([, value]) => value) + .map(([key]) => + key === CertKeyUsage.CRL_SIGN + ? "cRLSign" + : key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()) + ), + extendedKeyUsages: Object.entries(extendedKeyUsages) + .filter(([, value]) => value) + .map(([key]) => key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())) + }); - reset(); + reset(); - setCertificateDetails({ - serialNumber, - certificate, - certificateChain, - privateKey - }); + setCertificateDetails({ + serialNumber, + certificate, + certificateChain, + privateKey + }); - createNotification({ - text: "Successfully created certificate", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create certificate", - type: "error" - }); - } + createNotification({ + text: "Successfully created certificate", + type: "success" + }); }; useEffect(() => { @@ -277,15 +276,25 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { isRequired > )} @@ -556,6 +577,20 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" } )} + {/* ACME Configuration */} + {watchedEnrollmentType === "acme" && ( +
+ ( + +
{/* FIXME: ACME configuration */}
+
+ )} + /> +
+ )} {watchedAutoRenew && (
void; onDeleteProfile: (profile: TCertificateProfileWithDetails) => void; + onRevealProfileAcmeEabSecret: (profile: TCertificateProfileWithDetails) => void; } -export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => { +export const ProfileList = ({ + onEditProfile, + onRevealProfileAcmeEabSecret, + onDeleteProfile +}: Props) => { const { currentProject } = useProject(); const { data, isLoading } = useListCertificateProfiles({ @@ -88,6 +93,7 @@ export const ProfileList = ({ onEditProfile, onDeleteProfile }: Props) => { key={profile.id} profile={profile} onEditProfile={onEditProfile} + onRevealProfileAcmeEabSecret={onRevealProfileAcmeEabSecret} onDeleteProfile={onDeleteProfile} /> ))} diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx index e3bdea3c4..1e6f259e1 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/ProfileRow.tsx @@ -1,14 +1,15 @@ -import { useCallback } from "react"; import { faCheck, faCircleInfo, faCopy, faEdit, faEllipsis, + faEye, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useCallback } from "react"; import { createNotification } from "@app/components/notifications"; import { @@ -36,15 +37,21 @@ import { CertificateIssuanceModal } from "@app/pages/cert-manager/CertificatesPa interface Props { profile: TCertificateProfile; onEditProfile: (profile: TCertificateProfile) => void; + onRevealProfileAcmeEabSecret: (profile: TCertificateProfile) => void; onDeleteProfile: (profile: TCertificateProfile) => void; } -export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) => { +export const ProfileRow = ({ + profile, + onEditProfile, + onRevealProfileAcmeEabSecret, + onDeleteProfile +}: Props) => { const { permission } = useProjectPermission(); const { data: caData } = useGetCaById(profile.caId); - const { popUp, handlePopUpToggle } = usePopUp(["certificateIssuance"] as const); + const { popUp, handlePopUpToggle } = usePopUp(["issueCertificate"] as const); const [isIdCopied, setIsIdCopied] = useToggle(false); @@ -69,6 +76,11 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) = ProjectPermissionSub.CertificateAuthorities ); + const canRevealProfileAcmeEabSecret = permission.can( + ProjectPermissionCertificateProfileActions.RevealAcmeEabSecret, + ProjectPermissionSub.CertificateProfiles + ); + const canIssueCertificate = permission.can( ProjectPermissionCertificateProfileActions.IssueCert, ProjectPermissionSub.CertificateProfiles @@ -82,7 +94,8 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) = const getEnrollmentTypeBadge = (enrollmentType: string) => { const config = { api: { variant: "ghost" as const, label: "API" }, - est: { variant: "ghost" as const, label: "EST" } + est: { variant: "ghost" as const, label: "EST" }, + acme: { variant: "ghost" as const, label: "ACME" } } as const; const configKey = Object.keys(config).includes(enrollmentType) @@ -127,7 +140,7 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) = } + icon={} onClick={() => handleCopyId()} > Copy Profile ID @@ -138,18 +151,29 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) = e.stopPropagation(); onEditProfile(profile); }} - icon={} + icon={} > Edit Profile )} + {canRevealProfileAcmeEabSecret && profile.enrollmentType === "acme" && ( + { + e.stopPropagation(); + onRevealProfileAcmeEabSecret(profile); + }} + icon={} + > + Reveal ACME EAB + + )} {canIssueCertificate && profile.enrollmentType === "api" && ( { e.stopPropagation(); - handlePopUpToggle("certificateIssuance"); + handlePopUpToggle("issueCertificate"); }} - icon={} + icon={} > Issue Certificate @@ -160,7 +184,7 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) = e.stopPropagation(); onDeleteProfile(profile); }} - icon={} + icon={} > Delete Profile diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/RevealAcmeEabSecretModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/RevealAcmeEabSecretModal.tsx new file mode 100644 index 000000000..628e4106b --- /dev/null +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/RevealAcmeEabSecretModal.tsx @@ -0,0 +1,134 @@ +import { + Alert, + AlertDescription, + FormLabel, + IconButton, + Input, + Modal, + ModalContent, + Spinner +} from "@app/components/v2"; +import { useToggle } from "@app/hooks"; +import { TCertificateProfileWithDetails } from "@app/hooks/api/certificateProfiles"; +import { useRevealAcmeEabSecret } from "@app/hooks/api/certificateProfiles/queries"; +import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +const RESET_COPIED_DELAY = 1 * 1000; + +type Props = { + isOpen: boolean; + onClose: () => void; + profile: TCertificateProfileWithDetails; +}; + +export const RevealAcmeEabSecretModal = ({ isOpen, onClose, profile }: Props) => { + const [isAcmeDirectoryUrlCopied, setIsAcmeDirectoryUrlCopied] = useToggle(false); + const [isEabKidCopied, setIsEabKidCopied] = useToggle(false); + const [isEabSecretCopied, setIsEabSecretCopied] = useToggle(false); + const revealAcmeEabSecret = useRevealAcmeEabSecret({ profileId: profile.id }); + const { data, isLoading, isError, error } = revealAcmeEabSecret; + const { directoryUrl } = profile.acmeConfig!; + const { eabKid, eabSecret } = data ?? { eabKid: "", eabSecret: "" }; + + return ( + { + if (!open) { + onClose(); + } + }} + > + + {isLoading && ( +
+ +
+ )} + {isError && ( + + Failed to reveal EAB secret: {error.message} + + )} + {data && ( + <> + +
+ + { + navigator.clipboard.writeText(directoryUrl); + setIsAcmeDirectoryUrlCopied.on(); + setTimeout(() => { + setIsAcmeDirectoryUrlCopied.off(); + }, RESET_COPIED_DELAY); + }} + className="w-10" + > + + +
+ + +
+ + { + navigator.clipboard.writeText(eabKid); + setIsEabKidCopied.on(); + setTimeout(() => { + setIsEabKidCopied.off(); + }, RESET_COPIED_DELAY); + }} + className="w-10" + > + + +
+ + +
+ + { + navigator.clipboard.writeText(eabSecret); + setIsEabSecretCopied.on(); + setTimeout(() => { + setIsEabSecretCopied.off(); + }, RESET_COPIED_DELAY); + }} + className="w-10" + > + + +
+ + )} +
+
+ ); +}; diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx index ae14f2dea..ec660b339 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx @@ -48,23 +48,15 @@ export const CertificateTemplatesV2Tab = () => { const handleDeleteConfirm = async () => { if (!selectedTemplate) return; - try { - await deleteTemplateV2.mutateAsync({ - templateId: selectedTemplate.id - }); - setIsDeleteModalOpen(false); - setSelectedTemplate(null); - createNotification({ - text: `Certificate template "${selectedTemplate.name}" deleted successfully`, - type: "success" - }); - } catch (error) { - console.error("Failed to delete template:", error); - createNotification({ - text: "Failed to delete certificate template", - type: "error" - }); - } + await deleteTemplateV2.mutateAsync({ + templateId: selectedTemplate.id + }); + setIsDeleteModalOpen(false); + setSelectedTemplate(null); + createNotification({ + text: `Certificate template "${selectedTemplate.name}" deleted successfully`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx index a96beccad..b929ce36e 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx @@ -251,7 +251,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" const { control, handleSubmit, reset, watch, setValue, formState } = useForm({ resolver: zodResolver(templateSchema), - defaultValues: getDefaultValues() + defaultValues: getDefaultValues(), + mode: "onChange", + reValidateMode: "onChange", + criteriaMode: "all" }); useEffect(() => { @@ -407,59 +410,51 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }; const onFormSubmit = async (data: FormData) => { - try { - if (!currentProject?.id && !isEdit) return; + if (!currentProject?.id && !isEdit) return; - const hasEmptyAttributeValues = data.attributes?.some( - (attr) => !attr.value || attr.value.length === 0 || attr.value.some((v) => !v.trim()) - ); + const hasEmptyAttributeValues = data.attributes?.some( + (attr) => !attr.value || attr.value.length === 0 || attr.value.some((v) => !v.trim()) + ); - const hasEmptySanValues = data.subjectAlternativeNames?.some( - (san) => !san.value || san.value.length === 0 || san.value.some((v) => !v.trim()) - ); - - if (hasEmptyAttributeValues || hasEmptySanValues) { - createNotification({ - text: "All values must be non-empty. Use wildcards (*) if needed.", - type: "error" - }); - return; - } - - const transformedData = transformToApiFormat(data); - - if (isEdit) { - const updateData = { - templateId: template.id, - ...transformedData - }; - await updateTemplate.mutateAsync(updateData); - } else { - if (!currentProject?.id) { - throw new Error("Project ID is required for creating a template"); - } - - const createData = { - projectId: currentProject.id, - ...transformedData - }; - await createTemplate.mutateAsync(createData); - } + const hasEmptySanValues = data.subjectAlternativeNames?.some( + (san) => !san.value || san.value.length === 0 || san.value.some((v) => !v.trim()) + ); + if (hasEmptyAttributeValues || hasEmptySanValues) { createNotification({ - text: `Certificate template ${isEdit ? "updated" : "created"} successfully`, - type: "success" - }); - - reset(); - onClose(); - } catch (error) { - console.error(`Error ${isEdit ? "updating" : "creating"} template:`, error); - createNotification({ - text: `Failed to ${isEdit ? "update" : "create"} certificate template`, + text: "All values must be non-empty. Use wildcards (*) if needed.", type: "error" }); + return; } + + const transformedData = transformToApiFormat(data); + + if (isEdit) { + const updateData = { + templateId: template.id, + ...transformedData + }; + await updateTemplate.mutateAsync(updateData); + } else { + if (!currentProject?.id) { + throw new Error("Project ID is required for creating a template"); + } + + const createData = { + projectId: currentProject.id, + ...transformedData + }; + await createTemplate.mutateAsync(createData); + } + + createNotification({ + text: `Certificate template ${isEdit ? "updated" : "created"} successfully`, + type: "success" + }); + + reset(); + onClose(); }; const addAttribute = () => { diff --git a/frontend/src/pages/kms/KmipPage/components/DeleteKmipClientModal.tsx b/frontend/src/pages/kms/KmipPage/components/DeleteKmipClientModal.tsx index fe9c956ab..2d6accf76 100644 --- a/frontend/src/pages/kms/KmipPage/components/DeleteKmipClientModal.tsx +++ b/frontend/src/pages/kms/KmipPage/components/DeleteKmipClientModal.tsx @@ -17,28 +17,17 @@ export const DeleteKmipClientModal = ({ isOpen, onOpenChange, kmipClient }: Prop const { id, projectId, name } = kmipClient; const handleDeleteKmipClient = async () => { - try { - await deleteKmipClients.mutateAsync({ - id, - projectId - }); + await deleteKmipClients.mutateAsync({ + id, + projectId + }); - createNotification({ - text: "KMIP client successfully deleted", - type: "success" - }); + createNotification({ + text: "KMIP client successfully deleted", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete KMIP client"; - - createNotification({ - text, - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx b/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx index b3f3fb380..f10d077a4 100644 --- a/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx +++ b/frontend/src/pages/kms/KmipPage/components/KmipClientModal.tsx @@ -98,20 +98,12 @@ const KmipClientForm = ({ onComplete, kmipClient }: FormProps) => { .map(([key]) => key as KmipPermission) }); - try { - await mutation; - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "added"} KMIP client`, - type: "success" - }); - onComplete(); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${isUpdate ? "update" : "add"} KMIP client`, - type: "error" - }); - } + await mutation; + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "added"} KMIP client`, + type: "success" + }); + onComplete(); }; return ( diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx index 4c9cf685b..4fd6b3419 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekDecryptModal.tsx @@ -52,23 +52,15 @@ const DecryptForm = ({ cmek }: FormProps) => { }); const handleDecryptData = async (formData: FormData) => { - try { - const data = await cmekDecrypt.mutateAsync({ ...formData, keyId: cmek.id }); - createNotification({ - text: "Successfully decrypted data", - type: "success" - }); + const data = await cmekDecrypt.mutateAsync({ ...formData, keyId: cmek.id }); + createNotification({ + text: "Successfully decrypted data", + type: "success" + }); - setPlaintext( - shouldDecode ? Buffer.from(decodeBase64(data.plaintext)).toString("utf8") : data.plaintext - ); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to decrypt data", - type: "error" - }); - } + setPlaintext( + shouldDecode ? Buffer.from(decodeBase64(data.plaintext)).toString("utf8") : data.plaintext + ); }; useEffect(() => { diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekEncryptModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekEncryptModal.tsx index 4fb09cf4e..a5bf01d5a 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekEncryptModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekEncryptModal.tsx @@ -53,19 +53,11 @@ const EncryptForm = ({ cmek }: FormProps) => { }); const handleEncryptData = async (formData: FormData) => { - try { - await cmekEncrypt.mutateAsync({ ...formData, keyId: cmek.id }); - createNotification({ - text: "Successfully encrypted data", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to encrypt data", - type: "error" - }); - } + await cmekEncrypt.mutateAsync({ ...formData, keyId: cmek.id }); + createNotification({ + text: "Successfully encrypted data", + type: "success" + }); }; const ciphertext = cmekEncrypt.data?.ciphertext; diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx index c3cccd678..e79d91e15 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekModal.tsx @@ -86,20 +86,12 @@ const CmekForm = ({ onComplete, cmek }: FormProps) => { encryptionAlgorithm: encryptionAlgorithm as AsymmetricKeyAlgorithm | SymmetricKeyAlgorithm }); - try { - await mutation; - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "added"} key`, - type: "success" - }); - onComplete(); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${isUpdate ? "update" : "add"} key`, - type: "error" - }); - } + await mutation; + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "added"} key`, + type: "success" + }); + onComplete(); }; const selectedKeyUsage = watch("keyUsage"); diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekSignModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekSignModal.tsx index fec66a31f..e52c03dbe 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekSignModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekSignModal.tsx @@ -59,19 +59,11 @@ const SignForm = ({ cmek }: FormProps) => { }); const handleSignData = async (formData: FormData) => { - try { - await cmekSign.mutateAsync({ ...formData, keyId: cmek.id }); - createNotification({ - text: "Successfully signed data", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to sign data", - type: "error" - }); - } + await cmekSign.mutateAsync({ ...formData, keyId: cmek.id }); + createNotification({ + text: "Successfully signed data", + type: "success" + }); }; const signature = cmekSign.data?.signature; diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx index a6675ea88..c9a0054c5 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekTable.tsx @@ -152,28 +152,16 @@ export const CmekTable = () => { const updateCmek = useUpdateCmek(); const handleDisableCmek = async ({ id: keyId, isDisabled }: TCmek) => { - try { - await updateCmek.mutateAsync({ - keyId, - projectId, - isDisabled: !isDisabled - }); + await updateCmek.mutateAsync({ + keyId, + projectId, + isDisabled: !isDisabled + }); - createNotification({ - text: `Key successfully ${isDisabled ? "enabled" : "disabled"}`, - type: "success" - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = - error?.response?.data?.message ?? `Failed to ${isDisabled ? "enable" : "disable"} key`; - - createNotification({ - text, - type: "error" - }); - } + createNotification({ + text: `Key successfully ${isDisabled ? "enabled" : "disabled"}`, + type: "success" + }); }; const cannotEditKey = permission.cannot( diff --git a/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx b/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx index 5c9eac072..d8203e373 100644 --- a/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/CmekVerifyModal.tsx @@ -69,25 +69,17 @@ const VerifyForm = ({ cmek }: FormProps) => { }); const handleVerifyData = async (formData: FormData) => { - try { - const result = await cmekVerify.mutateAsync({ ...formData, keyId: cmek.id }); + const result = await cmekVerify.mutateAsync({ ...formData, keyId: cmek.id }); - if (result.signatureValid) { - createNotification({ - text: "Successfully verified signature", - type: "success" - }); - } else { - createNotification({ - title: "Signature Verification Failed", - text: "The signature is invalid. The signature was not created using the same signing algorithm and key as the one used to sign the data. The data and signature may have been tampered with.", - type: "error" - }); - } - } catch (err) { - console.error(err); + if (result.signatureValid) { createNotification({ - text: "Failed to sign data", + text: "Successfully verified signature", + type: "success" + }); + } else { + createNotification({ + title: "Signature Verification Failed", + text: "The signature is invalid. The signature was not created using the same signing algorithm and key as the one used to sign the data. The data and signature may have been tampered with.", type: "error" }); } diff --git a/frontend/src/pages/kms/OverviewPage/components/DeleteCmekModal.tsx b/frontend/src/pages/kms/OverviewPage/components/DeleteCmekModal.tsx index 4c5528c39..62389d1ba 100644 --- a/frontend/src/pages/kms/OverviewPage/components/DeleteCmekModal.tsx +++ b/frontend/src/pages/kms/OverviewPage/components/DeleteCmekModal.tsx @@ -16,28 +16,17 @@ export const DeleteCmekModal = ({ isOpen, onOpenChange, cmek }: Props) => { const { id: keyId, projectId, name } = cmek; const handleDeleteCmek = async () => { - try { - await deleteCmek.mutateAsync({ - keyId, - projectId - }); + await deleteCmek.mutateAsync({ + keyId, + projectId + }); - createNotification({ - text: "Key successfully deleted", - type: "success" - }); + createNotification({ + text: "Key successfully deleted", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete key"; - - createNotification({ - text, - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx index 908be4935..4b6ea0858 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupModal.tsx @@ -74,43 +74,36 @@ export const OrgGroupModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Pr }, [popUp?.group?.data, roles]); const onGroupModalSubmit = async ({ name, slug, role }: TGroupFormData) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - const group = popUp?.group?.data as { - groupId: string; - name: string; - slug: string; - }; + const group = popUp?.group?.data as { + groupId: string; + name: string; + slug: string; + }; - if (group) { - await updateMutateAsync({ - id: group.groupId, - name, - slug, - role: role.slug || undefined - }); - } else { - await createMutateAsync({ - name, - slug, - organizationId: currentOrg.id, - role: role.slug || undefined - }); - } - handlePopUpToggle("group", false); - reset(); - - createNotification({ - text: `Successfully ${popUp?.group?.data ? "updated" : "created"} group`, - type: "success" + if (group) { + await updateMutateAsync({ + id: group.groupId, + name, + slug, + role: role.slug || undefined }); - } catch { - createNotification({ - text: `Failed to ${popUp?.group?.data ? "updated" : "created"} group`, - type: "error" + } else { + await createMutateAsync({ + name, + slug, + organizationId: currentOrg.id, + role: role.slug || undefined }); } + handlePopUpToggle("group", false); + reset(); + + createNotification({ + text: `Successfully ${popUp?.group?.data ? "updated" : "created"} group`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx index 416b3c191..c9a0a23f4 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsSection.tsx @@ -36,21 +36,13 @@ export const OrgGroupsSection = () => { }; const onDeleteGroupSubmit = async ({ name, groupId }: { name: string; groupId: string }) => { - try { - await deleteMutateAsync({ - id: groupId - }); - createNotification({ - text: `Successfully deleted the group named ${name}`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to delete the group named ${name}`, - type: "error" - }); - } + await deleteMutateAsync({ + id: groupId + }); + createNotification({ + text: `Successfully deleted the group named ${name}`, + type: "success" + }); handlePopUpClose("deleteGroup"); }; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx index 61119fce1..cc6d7c7aa 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgGroupsTab/components/OrgGroupsSection/OrgGroupsTable.tsx @@ -79,23 +79,15 @@ export const OrgGroupsTable = ({ handlePopUpOpen }: Props) => { const { data: roles } = useGetOrgRoles(orgId); const handleChangeRole = async ({ id, role }: { id: string; role: string }) => { - try { - await updateMutateAsync({ - id, - role - }); + await updateMutateAsync({ + id, + role + }); - createNotification({ - text: "Successfully updated group role", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update group role", - type: "error" - }); - } + createNotification({ + text: "Successfully updated group role", + type: "success" + }); }; const { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm.tsx index 74850b475..94c9c49a3 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAliCloudAuthForm.tsx @@ -140,45 +140,38 @@ export const IdentityAliCloudAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - allowedArns, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - allowedArns, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + allowedArns, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + allowedArns, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthTemplateModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthTemplateModal.tsx index 3bd423982..6db81f19e 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthTemplateModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAuthTemplateModal.tsx @@ -103,56 +103,44 @@ export const IdentityAuthTemplateModal = ({ popUp, handlePopUpToggle }: Props) = const selectedMethod = watch("method"); const onFormSubmit = async (data: FormData) => { - try { - if (isEdit && template) { - await updateTemplate({ - templateId: template.id, - organizationId: orgId, - name: data.name, - templateFields: { - url: data.url, - bindDN: data.bindDN, - bindPass: data.bindPass, - searchBase: data.searchBase, - ldapCaCertificate: data.ldapCaCertificate - } - }); - createNotification({ - text: "Successfully updated auth template", - type: "success" - }); - } else { - await createTemplate({ - organizationId: orgId, - name: data.name, - authMethod: data.method, - templateFields: { - url: data.url, - bindDN: data.bindDN, - bindPass: data.bindPass, - searchBase: data.searchBase, - ldapCaCertificate: data.ldapCaCertificate - } - }); - createNotification({ - text: "Successfully created auth template", - type: "success" - }); - } - - handlePopUpToggle(isEdit ? "editTemplate" : "createTemplate", false); - reset(); - } catch (err) { - console.error(err); - const error = err as any; - const text = - error?.response?.data?.message ?? `Failed to ${isEdit ? "update" : "create"} auth template`; - + if (isEdit && template) { + await updateTemplate({ + templateId: template.id, + organizationId: orgId, + name: data.name, + templateFields: { + url: data.url, + bindDN: data.bindDN, + bindPass: data.bindPass, + searchBase: data.searchBase, + ldapCaCertificate: data.ldapCaCertificate + } + }); createNotification({ - text, - type: "error" + text: "Successfully updated auth template", + type: "success" + }); + } else { + await createTemplate({ + organizationId: orgId, + name: data.name, + authMethod: data.method, + templateFields: { + url: data.url, + bindDN: data.bindDN, + bindPass: data.bindPass, + searchBase: data.searchBase, + ldapCaCertificate: data.ldapCaCertificate + } + }); + createNotification({ + text: "Successfully created auth template", + type: "success" }); } + + handlePopUpToggle(isEdit ? "editTemplate" : "createTemplate", false); + reset(); }; const handleClose = () => { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx index 43b86e1b0..a05dcf8df 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAwsAuthForm.tsx @@ -150,49 +150,42 @@ export const IdentityAwsAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - stsEndpoint, - allowedPrincipalArns, - allowedAccountIds, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - stsEndpoint: stsEndpoint || "", - allowedPrincipalArns: allowedPrincipalArns || "", - allowedAccountIds: allowedAccountIds || "", - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + stsEndpoint, + allowedPrincipalArns, + allowedAccountIds, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + stsEndpoint: stsEndpoint || "", + allowedPrincipalArns: allowedPrincipalArns || "", + allowedAccountIds: allowedAccountIds || "", + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx index c99151c8a..ada799d13 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityAzureAuthForm.tsx @@ -146,49 +146,42 @@ export const IdentityAzureAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - identityId, - tenantId, - resource, - allowedServicePrincipalIds, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - tenantId: tenantId || "", - resource: resource || "", - allowedServicePrincipalIds: allowedServicePrincipalIds || "", - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + identityId, + tenantId, + resource, + allowedServicePrincipalIds, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + tenantId: tenantId || "", + resource: resource || "", + allowedServicePrincipalIds: allowedServicePrincipalIds || "", + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx index f99cfb235..960d4b561 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityGcpAuthForm.tsx @@ -155,51 +155,44 @@ export const IdentityGcpAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - identityId, - organizationId: orgId, - type, - allowedServiceAccounts, - allowedProjects, - allowedZones, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - identityId, - organizationId: orgId, - type, - allowedServiceAccounts: allowedServiceAccounts || "", - allowedProjects: allowedProjects || "", - allowedZones: allowedZones || "", - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + identityId, + organizationId: orgId, + type, + allowedServiceAccounts, + allowedProjects, + allowedZones, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + identityId, + organizationId: orgId, + type, + allowedServiceAccounts: allowedServiceAccounts || "", + allowedProjects: allowedProjects || "", + allowedZones: allowedZones || "", + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx index 96f4af954..10eab486d 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityJwtAuthForm.tsx @@ -220,61 +220,54 @@ export const IdentityJwtAuthForm = ({ boundClaims, boundSubject }: FormData) => { - try { - if (!identityId) { - return; - } + if (!identityId) { + return; + } - if (data) { - await updateMutateAsync({ - identityId, - organizationId: orgId, - configurationType, - jwksUrl, - jwksCaCert, - publicKeys: publicKeys?.map((field) => field.value).filter(Boolean), - boundIssuer, - boundAudiences, - boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), - boundSubject, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - identityId, - configurationType, - jwksUrl, - jwksCaCert, - publicKeys: publicKeys?.map((field) => field.value).filter(Boolean), - boundIssuer, - boundAudiences, - boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), - boundSubject, - organizationId: orgId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + identityId, + organizationId: orgId, + configurationType, + jwksUrl, + jwksCaCert, + publicKeys: publicKeys?.map((field) => field.value).filter(Boolean), + boundIssuer, + boundAudiences, + boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), + boundSubject, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + identityId, + configurationType, + jwksUrl, + jwksCaCert, + publicKeys: publicKeys?.map((field) => field.value).filter(Boolean), + boundIssuer, + boundAudiences, + boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), + boundSubject, + organizationId: orgId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx index 921e62a7a..c4ae8bac0 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx @@ -314,71 +314,64 @@ export const IdentityKubernetesAuthForm = ({ tokenReviewMode, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - ...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api - ? { - kubernetesHost: kubernetesHost || "" - } - : { - kubernetesHost: null - }), - tokenReviewerJwt: tokenReviewerJwt || null, - allowedNames, - allowedNamespaces, - allowedAudience, - caCert, - identityId, - gatewayId: gatewayId || null, - tokenReviewMode, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - ...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api - ? { - kubernetesHost: kubernetesHost || "" - } - : { - kubernetesHost: null - }), - tokenReviewerJwt: tokenReviewerJwt || undefined, - allowedNames: allowedNames || "", - allowedNamespaces: allowedNamespaces || "", - allowedAudience: allowedAudience || "", - gatewayId: gatewayId || null, - caCert: caCert || "", - tokenReviewMode, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + ...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api + ? { + kubernetesHost: kubernetesHost || "" + } + : { + kubernetesHost: null + }), + tokenReviewerJwt: tokenReviewerJwt || null, + allowedNames, + allowedNamespaces, + allowedAudience, + caCert, + identityId, + gatewayId: gatewayId || null, + tokenReviewMode, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + ...(tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Api + ? { + kubernetesHost: kubernetesHost || "" + } + : { + kubernetesHost: null + }), + tokenReviewerJwt: tokenReviewerJwt || undefined, + allowedNames: allowedNames || "", + allowedNamespaces: allowedNamespaces || "", + allowedAudience: allowedAudience || "", + gatewayId: gatewayId || null, + caCert: caCert || "", + tokenReviewMode, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; const tokenReviewMode = watch("tokenReviewMode"); diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx index 86da3abe4..a3ab70de0 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm.tsx @@ -316,83 +316,76 @@ export const IdentityLdapAuthForm = ({ }, [subscription, handlePopUpOpen, handlePopUpToggle]); const onFormSubmit = async (formData: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - const { - scope: submissionScope, - templateId: submissionTemplateId, - url: submissionUrl, - bindDN: submissionBindDN, - bindPass: submissionBindPass, - searchBase: submissionSearchBase, - searchFilter, - ldapCaCertificate, - allowedFields, - accessTokenTTL, - accessTokenMaxTTL, - accessTokenNumUsesLimit, - accessTokenTrustedIps, - lockoutEnabled, - lockoutThreshold, - lockoutDurationValue, - lockoutDurationUnit, - lockoutCounterResetValue, - lockoutCounterResetUnit - } = formData; + const { + scope: submissionScope, + templateId: submissionTemplateId, + url: submissionUrl, + bindDN: submissionBindDN, + bindPass: submissionBindPass, + searchBase: submissionSearchBase, + searchFilter, + ldapCaCertificate, + allowedFields, + accessTokenTTL, + accessTokenMaxTTL, + accessTokenNumUsesLimit, + accessTokenTrustedIps, + lockoutEnabled, + lockoutThreshold, + lockoutDurationValue, + lockoutDurationUnit, + lockoutCounterResetValue, + lockoutCounterResetUnit + } = formData; - const lockoutDurationSeconds = ms(`${lockoutDurationValue}${lockoutDurationUnit}`) / 1000; - const lockoutCounterResetSeconds = - ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000; + const lockoutDurationSeconds = ms(`${lockoutDurationValue}${lockoutDurationUnit}`) / 1000; + const lockoutCounterResetSeconds = + ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000; - const basePayload = { - organizationId: orgId, - identityId, - searchFilter, - ldapCaCertificate, - allowedFields, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps, - lockoutEnabled, - lockoutThreshold: Number(lockoutThreshold), - lockoutDurationSeconds, - lockoutCounterResetSeconds - }; + const basePayload = { + organizationId: orgId, + identityId, + searchFilter, + ldapCaCertificate, + allowedFields, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps, + lockoutEnabled, + lockoutThreshold: Number(lockoutThreshold), + lockoutDurationSeconds, + lockoutCounterResetSeconds + }; - // Add scope-specific fields - const payload = - submissionScope === "template" - ? { ...basePayload, templateId: submissionTemplateId } - : { - ...basePayload, - url: submissionUrl, - bindDN: submissionBindDN, - bindPass: submissionBindPass, - searchBase: submissionSearchBase - }; + // Add scope-specific fields + const payload = + submissionScope === "template" + ? { ...basePayload, templateId: submissionTemplateId } + : { + ...basePayload, + url: submissionUrl, + bindDN: submissionBindDN, + bindPass: submissionBindPass, + searchBase: submissionSearchBase + }; - if (data) { - await updateMutateAsync(payload); - } else { - await addMutateAsync(payload); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" - }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" - }); + if (data) { + await updateMutateAsync(payload); + } else { + await addMutateAsync(payload); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLinkForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLinkForm.tsx index b0977437b..545aea5aa 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLinkForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLinkForm.tsx @@ -42,31 +42,20 @@ export const IdentityLinkForm = ({ onClose }: Props) => { }); const onFormSubmit = async ({ identity, role }: FormData) => { - try { - await createMutateAsync({ - identityId: identity.id, - roles: [{ role: role.slug, isTemporary: false }] - }); - createNotification({ - text: "Successfully linked identity", - type: "success" - }); - navigate({ - to: "/organization/identities/$identityId", - params: { - identityId: identity.id - } - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to link identity"; - - createNotification({ - text, - type: "error" - }); - } + await createMutateAsync({ + identityId: identity.id, + roles: [{ role: role.slug, isTemporary: false }] + }); + createNotification({ + text: "Successfully linked identity", + type: "success" + }); + navigate({ + to: "/organization/identities/$identityId", + params: { + identityId: identity.id + } + }); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx index dacbba428..09e779fa9 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx @@ -108,81 +108,68 @@ export const IdentityModal = ({ popUp, handlePopUpToggle }: Props) => { }, [popUp?.identity?.data, roles]); const onFormSubmit = async ({ name, role, metadata, hasDeleteProtection }: FormData) => { - try { - const identity = popUp?.identity?.data as { - identityId: string; - name: string; - role: string; - hasDeleteProtection: boolean; - orgId: string; - }; + const identity = popUp?.identity?.data as { + identityId: string; + name: string; + role: string; + hasDeleteProtection: boolean; + orgId: string; + }; - if (identity) { - // update + if (identity) { + // update - await updateMutateAsync({ - identityId: identity.identityId, - name, - role: role.slug || undefined, - hasDeleteProtection, - organizationId: orgId, - metadata - }); - - handlePopUpToggle("identity", false); - } else { - // create - - const { id: createdId } = await createMutateAsync({ - name, - role: role.slug || undefined, - hasDeleteProtection, - organizationId: orgId, - metadata - }); - - await addMutateAsync({ - organizationId: orgId, - identityId: createdId, - clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], - accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], - accessTokenTTL: 2592000, - accessTokenMaxTTL: 2592000, - accessTokenNumUsesLimit: 0, - accessTokenPeriod: 0, - lockoutEnabled: true, - lockoutThreshold: 3, - lockoutDurationSeconds: 300, - lockoutCounterResetSeconds: 30 - }); - - handlePopUpToggle("identity", false); - navigate({ - to: "/organization/identities/$identityId", - params: { - identityId: createdId - } - }); - } - - createNotification({ - text: `Successfully ${popUp?.identity?.data ? "updated" : "created"} identity`, - type: "success" + await updateMutateAsync({ + identityId: identity.identityId, + name, + role: role.slug || undefined, + hasDeleteProtection, + organizationId: orgId, + metadata }); - reset(); - } catch (err) { - console.error(err); - const error = err as any; - const text = - error?.response?.data?.message ?? - `Failed to ${popUp?.identity?.data ? "update" : "create"} identity`; + handlePopUpToggle("identity", false); + } else { + // create - createNotification({ - text, - type: "error" + const { id: createdId } = await createMutateAsync({ + name, + role: role.slug || undefined, + hasDeleteProtection, + organizationId: orgId, + metadata + }); + + await addMutateAsync({ + organizationId: orgId, + identityId: createdId, + clientSecretTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }], + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenPeriod: 0, + lockoutEnabled: true, + lockoutThreshold: 3, + lockoutDurationSeconds: 300, + lockoutCounterResetSeconds: 30 + }); + + handlePopUpToggle("identity", false); + navigate({ + to: "/organization/identities/$identityId", + params: { + identityId: createdId + } }); } + + createNotification({ + text: `Successfully ${popUp?.identity?.data ? "updated" : "created"} identity`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx index 0fd7b45a0..3ebfceb4a 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOciAuthForm.tsx @@ -152,47 +152,40 @@ export const IdentityOciAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - tenancyOcid, - allowedUsernames, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - tenancyOcid, - allowedUsernames: allowedUsernames || undefined, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + tenancyOcid, + allowedUsernames, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + tenancyOcid, + allowedUsernames: allowedUsernames || undefined, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx index 87ab18eb6..7503e6f45 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx @@ -204,63 +204,56 @@ export const IdentityOidcAuthForm = ({ claimMetadataMapping, boundSubject }: FormData) => { - try { - if (!identityId) { - return; - } + if (!identityId) { + return; + } - if (data) { - await updateMutateAsync({ - identityId, - organizationId: orgId, - oidcDiscoveryUrl, - caCert, - boundIssuer, - boundAudiences, - boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), - claimMetadataMapping: claimMetadataMapping - ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value])) - : undefined, - boundSubject, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - identityId, - oidcDiscoveryUrl, - caCert, - boundIssuer, - boundAudiences, - boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), - claimMetadataMapping: claimMetadataMapping - ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value])) - : undefined, - boundSubject, - organizationId: orgId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + identityId, + organizationId: orgId, + oidcDiscoveryUrl, + caCert, + boundIssuer, + boundAudiences, + boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), + claimMetadataMapping: claimMetadataMapping + ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value])) + : undefined, + boundSubject, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + identityId, + oidcDiscoveryUrl, + caCert, + boundIssuer, + boundAudiences, + boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])), + claimMetadataMapping: claimMetadataMapping + ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value])) + : undefined, + boundSubject, + organizationId: orgId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx index 93f4084b3..8380836cb 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentitySection.tsx @@ -56,53 +56,31 @@ export const IdentitySection = withPermission( const isEnterprise = subscription?.slug === "enterprise"; const onDeleteIdentitySubmit = async (identityId: string) => { - try { - await deleteMutateAsync({ - identityId, - organizationId: orgId - }); + await deleteMutateAsync({ + identityId, + organizationId: orgId + }); - createNotification({ - text: "Successfully deleted identity", - type: "success" - }); + createNotification({ + text: "Successfully deleted identity", + type: "success" + }); - handlePopUpClose("deleteIdentity"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete identity"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteIdentity"); }; const onDeleteTemplateSubmit = async (templateId: string) => { - try { - await deleteTemplateMutateAsync({ - templateId, - organizationId: orgId - }); + await deleteTemplateMutateAsync({ + templateId, + organizationId: orgId + }); - createNotification({ - text: "Successfully deleted template", - type: "success" - }); + createNotification({ + text: "Successfully deleted template", + type: "success" + }); - handlePopUpClose("deleteTemplate"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete template"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteTemplate"); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx index c4c1561e7..d202b909a 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTable.tsx @@ -145,27 +145,16 @@ export const IdentityTable = ({ handlePopUpOpen }: Props) => { }; const handleChangeRole = async ({ identityId, role }: { identityId: string; role: string }) => { - try { - await updateMutateAsync({ - identityId, - role, - organizationId - }); + await updateMutateAsync({ + identityId, + role, + organizationId + }); - createNotification({ - text: "Successfully updated identity role", - type: "success" - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to update identity role"; - - createNotification({ - text, - type: "error" - }); - } + createNotification({ + text: "Successfully updated identity role", + type: "success" + }); }; const handleRoleToggle = useCallback( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm.tsx index fc79035b1..5666be39f 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTlsCertAuthForm.tsx @@ -137,47 +137,40 @@ export const IdentityTlsCertAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - caCertificate, - allowedCommonNames: allowedCommonNames || null, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - caCertificate, - allowedCommonNames: allowedCommonNames || undefined, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + caCertificate, + allowedCommonNames: allowedCommonNames || null, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + caCertificate, + allowedCommonNames: allowedCommonNames || undefined, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx index 502580c9d..af2404c5c 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityTokenAuthForm.tsx @@ -130,43 +130,36 @@ export const IdentityTokenAuthForm = ({ accessTokenNumUsesLimit, accessTokenTrustedIps }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - if (data) { - await updateMutateAsync({ - organizationId: orgId, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } else { - await addMutateAsync({ - organizationId: orgId, - identityId, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, - type: "success" + if (data) { + await updateMutateAsync({ + organizationId: orgId, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); - - reset(); - } catch { - createNotification({ - text: `Failed to ${isUpdate ? "update" : "configure"} identity`, - type: "error" + } else { + await addMutateAsync({ + organizationId: orgId, + identityId, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx index dbaeff687..d4de4bd0e 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityUniversalAuthForm.tsx @@ -223,64 +223,55 @@ export const IdentityUniversalAuthForm = ({ lockoutCounterResetValue, lockoutCounterResetUnit }: FormData) => { - try { - if (!identityId) return; + if (!identityId) return; - const lockoutDurationSeconds = ms(`${lockoutDurationValue}${lockoutDurationUnit}`) / 1000; - const lockoutCounterResetSeconds = - ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000; + const lockoutDurationSeconds = ms(`${lockoutDurationValue}${lockoutDurationUnit}`) / 1000; + const lockoutCounterResetSeconds = + ms(`${lockoutCounterResetValue}${lockoutCounterResetUnit}`) / 1000; - if (data) { - // update universal auth configuration - await updateMutateAsync({ - organizationId: orgId, - identityId, - clientSecretTrustedIps, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps, - accessTokenPeriod: Number(accessTokenPeriod), - lockoutEnabled, - lockoutThreshold: Number(lockoutThreshold), - lockoutDurationSeconds, - lockoutCounterResetSeconds - }); - } else { - // create new universal auth configuration - - await addMutateAsync({ - organizationId: orgId, - identityId, - clientSecretTrustedIps, - accessTokenTTL: Number(accessTokenTTL), - accessTokenMaxTTL: Number(accessTokenMaxTTL), - accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), - accessTokenTrustedIps, - accessTokenPeriod: Number(accessTokenPeriod), - lockoutEnabled, - lockoutThreshold: Number(lockoutThreshold), - lockoutDurationSeconds: Number(lockoutDurationSeconds), - lockoutCounterResetSeconds: Number(lockoutCounterResetSeconds) - }); - } - - handlePopUpToggle("identityAuthMethod", false); - - createNotification({ - text: `Successfully ${isUpdate ? "updated" : "created"} auth method`, - type: "success" + if (data) { + // update universal auth configuration + await updateMutateAsync({ + organizationId: orgId, + identityId, + clientSecretTrustedIps, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps, + accessTokenPeriod: Number(accessTokenPeriod), + lockoutEnabled, + lockoutThreshold: Number(lockoutThreshold), + lockoutDurationSeconds, + lockoutCounterResetSeconds }); + } else { + // create new universal auth configuration - reset(); - } catch { - const text = `Failed to ${isUpdate ? "update" : "configure"} identity`; - - createNotification({ - text, - type: "error" + await addMutateAsync({ + organizationId: orgId, + identityId, + clientSecretTrustedIps, + accessTokenTTL: Number(accessTokenTTL), + accessTokenMaxTTL: Number(accessTokenMaxTTL), + accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit), + accessTokenTrustedIps, + accessTokenPeriod: Number(accessTokenPeriod), + lockoutEnabled, + lockoutThreshold: Number(lockoutThreshold), + lockoutDurationSeconds: Number(lockoutDurationSeconds), + lockoutCounterResetSeconds: Number(lockoutCounterResetSeconds) }); } + + handlePopUpToggle("identityAuthMethod", false); + + createNotification({ + text: `Successfully ${isUpdate ? "updated" : "created"} auth method`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx index e49beadae..da88603fc 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddOrgMemberModal.tsx @@ -122,64 +122,55 @@ export const AddOrgMemberModal = ({ } } - try { - const parsedEmails = emails - .replace(/\s/g, "") - .split(",") - .map((email) => { - if (EmailSchema.safeParse(email).success) { - return email.trim(); - } + const parsedEmails = emails + .replace(/\s/g, "") + .split(",") + .map((email) => { + if (EmailSchema.safeParse(email).success) { + return email.trim(); + } - return null; - }); - - if (parsedEmails.includes(null)) { - createNotification({ - text: "Invalid email addresses provided.", - type: "error" - }); - return; - } - - const usernames = emails.split(",").map((email) => email.trim()); - const { data } = await addUsersMutateAsync({ - organizationId: currentOrg?.id, - inviteeEmails: usernames, - organizationRoleSlug: organizationRole.slug + return null; }); - await Promise.allSettled( - selectedProjects.map((el) => - addUserToProject({ - orgId: currentOrg.id, - projectId: el.id, - roleSlugs: [projectRoleSlug], - usernames - }) - ) - ); - - setCompleteInviteLinks(data?.completeInviteLinks ?? null); - - // only show this notification when email is configured. - // A [completeInviteLink] will not be sent if smtp is configured - - if (!data.completeInviteLinks) { - createNotification({ - text: "Successfully invited user to the organization.", - type: "success" - }); - } - } catch (error) { - console.error(error); + if (parsedEmails.includes(null)) { createNotification({ - text: "Failed to invite user to org", + text: "Invalid email addresses provided.", type: "error" }); return; } + const usernames = emails.split(",").map((email) => email.trim()); + const { data } = await addUsersMutateAsync({ + organizationId: currentOrg?.id, + inviteeEmails: usernames, + organizationRoleSlug: organizationRole.slug + }); + + await Promise.allSettled( + selectedProjects.map((el) => + addUserToProject({ + orgId: currentOrg.id, + projectId: el.id, + roleSlugs: [projectRoleSlug], + usernames + }) + ) + ); + + setCompleteInviteLinks(data?.completeInviteLinks ?? null); + + // only show this notification when email is configured. + // A [completeInviteLink] will not be sent if smtp is configured + + if (!data.completeInviteLinks) { + createNotification({ + text: "Successfully invited user to the organization.", + type: "success" + }); + } + if (serverDetails?.emailConfigured) { handlePopUpToggle("addMember", false); } diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddSubOrgMemberModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddSubOrgMemberModal.tsx index 290f8db4b..5edba7cde 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddSubOrgMemberModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/AddSubOrgMemberModal.tsx @@ -106,32 +106,24 @@ export const AddSubOrgMemberModal = ({ onClose }: Props) => { } } - try { - const usernames = users.map((el) => el.username); - await addUsersMutateAsync({ - organizationId: currentOrg?.id, - inviteeEmails: usernames, - organizationRoleSlug: organizationRole.slug - }); + const usernames = users.map((el) => el.username); + await addUsersMutateAsync({ + organizationId: currentOrg?.id, + inviteeEmails: usernames, + organizationRoleSlug: organizationRole.slug + }); - await Promise.allSettled( - selectedProjects.map((el) => - addUserToProject({ - orgId: currentOrg.id, - projectId: el.id, - roleSlugs: [projectRoleSlug], - usernames - }) - ) - ); - onClose(); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to add user to suborganization", - type: "error" - }); - } + await Promise.allSettled( + selectedProjects.map((el) => + addUserToProject({ + orgId: currentOrg.id, + projectId: el.id, + roleSlugs: [projectRoleSlug], + usernames + }) + ) + ); + onClose(); }; const getGroupHeaderLabel = (type: ProjectType) => { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx index ed9c66b3c..0b71dea25 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersSection.tsx @@ -85,46 +85,30 @@ export const OrgMembersSection = () => { }; const onDeactivateMemberSubmit = async (orgMembershipId: string) => { - try { - await updateOrgMembership({ - organizationId: orgId, - membershipId: orgMembershipId, - isActive: false - }); + await updateOrgMembership({ + organizationId: orgId, + membershipId: orgMembershipId, + isActive: false + }); - createNotification({ - text: "Successfully deactivated user in organization", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to deactivate user in organization", - type: "error" - }); - } + createNotification({ + text: "Successfully deactivated user in organization", + type: "success" + }); handlePopUpClose("deactivateMember"); }; const onRemoveMemberSubmit = async (orgMembershipId: string) => { - try { - await deleteMutateAsync({ - orgId, - membershipId: orgMembershipId - }); + await deleteMutateAsync({ + orgId, + membershipId: orgMembershipId + }); - createNotification({ - text: "Successfully removed user from org", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to remove user from the organization", - type: "error" - }); - } + createNotification({ + text: "Successfully removed user from org", + type: "success" + }); handlePopUpClose("removeMember"); }; @@ -132,27 +116,20 @@ export const OrgMembersSection = () => { const { data: members = [] } = useGetOrgUsers(orgId); const handleRemoveMembers = async (selectedMembers: OrgUser[]) => { - try { - await deleteBatchMutateAsync({ - orgId, - membershipIds: selectedMembers - .filter((member) => member.user.id !== userId) - .map((member) => member.id) - }); + await deleteBatchMutateAsync({ + orgId, + membershipIds: selectedMembers + .filter((member) => member.user.id !== userId) + .map((member) => member.id) + }); - createNotification({ - text: "Successfully removed users from organization", - type: "success" - }); + createNotification({ + text: "Successfully removed users from organization", + type: "success" + }); - setSelectedMemberIds([]); - handlePopUpClose("removeMembers"); - } catch { - createNotification({ - text: "Failed to remove users from the organization", - type: "error" - }); - } + setSelectedMemberIds([]); + handlePopUpClose("removeMembers"); }; return ( diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx index 25a694705..66e1a3fa7 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgMembersTab/components/OrgMembersSection/OrgMembersTable.tsx @@ -127,34 +127,26 @@ export const OrgMembersTable = ({ const onRoleChange = async (membershipId: string, role: string) => { if (!currentOrg?.id) return; - try { - // TODO: replace hardcoding default role - const isCustomRole = !["admin", "member", "no-access"].includes(role); + // TODO: replace hardcoding default role + const isCustomRole = !["admin", "member", "no-access"].includes(role); - if (isCustomRole && subscription && !subscription?.rbac) { - handlePopUpOpen("upgradePlan", { - text: "Your current plan does not include access to assigning custom roles to members. To unlock this feature, please upgrade to Infisical Pro plan." - }); - return; - } - - await updateOrgMembership({ - organizationId: currentOrg?.id, - membershipId, - role - }); - - createNotification({ - text: "Successfully updated user role", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to update user role", - type: "error" + if (isCustomRole && subscription && !subscription?.rbac) { + handlePopUpOpen("upgradePlan", { + text: "Your current plan does not include access to assigning custom roles to members. To unlock this feature, please upgrade to Infisical Pro plan." }); + return; } + + await updateOrgMembership({ + organizationId: currentOrg?.id, + membershipId, + role + }); + + createNotification({ + text: "Successfully updated user role", + type: "success" + }); }; const onResendInvite = async (membershipId: string) => { @@ -173,12 +165,6 @@ export const OrgMembersTable = ({ text: "Successfully resent org invitation", type: "success" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to resend org invitation", - type: "error" - }); } finally { setResendInviteId(null); } diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx index a5b481f4b..aa14ac130 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgRoleTabSection/OrgRoleTable.tsx @@ -86,17 +86,12 @@ export const OrgRoleTable = () => { const handleRoleDelete = async () => { const { id } = popUp?.deleteRole?.data as TOrgRole; - try { - await deleteRole({ - orgId, - id - }); - createNotification({ type: "success", text: "Successfully removed the role" }); - handlePopUpClose("deleteRole"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete role" }); - } + await deleteRole({ + orgId, + id + }); + createNotification({ type: "success", text: "Successfully removed the role" }); + handlePopUpClose("deleteRole"); }; const handleSetRoleAsDefault = async (defaultMembershipRoleSlug: string) => { @@ -109,17 +104,12 @@ export const OrgRoleTable = () => { return; } - try { - await updateOrg({ - orgId, - defaultMembershipRoleSlug - }); - createNotification({ type: "success", text: "Successfully updated default membership role" }); - handlePopUpClose("deleteRole"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update default membership role" }); - } + await updateOrg({ + orgId, + defaultMembershipRoleSlug + }); + createNotification({ type: "success", text: "Successfully updated default membership role" }); + handlePopUpClose("deleteRole"); }; const { diff --git a/frontend/src/pages/organization/AccessManagementPage/components/UpgradePrivilegeSystemModal/UpgradePrivilegeSystemModal.tsx b/frontend/src/pages/organization/AccessManagementPage/components/UpgradePrivilegeSystemModal/UpgradePrivilegeSystemModal.tsx index 97aaff73d..c2a200805 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/UpgradePrivilegeSystemModal/UpgradePrivilegeSystemModal.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/UpgradePrivilegeSystemModal/UpgradePrivilegeSystemModal.tsx @@ -44,21 +44,14 @@ export const UpgradePrivilegeSystemModal = ({ isOpen, onOpenChange }: Props) => acknowledgesPermanentChange; const handlePrivilegeSystemUpgrade = async () => { - try { - await upgradePrivilegeSystem(); + await upgradePrivilegeSystem(); - createNotification({ - text: "Privilege system upgrade completed", - type: "success" - }); + createNotification({ + text: "Privilege system upgrade completed", + type: "success" + }); - onOpenChange(false); - } catch { - createNotification({ - text: "Failed to upgrade privilege system", - type: "error" - }); - } + onOpenChange(false); }; const handleClose = () => { diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx index 04292d94f..aca33ffa2 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx @@ -74,24 +74,15 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => { "method" | "name" | "app" | "credentials" | "isPlatformManagedCredentials" > ) => { - try { - const connection = await createAppConnection.mutateAsync({ - ...formData, - projectId - }); - createNotification({ - text: `Successfully added ${appName} Connection`, - type: "success" - }); - onComplete(connection); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to add ${appName} Connection`, - text: err.message, - type: "error" - }); - } + const connection = await createAppConnection.mutateAsync({ + ...formData, + projectId + }); + createNotification({ + text: `Successfully added ${appName} Connection`, + type: "success" + }); + onComplete(connection); }; switch (app) { @@ -194,24 +185,15 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { "method" | "name" | "app" | "credentials" | "isPlatformManagedCredentials" > ) => { - try { - const connection = await updateAppConnection.mutateAsync({ - connectionId: appConnection.id, - ...formData - }); - createNotification({ - text: `Successfully updated ${appName} Connection`, - type: "success" - }); - onComplete(connection); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${appName} Connection`, - text: err.message, - type: "error" - }); - } + const connection = await updateAppConnection.mutateAsync({ + connectionId: appConnection.id, + ...formData + }); + createNotification({ + text: `Successfully updated ${appName} Connection`, + type: "success" + }); + onComplete(connection); }; switch (appConnection.app) { diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/DeleteAppConnectionModal.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/DeleteAppConnectionModal.tsx index e5f6b3684..f5782adbb 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/DeleteAppConnectionModal.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/DeleteAppConnectionModal.tsx @@ -18,26 +18,17 @@ export const DeleteAppConnectionModal = ({ isOpen, onOpenChange, appConnection } const { id: connectionId, name, app } = appConnection; const handleDeleteAppConnection = async () => { - try { - await deleteAppConnection.mutateAsync({ - connectionId, - app - }); + await deleteAppConnection.mutateAsync({ + connectionId, + app + }); - createNotification({ - text: `Successfully removed ${APP_CONNECTION_MAP[app].name} connection`, - type: "success" - }); + createNotification({ + text: `Successfully removed ${APP_CONNECTION_MAP[app].name} connection`, + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to remove ${APP_CONNECTION_MAP[app].name} connection`, - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionDetailsModal.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionDetailsModal.tsx index f46a3b3cf..7d1a542c0 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionDetailsModal.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionDetailsModal.tsx @@ -43,24 +43,15 @@ const Content = ({ appConnection, onComplete }: ContentProps) => { } = form; const onSubmit = async (formData: FormData) => { - try { - await updateAppConnection.mutateAsync({ - connectionId: appConnection.id, - ...formData - }); - createNotification({ - text: `Successfully updated ${appName} Connection`, - type: "success" - }); - onComplete(); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${appName} Connection`, - text: err.message, - type: "error" - }); - } + await updateAppConnection.mutateAsync({ + connectionId: appConnection.id, + ...formData + }); + createNotification({ + text: `Successfully updated ${appName} Connection`, + type: "success" + }); + onComplete(); }; return ( diff --git a/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx b/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx index 71e14e026..6575a0780 100644 --- a/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx +++ b/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx @@ -127,12 +127,7 @@ export const OAuthCallbackPage = () => { projectId, connection }; - } catch (err: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} GitLab Connection`, - text: err?.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -176,12 +171,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (err: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} Azure Key Vault Connection`, - text: err?.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -233,12 +223,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (err: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} Azure App Configuration Connection`, - text: err?.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -290,12 +275,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (err: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} Azure Client Secrets Connection`, - text: err?.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -353,12 +333,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (err: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} Azure DevOps Connection`, - text: err?.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -441,12 +416,7 @@ export const OAuthCallbackPage = () => { }) }); } - } catch (e: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} GitHub Connection`, - text: e.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -498,12 +468,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (e: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} GitHub Radar Connection`, - text: e.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { @@ -553,12 +518,7 @@ export const OAuthCallbackPage = () => { } }); } - } catch (e: any) { - createNotification({ - title: `Failed to ${connectionId ? "update" : "add"} Heroku Connection`, - text: e.message, - type: "error" - }); + } catch { navigate({ to: returnUrl, params: { diff --git a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx index 72561b90d..ece7b522a 100644 --- a/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx +++ b/frontend/src/pages/organization/AuditLogsPage/components/LogsFilter.tsx @@ -1,7 +1,8 @@ /* eslint-disable no-nested-ternary */ import { useMemo } from "react"; import { Controller, useForm } from "react-hook-form"; -import { faCaretDown, faCheckCircle, faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; +import { MultiValue, SingleValue } from "react-select"; +import { faFilterCircleXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { AnimatePresence, motion } from "framer-motion"; @@ -11,13 +12,10 @@ import { Button, DropdownMenu, DropdownMenuContent, - DropdownMenuItem, DropdownMenuTrigger, FilterableSelect, FormControl, - Input, - Select, - SelectItem + Input } from "@app/components/v2"; import { Badge } from "@app/components/v3"; import { useOrganization } from "@app/context"; @@ -132,7 +130,7 @@ export const LogsFilter = ({ presets, setFilter, filter, project }: Props) => {
-
+
@@ -165,7 +163,7 @@ export const LogsFilter = ({ presets, setFilter, filter, project }: Props) => { { - resetField("eventType"); + setValue("eventType", [], { shouldDirty: true }); }} > { name="eventType" render={({ field }) => ( - - -
- {selectedEventTypes?.length === 1 - ? filteredEventTypes.find( - (eventType) => eventType.value === selectedEventTypes[0] - )?.label - : selectedEventTypes?.length === 0 - ? "All events" - : `${selectedEventTypes?.length} events selected`} - -
-
- -
- {filteredEventTypes.length > 0 ? ( - filteredEventTypes.map((eventType) => { - const isSelected = selectedEventTypes?.includes( - eventType.value as EventType - ); - - return ( - - filteredEventTypes.length > 1 && event.preventDefault() - } - onClick={() => { - if ( - selectedEventTypes?.includes(eventType.value as EventType) - ) { - field.onChange( - selectedEventTypes?.filter( - (e: string) => e !== eventType.value - ) - ); - } else { - field.onChange([ - ...(selectedEventTypes || []), - eventType.value - ]); - } - }} - key={`event-type-${eventType.value}`} - icon={ - isSelected ? ( - - ) : ( -
- ) - } - iconPos="left" - className="w-[28.4rem] text-sm" - > - {eventType.label} - - ); - }) - ) : ( -
- )} -
- - + + field.value.includes(eventType.value as EventType) + )} + isMulti + isClearable + onChange={(options) => + field.onChange( + (options as MultiValue<(typeof filteredEventTypes)[number]>).map( + (option) => option.value + ) + ) + } + placeholder="All events" + options={filteredEventTypes} + getOptionValue={(option) => option.value} + getOptionLabel={(option) => option.label} + /> )} /> @@ -250,37 +196,33 @@ export const LogsFilter = ({ presets, setFilter, filter, project }: Props) => { { - resetField("userAgentType"); + setValue("userAgentType", undefined, { shouldDirty: true }); }} > ( + render={({ field: { onChange, value }, fieldState: { error } }) => ( - + value === (userAgentType.value as UserAgentType) + ) ?? null + } + isClearable + onChange={(option) => + onChange((option as SingleValue<(typeof userAgentTypes)[number]>)?.value) + } + placeholder="All sources" + options={userAgentTypes} + getOptionValue={(option) => option.value} + getOptionLabel={(option) => option.label} + /> )} /> @@ -289,10 +231,10 @@ export const LogsFilter = ({ presets, setFilter, filter, project }: Props) => { { - resetField("project"); - resetField("environment"); - setValue("secretPath", ""); - setValue("secretKey", ""); + setValue("project", null, { shouldDirty: true }); + setValue("environment", undefined, { shouldDirty: true }); + setValue("secretPath", "", { shouldDirty: true }); + setValue("secretKey", "", { shouldDirty: true }); }} > { } className={twMerge(!selectedProject && "opacity-50")} onClear={() => { - resetField("environment"); + setValue("environment", undefined, { shouldDirty: true }); }} > { } className={twMerge(!selectedProject && "opacity-50")} onClear={() => { - setValue("secretPath", ""); + setValue("secretPath", "", { shouldDirty: true }); }} > { className={twMerge(!selectedProject && "opacity-50")} label="Secret Key" onClear={() => { - setValue("secretKey", ""); + setValue("secretKey", "", { shouldDirty: true }); }} > { }, [data]); const onFormSubmit = async ({ name }: { name: string }) => { - try { - if (!currentOrg?.id) return; - if (name === "") return; - await mutateAsync({ - name, - organizationId: currentOrg.id - }); + if (!currentOrg?.id) return; + if (name === "") return; + await mutateAsync({ + name, + organizationId: currentOrg.id + }); - createNotification({ - text: "Successfully updated business name", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update business name", - type: "error" - }); - } + createNotification({ + text: "Successfully updated business name", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/InvoiceEmailSection.tsx b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/InvoiceEmailSection.tsx index 8b471346f..dc05a7208 100644 --- a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/InvoiceEmailSection.tsx +++ b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/InvoiceEmailSection.tsx @@ -35,26 +35,18 @@ export const InvoiceEmailSection = () => { }, [data]); const onFormSubmit = async ({ email }: { email: string }) => { - try { - if (!currentOrg?.id) return; - if (email === "") return; + if (!currentOrg?.id) return; + if (email === "") return; - await mutateAsync({ - email, - organizationId: currentOrg.id - }); + await mutateAsync({ + email, + organizationId: currentOrg.id + }); - createNotification({ - text: "Successfully updated invoice email recipient", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update invoice email recipient", - type: "error" - }); - } + createNotification({ + text: "Successfully updated invoice email recipient", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx index bf0851e11..15a6bc8e9 100644 --- a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx +++ b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/PmtMethodsTable.tsx @@ -39,22 +39,15 @@ export const PmtMethodsTable = () => { }); return; } - try { - await deleteOrgPmtMethod.mutateAsync({ - organizationId: currentOrg.id, - pmtMethodId: pmtMethodToRemove.id - }); - createNotification({ - type: "success", - text: "Successfully removed payment method" - }); - handlePopUpClose("removeCard"); - } catch (error: any) { - createNotification({ - type: "error", - text: error.message ?? "Error removing payment method" - }); - } + await deleteOrgPmtMethod.mutateAsync({ + organizationId: currentOrg.id, + pmtMethodId: pmtMethodToRemove.id + }); + createNotification({ + type: "success", + text: "Successfully removed payment method" + }); + handlePopUpClose("removeCard"); }; return ( diff --git a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDModal.tsx b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDModal.tsx index 01b11e81a..23542ac8d 100644 --- a/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDModal.tsx +++ b/frontend/src/pages/organization/BillingPage/components/BillingDetailsTab/TaxIDModal.tsx @@ -98,26 +98,18 @@ export const TaxIDModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props }); const onTaxIDModalSubmit = async ({ type, value }: AddTaxIDFormData) => { - try { - if (!currentOrg?.id) return; - await addOrgTaxId.mutateAsync({ - organizationId: currentOrg.id, - type, - value - }); + if (!currentOrg?.id) return; + await addOrgTaxId.mutateAsync({ + organizationId: currentOrg.id, + type, + value + }); - createNotification({ - text: "Successfully added Tax ID", - type: "success" - }); - handlePopUpClose("addTaxID"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to add Tax ID", - type: "error" - }); - } + createNotification({ + text: "Successfully added Tax ID", + type: "success" + }); + handlePopUpClose("addTaxID"); }; return ( diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx index 33acbc418..e6d3736fb 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/GroupDetailsByIDPage.tsx @@ -54,27 +54,19 @@ const Page = () => { ] as const); const onDeleteGroupSubmit = async ({ name, id }: { name: string; id: string }) => { - try { - await deleteMutateAsync({ - id - }); - createNotification({ - text: `Successfully deleted the ${name} group`, - type: "success" - }); - navigate({ - to: "/organization/access-management" as const, - search: { - selectedTab: TabSections.Groups - } - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to delete the ${name} group`, - type: "error" - }); - } + await deleteMutateAsync({ + id + }); + createNotification({ + text: `Successfully deleted the ${name} group`, + type: "success" + }); + navigate({ + to: "/organization/access-management" as const, + search: { + selectedTab: TabSections.Groups + } + }); handlePopUpClose("deleteGroup"); }; diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/AddGroupMemberModal.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/AddGroupMemberModal.tsx index 0995830e5..b9d1b184c 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/AddGroupMemberModal.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/AddGroupMemberModal.tsx @@ -63,31 +63,24 @@ export const AddGroupMembersModal = ({ popUp, handlePopUpToggle }: Props) => { const { mutateAsync: addUserToGroupMutateAsync } = useAddUserToGroup(); const handleAddMember = async (username: string) => { - try { - if (!popUpData?.slug) { - createNotification({ - text: "Some data is missing, please refresh the page and try again", - type: "error" - }); - return; - } - - await addUserToGroupMutateAsync({ - groupId: popUpData.groupId, - username, - slug: popUpData.slug - }); - + if (!popUpData?.slug) { createNotification({ - text: "Successfully assigned user to the group", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to assign user to the group", + text: "Some data is missing, please refresh the page and try again", type: "error" }); + return; } + + await addUserToGroupMutateAsync({ + groupId: popUpData.groupId, + username, + slug: popUpData.slug + }); + + createNotification({ + text: "Successfully assigned user to the group", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupCreateUpdateModal.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupCreateUpdateModal.tsx index e4364e874..bc6e3fab5 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupCreateUpdateModal.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupCreateUpdateModal.tsx @@ -77,43 +77,36 @@ export const GroupCreateUpdateModal = ({ popUp, handlePopUpClose, handlePopUpTog }, [popUp?.groupCreateUpdate?.data, roles]); const onGroupModalSubmit = async ({ name, slug, role }: TGroupFormData) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - const group = popUp?.groupCreateUpdate?.data as { - groupId: string; - name: string; - slug: string; - }; + const group = popUp?.groupCreateUpdate?.data as { + groupId: string; + name: string; + slug: string; + }; - if (group) { - await updateMutateAsync({ - id: group.groupId, - name, - slug, - role: role.slug || undefined - }); - } else { - await createMutateAsync({ - name, - slug, - organizationId: currentOrg.id, - role: role.slug || undefined - }); - } - handlePopUpToggle("groupCreateUpdate", false); - reset(); - - createNotification({ - text: `Successfully ${popUp?.groupCreateUpdate?.data ? "updated" : "created"} group`, - type: "success" + if (group) { + await updateMutateAsync({ + id: group.groupId, + name, + slug, + role: role.slug || undefined }); - } catch { - createNotification({ - text: `Failed to ${popUp?.groupCreateUpdate?.data ? "updated" : "created"} group`, - type: "error" + } else { + await createMutateAsync({ + name, + slug, + organizationId: currentOrg.id, + role: role.slug || undefined }); } + handlePopUpToggle("groupCreateUpdate", false); + reset(); + + createNotification({ + text: `Successfully ${popUp?.groupCreateUpdate?.data ? "updated" : "created"} group`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx index 2ca6859b0..c78b5404e 100644 --- a/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx +++ b/frontend/src/pages/organization/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersSection.tsx @@ -29,25 +29,18 @@ export const GroupMembersSection = ({ groupId, groupSlug }: Props) => { const { mutateAsync: removeUserFromGroupMutateAsync } = useRemoveUserFromGroup(); const handleRemoveUserFromGroup = async (username: string) => { - try { - await removeUserFromGroupMutateAsync({ - groupId, - username, - slug: groupSlug - }); + await removeUserFromGroupMutateAsync({ + groupId, + username, + slug: groupSlug + }); - createNotification({ - text: `Successfully removed user ${username} from the group`, - type: "success" - }); + createNotification({ + text: `Successfully removed user ${username} from the group`, + type: "success" + }); - handlePopUpToggle("removeMemberFromGroup", false); - } catch { - createNotification({ - text: `Failed to remove user ${username} from the group`, - type: "error" - }); - } + handlePopUpToggle("removeMemberFromGroup", false); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index 797e740ee..33753da39 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -44,34 +44,23 @@ const Page = () => { ] as const); const onDeleteIdentitySubmit = async (id: string) => { - try { - await deleteIdentity({ - identityId: id, - organizationId: orgId - }); + await deleteIdentity({ + identityId: id, + organizationId: orgId + }); - createNotification({ - text: "Successfully deleted identity", - type: "success" - }); + createNotification({ + text: "Successfully deleted identity", + type: "success" + }); - handlePopUpClose("deleteIdentity"); - navigate({ - to: "/organization/access-management", - search: { - selectedTab: OrgAccessControlTabSections.Identities - } - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete identity"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteIdentity"); + navigate({ + to: "/organization/access-management", + search: { + selectedTab: OrgAccessControlTabSections.Identities + } + }); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx index 2e5c7101c..2f407142a 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityAddToProjectModal.tsx @@ -75,30 +75,19 @@ const Content = ({ identityId, handlePopUpToggle }: Omit) => { }, [workspaces, projectMemberships]); const onFormSubmit = async ({ project: selectedProject, role }: FormData) => { - try { - await addIdentityToWorkspace({ - projectId: selectedProject.id, - identityId, - role: role.slug || undefined - }); + await addIdentityToWorkspace({ + projectId: selectedProject.id, + identityId, + role: role.slug || undefined + }); - createNotification({ - text: "Successfully added identity to project", - type: "success" - }); + createNotification({ + text: "Successfully added identity to project", + type: "success" + }); - reset(); - handlePopUpToggle("addIdentityToProject", false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to add identity to project"; - - createNotification({ - text, - type: "error" - }); - } + reset(); + handlePopUpToggle("addIdentityToProject", false); }; const isProjectSelected = Boolean(projectId); diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx index 6530b9ae5..8d67ef6a4 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityProjectsSection/IdentityProjectsSection.tsx @@ -22,28 +22,17 @@ export const IdentityProjectsSection = ({ identityId }: Props) => { ] as const); const onRemoveIdentitySubmit = async (id: string, projectId: string) => { - try { - await deleteMutateAsync({ - identityId: id, - projectId - }); + await deleteMutateAsync({ + identityId: id, + projectId + }); - createNotification({ - text: "Successfully removed identity from project", - type: "success" - }); + createNotification({ + text: "Successfully removed identity from project", + type: "success" + }); - handlePopUpClose("removeIdentityFromProject"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to remove identity from project"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("removeIdentityFromProject"); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityTokenModal.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityTokenModal.tsx index d2b865a5a..d5c1aa2ac 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityTokenModal.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/IdentityTokenModal.tsx @@ -72,46 +72,33 @@ export const IdentityTokenModal = ({ popUp, handlePopUpToggle }: Props) => { }, [popUp?.token?.data]); const onFormSubmit = async ({ name }: FormData) => { - try { - if (tokenData?.tokenId) { - // update + if (tokenData?.tokenId) { + // update - await updateToken({ - identityId: tokenData.identityId, - tokenId: tokenData.tokenId, - name - }); - - handlePopUpToggle("token", false); - } else { - // create - - const newTokenData = await createToken({ - identityId: tokenData.identityId, - name - }); - - setToken(newTokenData.accessToken); - } - - createNotification({ - text: `Successfully ${popUp?.token?.data ? "updated" : "created"} token`, - type: "success" + await updateToken({ + identityId: tokenData.identityId, + tokenId: tokenData.tokenId, + name }); - reset(); - } catch (err) { - console.error(err); - const error = err as any; - const text = - error?.response?.data?.message ?? - `Failed to ${popUp?.token?.data ? "update" : "create"} token`; + handlePopUpToggle("token", false); + } else { + // create - createNotification({ - text, - type: "error" + const newTokenData = await createToken({ + identityId: tokenData.identityId, + name }); + + setToken(newTokenData.accessToken); } + + createNotification({ + text: `Successfully ${popUp?.token?.data ? "updated" : "created"} token`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthLockoutFields.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthLockoutFields.tsx index b5cb2d901..4ec5871a2 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthLockoutFields.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityAuthLockoutFields.tsx @@ -31,23 +31,15 @@ export const LockoutFields = ({ const [lockedOutState, setLockedOutState] = useState(lockedOut); - async function clearLockouts() { - try { - const deleted = await mutateAsync({ identityId }); - createNotification({ - text: `Successfully cleared ${deleted} lockout${deleted === 1 ? "" : "s"}`, - type: "success" - }); - setLockedOutState(false); - onResetAllLockouts(); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to clear lockouts. Please try again.", - type: "error" - }); - } - } + const clearLockouts = async () => { + const deleted = await mutateAsync({ identityId }); + createNotification({ + text: `Successfully cleared ${deleted} lockout${deleted === 1 ? "" : "s"}`, + type: "success" + }); + setLockedOutState(false); + onResetAllLockouts(); + }; return ( <> diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx index be9c166b3..cc11d9d10 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityTokenAuthTokensTable.tsx @@ -51,28 +51,17 @@ export const IdentityTokenAuthTokensTable = ({ tokens, identityId }: Props) => { tokenId: string; name: string; }) => { - try { - await revokeToken({ - identityId: parentIdentityId, - tokenId - }); + await revokeToken({ + identityId: parentIdentityId, + tokenId + }); - handlePopUpClose("revokeToken"); + handlePopUpClose("revokeToken"); - createNotification({ - text: `Successfully revoked token ${name ?? ""}`, - type: "success" - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to revoke token"; - - createNotification({ - text, - type: "error" - }); - } + createNotification({ + text: `Successfully revoked token ${name ?? ""}`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx index b034b5367..017f30719 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/IdentityUniversalAuthClientSecretsTable.tsx @@ -43,25 +43,17 @@ export const IdentityUniversalAuthClientSecretsTable = ({ clientSecrets, identit const { mutateAsync: revokeClientSecret } = useRevokeIdentityUniversalAuthClientSecret(); const onDeleteClientSecretSubmit = async (clientSecretId: string) => { - try { - await revokeClientSecret({ - identityId, - clientSecretId - }); + await revokeClientSecret({ + identityId, + clientSecretId + }); - handlePopUpToggle("revokeClientSecret", false); + handlePopUpToggle("revokeClientSecret", false); - createNotification({ - text: "Successfully deleted client secret", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete client secret", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted client secret", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx index d49b44f46..be5ae2cc9 100644 --- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx +++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityAuthModal.tsx @@ -140,25 +140,17 @@ export const Content = ({ } const handleDeleteAuthMethod = async () => { - try { - await revokeMethod({ - identityId, - organizationId: orgId - }); + await revokeMethod({ + identityId, + organizationId: orgId + }); - createNotification({ - text: "Successfully removed auth method", - type: "success" - }); - - handlePopUpToggle("revokeAuthMethod", false); - onDeleteAuthMethod(); - } catch { - createNotification({ - text: "Failed to remove auth method", - type: "error" - }); - } + createNotification({ + text: "Successfully removed auth method", + type: "success" + }); + handlePopUpToggle("revokeAuthMethod", false); + onDeleteAuthMethod(); }; return ( diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx index 7537d95a3..ae44cdd1e 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx @@ -36,7 +36,6 @@ import { RelayOption } from "./RelayOption"; const baseFormSchema = z.object({ name: slugSchema({ field: "name" }), - instanceDomain: z.string().url("Must be a valid URL").or(z.literal("")), relay: z .object( { @@ -78,7 +77,6 @@ export const GatewayCliDeploymentMethod = () => { const [autogenerateToken, setAutogenerateToken] = useState(true); const [step, setStep] = useState<"form" | "command">("form"); const [name, setName] = useState(""); - const [instanceDomain, setInstanceDomain] = useState(siteURL); const [relay, setRelay] = useState { const validation = formSchemaWithIdentity.safeParse({ name, relay, - identity, - instanceDomain + identity }); if (!validation.success) { setFormErrors(validation.error.issues); @@ -168,20 +165,14 @@ export const GatewayCliDeploymentMethod = () => { type: "info" }); setStep("command"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to generate token for the selected identity", - type: "error" - }); + } catch { setIdentityToken(""); } } else { const validation = formSchemaWithToken.safeParse({ name, relay, - identityToken, - instanceDomain + identityToken }); if (!validation.success) { setFormErrors(validation.error.issues); @@ -192,11 +183,10 @@ export const GatewayCliDeploymentMethod = () => { }; const command = useMemo(() => { - const domainFlag = instanceDomain ? ` --domain=${instanceDomain}` : ""; return `infisical gateway start --name=${name} --relay=${ relay?.name || "" - }${domainFlag} --token=${identityToken}`; - }, [name, relay, identityToken, instanceDomain]); + } --domain=${siteURL} --token=${identityToken}`; + }, [name, relay, identityToken, siteURL]); if (step === "command") { return ( @@ -279,19 +269,6 @@ export const GatewayCliDeploymentMethod = () => { /> {errors.relay &&

{errors.relay}

} - - setInstanceDomain(e.target.value)} - placeholder="https://app.infisical.com" - isError={Boolean(errors.instanceDomain)} - /> - {errors.instanceDomain &&

{errors.instanceDomain}

} - {canCreateToken && autogenerateToken ? ( <> { const [name, setName] = useState(""); const [host, setHost] = useState(""); - const [instanceDomain, setInstanceDomain] = useState(siteURL); const [identity, setIdentity] = useState { setFormErrors([]); if (canCreateToken && autogenerateToken) { - const validation = formSchemaWithIdentity.safeParse({ name, host, instanceDomain, identity }); + const validation = formSchemaWithIdentity.safeParse({ name, host, identity }); if (!validation.success) { setFormErrors(validation.error.issues); return; @@ -141,19 +139,13 @@ export const RelayCliDeploymentMethod = () => { type: "info" }); setStep("command"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to generate token for the selected identity", - type: "error" - }); + } catch { setIdentityToken(""); } } else { const validation = formSchemaWithToken.safeParse({ name, host, - instanceDomain, identityToken }); if (!validation.success) { @@ -174,9 +166,8 @@ export const RelayCliDeploymentMethod = () => { }; const command = useMemo(() => { - const domainFlag = instanceDomain ? ` --domain=${instanceDomain}` : ""; - return `infisical relay start --name=${name}${domainFlag} --host=${host} --token=${identityToken}`; - }, [name, instanceDomain, host, identityToken]); + return `infisical relay start --name=${name} --domain=${siteURL} --host=${host} --token=${identityToken}`; + }, [name, siteURL, host, identityToken]); if (step === "command") { return ( @@ -244,19 +235,6 @@ export const RelayCliDeploymentMethod = () => { /> {errors.host &&

{errors.host}

} - - setInstanceDomain(e.target.value)} - placeholder="https://app.infisical.com" - isError={Boolean(errors.instanceDomain)} - /> - {errors.instanceDomain &&

{errors.instanceDomain}

} - {canCreateToken && autogenerateToken ? ( <> val !== null, { message: "Identity is required" }) +}); + +const formSchemaWithToken = baseFormSchema.extend({ + identityToken: z.string().min(1, "Token is required") +}); + +const ec2FormSchema = z.object({ + awsRegion: z.string().min(1, "AWS Region is required"), + vpcId: z.string().min(1, "VPC ID is required"), + ami: z.string().min(1, "AMI ID is required"), + subnetId: z.string().min(1, "Subnet ID is required") +}); + +export const RelayTerraformDeploymentMethod = () => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const [selectedTabIndex, setSelectedTabIndex] = useState(0); + + const [autogenerateToken, setAutogenerateToken] = useState(true); + const [step, setStep] = useState<"form" | "command">("form"); + const [name, setName] = useState(""); + + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + const [formErrors, setFormErrors] = useState([]); + + const [awsRegion, setAwsRegion] = useState("us-east-1"); + const [vpcId, setVpcId] = useState(""); + const [ami, setAmi] = useState("ami-01b2110eef525172b"); + const [subnetId, setSubnetId] = useState(""); + + const errors = useMemo(() => { + const errorMap: Record = {}; + formErrors.forEach((issue) => { + if (issue.path.length > 0) { + errorMap[String(issue.path[0])] = issue.message; + } + }); + return errorMap; + }, [formErrors]); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { permission } = useOrgPermission(); + const canCreateToken = permission.can( + OrgPermissionIdentityActions.CreateToken, + OrgPermissionSubjects.Identity + ); + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken, isPending: isCreatingToken } = + useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth, isPending: isAddingTokenAuth } = + useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + const handleGenerateCommand = async () => { + setFormErrors([]); + + if (canCreateToken && autogenerateToken) { + const validation = formSchemaWithIdentity.safeParse({ name, identity }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + + if (selectedTabIndex === 0) { + const ec2Validation = ec2FormSchema.safeParse({ awsRegion, vpcId, ami, subnetId }); + if (!ec2Validation.success) { + setFormErrors(ec2Validation.error.issues); + return; + } + } + + const validatedIdentity = validation.data.identity; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: validatedIdentity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Token authentication has been automatically enabled for the selected identity. By default, it is configured to allow all IP addresses with a default token TTL of 30 days. You can manage these settings in Access Control.", + type: "warning" + }); + } + + const token = await createToken({ + identityId: validatedIdentity.id, + name: `relay token for ${name} (autogenerated)` + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for the selected identity.", + type: "info" + }); + setStep("command"); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for the selected identity", + type: "error" + }); + setIdentityToken(""); + } + } else { + const validation = formSchemaWithToken.safeParse({ + name, + identityToken + }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + + if (selectedTabIndex === 0) { + const ec2Validation = ec2FormSchema.safeParse({ awsRegion, vpcId, ami, subnetId }); + if (!ec2Validation.success) { + setFormErrors(ec2Validation.error.issues); + return; + } + } + setStep("command"); + } + }; + + const handleIdentityChange = ( + selectedIdentity: SingleValue<{ + id: string; + name: string; + }> + ) => { + setIdentity(selectedIdentity); + }; + + const terraformCommand = useMemo(() => { + return `terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = "${awsRegion}" +} + +# Security Group for the Infisical Relay instance +resource "aws_security_group" "infisical_relay_sg" { + name = "${name}-relay-sg" + description = "Allows inbound traffic for Infisical Relay and SSH" + vpc_id = "${vpcId}" + + # Inbound: Allows the Infisical platform to securely communicate with the Relay server. + ingress { + from_port = 8443 + to_port = 8443 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Inbound: Allows Infisical Gateway to securely communicate via the Relay. + ingress { + from_port = 2222 + to_port = 2222 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Inbound: Allows secure shell (SSH) access for administration. + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] # Restrict this to your IP in production + } + + # Outbound: Allows the Relay server to make necessary outbound connections to the Infisical platform. + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${name}-relay-sg" + } +} + +# Elastic IP for a static public IP address +resource "aws_eip" "infisical_relay_eip" { + tags = { + Name = "${name}-relay-eip" + } +} + +# EC2 instance to run Infisical Relay +module "infisical_relay_instance" { + source = "terraform-aws-modules/ec2-instance/aws" + version = "~> 5.6" + + name = "${name}-relay-instance" + ami = "${ami}" + instance_type = "t3.micro" + subnet_id = "${subnetId}" + + vpc_security_group_ids = [aws_security_group.infisical_relay_sg.id] + associate_public_ip_address = false # We are using an Elastic IP instead + + user_data = <<-EOT + #!/bin/bash + set -e + # Install Infisical CLI + curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash + apt-get update && apt-get install -y infisical + + # Install the relay as a systemd service. + # This example uses a Machine Identity token for authentication via the INFISICAL_TOKEN environment variable. + # + # Note: For production environments, you might consider fetching the token from AWS Parameter Store or AWS Secrets Manager. + export INFISICAL_TOKEN="${identityToken}" + sudo -E infisical relay systemd install \\ + --name "${name}" \\ + --domain "${siteURL}" \\ + --host "\${aws_eip.infisical_relay_eip.public_ip}" + + # Start and enable the service to run on boot + sudo systemctl start infisical-relay + sudo systemctl enable infisical-relay + EOT +} + +# Associate the Elastic IP with the EC2 instance +resource "aws_eip_association" "eip_assoc" { + instance_id = module.infisical_relay_instance.id + allocation_id = aws_eip.infisical_relay_eip.id +} +`; + }, [name, siteURL, identityToken, awsRegion, vpcId, ami, subnetId]); + + if (step === "command") { + return ( + <> +
+ Terraform Configuration + { + navigator.clipboard.writeText(terraformCommand); + createNotification({ + text: "Terraform configuration copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+
+
+            {terraformCommand}
+          
+
+
+ + + +
+ + ); + } + + return ( + <> + + setName(e.target.value)} + placeholder="Enter relay name..." + isError={Boolean(errors.name)} + /> + {errors.name &&

{errors.name}

} + + {canCreateToken && autogenerateToken ? ( + <> + + + handleIdentityChange( + e as SingleValue<{ + id: string; + name: string; + }> + ) + } + isLoading={isIdentitiesLoading} + placeholder="Select identity..." + options={identityMembershipOrgs.map((membership) => membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + {errors.identity &&

{errors.identity}

} + + ) : ( + <> + + setIdentityToken(e.target.value)} + placeholder="Enter identity token..." + isError={Boolean(errors.identityToken)} + /> + {errors.identityToken &&

{errors.identityToken}

} + + )} + + {canCreateToken && ( +
+ { + setAutogenerateToken(Boolean(e)); + }} + id="autogenerate-token" + className="mr-2" + > +
+ Automatically enable token auth and generate a token for identity + + Token authentication will be automatically enabled for the selected identity if + it isn't already configured. By default, it will be configured to allow all + IP addresses with a token TTL of 30 days. You can manage these settings in + Access Control. +
+
A token will automatically be generated to be used with the CLI command. + + } + > + +
+
+
+
+ )} + + + + + `-mb-[0.14rem] px-4 py-2 text-sm font-medium whitespace-nowrap outline-hidden disabled:opacity-60 ${ + selected ? "border-b-2 border-mineshaft-300 text-mineshaft-200" : "text-bunker-300" + }` + } + > + EC2 + + + + + + r.slug === awsRegion)} + onChange={(selected) => { + if (selected) { + setAwsRegion((selected as SingleValue<{ slug: string; name: string }>)!.slug); + } + }} + options={AWS_REGIONS} + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.slug} + /> + {errors.awsRegion &&

{errors.awsRegion}

} + + setVpcId(e.target.value)} + placeholder="vpc-..." + isError={Boolean(errors.vpcId)} + /> + {errors.vpcId &&

{errors.vpcId}

} + + setAmi(e.target.value)} + placeholder="ami-..." + isError={Boolean(errors.ami)} + /> + {errors.ami &&

{errors.ami}

} + + setSubnetId(e.target.value)} + placeholder="subnet-..." + isError={Boolean(errors.subnetId)} + /> + {errors.subnetId &&

{errors.subnetId}

} +
+
+
+ +
+ + + + +
+ + ); +}; diff --git a/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx b/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx index cf60ffa59..a1bd8c9e4 100644 --- a/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx +++ b/frontend/src/pages/organization/ProjectsPage/components/AllProjectView.tsx @@ -13,7 +13,6 @@ import { useNavigate } from "@tanstack/react-router"; import { CheckIcon } from "lucide-react"; import { twMerge } from "tailwind-merge"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { RequestProjectAccessModal } from "@app/components/projects/RequestProjectAccessModal"; import { @@ -103,22 +102,15 @@ export const AllProjectView = ({ projectId: string, environments: ProjectEnv[] ) => { - try { - await orgAdminAccessProject.mutateAsync({ + await orgAdminAccessProject.mutateAsync({ + projectId + }); + await navigate({ + to: getProjectHomePage(type, environments), + params: { projectId - }); - await navigate({ - to: getProjectHomePage(type, environments), - params: { - projectId - } - }); - } catch { - createNotification({ - text: "Failed to access project", - type: "error" - }); - } + } + }); }; useResetPageHelper({ diff --git a/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx b/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx index a86023b6c..32301923b 100644 --- a/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx +++ b/frontend/src/pages/organization/ProjectsPage/components/MyProjectView.tsx @@ -15,7 +15,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate } from "@tanstack/react-router"; import { twMerge } from "tailwind-merge"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Button, @@ -158,32 +157,18 @@ export const MyProjectView = ({ }; const addProjectToFavorites = async (projectId: string) => { - try { - if (currentOrg?.id) { - await updateUserProjectFavorites({ - orgId: currentOrg?.id, - projectFavorites: [...(projectFavorites || []), projectId] - }); - } - } catch { - createNotification({ - text: "Failed to add project to favorites.", - type: "error" + if (currentOrg?.id) { + await updateUserProjectFavorites({ + orgId: currentOrg?.id, + projectFavorites: [...(projectFavorites || []), projectId] }); } }; const removeProjectFromFavorites = async (projectId: string) => { - try { - if (currentOrg?.id) { - await updateUserProjectFavorites({ - orgId: currentOrg?.id, - projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] - }); - } - } catch { - createNotification({ - text: "Failed to remove project from favorites.", - type: "error" + if (currentOrg?.id) { + await updateUserProjectFavorites({ + orgId: currentOrg?.id, + projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] }); } }; diff --git a/frontend/src/pages/organization/RoleByIDPage/RoleByIDPage.tsx b/frontend/src/pages/organization/RoleByIDPage/RoleByIDPage.tsx index 0d30dd2f8..78c5393a7 100644 --- a/frontend/src/pages/organization/RoleByIDPage/RoleByIDPage.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/RoleByIDPage.tsx @@ -42,36 +42,25 @@ export const Page = () => { ] as const); const onDeleteOrgRoleSubmit = async () => { - try { - if (!orgId || !roleId) return; + if (!orgId || !roleId) return; - await deleteOrgRole({ - orgId, - id: roleId - }); + await deleteOrgRole({ + orgId, + id: roleId + }); - createNotification({ - text: "Successfully deleted organization role", - type: "success" - }); + createNotification({ + text: "Successfully deleted organization role", + type: "success" + }); - handlePopUpClose("deleteOrgRole"); - navigate({ - to: "/organization/access-management" as const, - search: { - selectedTab: OrgAccessControlTabSections.Roles - } - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete organization role"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteOrgRole"); + navigate({ + to: "/organization/access-management" as const, + search: { + selectedTab: OrgAccessControlTabSections.Roles + } + }); }; const isCustomRole = !["admin", "member", "no-access"].includes(data?.slug ?? ""); diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RoleModal.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RoleModal.tsx index 88f568c4a..73da4f161 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RoleModal.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RoleModal.tsx @@ -70,55 +70,46 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => { }, [role]); const onFormSubmit = async ({ name, description, slug }: FormData) => { - try { - if (!orgId) return; + if (!orgId) return; - if (role) { - // update + if (role) { + // update - await updateOrgRole({ - orgId, - id: role.id, - name, - description, - slug - }); - - handlePopUpToggle("role", false); - } else { - // create - - const newRole = await createOrgRole({ - orgId, - name, - description, - slug, - permissions: [] - }); - - handlePopUpToggle("role", false); - navigate({ - to: "/organization/roles/$roleId", - params: { - roleId: newRole.id - } - }); - } - - createNotification({ - text: `Successfully ${popUp?.role?.data ? "updated" : "created"} role`, - type: "success" + await updateOrgRole({ + orgId, + id: role.id, + name, + description, + slug }); - reset(); - } catch { - const text = `Failed to ${popUp?.role?.data ? "update" : "create"} role`; + handlePopUpToggle("role", false); + } else { + // create - createNotification({ - text, - type: "error" + const newRole = await createOrgRole({ + orgId, + name, + description, + slug, + permissions: [] + }); + + handlePopUpToggle("role", false); + navigate({ + to: "/organization/roles/$roleId", + params: { + roleId: newRole.id + } }); } + + createNotification({ + text: `Successfully ${popUp?.role?.data ? "updated" : "created"} role`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx index 0d6b269a8..c25d11cb7 100644 --- a/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx +++ b/frontend/src/pages/organization/RoleByIDPage/components/RolePermissionsSection/RolePermissionsSection.tsx @@ -107,18 +107,13 @@ export const RolePermissionsSection = ({ roleId }: Props) => { const { mutateAsync: updateRole } = useUpdateOrgRole(); const onSubmit = async (el: TFormSchema) => { - try { - await updateRole({ - orgId, - id: roleId, - ...el, - permissions: formRolePermission2API(el.permissions) - }); - createNotification({ type: "success", text: "Successfully updated role" }); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update role" }); - } + await updateRole({ + orgId, + id: roleId, + ...el, + permissions: formRolePermission2API(el.permissions) + }); + createNotification({ type: "success", text: "Successfully updated role" }); }; const isCustomRole = !["admin", "member", "no-access"].includes(role?.slug ?? ""); diff --git a/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretForm.tsx b/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretForm.tsx index 50a9e0f58..d50873339 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretForm.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretForm.tsx @@ -56,35 +56,27 @@ export const RequestSecretForm = () => { const onFormSubmit = async ({ name, accessType, expiresIn }: FormData) => { const expiresAt = new Date(new Date().getTime() + Number(expiresIn)); - try { - const { id } = await createSecretRequest({ - name, - accessType, - expiresAt - }); + const { id } = await createSecretRequest({ + name, + accessType, + expiresAt + }); - const link = new URL(`${window.location.origin}/secret-request/secret/${id}`); - if (subOrganization) { - link.searchParams.set("subOrganization", subOrganization); - } - - setSecretLink(link.toString()); - reset(); - - navigator.clipboard.writeText(link.toString()); - setCopyTextSecret("secret"); - - createNotification({ - text: "Shared secret link copied to clipboard.", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to create a shared secret.", - type: "error" - }); + const link = new URL(`${window.location.origin}/secret-request/secret/${id}`); + if (subOrganization) { + link.searchParams.set("subOrganization", subOrganization); } + + setSecretLink(link.toString()); + reset(); + + navigator.clipboard.writeText(link.toString()); + setCopyTextSecret("secret"); + + createNotification({ + text: "Shared secret link copied to clipboard.", + type: "success" + }); }; const hasSecretLink = Boolean(secretLink); diff --git a/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretTab.tsx b/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretTab.tsx index fd3b1d5b5..1253cdbc0 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretTab.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/RequestSecret/RequestSecretTab.tsx @@ -22,23 +22,15 @@ export const RequestSecretTab = () => { const { mutateAsync: deleteSecretRequest } = useDeleteSecretRequest(); const onDeleteApproved = async () => { - try { - await deleteSecretRequest({ - secretRequestId: popUp.deleteSecretRequestConfirmation.data?.id - }); - createNotification({ - text: "Successfully deleted secret request", - type: "success" - }); + await deleteSecretRequest({ + secretRequestId: popUp.deleteSecretRequestConfirmation.data?.id + }); + createNotification({ + text: "Successfully deleted secret request", + type: "success" + }); - handlePopUpClose("deleteSecretRequestConfirmation"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete shared secret", - type: "error" - }); - } + handlePopUpClose("deleteSecretRequestConfirmation"); }; return ( diff --git a/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/OrgSecretShareLimitSection.tsx b/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/OrgSecretShareLimitSection.tsx index 034c7d8fe..d7c337332 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/OrgSecretShareLimitSection.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/OrgSecretShareLimitSection.tsx @@ -90,28 +90,21 @@ export const OrgSecretShareLimitSection = () => { }, [currentOrg, reset]); const handleFormSubmit = async (formData: TForm) => { - try { - const maxSharedSecretLifetimeSeconds = - ms(`${formData.maxLifetimeValue}${formData.maxLifetimeUnit}`) / 1000; + const maxSharedSecretLifetimeSeconds = + ms(`${formData.maxLifetimeValue}${formData.maxLifetimeUnit}`) / 1000; - await mutateAsync({ - orgId: currentOrg.id, - maxSharedSecretViewLimit: formData.shouldLimitView ? Number(formData.maxViewLimit) : null, - maxSharedSecretLifetime: maxSharedSecretLifetimeSeconds - }); + await mutateAsync({ + orgId: currentOrg.id, + maxSharedSecretViewLimit: formData.shouldLimitView ? Number(formData.maxViewLimit) : null, + maxSharedSecretLifetime: maxSharedSecretLifetimeSeconds + }); - createNotification({ - text: "Successfully updated secret share limits", - type: "success" - }); + createNotification({ + text: "Successfully updated secret share limits", + type: "success" + }); - reset(formData); - } catch { - createNotification({ - text: "Failed to update secret share limits", - type: "error" - }); - } + reset(formData); }; // Units for the dropdown with readable labels diff --git a/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/SecretSharingAllowShareToAnyone.tsx b/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/SecretSharingAllowShareToAnyone.tsx index 1ea62c187..31b2221ce 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/SecretSharingAllowShareToAnyone.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/SecretSharingSettings/SecretSharingAllowShareToAnyone.tsx @@ -9,25 +9,17 @@ export const SecretSharingAllowShareToAnyone = () => { const { mutateAsync } = useUpdateOrg(); const handleSecretSharingToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - await mutateAsync({ - orgId: currentOrg.id, - allowSecretSharingOutsideOrganization: value - }); + await mutateAsync({ + orgId: currentOrg.id, + allowSecretSharingOutsideOrganization: value + }); - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} secret sharing to members outside of this organization`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: (err as { response: { data: { message: string } } }).response.data.message, - type: "error" - }); - } + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} secret sharing to members outside of this organization`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretTab.tsx b/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretTab.tsx index d4f2f0a1f..2ea61ca98 100644 --- a/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretTab.tsx +++ b/frontend/src/pages/organization/SecretSharingPage/components/ShareSecret/ShareSecretTab.tsx @@ -20,23 +20,15 @@ export const ShareSecretTab = () => { const deleteSecretShare = useDeleteSharedSecret(); const onDeleteApproved = async () => { - try { - deleteSecretShare.mutateAsync({ - sharedSecretId: (popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.id - }); - createNotification({ - text: "Successfully deleted shared secret", - type: "success" - }); + deleteSecretShare.mutateAsync({ + sharedSecretId: (popUp?.deleteSharedSecretConfirmation?.data as DeleteModalData)?.id + }); + createNotification({ + text: "Successfully deleted shared secret", + type: "success" + }); - handlePopUpClose("deleteSharedSecretConfirmation"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete shared secret", - type: "error" - }); - } + handlePopUpClose("deleteSharedSecretConfirmation"); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx b/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx index df5bf4f60..5efec93d9 100644 --- a/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx +++ b/frontend/src/pages/organization/SettingsPage/OauthCallbackPage/OauthCallbackPage.tsx @@ -72,20 +72,12 @@ export const OAuthCallbackPage = () => { if (!isReady) return; (async () => { - try { - await handleMicrosoftTeams(); + await handleMicrosoftTeams(); - createNotification({ - text: "Successfully created Microsoft Teams workflow integration", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create Microsoft Teams workflow integration", - type: "error" - }); - } + createNotification({ + text: "Successfully created Microsoft Teams workflow integration", + type: "success" + }); })(); }, [isReady]); diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/AuditLogStreamForm.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/AuditLogStreamForm.tsx index f59ae23aa..5ea16050a 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/AuditLogStreamForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/AuditLogStreamForm.tsx @@ -28,21 +28,12 @@ const CreateForm = ({ provider, onComplete }: CreateFormProps) => { const onSubmit = async ( formData: DiscriminativePick ) => { - try { - const logStream = await createAuditLogStream.mutateAsync(formData); - createNotification({ - text: `Successfully created ${providerName} Log Stream`, - type: "success" - }); - onComplete(logStream); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to create ${providerName} Log Stream`, - text: err.message, - type: "error" - }); - } + const logStream = await createAuditLogStream.mutateAsync(formData); + createNotification({ + text: `Successfully created ${providerName} Log Stream`, + type: "success" + }); + onComplete(logStream); }; switch (provider) { @@ -68,24 +59,15 @@ const UpdateForm = ({ auditLogStream, onComplete }: UpdateFormProps) => { const onSubmit = async ( formData: DiscriminativePick ) => { - try { - const connection = await updateAuditLogStream.mutateAsync({ - auditLogStreamId: auditLogStream.id, - ...formData - }); - createNotification({ - text: `Successfully updated ${providerName} Log Stream`, - type: "success" - }); - onComplete(connection); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${providerName} Log Stream`, - text: err.message, - type: "error" - }); - } + const connection = await updateAuditLogStream.mutateAsync({ + auditLogStreamId: auditLogStream.id, + ...formData + }); + createNotification({ + text: `Successfully updated ${providerName} Log Stream`, + type: "success" + }); + onComplete(connection); }; switch (auditLogStream.provider) { diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/DeleteAuditLogStreamModal.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/DeleteAuditLogStreamModal.tsx index 1a65e6c67..74eb19af3 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/DeleteAuditLogStreamModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/components/DeleteAuditLogStreamModal.tsx @@ -20,26 +20,17 @@ export const DeleteAuditLogStreamModal = ({ isOpen, onOpenChange, auditLogStream const providerDetails = AUDIT_LOG_STREAM_PROVIDER_MAP[provider]; const handleDelete = async () => { - try { - await deleteAuditLogStream.mutateAsync({ - auditLogStreamId, - provider - }); + await deleteAuditLogStream.mutateAsync({ + auditLogStreamId, + provider + }); - createNotification({ - text: `Successfully deleted ${providerDetails.name} stream`, - type: "success" - }); + createNotification({ + text: `Successfully deleted ${providerDetails.name} stream`, + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - - createNotification({ - text: `Failed to delete ${providerDetails.name} stream`, - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultConnectionSection.tsx b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultConnectionSection.tsx index c91e9c24c..e802e250b 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultConnectionSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultConnectionSection.tsx @@ -54,21 +54,13 @@ export const VaultConnectionSection = () => { const handleDeleteConfirm = async () => { if (!configToDelete) return; - try { - await deleteConfig({ id: configToDelete.id }); - createNotification({ - type: "success", - text: "Namespace configuration deleted successfully" - }); - setIsDeleteModalOpen(false); - setConfigToDelete(null); - } catch (error) { - console.error("Failed to delete namespace config:", error); - createNotification({ - type: "error", - text: "Failed to delete namespace configuration" - }); - } + await deleteConfig({ id: configToDelete.id }); + createNotification({ + type: "success", + text: "Namespace configuration deleted successfully" + }); + setIsDeleteModalOpen(false); + setConfigToDelete(null); }; const getConnectionName = (connectionId: string | null) => { diff --git a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultNamespaceConfigModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultNamespaceConfigModal.tsx index 62fb78b48..f08aae204 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultNamespaceConfigModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ExternalMigrationsTab/components/VaultNamespaceConfigModal.tsx @@ -74,36 +74,28 @@ export const VaultNamespaceConfigModal = ({ isOpen, onOpenChange, editConfig }: }, [isOpen, editConfig, reset]); const onFormSubmit = async (data: FormData) => { - try { - if (isEdit && editConfig) { - await updateConfig({ - id: editConfig.id, - namespace: data.namespace, - connectionId: data.connectionId - }); - createNotification({ - type: "success", - text: "Namespace configuration updated successfully" - }); - } else { - await createConfig({ - namespace: data.namespace, - connectionId: data.connectionId - }); - createNotification({ - type: "success", - text: "Namespace configuration created successfully" - }); - } - reset(); - onOpenChange(false); - } catch (error) { - console.error("Failed to save namespace config:", error); + if (isEdit && editConfig) { + await updateConfig({ + id: editConfig.id, + namespace: data.namespace, + connectionId: data.connectionId + }); createNotification({ - type: "error", - text: `Failed to ${isEdit ? "update" : "create"} namespace configuration` + type: "success", + text: "Namespace configuration updated successfully" + }); + } else { + await createConfig({ + namespace: data.namespace, + connectionId: data.connectionId + }); + createNotification({ + type: "success", + text: "Namespace configuration created successfully" }); } + reset(); + onOpenChange(false); }; const handleClose = () => { diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx index 4dfe89f71..6e5c0e1cb 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgDeleteSection/OrgDeleteSection.tsx @@ -19,27 +19,19 @@ export const OrgDeleteSection = () => { const { mutateAsync, isPending } = useDeleteOrgById(); const handleDeleteOrgSubmit = async () => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - await mutateAsync({ - organizationId: currentOrg?.id - }); + await mutateAsync({ + organizationId: currentOrg?.id + }); - createNotification({ - text: "Successfully deleted organization", - type: "success" - }); + createNotification({ + text: "Successfully deleted organization", + type: "success" + }); - clearSession(); - navigate({ to: "/login" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete organization", - type: "error" - }); - } + clearSession(); + navigate({ to: "/login" }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx index 564b08969..81649b604 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgEncryptionTab/OrgEncryptionTab.tsx @@ -51,18 +51,14 @@ export const OrgEncryptionTab = withPermission( kmsId: string; }; - try { - await removeExternalKms(kmsId); + await removeExternalKms(kmsId); - createNotification({ - text: "Successfully deleted external KMS", - type: "success" - }); + createNotification({ + text: "Successfully deleted external KMS", + type: "success" + }); - handlePopUpToggle("removeExternalKms", false); - } catch (err) { - console.error(err); - } + handlePopUpToggle("removeExternalKms", false); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/AddOrgIncidentContactModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/AddOrgIncidentContactModal.tsx index 84bb083ec..125a3f310 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/AddOrgIncidentContactModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/AddOrgIncidentContactModal.tsx @@ -35,31 +35,23 @@ export const AddOrgIncidentContactModal = ({ const { mutateAsync, isPending } = useAddIncidentContact(); const onFormSubmit = async ({ email }: TAddContactForm) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - await mutateAsync({ - orgId: currentOrg.id, - email - }); + await mutateAsync({ + orgId: currentOrg.id, + email + }); - createNotification({ - text: "Successfully added incident contact", - type: "success" - }); + createNotification({ + text: "Successfully added incident contact", + type: "success" + }); - if (serverDetails?.emailConfigured) { - handlePopUpClose("addContact"); - } - - reset(); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to add incident contact", - type: "error" - }); + if (serverDetails?.emailConfigured) { + handlePopUpClose("addContact"); } + + reset(); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx index d0723fdaf..c16431b9c 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgIncidentContactsSection/OrgIncidentContactsTable.tsx @@ -33,28 +33,20 @@ export const OrgIncidentContactsTable = () => { const { mutateAsync } = useDeleteIncidentContact(); const onRemoveIncidentContact = async () => { - try { - const incidentContactId = (popUp?.removeContact?.data as { id: string })?.id; + const incidentContactId = (popUp?.removeContact?.data as { id: string })?.id; - if (!currentOrg?.id) return; - await mutateAsync({ - orgId: currentOrg.id, - incidentContactId - }); + if (!currentOrg?.id) return; + await mutateAsync({ + orgId: currentOrg.id, + incidentContactId + }); - createNotification({ - text: "Successfully removed incident contact", - type: "success" - }); + createNotification({ + text: "Successfully removed incident contact", + type: "success" + }); - handlePopUpClose("removeContact"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to remove incident contact", - type: "error" - }); - } + handlePopUpClose("removeContact"); }; const filteredContacts = contacts diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx index 766a22158..03fb14dc7 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/OrgNameChangeSection.tsx @@ -56,27 +56,19 @@ export const OrgNameChangeSection = (): JSX.Element => { }, [roles]); const onFormSubmit = async ({ name, slug, defaultMembershipRole }: FormData) => { - try { - if (!currentOrg?.id || !roles?.length) return; + if (!currentOrg?.id || !roles?.length) return; - await mutateAsync({ - orgId: currentOrg?.id, - name, - slug, - defaultMembershipRoleSlug: defaultMembershipRole - }); + await mutateAsync({ + orgId: currentOrg?.id, + name, + slug, + defaultMembershipRoleSlug: defaultMembershipRole + }); - createNotification({ - text: "Successfully updated organization details", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to update organization details", - type: "error" - }); - } + createNotification({ + text: "Successfully updated organization details", + type: "success" + }); }; if (!isFormInitialized) { diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/SubOrgNameChangeSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/SubOrgNameChangeSection.tsx index ca625be6c..e08d4d1b5 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/SubOrgNameChangeSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgNameChangeSection/SubOrgNameChangeSection.tsx @@ -39,26 +39,18 @@ export const SubOrgNameChangeSection = (): JSX.Element => { const { mutateAsync, isPending } = useUpdateSubOrganization(); const onFormSubmit = async ({ name }: FormData) => { - try { - await mutateAsync({ - name, - subOrgId: currentOrg.id - }); + await mutateAsync({ + name, + subOrgId: currentOrg.id + }); - navigate({ to: "/organization/settings", search: { subOrganization: name } }); - queryClient.invalidateQueries(); - await router.invalidate({ sync: true }); - createNotification({ - text: "Successfully updated sub-organization details", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to update sub-organization details", - type: "error" - }); - } + navigate({ to: "/organization/settings", search: { subOrganization: name } }); + queryClient.invalidateQueries(); + await router.invalidate({ sync: true }); + createNotification({ + text: "Successfully updated sub-organization details", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx index 5be15edad..e1fc2712d 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProductSelectSection/OrgProductSelectSection.tsx @@ -1,7 +1,5 @@ import { useEffect, useState } from "react"; -import axios from "axios"; -import { createNotification } from "@app/components/notifications"; import { Switch } from "@app/components/v2"; import { useOrganization } from "@app/context"; import { useUpdateOrg } from "@app/hooks/api"; @@ -60,20 +58,10 @@ export const OrgProductSelectSection = () => { [key]: { ...products[key], enabled: value } })); - try { - await mutateAsync({ - orgId: currentOrg.id, - [key]: value - }); - } catch (e) { - if (axios.isAxiosError(e)) { - const { message = "Something went wrong" } = e.response?.data as { message: string }; - createNotification({ - type: "error", - text: message - }); - } - } + await mutateAsync({ + orgId: currentOrg.id, + [key]: value + }); setIsLoading(false); }; diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProductSettingsTab/OrgProductSettingsTab.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProductSettingsTab/OrgProductSettingsTab.tsx index e4d935996..95cc7b870 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProductSettingsTab/OrgProductSettingsTab.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProductSettingsTab/OrgProductSettingsTab.tsx @@ -30,12 +30,6 @@ export const OrgProductSettingsTab = () => { text: `Successfully ${state ? "enabled" : "disabled"} blocking duplicate secret sync destinations for this organization`, type: "success" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update blocking duplicate secret sync destinations setting for this organization", - type: "error" - }); } finally { setIsLoading(false); } diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ExternalGroupOrgRoleMappings.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ExternalGroupOrgRoleMappings.tsx index 8e181e8c4..bc9a02921 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ExternalGroupOrgRoleMappings.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ExternalGroupOrgRoleMappings.tsx @@ -75,19 +75,11 @@ export const ExternalGroupOrgRoleMappings = () => { const mappingField = useFieldArray({ control, name: "mappings" }); const handleUpdateMappings = async (form: TForm) => { - try { - await updateMappings.mutateAsync(form); - createNotification({ - text: "Group organization role mappings updated.", - type: "success" - }); - } catch (e) { - console.error(e); - createNotification({ - text: "Failed to update group organization role mappings.", - type: "error" - }); - } + await updateMappings.mutateAsync(form); + createNotification({ + text: "Group organization role mappings updated.", + type: "success" + }); }; const disableScimEdit = permission.cannot(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/GithubOrgSyncConfigModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/GithubOrgSyncConfigModal.tsx index 180b63278..868dd17f7 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/GithubOrgSyncConfigModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/GithubOrgSyncConfigModal.tsx @@ -55,55 +55,40 @@ export const GithubOrgSyncConfigModal = ({ }); const onFormSubmit = async ({ githubOrgName, githubOrgAccessToken }: FormData) => { - try { - if (isUpdate) { - await updateGithubSyncOrgConfig({ - githubOrgName, - githubOrgAccessToken - }); + if (isUpdate) { + await updateGithubSyncOrgConfig({ + githubOrgName, + githubOrgAccessToken + }); - createNotification({ - text: "Successfully updated GitHub Organization Sync", - type: "success" - }); - } else { - await createGithubSyncOrgConfig({ - githubOrgName, - githubOrgAccessToken, - isActive: false - }); - - createNotification({ - text: "Successfully created GitHub Organization Sync", - type: "success" - }); - } - handlePopUpToggle("githubOrgSyncConfig"); - } catch { createNotification({ - text: "Failed to setup GitHub Organization Sync", - type: "error" + text: "Successfully updated GitHub Organization Sync", + type: "success" + }); + } else { + await createGithubSyncOrgConfig({ + githubOrgName, + githubOrgAccessToken, + isActive: false + }); + + createNotification({ + text: "Successfully created GitHub Organization Sync", + type: "success" }); } + handlePopUpToggle("githubOrgSyncConfig"); }; const onDelete = async () => { - try { - await deleteGithubSyncOrgConfig(); + await deleteGithubSyncOrgConfig(); - handlePopUpToggle("deleteGithubOrgSyncConfig", false); - handlePopUpToggle("githubOrgSyncConfig", false); - createNotification({ - text: "Successfully deleted GitHub Organization Sync", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete GitHub Organization Sync", - type: "error" - }); - } + handlePopUpToggle("deleteGithubOrgSyncConfig", false); + handlePopUpToggle("githubOrgSyncConfig", false); + createNotification({ + text: "Successfully deleted GitHub Organization Sync", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgGithubSyncSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgGithubSyncSection.tsx index 105f8eb8e..f2115df96 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgGithubSyncSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgGithubSyncSection.tsx @@ -35,64 +35,41 @@ export const OrgGithubSyncSection = () => { const data = !isPending && !githubOrgSyncConfig?.isError ? githubOrgSyncConfig?.data : undefined; const handleBulkSync = async () => { - try { - const result = await syncAllTeamsMutation.mutateAsync(); - let message = "Successfully synced teams"; + const result = await syncAllTeamsMutation.mutateAsync(); + let message = "Successfully synced teams"; - const details = []; - if (result.createdTeams.length > 0) { - details.push( - `${result.createdTeams.length} new team${result.createdTeams.length === 1 ? "" : "s"} created` - ); - } - if (result.updatedTeams.length > 0) { - details.push( - `${result.updatedTeams.length} team${result.updatedTeams.length === 1 ? "" : "s"} updated` - ); - } - if (result.removedMemberships > 0) { - details.push( - `${result.removedMemberships} membership${result.removedMemberships === 1 ? "" : "s"} removed` - ); - } + const details = []; + if (result.createdTeams.length > 0) { + details.push( + `${result.createdTeams.length} new team${result.createdTeams.length === 1 ? "" : "s"} created` + ); + } + if (result.updatedTeams.length > 0) { + details.push( + `${result.updatedTeams.length} team${result.updatedTeams.length === 1 ? "" : "s"} updated` + ); + } + if (result.removedMemberships > 0) { + details.push( + `${result.removedMemberships} membership${result.removedMemberships === 1 ? "" : "s"} removed` + ); + } - if (details.length > 0) { - message += `. ${details.join(", ")}`; - } + if (details.length > 0) { + message += `. ${details.join(", ")}`; + } + createNotification({ + text: message, + type: "success" + }); + + if (result.errors && result.errors.length > 0) { createNotification({ - text: message, - type: "success" + text: `Sync completed with ${result.errors.length} warnings. Check the console for details.`, + type: "warning" }); - - if (result.errors && result.errors.length > 0) { - createNotification({ - text: `Sync completed with ${result.errors.length} warnings. Check the console for details.`, - type: "warning" - }); - console.warn("Sync errors:", result.errors); - } - } catch (error) { - const errorMessage = - (error as any)?.response?.data?.message || (error as Error)?.message || "Unknown error"; - - if ( - errorMessage.includes("token") && - (errorMessage.includes("required") || - errorMessage.includes("invalid") || - errorMessage.includes("expired") || - errorMessage.includes("set a token first")) - ) { - createNotification({ - text: "Please set a GitHub access token in the configuration modal to continue with the sync", - type: "error" - }); - } else { - createNotification({ - text: `Failed to sync GitHub teams: ${errorMessage}`, - type: "error" - }); - } + console.warn("Sync errors:", result.errors); } }; diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx index 4ba89683c..f7551d1e4 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/OrgSCIMSection.tsx @@ -36,30 +36,23 @@ export const OrgScimSection = () => { }; const handleEnableSCIMToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; - if (!subscription?.scim) { - handlePopUpOpen("upgradePlan", { - isEnterpriseFeature: true - }); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - scimEnabled: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} SCIM provisioning`, - type: "success" - }); - } catch (err) { - createNotification({ - text: (err as { response: { data: { message: string } } }).response.data.message, - type: "error" + if (!currentOrg?.id) return; + if (!subscription?.scim) { + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true }); + return; } + + await mutateAsync({ + orgId: currentOrg?.id, + scimEnabled: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} SCIM provisioning`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ScimTokenModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ScimTokenModal.tsx index c542a88bb..5d6007ac0 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ScimTokenModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgProvisioningTab/ScimTokenModal.tsx @@ -92,52 +92,36 @@ export const ScimTokenModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Pr }, [isScimTokenCopied, isScimUrlCopied]); const onFormSubmit = async ({ description, ttlDays }: FormData) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - const { scimToken } = await createScimTokenMutateAsync({ - organizationId: currentOrg.id, - description, - ttlDays: Number(ttlDays) - }); + const { scimToken } = await createScimTokenMutateAsync({ + organizationId: currentOrg.id, + description, + ttlDays: Number(ttlDays) + }); - setToken(scimToken); + setToken(scimToken); - createNotification({ - text: "Successfully created SCIM token", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create SCIM token", - type: "error" - }); - } + createNotification({ + text: "Successfully created SCIM token", + type: "success" + }); }; const onDeleteScimTokenSubmit = async (scimTokenId: string) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - await deleteScimTokenMutateAsync({ - organizationId: currentOrg.id, - scimTokenId - }); + await deleteScimTokenMutateAsync({ + organizationId: currentOrg.id, + scimTokenId + }); - handlePopUpToggle("deleteScimToken", false); + handlePopUpToggle("deleteScimToken", false); - createNotification({ - text: "Successfully deleted SCIM token", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SCIM token", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted SCIM token", + type: "success" + }); }; const hasToken = Boolean(token); diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgGenericAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgGenericAuthSection.tsx index 1500098e9..7e6922318 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgGenericAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgGenericAuthSection.tsx @@ -20,55 +20,39 @@ export const OrgGenericAuthSection = () => { const { mutateAsync } = useUpdateOrg(); const handleEnforceMfaToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; - if (!subscription?.enforceMfa) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - enforceMfa: value - }); - - createNotification({ - text: `Successfully ${value ? "enforced" : "un-enforced"} MFA`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: (err as { response: { data: { message: string } } }).response.data.message, - type: "error" - }); + if (!currentOrg?.id) return; + if (!subscription?.enforceMfa) { + handlePopUpOpen("upgradePlan"); + return; } + + await mutateAsync({ + orgId: currentOrg?.id, + enforceMfa: value + }); + + createNotification({ + text: `Successfully ${value ? "enforced" : "un-enforced"} MFA`, + type: "success" + }); }; const handleUpdateSelectedMfa = async (selectedMfaMethod: MfaMethod) => { - try { - if (!currentOrg?.id) return; - if (!subscription?.enforceMfa) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - selectedMfaMethod - }); - - createNotification({ - text: "Successfully updated selected MFA method", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: (err as { response: { data: { message: string } } }).response.data.message, - type: "error" - }); + if (!currentOrg?.id) return; + if (!subscription?.enforceMfa) { + handlePopUpOpen("upgradePlan"); + return; } + + await mutateAsync({ + orgId: currentOrg?.id, + selectedMfaMethod + }); + + createNotification({ + text: "Successfully updated selected MFA method", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgUserAccessTokenLimitSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgUserAccessTokenLimitSection.tsx index 6e9865532..c78b5475e 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgUserAccessTokenLimitSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSecurityTab/OrgUserAccessTokenLimitSection.tsx @@ -57,24 +57,17 @@ export const OrgUserAccessTokenLimitSection = () => { if (!currentOrg) return null; const handleUserTokenExpirationSubmit = async (formData: TForm) => { - try { - const userTokenExpiration = formatDuration(formData.expirationValue, formData.expirationUnit); + const userTokenExpiration = formatDuration(formData.expirationValue, formData.expirationUnit); - await updateUserTokenExpiration({ - userTokenExpiration, - orgId: currentOrg.id - }); + await updateUserTokenExpiration({ + userTokenExpiration, + orgId: currentOrg.id + }); - createNotification({ - text: "Successfully updated user token expiration", - type: "success" - }); - } catch { - createNotification({ - text: "Failed updating user token expiration", - type: "error" - }); - } + createNotification({ + text: "Successfully updated user token expiration", + type: "success" + }); }; // Units for the dropdown with readable labels diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPGroupMapModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPGroupMapModal.tsx index aee845187..31e96a1cb 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPGroupMapModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPGroupMapModal.tsx @@ -79,28 +79,20 @@ export const LDAPGroupMapModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: }); const onFormSubmit = async ({ groupSlug, ldapGroupCN }: TFormData) => { - try { - if (!ldapConfig) return; + if (!ldapConfig) return; - await createLDAPGroupMapping({ - ldapConfigId: ldapConfig.id, - groupSlug, - ldapGroupCN - }); + await createLDAPGroupMapping({ + ldapConfigId: ldapConfig.id, + groupSlug, + ldapGroupCN + }); - reset(); + reset(); - createNotification({ - text: `Successfully added LDAP group mapping for ${ldapGroupCN}`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to add LDAP group mapping for ${ldapGroupCN}`, - type: "error" - }); - } + createNotification({ + text: `Successfully added LDAP group mapping for ${ldapGroupCN}`, + type: "success" + }); }; const onDeleteGroupMapSubmit = async ({ @@ -112,25 +104,17 @@ export const LDAPGroupMapModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: ldapGroupMapId: string; ldapGroupCN: string; }) => { - try { - await deleteLDAPGroupMapping({ - ldapConfigId, - ldapGroupMapId - }); + await deleteLDAPGroupMapping({ + ldapConfigId, + ldapGroupMapId + }); - handlePopUpToggle("deleteLdapGroupMap", false); + handlePopUpToggle("deleteLdapGroupMap", false); - createNotification({ - text: `Successfully deleted LDAP group mapping ${ldapGroupCN}`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to delete LDAP group mapping ${ldapGroupCN}`, - type: "error" - }); - } + createNotification({ + text: `Successfully deleted LDAP group mapping ${ldapGroupCN}`, + type: "success" + }); }; useEffect(() => { diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPModal.tsx index 580ecdeb1..01429f512 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/LDAPModal.tsx @@ -62,31 +62,24 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele if (!currentOrg) { return; } - try { - await updateMutateAsync({ - organizationId: currentOrg.id, - isActive: false, - url: "", - bindDN: "", - bindPass: "", - searchBase: "", - searchFilter: "", - uniqueUserAttribute: "", - groupSearchBase: "", - groupSearchFilter: "", - caCert: "" - }); + await updateMutateAsync({ + organizationId: currentOrg.id, + isActive: false, + url: "", + bindDN: "", + bindPass: "", + searchBase: "", + searchFilter: "", + uniqueUserAttribute: "", + groupSearchBase: "", + groupSearchFilter: "", + caCert: "" + }); - createNotification({ - text: "Successfully deleted OIDC configuration.", - type: "success" - }); - } catch { - createNotification({ - text: "Failed deleting OIDC configuration.", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted OIDC configuration.", + type: "success" + }); }; const watchUrl = watch("url"); @@ -122,83 +115,59 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele caCert, shouldCloseModal = true }: TLDAPFormData & { shouldCloseModal?: boolean }) => { - try { - if (!currentOrg) return; + if (!currentOrg) return; - if (!data) { - await createMutateAsync({ - organizationId: currentOrg.id, - isActive: false, - url, - bindDN, - bindPass, - searchBase, - searchFilter, - uniqueUserAttribute, - groupSearchBase, - groupSearchFilter, - caCert - }); - } else { - await updateMutateAsync({ - organizationId: currentOrg.id, - url, - bindDN, - bindPass, - searchBase, - searchFilter, - uniqueUserAttribute, - groupSearchBase, - groupSearchFilter, - caCert - }); - } - - if (shouldCloseModal) { - handlePopUpClose("addLDAP"); - } - - createNotification({ - text: `Successfully ${!data ? "added" : "updated"} LDAP configuration`, - type: "success" + if (!data) { + await createMutateAsync({ + organizationId: currentOrg.id, + isActive: false, + url, + bindDN, + bindPass, + searchBase, + searchFilter, + uniqueUserAttribute, + groupSearchBase, + groupSearchFilter, + caCert }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${!data ? "add" : "update"} LDAP configuration`, - type: "error" + } else { + await updateMutateAsync({ + organizationId: currentOrg.id, + url, + bindDN, + bindPass, + searchBase, + searchFilter, + uniqueUserAttribute, + groupSearchBase, + groupSearchFilter, + caCert }); } + + if (shouldCloseModal) { + handlePopUpClose("addLDAP"); + } + + createNotification({ + text: `Successfully ${!data ? "added" : "updated"} LDAP configuration`, + type: "success" + }); }; const handleTestLDAPConnection = async () => { - try { - const result = await testLDAPConnection({ - url: watchUrl, - bindDN: watchBindDN, - bindPass: watchBindPass, - caCert: watchCaCert ?? "" - }); + await testLDAPConnection({ + url: watchUrl, + bindDN: watchBindDN, + bindPass: watchBindPass, + caCert: watchCaCert ?? "" + }); - if (!result) { - createNotification({ - text: "Failed to test the LDAP connection: Bind operation was unsuccessful", - type: "error" - }); - return; - } - - createNotification({ - text: "Successfully tested the LDAP connection: Bind operation was successful", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to test the LDAP connection", - type: "error" - }); - } + createNotification({ + text: "Successfully tested the LDAP connection: Bind operation was successful", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OIDCModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OIDCModal.tsx index 03f946ace..f980b2e4f 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OIDCModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OIDCModal.tsx @@ -122,31 +122,24 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele if (!currentOrg) { return; } - try { - await updateMutateAsync({ - issuer: "", - discoveryURL: "", - authorizationEndpoint: "", - allowedEmailDomains: "", - jwksUri: "", - tokenEndpoint: "", - userinfoEndpoint: "", - clientId: "", - clientSecret: "", - isActive: false, - organizationId: currentOrg.id - }); + await updateMutateAsync({ + issuer: "", + discoveryURL: "", + authorizationEndpoint: "", + allowedEmailDomains: "", + jwksUri: "", + tokenEndpoint: "", + userinfoEndpoint: "", + clientId: "", + clientSecret: "", + isActive: false, + organizationId: currentOrg.id + }); - createNotification({ - text: "Successfully deleted OIDC configuration.", - type: "success" - }); - } catch { - createNotification({ - text: "Failed deleting OIDC configuration.", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted OIDC configuration.", + type: "success" + }); }; useEffect(() => { @@ -178,58 +171,50 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele clientSecret, jwtSignatureAlgorithm }: OIDCFormData) => { - try { - if (!currentOrg) { - return; - } + if (!currentOrg) { + return; + } - if (!data) { - await createMutateAsync({ - issuer, - configurationType, - discoveryURL, - authorizationEndpoint, - allowedEmailDomains, - jwksUri, - tokenEndpoint, - userinfoEndpoint, - clientId, - clientSecret, - isActive: true, - organizationId: currentOrg.id, - jwtSignatureAlgorithm - }); - } else { - await updateMutateAsync({ - issuer, - configurationType, - discoveryURL, - authorizationEndpoint, - allowedEmailDomains, - jwksUri, - tokenEndpoint, - userinfoEndpoint, - clientId, - clientSecret, - isActive: true, - organizationId: currentOrg.id, - jwtSignatureAlgorithm - }); - } - - handlePopUpClose("addOIDC"); - - createNotification({ - text: `Successfully ${!data ? "added" : "updated"} OIDC SSO configuration`, - type: "success" + if (!data) { + await createMutateAsync({ + issuer, + configurationType, + discoveryURL, + authorizationEndpoint, + allowedEmailDomains, + jwksUri, + tokenEndpoint, + userinfoEndpoint, + clientId, + clientSecret, + isActive: true, + organizationId: currentOrg.id, + jwtSignatureAlgorithm }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${!data ? "add" : "update"} OIDC SSO configuration`, - type: "error" + } else { + await updateMutateAsync({ + issuer, + configurationType, + discoveryURL, + authorizationEndpoint, + allowedEmailDomains, + jwksUri, + tokenEndpoint, + userinfoEndpoint, + clientId, + clientSecret, + isActive: true, + organizationId: currentOrg.id, + jwtSignatureAlgorithm }); } + + handlePopUpClose("addOIDC"); + + createNotification({ + text: `Successfully ${!data ? "added" : "updated"} OIDC SSO configuration`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx index 5be97dc1b..d35a04323 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgGeneralAuthSection.tsx @@ -45,69 +45,61 @@ export const OrgGeneralAuthSection = ({ const logout = useLogoutUser(); const handleEnforceOrgAuthToggle = async (value: boolean, type: EnforceAuthType) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - if (type === EnforceAuthType.SAML) { - if (!subscription?.samlSSO) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - authEnforced: value - }); - } else if (type === EnforceAuthType.GOOGLE) { - if (!subscription?.enforceGoogleSSO) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - googleSsoAuthEnforced: value - }); - } else if (type === EnforceAuthType.OIDC) { - if (!subscription?.oidcSSO) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - orgId: currentOrg?.id, - authEnforced: value - }); - } else { - createNotification({ - text: `Invalid auth enforcement type ${type}`, - type: "error" - }); + if (type === EnforceAuthType.SAML) { + if (!subscription?.samlSSO) { + handlePopUpOpen("upgradePlan"); + return; } - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} org-level auth`, - type: "success" + await mutateAsync({ + orgId: currentOrg?.id, + authEnforced: value }); - - if (value) { - await logout.mutateAsync(); - - if (type === EnforceAuthType.SAML) { - window.open(`/api/v1/sso/redirect/saml2/organizations/${currentOrg.slug}`); - } else if (type === EnforceAuthType.GOOGLE) { - window.open(`/api/v1/sso/redirect/google?org_slug=${currentOrg.slug}`); - } - - window.close(); + } else if (type === EnforceAuthType.GOOGLE) { + if (!subscription?.enforceGoogleSSO) { + handlePopUpOpen("upgradePlan"); + return; } - } catch (err) { - console.error(err); + + await mutateAsync({ + orgId: currentOrg?.id, + googleSsoAuthEnforced: value + }); + } else if (type === EnforceAuthType.OIDC) { + if (!subscription?.oidcSSO) { + handlePopUpOpen("upgradePlan"); + return; + } + + await mutateAsync({ + orgId: currentOrg?.id, + authEnforced: value + }); + } else { createNotification({ - text: (err as { response: { data: { message: string } } }).response.data.message, + text: `Invalid auth enforcement type ${type}`, type: "error" }); } + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} org-level auth`, + type: "success" + }); + + if (value) { + await logout.mutateAsync(); + + if (type === EnforceAuthType.SAML) { + window.open(`/api/v1/sso/redirect/saml2/organizations/${currentOrg.slug}`); + } else if (type === EnforceAuthType.GOOGLE) { + window.open(`/api/v1/sso/redirect/google?org_slug=${currentOrg.slug}`); + } + + window.close(); + } }; const handleEnableBypassOrgAuthToggle = async (value: boolean) => { diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx index 48625f218..311a7ea4b 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgLDAPSection.tsx @@ -31,31 +31,23 @@ export const OrgLDAPSection = (): JSX.Element => { const { mutateAsync: createMutateAsync } = useCreateLDAPConfig(); const handleLDAPToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; - if (!subscription?.ldap) { - handlePopUpOpen("upgradePlan", { - isEnterpriseFeature: true - }); - return; - } - - await mutateAsync({ - organizationId: currentOrg?.id, - isActive: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} LDAP`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${value ? "enable" : "disable"} LDAP`, - type: "error" + if (!currentOrg?.id) return; + if (!subscription?.ldap) { + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true }); + return; } + + await mutateAsync({ + organizationId: currentOrg?.id, + isActive: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} LDAP`, + type: "success" + }); }; const addLDAPBtnClick = async () => { diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgOIDCSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgOIDCSection.tsx index 3db364bc8..b722762e6 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgOIDCSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgOIDCSection.tsx @@ -30,49 +30,41 @@ export const OrgOIDCSection = (): JSX.Element => { ] as const); const handleOIDCToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - if (!subscription?.oidcSSO) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - organizationId: currentOrg?.id, - isActive: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} OIDC SSO`, - type: "success" - }); - } catch (err) { - console.error(err); + if (!subscription?.oidcSSO) { + handlePopUpOpen("upgradePlan"); + return; } + + await mutateAsync({ + organizationId: currentOrg?.id, + isActive: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} OIDC SSO`, + type: "success" + }); }; const handleOIDCGroupManagement = async (value: boolean) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - if (!subscription?.oidcSSO) { - handlePopUpOpen("upgradePlan"); - return; - } - - await mutateAsync({ - organizationId: currentOrg?.id, - manageGroupMemberships: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} OIDC group membership mapping`, - type: "success" - }); - } catch (err) { - console.error(err); + if (!subscription?.oidcSSO) { + handlePopUpOpen("upgradePlan"); + return; } + + await mutateAsync({ + organizationId: currentOrg?.id, + manageGroupMemberships: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} OIDC group membership mapping`, + type: "success" + }); }; const addOidcButtonClick = async () => { diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx index 343b549da..ba2d23abd 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/OrgSSOSection.tsx @@ -33,62 +33,46 @@ export const OrgSSOSection = (): JSX.Element => { const { mutateAsync: createMutateAsync } = useCreateSSOConfig(); const handleSamlSSOToggle = async (value: boolean) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - if (!subscription?.samlSSO) { - handlePopUpOpen("upgradePlan", { - text: "Your current plan does not include access to SAML SSO. To unlock this feature, please upgrade to Infisical Pro plan." - }); - return; - } - - await mutateAsync({ - organizationId: currentOrg?.id, - isActive: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} SAML SSO`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${value ? "enable" : "disable"} SAML SSO`, - type: "error" + if (!subscription?.samlSSO) { + handlePopUpOpen("upgradePlan", { + text: "Your current plan does not include access to SAML SSO. To unlock this feature, please upgrade to Infisical Pro plan." }); + return; } + + await mutateAsync({ + organizationId: currentOrg?.id, + isActive: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} SAML SSO`, + type: "success" + }); }; const handleSamlGroupManagement = async (value: boolean) => { - try { - if (!currentOrg?.id) return; + if (!currentOrg?.id) return; - if (!subscription?.samlSSO || !subscription?.groups) { - handlePopUpOpen("upgradePlan", { - isEnterpriseFeature: true, - text: "Your current plan does not include access to SAML group mapping. To unlock this feature, please upgrade to Infisical Enterprise plan." - }); - return; - } - - await mutateAsync({ - organizationId: currentOrg?.id, - enableGroupSync: value - }); - - createNotification({ - text: `Successfully ${value ? "enabled" : "disabled"} SAML group membership mapping`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${value ? "enable" : "disable"} SAML group membership mapping`, - type: "error" + if (!subscription?.samlSSO || !subscription?.groups) { + handlePopUpOpen("upgradePlan", { + isEnterpriseFeature: true, + text: "Your current plan does not include access to SAML group mapping. To unlock this feature, please upgrade to Infisical Enterprise plan." }); + return; } + + await mutateAsync({ + organizationId: currentOrg?.id, + enableGroupSync: value + }); + + createNotification({ + text: `Successfully ${value ? "enabled" : "disabled"} SAML group membership mapping`, + type: "success" + }); }; const addSSOBtnClick = async () => { diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModal.tsx index df5fca59f..558f37dcb 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgSsoTab/SSOModal.tsx @@ -108,64 +108,49 @@ export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDelet if (!currentOrg) { return; } - try { - await updateMutateAsync({ - organizationId: currentOrg.id, - isActive: false, - entryPoint: "", - issuer: "", - cert: "" - }); + await updateMutateAsync({ + organizationId: currentOrg.id, + isActive: false, + entryPoint: "", + issuer: "", + cert: "" + }); - createNotification({ - text: "Successfully deleted SAML SSO configuration.", - type: "success" - }); - } catch { - createNotification({ - text: "Failed deleting SAML SSO configuration.", - type: "error" - }); - } + createNotification({ + text: "Successfully deleted SAML SSO configuration.", + type: "success" + }); }; const onSSOModalSubmit = async ({ authProvider, entryPoint, issuer, cert }: AddSSOFormData) => { - try { - if (!currentOrg) return; + if (!currentOrg) return; - if (!data) { - await createMutateAsync({ - organizationId: currentOrg.id, - authProvider, - isActive: false, - entryPoint, - issuer, - cert - }); - } else { - await updateMutateAsync({ - organizationId: currentOrg.id, - authProvider, - isActive: false, - entryPoint, - issuer, - cert - }); - } - - handlePopUpClose("addSSO"); - - createNotification({ - text: `Successfully ${!data ? "added" : "updated"} SAML SSO configuration`, - type: "success" + if (!data) { + await createMutateAsync({ + organizationId: currentOrg.id, + authProvider, + isActive: false, + entryPoint, + issuer, + cert }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${!data ? "add" : "update"} SAML SSO configuration`, - type: "error" + } else { + await updateMutateAsync({ + organizationId: currentOrg.id, + authProvider, + isActive: false, + entryPoint, + issuer, + cert }); } + + handlePopUpClose("addSSO"); + + createNotification({ + text: `Successfully ${!data ? "added" : "updated"} SAML SSO configuration`, + type: "success" + }); }; const renderLabels = (authProvider: string) => { diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/DeleteProjectTemplateModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/DeleteProjectTemplateModal.tsx index 8ccdb0191..33b54afca 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/DeleteProjectTemplateModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/DeleteProjectTemplateModal.tsx @@ -16,25 +16,16 @@ export const DeleteProjectTemplateModal = ({ isOpen, onOpenChange, template }: P const { id: templateId, name } = template; const handleDeleteProjectTemplate = async () => { - try { - await deleteTemplate.mutateAsync({ - templateId - }); + await deleteTemplate.mutateAsync({ + templateId + }); - createNotification({ - text: "Successfully removed project template", - type: "success" - }); + createNotification({ + text: "Successfully removed project template", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - - createNotification({ - text: "Failed remove project template", - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx index 7a99af1d6..afa8261b3 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/EditProjectTemplate.tsx @@ -31,22 +31,14 @@ export const EditProjectTemplate = ({ isInfisicalTemplate, projectTemplate, onBa const deleteProjectTemplate = useDeleteProjectTemplate(); const handleRemoveTemplate = async () => { - try { - await deleteProjectTemplate.mutateAsync({ - templateId - }); - createNotification({ - text: "Successfully removed project template", - type: "success" - }); - onBack(); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to remove project template", - type: "error" - }); - } + await deleteProjectTemplate.mutateAsync({ + templateId + }); + createNotification({ + text: "Successfully removed project template", + type: "success" + }); + onBack(); handlePopUpClose("removeTemplate"); }; diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx index 09a2a69af..e54eaf19d 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEditRoleForm.tsx @@ -57,31 +57,23 @@ export const ProjectTemplateEditRoleForm = ({ const updateProjectTemplate = useUpdateProjectTemplate(); const onSubmit = async (form: TFormSchema) => { - try { - await updateProjectTemplate.mutateAsync({ - templateId: projectTemplate.id, - roles: [ - ...projectTemplate.roles.filter( - (r) => r.slug !== role?.slug && isCustomProjectRole(r.slug) // filter out default roles as well - ), - { - ...form, - permissions: formRolePermission2API(form.permissions) - } - ] - }); - onGoBack(); - createNotification({ - text: "Template roles successfully updated", - type: "success" - }); - } catch (e: any) { - console.error(e); - createNotification({ - text: "Failed to update template roles", - type: "error" - }); - } + await updateProjectTemplate.mutateAsync({ + templateId: projectTemplate.id, + roles: [ + ...projectTemplate.roles.filter( + (r) => r.slug !== role?.slug && isCustomProjectRole(r.slug) // filter out default roles as well + ), + { + ...form, + permissions: formRolePermission2API(form.permissions) + } + ] + }); + onGoBack(); + createNotification({ + text: "Template roles successfully updated", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEnvironmentsForm.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEnvironmentsForm.tsx index ec1899369..f11e9ecc8 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEnvironmentsForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateEnvironmentsForm.tsx @@ -68,28 +68,20 @@ export const ProjectTemplateEnvironmentsForm = ({ const updateProjectTemplate = useUpdateProjectTemplate(); const onFormSubmit = async (form: TFormSchema) => { - try { - const { environments: updatedEnvs } = await updateProjectTemplate.mutateAsync({ - environments: form.environments?.map((env, index) => ({ - ...env, - position: index + 1 - })), - templateId: projectTemplate.id - }); + const { environments: updatedEnvs } = await updateProjectTemplate.mutateAsync({ + environments: form.environments?.map((env, index) => ({ + ...env, + position: index + 1 + })), + templateId: projectTemplate.id + }); - reset({ environments: updatedEnvs }); + reset({ environments: updatedEnvs }); - createNotification({ - text: "Project template updated successfully", - type: "success" - }); - } catch (e: any) { - console.error(e); - createNotification({ - text: e.message ?? "Failed to update project template", - type: "error" - }); - } + createNotification({ + text: "Project template updated successfully", + type: "success" + }); }; const isEnvironmentLimitExceeded = diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateRolesSection.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateRolesSection.tsx index d65f7d785..cd5e41d2a 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateRolesSection.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/EditProjectTemplateSection/components/ProjectTemplateRolesSection.tsx @@ -42,26 +42,18 @@ export const ProjectTemplateRolesSection = ({ projectTemplate, isInfisicalTempla const updateProjectTemplate = useUpdateProjectTemplate(); const handleRemoveRole = async (slug: string) => { - try { - await updateProjectTemplate.mutateAsync({ - templateId: projectTemplate.id, - roles: projectTemplate.roles.filter( - (role) => role.slug !== slug && isCustomProjectRole(role.slug) // filter out default roles as well - ) - }); + await updateProjectTemplate.mutateAsync({ + templateId: projectTemplate.id, + roles: projectTemplate.roles.filter( + (role) => role.slug !== slug && isCustomProjectRole(role.slug) // filter out default roles as well + ) + }); - createNotification({ - text: "Successfully removed role from template", - type: "success" - }); - handlePopUpClose("removeRole"); - } catch (e) { - console.error(e); - createNotification({ - text: "Error removing role from template", - type: "error" - }); - } + createNotification({ + text: "Successfully removed role from template", + type: "success" + }); + handlePopUpClose("removeRole"); }; const editRole = popUp?.editRole?.data as TProjectRole; diff --git a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx index 6010d61f1..17224536d 100644 --- a/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/ProjectTemplatesTab/components/ProjectTemplateDetailsModal.tsx @@ -93,25 +93,15 @@ const ProjectTemplateForm = ({ onComplete, projectTemplate }: FormProps) => { ? updateProjectTemplate.mutateAsync({ templateId: projectTemplate.id, ...data }) : createProjectTemplate.mutateAsync({ ...data }); - try { - const template = await mutation; - createNotification({ - text: `Successfully ${ - projectTemplate ? "updated template details" : "created project template" - }`, - type: "success" - }); + const template = await mutation; + createNotification({ + text: `Successfully ${ + projectTemplate ? "updated template details" : "created project template" + }`, + type: "success" + }); - onComplete(template); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${ - projectTemplate ? "update template details" : "create project template" - }`, - type: "error" - }); - } + onComplete(template); }; return ( diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/UserDetailsByIDPage.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/UserDetailsByIDPage.tsx index 47a607f4e..d607bc100 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/UserDetailsByIDPage.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/UserDetailsByIDPage.tsx @@ -64,56 +64,38 @@ const Page = withPermission( ] as const); const onDeactivateMemberSubmit = async (orgMembershipId: string) => { - try { - await updateOrgMembership({ - organizationId: orgId, - membershipId: orgMembershipId, - isActive: false - }); + await updateOrgMembership({ + organizationId: orgId, + membershipId: orgMembershipId, + isActive: false + }); - createNotification({ - text: "Successfully deactivated user in organization", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to deactivate user in organization", - type: "error" - }); - } + createNotification({ + text: "Successfully deactivated user in organization", + type: "success" + }); handlePopUpClose("deactivateMember"); }; const onRemoveMemberSubmit = async (orgMembershipId: string) => { - try { - await deleteOrgMembership({ - orgId, - membershipId: orgMembershipId - }); + await deleteOrgMembership({ + orgId, + membershipId: orgMembershipId + }); - createNotification({ - text: "Successfully removed user from org", - type: "success" - }); - - handlePopUpClose("removeMember"); - navigate({ - to: "/organization/access-management" as const, - search: { - selectedTab: OrgAccessControlTabSections.Member - } - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to remove user from the organization", - type: "error" - }); - } + createNotification({ + text: "Successfully removed user from org", + type: "success" + }); handlePopUpClose("removeMember"); + navigate({ + to: "/organization/access-management" as const, + search: { + selectedTab: OrgAccessControlTabSections.Member + } + }); }; return ( diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserDetailsSection.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserDetailsSection.tsx index bef98e875..d26163de0 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserDetailsSection.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserDetailsSection.tsx @@ -46,26 +46,18 @@ export const UserDetailsSection = ({ membershipId, handlePopUpOpen }: Props) => const { mutateAsync: resendOrgMemberInvitation, isPending } = useResendOrgMemberInvitation(); const onResendInvite = async () => { - try { - const signupToken = await resendOrgMemberInvitation({ - membershipId - }); + const signupToken = await resendOrgMemberInvitation({ + membershipId + }); - if (signupToken) { - return; - } - - createNotification({ - text: "Successfully resent org invitation", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to resend org invitation", - type: "error" - }); + if (signupToken) { + return; } + + createNotification({ + text: "Successfully resent org invitation", + type: "success" + }); }; const getStatus = (m: OrgUser) => { diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx index 2518a5434..e8b976734 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserOrgMembershipModal.tsx @@ -87,34 +87,23 @@ export const UserOrgMembershipModal = ({ popUp, handlePopUpOpen, handlePopUpTogg }, [popUp?.orgMembership?.data, roles]); const onFormSubmit = async ({ role, metadata }: FormData) => { - try { - if (!orgId) return; + if (!orgId) return; - await updateOrgMembership({ - organizationId: orgId, - membershipId: popUpData.membershipId, - role: role.slug, - metadata - }); + await updateOrgMembership({ + organizationId: orgId, + membershipId: popUpData.membershipId, + role: role.slug, + metadata + }); - handlePopUpToggle("orgMembership", false); + handlePopUpToggle("orgMembership", false); - createNotification({ - text: "Successfully updated user organization role", - type: "success" - }); + createNotification({ + text: "Successfully updated user organization role", + type: "success" + }); - reset(); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to update user organization role"; - - createNotification({ - text, - type: "error" - }); - } + reset(); }; return ( diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx index 572be1d81..013358c5f 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserAddToProjectModal.tsx @@ -67,30 +67,19 @@ const UserAddToProjectModalChild = ({ membershipId, popUp, handlePopUpToggle }: }, [workspaces, projectMemberships]); const onFormSubmit = async ({ projectId }: FormData) => { - try { - await addUserToWorkspaceNonE2EE({ - projectId, - usernames: [popupData.username], - orgId - }); + await addUserToWorkspaceNonE2EE({ + projectId, + usernames: [popupData.username], + orgId + }); - createNotification({ - text: "Successfully added user to project", - type: "success" - }); + createNotification({ + text: "Successfully added user to project", + type: "success" + }); - reset(); - handlePopUpToggle("addUserToProject", false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to add identity to project"; - - createNotification({ - text, - type: "error" - }); - } + reset(); + handlePopUpToggle("addUserToProject", false); }; return ( diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsSection.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsSection.tsx index 8e7cf3ce8..fafa7aff3 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsSection.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserGroupsSection.tsx @@ -20,25 +20,18 @@ export const UserGroupsSection = ({ orgMembership }: Props) => { const { mutateAsync: removeUserFromGroup } = useRemoveUserFromGroup(); const handleRemoveUserFromGroup = useCallback(async (groupId: string, groupSlug: string) => { - try { - await removeUserFromGroup({ - groupId, - slug: groupSlug, - username: orgMembership.user.username - }); + await removeUserFromGroup({ + groupId, + slug: groupSlug, + username: orgMembership.user.username + }); - createNotification({ - type: "success", - text: "User removed from group successfully" - }); + createNotification({ + type: "success", + text: "User removed from group successfully" + }); - handlePopUpClose("removeUserFromGroup"); - } catch { - createNotification({ - type: "error", - text: "Failed to remove user from group" - }); - } + handlePopUpClose("removeUserFromGroup"); }, []); return ( diff --git a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx index 843b5a63c..6e128036b 100644 --- a/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx +++ b/frontend/src/pages/organization/UserDetailsByIDPage/components/UserProjectsSection/UserProjectsSection.tsx @@ -31,19 +31,11 @@ export const UserProjectsSection = ({ membershipId }: Props) => { ] as const); const handleRemoveUser = async (projectId: string, username: string) => { - try { - await removeUserFromWorkspace({ projectId, usernames: [username], orgId }); - createNotification({ - text: "Successfully removed user from project", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to remove user from the project", - type: "error" - }); - } + await removeUserFromWorkspace({ projectId, usernames: [username], orgId }); + createNotification({ + text: "Successfully removed user from project", + type: "success" + }); handlePopUpClose("removeUserFromProject"); }; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx index b9599be2d..346195e11 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx @@ -38,27 +38,18 @@ const CreateForm = ({ const onSubmit = async ( formData: DiscriminativePick ) => { - try { - const account = await createPamAccount.mutateAsync({ - ...formData, - folderId, - resourceId, - resourceType, - projectId - }); - createNotification({ - text: "Successfully created account", - type: "success" - }); - onComplete(account); - } catch (err: any) { - console.error(err); - createNotification({ - title: "Failed to create account", - text: err.message, - type: "error" - }); - } + const account = await createPamAccount.mutateAsync({ + ...formData, + folderId, + resourceId, + resourceType, + projectId + }); + createNotification({ + text: "Successfully created account", + type: "success" + }); + onComplete(account); }; switch (resourceType) { @@ -85,25 +76,16 @@ const UpdateForm = ({ account, onComplete }: UpdateFormProps) => { const onSubmit = async ( formData: DiscriminativePick ) => { - try { - const updatedAccount = await updatePamAccount.mutateAsync({ - accountId: account.id, - resourceType: account.resource.resourceType, - ...formData - }); - createNotification({ - text: "Successfully updated account", - type: "success" - }); - onComplete(updatedAccount); - } catch (err: any) { - console.error(err); - createNotification({ - title: "Failed to update account", - text: err.message, - type: "error" - }); - } + const updatedAccount = await updatePamAccount.mutateAsync({ + accountId: account.id, + resourceType: account.resource.resourceType, + ...formData + }); + createNotification({ + text: "Successfully updated account", + type: "success" + }); + onComplete(updatedAccount); }; switch (account.resource.resourceType) { diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAddFolderModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAddFolderModal.tsx index 7b057a648..266a724cd 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAddFolderModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAddFolderModal.tsx @@ -17,25 +17,16 @@ export const PamAddFolderModal = ({ isOpen, onOpenChange, projectId, currentFold console.log({ currentFolderId }); const onSubmit = async (formData: Pick) => { - try { - await createPamFolder.mutateAsync({ - ...formData, - parentId: currentFolderId, - projectId - }); - createNotification({ - text: "Successfully created folder", - type: "success" - }); - onOpenChange(false); - } catch (err: any) { - console.error(err); - createNotification({ - title: "Failed to create folder", - text: err.message, - type: "error" - }); - } + await createPamFolder.mutateAsync({ + ...formData, + parentId: currentFolderId, + projectId + }); + createNotification({ + text: "Successfully created folder", + type: "success" + }); + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteAccountModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteAccountModal.tsx index 45ac03082..c094d3c53 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteAccountModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteAccountModal.tsx @@ -20,25 +20,17 @@ export const PamDeleteAccountModal = ({ isOpen, onOpenChange, account }: Props) } = account; const handleDelete = async () => { - try { - await deletePamAccount.mutateAsync({ - accountId, - resourceType - }); + await deletePamAccount.mutateAsync({ + accountId, + resourceType + }); - createNotification({ - text: "Successfully deleted account", - type: "success" - }); + createNotification({ + text: "Successfully deleted account", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete account", - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteFolderModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteFolderModal.tsx index f0e2476c2..d901d0ba0 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteFolderModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamDeleteFolderModal.tsx @@ -16,24 +16,16 @@ export const PamDeleteFolderModal = ({ isOpen, onOpenChange, folder }: Props) => const { id: folderId, name } = folder; const handleDelete = async () => { - try { - await deletePamFolder.mutateAsync({ - folderId - }); + await deletePamFolder.mutateAsync({ + folderId + }); - createNotification({ - text: "Successfully deleted folder", - type: "success" - }); + createNotification({ + text: "Successfully deleted folder", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete folder", - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamUpdateFolderModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamUpdateFolderModal.tsx index b4bcc46a4..4162ce25f 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamUpdateFolderModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamUpdateFolderModal.tsx @@ -16,24 +16,15 @@ export const PamUpdateFolderModal = ({ isOpen, onOpenChange, folder }: Props) => if (!folder) return null; const onSubmit = async (formData: Pick) => { - try { - await updatePamFolder.mutateAsync({ - ...formData, - folderId: folder.id - }); - createNotification({ - text: "Successfully updated folder", - type: "success" - }); - onOpenChange(false); - } catch (err: any) { - console.error(err); - createNotification({ - title: "Failed to updated folder", - text: err.message, - type: "error" - }); - } + await updatePamFolder.mutateAsync({ + ...formData, + folderId: folder.id + }); + createNotification({ + text: "Successfully updated folder", + type: "success" + }); + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamDeleteResourceModal.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamDeleteResourceModal.tsx index efda53466..7f0d2bd19 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamDeleteResourceModal.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamDeleteResourceModal.tsx @@ -16,25 +16,17 @@ export const PamDeleteResourceModal = ({ isOpen, onOpenChange, resource }: Props const { id: resourceId, name, resourceType } = resource; const handleDelete = async () => { - try { - await deletePamResource.mutateAsync({ - resourceId, - resourceType - }); + await deletePamResource.mutateAsync({ + resourceId, + resourceType + }); - createNotification({ - text: `Successfully removed ${PAM_RESOURCE_TYPE_MAP[resourceType].name} resource`, - type: "success" - }); + createNotification({ + text: `Successfully removed ${PAM_RESOURCE_TYPE_MAP[resourceType].name} resource`, + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to remove ${PAM_RESOURCE_TYPE_MAP[resourceType].name} resource`, - type: "error" - }); - } + onOpenChange(false); }; return ( diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx index 2bc54e7cd..3dd3aeec8 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx @@ -35,24 +35,15 @@ const CreateForm = ({ resourceType, onComplete, projectId }: CreateFormProps) => "name" | "resourceType" | "connectionDetails" | "gatewayId" > ) => { - try { - const resource = await createPamResource.mutateAsync({ - ...formData, - projectId - }); - createNotification({ - text: `Successfully created ${resourceName} resource`, - type: "success" - }); - onComplete(resource); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to create ${resourceName} resource`, - text: err.message, - type: "error" - }); - } + const resource = await createPamResource.mutateAsync({ + ...formData, + projectId + }); + createNotification({ + text: `Successfully created ${resourceName} resource`, + type: "success" + }); + onComplete(resource); }; switch (resourceType) { @@ -72,24 +63,15 @@ const UpdateForm = ({ resource, onComplete }: UpdateFormProps) => { const onSubmit = async ( formData: DiscriminativePick ) => { - try { - const updatedResource = await updatePamResource.mutateAsync({ - resourceId: resource.id, - ...formData - }); - createNotification({ - text: `Successfully updated ${resourceName} resource`, - type: "success" - }); - onComplete(updatedResource); - } catch (err: any) { - console.error(err); - createNotification({ - title: `Failed to update ${resourceName} resource`, - text: err.message, - type: "error" - }); - } + const updatedResource = await updatePamResource.mutateAsync({ + resourceId: resource.id, + ...formData + }); + createNotification({ + text: `Successfully updated ${resourceName} resource`, + type: "success" + }); + onComplete(updatedResource); }; switch (resource.resourceType) { diff --git a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogOutput.tsx b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogOutput.tsx deleted file mode 100644 index 99e30627d..000000000 --- a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogOutput.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { useState } from "react"; - -import { HighlightText } from "@app/components/v2/HighlightText"; -import { PamResourceType } from "@app/hooks/api/pam"; - -type TableLog = { - command?: string; - data_rows: Record[]; - total_rows?: number; -}; - -export const PamSessionLogOutput = ({ - content, - resourceType, - search -}: { - content: string; - resourceType: PamResourceType; - search: string; -}) => { - const [isRawView, setIsRawView] = useState(false); - - let parsedContent: TableLog | null = null; - - if (resourceType === PamResourceType.Postgres || resourceType === PamResourceType.MySQL) { - try { - const parsed = JSON.parse(content); - - if ( - parsed && - typeof parsed === "object" && - !Array.isArray(parsed) && - parsed.data_rows && - Array.isArray(parsed.data_rows) && - parsed.data_rows.length > 0 && - typeof parsed.data_rows[0] === "object" && - parsed.data_rows[0] !== null - ) { - parsedContent = parsed; - } - } catch { - // Not a valid JSON or doesn't match structure, will render as plain text - } - } - - if (parsedContent) { - const headers = Object.keys(parsedContent.data_rows[0]); - return ( -
- {isRawView ? ( -
- -
- ) : ( - <> - {parsedContent.command && ( -
{`> ${parsedContent.command}`}
- )} -
- - - - {headers.map((header) => ( - - ))} - - - - {parsedContent.data_rows.map((row, rowIndex) => ( - - {headers.map((header) => ( - - ))} - - ))} - -
- -
- -
-
- - )} -
- - - {parsedContent.total_rows !== undefined && ( -
- Total rows: {parsedContent.total_rows} -
- )} -
-
- ); - } - - return ( -
- -
- ); -}; diff --git a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx index 0fbccbc9b..7a4945540 100644 --- a/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx +++ b/frontend/src/pages/pam/PamSessionsByIDPage/components/PamSessionLogsSection.tsx @@ -7,7 +7,6 @@ import { Input } from "@app/components/v2"; import { HighlightText } from "@app/components/v2/HighlightText"; import { TPamSession } from "@app/hooks/api/pam"; -import { PamSessionLogOutput } from "./PamSessionLogOutput"; import { formatLogContent } from "./PamSessionLogsSection.utils"; type Props = { @@ -104,20 +103,9 @@ export const PamSessionLogsSection = ({ session }: Props) => { >
{log.output && ( - <> -
-
- OUTPUT -
-
-
- -
- +
+ +
)}
diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupModal.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupModal.tsx index a279bd1b0..f8788762c 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupModal.tsx @@ -62,26 +62,19 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => { }); const onFormSubmit = async ({ group, role }: FormData) => { - try { - await addGroupToWorkspaceMutateAsync({ - projectId: currentProject?.id || "", - groupId: group.id, - role: role.slug || undefined - }); + await addGroupToWorkspaceMutateAsync({ + projectId: currentProject?.id || "", + groupId: group.id, + role: role.slug || undefined + }); - reset(); - handlePopUpToggle("group", false); + reset(); + handlePopUpToggle("group", false); - createNotification({ - text: "Successfully added group to project", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to add group to project", - type: "error" - }); - } + createNotification({ + text: "Successfully added group to project", + type: "success" + }); }; return filteredGroupMembershipOrgs.length ? ( diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx index 188c2ba7d..67e962cfc 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupRoles.tsx @@ -253,18 +253,14 @@ const GroupRolesForm = ({ projectRoles, roles, groupId, onClose }: FormProps) => }; }); - try { - await updateGroupWorkspaceRole.mutateAsync({ - projectId: currentProject?.id || "", - groupId, - roles: selectedRoles - }); - createNotification({ text: "Successfully updated group role", type: "success" }); - onClose(); - setSearchRoles(""); - } catch { - createNotification({ text: "Failed to update group role", type: "error" }); - } + await updateGroupWorkspaceRole.mutateAsync({ + projectId: currentProject?.id || "", + groupId, + roles: selectedRoles + }); + createNotification({ text: "Successfully updated group role", type: "success" }); + onClose(); + setSearchRoles(""); }; return ( diff --git a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx index 2df59b182..8415a5e88 100644 --- a/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/GroupsTab/components/GroupsSection/GroupsSection.tsx @@ -42,28 +42,17 @@ export const GroupsSection = () => { }; const onRemoveGroupSubmit = async (groupId: string) => { - try { - await deleteMutateAsync({ - groupId, - projectId: currentProject?.id || "" - }); + await deleteMutateAsync({ + groupId, + projectId: currentProject?.id || "" + }); - createNotification({ - text: "Successfully removed identity from project", - type: "success" - }); + createNotification({ + text: "Successfully removed identity from project", + type: "success" + }); - handlePopUpClose("deleteGroup"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to remove group from project"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteGroup"); }; return ( diff --git a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx index 17dbdca27..48e01e2aa 100644 --- a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/IdentityTab.tsx @@ -119,28 +119,17 @@ export const IdentityTab = withProjectPermission( ] as const); const onRemoveIdentitySubmit = async (identityId: string) => { - try { - await deleteMutateAsync({ - identityId, - projectId - }); + await deleteMutateAsync({ + identityId, + projectId + }); - createNotification({ - text: "Successfully removed identity from project", - type: "success" - }); + createNotification({ + text: "Successfully removed identity from project", + type: "success" + }); - handlePopUpClose("deleteIdentity"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to remove identity from project"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteIdentity"); }; const handleSort = (column: ProjectIdentityOrderBy) => { diff --git a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx index 55f22c5ae..3274185b1 100644 --- a/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/IdentityTab/components/IdentityModal.tsx @@ -104,40 +104,29 @@ const Content = ({ popUp, handlePopUpToggle }: Props) => { }); const onFormSubmit = async ({ identity, role }: FormData) => { - try { - await addIdentityToWorkspaceMutateAsync({ - projectId, - identityId: identity.id, - role: role.slug || undefined - }); + await addIdentityToWorkspaceMutateAsync({ + projectId, + identityId: identity.id, + role: role.slug || undefined + }); - createNotification({ - text: "Successfully added identity to project", - type: "success" - }); + createNotification({ + text: "Successfully added identity to project", + type: "success" + }); - const nextAvailableMembership = filteredIdentityMembershipOrgs.filter( - (membership) => membership.identity.id !== identity.id - )[0]; + const nextAvailableMembership = filteredIdentityMembershipOrgs.filter( + (membership) => membership.identity.id !== identity.id + )[0]; - // prevents combobox from displaying previously added identity - reset({ - identity: { - name: nextAvailableMembership?.identity.name, - id: nextAvailableMembership?.identity.id - } - }); - handlePopUpToggle("identity", false); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to add identity to project"; - - createNotification({ - text, - type: "error" - }); - } + // prevents combobox from displaying previously added identity + reset({ + identity: { + name: nextAvailableMembership?.identity.name, + id: nextAvailableMembership?.identity.id + } + }); + handlePopUpToggle("identity", false); }; if (isMembershipsLoading || isRolesLoading) diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx index 3477440fe..7498b3181 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx @@ -25,14 +25,12 @@ import { useProject } from "@app/context"; import { - useAddUsersToOrg, useAddUserToWsNonE2EE, useGetOrgUsers, useGetProjectRoles, useGetWorkspaceUsers } from "@app/hooks/api"; import { ProjectVersion } from "@app/hooks/api/projects/types"; -import { ProjectMembershipRole } from "@app/hooks/api/roles/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; const addMemberFormSchema = z.object({ @@ -86,7 +84,6 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { defaultValues: { orgMemberships: [], projectRoleSlugs: [] } }); - const { mutateAsync: addMemberToOrg } = useAddUsersToOrg(); const { mutateAsync: addUserToProject } = useAddUserToWsNonE2EE(); useEffect(() => { @@ -110,65 +107,49 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { if (!selectedMembers) return; - try { - if (currentProject.version === ProjectVersion.V1) { + if (currentProject.version === ProjectVersion.V1) { + createNotification({ + type: "error", + text: "Please upgrade your project to invite new members to the project." + }); + } else { + const inviteeEmails = selectedMembers + .map((member) => { + if (!member) return null; + + if (member.user.username) { + return member.user.username; + } + + if (member.user.email) { + return member.user.email; + } + + return null; + }) + .filter(Boolean) as string[]; + + if (inviteeEmails.length !== selectedMembers.length) { createNotification({ - type: "error", - text: "Please upgrade your project to invite new members to the project." + text: "Failed to add users to project. One or more users were invalid.", + type: "error" + }); + return; + } + + if (newInvitees.length || inviteeEmails.length) { + await addUserToProject({ + usernames: [...inviteeEmails, ...newInvitees], + orgId, + projectId: currentProject.id, + roleSlugs: projectRoleSlugs.map((role) => role.slug) }); - } else { - const inviteeEmails = selectedMembers - .map((member) => { - if (!member) return null; - - if (member.user.username) { - return member.user.username; - } - - if (member.user.email) { - return member.user.email; - } - - return null; - }) - .filter(Boolean) as string[]; - - if (inviteeEmails.length !== selectedMembers.length) { - createNotification({ - text: "Failed to add users to project. One or more users were invalid.", - type: "error" - }); - return; - } - - if (newInvitees.length) { - await addMemberToOrg({ - inviteeEmails: newInvitees, - organizationId: orgId, - organizationRoleSlug: ProjectMembershipRole.Member // only applies to new invites - }); - } - if (newInvitees.length || inviteeEmails.length) { - await addUserToProject({ - usernames: [...inviteeEmails, ...newInvitees], - orgId, - projectId: currentProject.id, - roleSlugs: projectRoleSlugs.map((role) => role.slug) - }); - } } - createNotification({ - text: "Successfully added user to the project", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to add user to project", - type: "error" - }); - return; } + createNotification({ + text: "Successfully added user to the project", + type: "success" + }); handlePopUpToggle("addMember", false); reset(); }; diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx index 291a5b58b..5be239042 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/MemberRbacSection.tsx @@ -127,17 +127,13 @@ export const MemberRbacSection = ({ projectMember, onOpenUpgradeModal }: Props) return; } - try { - await updateMembershipRole.mutateAsync({ - projectId, - membershipId: projectMember.id, - roles: sanitizedRoles - }); - createNotification({ text: "Successfully updated roles", type: "success" }); - roleForm.reset(undefined, { keepValues: true }); - } catch { - createNotification({ text: "Failed to update role", type: "error" }); - } + await updateMembershipRole.mutateAsync({ + projectId, + membershipId: projectMember.id, + roles: sanitizedRoles + }); + createNotification({ text: "Successfully updated roles", type: "success" }); + roleForm.reset(undefined, { keepValues: true }); }; if (isRolesLoading) diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx index 33c0d1077..c3eb0c9aa 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MemberRoleForm/SpecificPrivilegeSection.tsx @@ -182,21 +182,14 @@ export const SpecificPrivilegeSecretForm = ({ } if (deleteUserPrivilege.isPending) return; - try { - await deleteUserPrivilege.mutateAsync({ - privilegeId: privilege.id, - projectMembershipId: privilege.projectMembershipId - }); - createNotification({ - type: "success", - text: "Successfully deleted privilege" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to delete privilege" - }); - } + await deleteUserPrivilege.mutateAsync({ + privilegeId: privilege.id, + projectMembershipId: privilege.projectMembershipId + }); + createNotification({ + type: "success", + text: "Successfully deleted privilege" + }); }; // This is used for requesting access additional privileges, not directly creating a privilege! diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx index f18669bf8..38dc5e47e 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/MembersSection.tsx @@ -33,23 +33,15 @@ export const MembersSection = () => { if (!currentOrg?.id) return; if (!currentProject?.id) return; - try { - await removeUserFromWorkspace({ - projectId: currentProject.id, - usernames: [username], - orgId: currentOrg.id - }); - createNotification({ - text: "Successfully removed user from project", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to remove user from the project", - type: "error" - }); - } + await removeUserFromWorkspace({ + projectId: currentProject.id, + usernames: [username], + orgId: currentOrg.id + }); + createNotification({ + text: "Successfully removed user from project", + type: "success" + }); handlePopUpClose("removeMember"); }; diff --git a/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx b/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx index fca53fa06..eeb29725f 100644 --- a/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx @@ -77,17 +77,12 @@ export const ProjectRoleList = () => { const handleRoleDelete = async () => { const { id } = popUp?.deleteRole?.data as TProjectRole; - try { - await deleteRole({ - projectId, - id - }); - createNotification({ type: "success", text: "Successfully removed the role" }); - handlePopUpClose("deleteRole"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete role" }); - } + await deleteRole({ + projectId, + id + }); + createNotification({ type: "success", text: "Successfully removed the role" }); + handlePopUpClose("deleteRole"); }; const { diff --git a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx index 873422f3c..25d943f4a 100644 --- a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx @@ -6,7 +6,6 @@ import { useTranslation } from "react-i18next"; import { faCheck, faCopy, faPlus, faTrashCan } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; -import { AxiosError } from "axios"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; @@ -110,45 +109,29 @@ const ServiceTokenForm = () => { }; const onFormSubmit = async ({ name, scopes, expiresIn, permissions }: FormData) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - const randomBytes = crypto.randomBytes(16).toString("hex"); + const randomBytes = crypto.randomBytes(16).toString("hex"); - const { serviceToken } = await createServiceToken.mutateAsync({ - encryptedKey: "", - iv: "", - tag: "", - scopes, - expiresIn: Number(expiresIn), - name, - workspaceId: currentProject.id, - randomBytes, - permissions: Object.entries(permissions) - .filter(([, permissionsValue]) => permissionsValue) - .map(([permissionsKey]) => permissionsKey) - }); + const { serviceToken } = await createServiceToken.mutateAsync({ + encryptedKey: "", + iv: "", + tag: "", + scopes, + expiresIn: Number(expiresIn), + name, + workspaceId: currentProject.id, + randomBytes, + permissions: Object.entries(permissions) + .filter(([, permissionsValue]) => permissionsValue) + .map(([permissionsKey]) => permissionsKey) + }); - setToken(serviceToken); - createNotification({ - text: "Successfully created a service token", - type: "success" - }); - } catch (err) { - console.error(err); - const axiosError = err as AxiosError; - if (axiosError?.response?.status === 401) { - createNotification({ - text: "You do not have access to the selected environment/path", - type: "error" - }); - } else { - createNotification({ - text: "Failed to create a service token", - type: "error" - }); - } - } + setToken(serviceToken); + createNotification({ + text: "Successfully created a service token", + type: "success" + }); }; return !hasServiceToken ? ( diff --git a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenSection.tsx b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenSection.tsx index 64b042c35..6f2e82e02 100644 --- a/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenSection.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenSection.tsx @@ -28,23 +28,15 @@ export const ServiceTokenSection = withProjectPermission( ] as const); const onDeleteApproved = async () => { - try { - deleteServiceToken.mutateAsync( - (popUp?.deleteAPITokenConfirmation?.data as DeleteModalData)?.id - ); - createNotification({ - text: "Successfully deleted service token", - type: "success" - }); + await deleteServiceToken.mutateAsync( + (popUp?.deleteAPITokenConfirmation?.data as DeleteModalData)?.id + ); + createNotification({ + text: "Successfully deleted service token", + type: "success" + }); - handlePopUpClose("deleteAPITokenConfirmation"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete service token", - type: "error" - }); - } + handlePopUpClose("deleteAPITokenConfirmation"); }; return ( diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx index cca6cabfd..368a740c2 100644 --- a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupDetailsSection.tsx @@ -35,38 +35,27 @@ export const GroupDetailsSection = ({ groupMembership }: Props) => { const navigate = useNavigate(); const onRemoveGroupSubmit = async () => { - try { - await deleteMutateAsync({ - groupId: groupMembership.group.id, + await deleteMutateAsync({ + groupId: groupMembership.group.id, + projectId: currentProject.id + }); + + createNotification({ + text: "Successfully removed group from project", + type: "success" + }); + + navigate({ + to: `${getProjectBaseURL(currentProject.type)}/access-management`, + params: { projectId: currentProject.id - }); + }, + search: { + selectedTab: "groups" + } + }); - createNotification({ - text: "Successfully removed group from project", - type: "success" - }); - - navigate({ - to: `${getProjectBaseURL(currentProject.type)}/access-management`, - params: { - projectId: currentProject.id - }, - search: { - selectedTab: "groups" - } - }); - - handlePopUpClose("deleteGroup"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to remove group from project"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteGroup"); }; return ( diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx index 1df989602..7ced08534 100644 --- a/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx +++ b/frontend/src/pages/project/GroupDetailsByIDPage/components/GroupMembersSection/GroupMembersTable.tsx @@ -1,4 +1,4 @@ -import { useMemo } from "react"; +import { useEffect, useMemo } from "react"; import { faArrowDown, faArrowUp, @@ -7,6 +7,7 @@ import { faSearch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate, useSearch } from "@tanstack/react-router"; import { createNotification } from "@app/components/notifications"; import { @@ -48,6 +49,7 @@ enum GroupMembersOrderBy { } export const GroupMembersTable = ({ groupMembership }: Props) => { + const navigate = useNavigate(); const { search, setSearch, @@ -62,6 +64,21 @@ export const GroupMembersTable = ({ groupMembership }: Props) => { initPerPage: getUserTablePreference("projectGroupMembersTable", PreferenceKey.PerPage, 20) }); + // this handles links from secret versions when the actor is in a group membership + const { username, ...restSearch } = useSearch({ + strict: false + }); + useEffect(() => { + if (username) { + setSearch(username); + navigate({ + to: ".", + replace: true, + search: restSearch + }); + } + }, [username]); + const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp(["assumePrivileges"] as const); const handlePerPageChange = (newPerPage: number) => { diff --git a/frontend/src/pages/project/GroupDetailsByIDPage/route-secret-manager.tsx b/frontend/src/pages/project/GroupDetailsByIDPage/route-secret-manager.tsx index 01349a83b..c7c1e6e85 100644 --- a/frontend/src/pages/project/GroupDetailsByIDPage/route-secret-manager.tsx +++ b/frontend/src/pages/project/GroupDetailsByIDPage/route-secret-manager.tsx @@ -1,4 +1,6 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { zodValidator } from "@tanstack/zod-adapter"; +import { z } from "zod"; import { ProjectAccessControlTabs } from "@app/types/project"; @@ -8,6 +10,11 @@ export const Route = createFileRoute( "/_authenticate/_inject-org-details/_org-layout/projects/secret-management/$projectId/_secret-manager-layout/groups/$groupId" )({ component: GroupDetailsByIDPage, + validateSearch: zodValidator( + z.object({ + username: z.string().optional().catch(undefined) + }) + ), beforeLoad: ({ context, params }) => { return { breadcrumbs: [ diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx index e49e05022..65a2a5710 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/IdentityDetailsByIDPage.tsx @@ -76,35 +76,24 @@ const Page = () => { }; const onRemoveIdentitySubmit = async () => { - try { - await deleteMutateAsync({ - identityId, + await deleteMutateAsync({ + identityId, + projectId + }); + createNotification({ + text: "Successfully removed identity from project", + type: "success" + }); + handlePopUpClose("deleteIdentity"); + navigate({ + to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, + params: { projectId - }); - createNotification({ - text: "Successfully removed identity from project", - type: "success" - }); - handlePopUpClose("deleteIdentity"); - navigate({ - to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, - params: { - projectId - }, - search: { - selectedTab: "identities" - } - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to remove identity from project"; - - createNotification({ - text, - type: "error" - }); - } + }, + search: { + selectedTab: "identities" + } + }); }; if (isMembershipDetailsLoading) { diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx index a5d74271c..956c01d69 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeModifySection.tsx @@ -131,33 +131,28 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({ temporaryAccessStartTime: el.temporaryAccess.temporaryAccessStartTime }; - try { - if (isCreate) { - await createIdentityProjectAdditionalPrivilege({ - permissions: formRolePermission2API(el.permissions), - identityId, - projectId, - slug: el.slug || undefined, - type: accessType - }); - createNotification({ type: "success", text: "Successfully created privilege" }); - } else { - if (!projectId || !privilegeDetails?.id) return; - await updateIdentityProjectAdditionalPrivilege({ - privilegeId: privilegeDetails.id, - permissions: formRolePermission2API(el.permissions), - projectId, - identityId, - slug: el.slug || undefined, - type: accessType - }); - createNotification({ type: "success", text: "Successfully updated privilege" }); - } - onGoBack(); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update privilege" }); + if (isCreate) { + await createIdentityProjectAdditionalPrivilege({ + permissions: formRolePermission2API(el.permissions), + identityId, + projectId, + slug: el.slug || undefined, + type: accessType + }); + createNotification({ type: "success", text: "Successfully created privilege" }); + } else { + if (!projectId || !privilegeDetails?.id) return; + await updateIdentityProjectAdditionalPrivilege({ + privilegeId: privilegeDetails.id, + permissions: formRolePermission2API(el.permissions), + projectId, + identityId, + slug: el.slug || undefined, + type: accessType + }); + createNotification({ type: "success", text: "Successfully updated privilege" }); } + onGoBack(); }; const privilegeTemporaryAccess = form.watch("temporaryAccess"); diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx index f98f6ebf8..431875b3c 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityProjectAdditionalPrivilegeSection/IdentityProjectAdditionalPrivilegeSection.tsx @@ -57,18 +57,13 @@ export const IdentityProjectAdditionalPrivilegeSection = ({ identityMembershipDe const handlePrivilegeDelete = async () => { const { id } = popUp?.deletePrivilege?.data as { id: string }; - try { - await deletePrivilege({ - privilegeId: id, - projectId, - identityId - }); - createNotification({ type: "success", text: "Successfully removed the privilege" }); - handlePopUpClose("deletePrivilege"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete privilege" }); - } + await deletePrivilege({ + privilegeId: id, + projectId, + identityId + }); + createNotification({ type: "success", text: "Successfully removed the privilege" }); + handlePopUpClose("deletePrivilege"); }; return ( diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx index 34b5229d6..dce632121 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleDetailsSection.tsx @@ -50,42 +50,37 @@ export const IdentityRoleDetailsSection = ({ const handleRoleDelete = async () => { const { id } = popUp?.deleteRole?.data as TProjectRole; - try { - const updatedRoles = identityMembershipDetails?.roles?.filter((el) => el.id !== id); - await updateIdentityWorkspaceRole({ - projectId: currentProject?.id || "", - identityId: identityMembershipDetails.identity.id, - roles: updatedRoles.map( - ({ - role, - customRoleSlug, - isTemporary, - temporaryMode, - temporaryRange, - temporaryAccessStartTime, - temporaryAccessEndTime - }) => ({ - role: role === "custom" ? customRoleSlug : role, - ...(isTemporary - ? { - isTemporary, - temporaryMode, - temporaryRange, - temporaryAccessStartTime, - temporaryAccessEndTime - } - : { - isTemporary - }) - }) - ) - }); - createNotification({ type: "success", text: "Successfully removed role" }); - handlePopUpClose("deleteRole"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete role" }); - } + const updatedRoles = identityMembershipDetails?.roles?.filter((el) => el.id !== id); + await updateIdentityWorkspaceRole({ + projectId: currentProject?.id || "", + identityId: identityMembershipDetails.identity.id, + roles: updatedRoles.map( + ({ + role, + customRoleSlug, + isTemporary, + temporaryMode, + temporaryRange, + temporaryAccessStartTime, + temporaryAccessEndTime + }) => ({ + role: role === "custom" ? customRoleSlug : role, + ...(isTemporary + ? { + isTemporary, + temporaryMode, + temporaryRange, + temporaryAccessStartTime, + temporaryAccessEndTime + } + : { + isTemporary + }) + }) + ) + }); + createNotification({ type: "success", text: "Successfully removed role" }); + handlePopUpClose("deleteRole"); }; return ( diff --git a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx index a74428e98..8bc451151 100644 --- a/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx +++ b/frontend/src/pages/project/IdentityDetailsByIDPage/components/IdentityRoleDetailsSection/IdentityRoleModify.tsx @@ -114,16 +114,12 @@ export const IdentityRoleModify = ({ identityProjectMembership }: Props) => { }; }); - try { - await updateIdentityWorkspaceRole.mutateAsync({ - projectId, - identityId: identityProjectMembership.identity.id, - roles: sanitizedRoles - }); - createNotification({ text: "Successfully updated roles", type: "success" }); - } catch { - createNotification({ text: "Failed to update roles", type: "error" }); - } + await updateIdentityWorkspaceRole.mutateAsync({ + projectId, + identityId: identityProjectMembership.identity.id, + roles: sanitizedRoles + }); + createNotification({ text: "Successfully updated roles", type: "success" }); }; if (isRolesLoading) diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx index 31e10986f..5d8ae812c 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/MemberDetailsByIDPage.tsx @@ -83,29 +83,21 @@ export const Page = () => { const handleRemoveUser = async () => { if (!currentOrg?.id || !currentProject?.id || !membershipDetails?.user?.username) return; - try { - await removeUserFromWorkspace({ - projectId, - usernames: [membershipDetails?.user?.username], - orgId: currentOrg.id - }); - createNotification({ - text: "Successfully removed user from project", - type: "success" - }); - navigate({ - to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, - params: { - projectId: currentProject.id - } - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to remove user from the project", - type: "error" - }); - } + await removeUserFromWorkspace({ + projectId, + usernames: [membershipDetails?.user?.username], + orgId: currentOrg.id + }); + createNotification({ + text: "Successfully removed user from project", + type: "success" + }); + navigate({ + to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, + params: { + projectId: currentProject.id + } + }); handlePopUpClose("removeMember"); }; diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MemberProjectAdditionalPrivilegeSection.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MemberProjectAdditionalPrivilegeSection.tsx index 7094c9ca8..28c3c9b5f 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MemberProjectAdditionalPrivilegeSection.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MemberProjectAdditionalPrivilegeSection.tsx @@ -60,17 +60,12 @@ export const MemberProjectAdditionalPrivilegeSection = ({ membershipDetails }: P const handlePrivilegeDelete = async () => { const { id } = popUp?.deletePrivilege?.data as { id: string }; - try { - await deletePrivilege({ - privilegeId: id, - projectMembershipId: membershipDetails.id - }); - createNotification({ type: "success", text: "Successfully removed the privilege" }); - handlePopUpClose("deletePrivilege"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete privilege" }); - } + await deletePrivilege({ + privilegeId: id, + projectMembershipId: membershipDetails.id + }); + createNotification({ type: "success", text: "Successfully removed the privilege" }); + handlePopUpClose("deletePrivilege"); }; return ( diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx index 1a217a509..bc5bd3a6f 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberProjectAdditionalPrivilegeSection/MembershipProjectAdditionalPrivilegeModifySection.tsx @@ -129,31 +129,26 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({ temporaryAccessStartTime: el.temporaryAccess.temporaryAccessStartTime }; - try { - if (isCreate) { - await createUserProjectAdditionalPrivilege({ - permissions: formRolePermission2API(el.permissions), - projectMembershipId, - slug: el.slug || undefined, - type: accessType - }); - createNotification({ type: "success", text: "Successfully created privilege" }); - } else { - if (!projectId || !privilegeDetails?.id) return; - await updateUserProjectAdditionalPrivilege({ - privilegeId: privilegeDetails.id, - permissions: formRolePermission2API(el.permissions), - projectMembershipId, - slug: el.slug || undefined, - type: accessType - }); - createNotification({ type: "success", text: "Successfully updated privilege" }); - } - onGoBack(); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update privilege" }); + if (isCreate) { + await createUserProjectAdditionalPrivilege({ + permissions: formRolePermission2API(el.permissions), + projectMembershipId, + slug: el.slug || undefined, + type: accessType + }); + createNotification({ type: "success", text: "Successfully created privilege" }); + } else { + if (!projectId || !privilegeDetails?.id) return; + await updateUserProjectAdditionalPrivilege({ + privilegeId: privilegeDetails.id, + permissions: formRolePermission2API(el.permissions), + projectMembershipId, + slug: el.slug || undefined, + type: accessType + }); + createNotification({ type: "success", text: "Successfully updated privilege" }); } + onGoBack(); }; const privilegeTemporaryAccess = form.watch("temporaryAccess"); diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx index 703511c84..2654e237e 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleDetailsSection.tsx @@ -55,42 +55,37 @@ export const MemberRoleDetailsSection = ({ const handleRoleDelete = async () => { const { id } = popUp?.deleteRole?.data as TProjectRole; - try { - const updatedRoles = membershipDetails?.roles?.filter((el) => el.id !== id); - await updateUserWorkspaceRole({ - projectId, - roles: updatedRoles.map( - ({ - role, - customRoleSlug, - isTemporary, - temporaryMode, - temporaryRange, - temporaryAccessStartTime, - temporaryAccessEndTime - }) => ({ - role: role === "custom" ? customRoleSlug : role, - ...(isTemporary - ? { - isTemporary, - temporaryMode, - temporaryRange, - temporaryAccessStartTime, - temporaryAccessEndTime - } - : { - isTemporary - }) - }) - ), - membershipId: membershipDetails.id - }); - createNotification({ type: "success", text: "Successfully removed role" }); - handlePopUpClose("deleteRole"); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to delete role" }); - } + const updatedRoles = membershipDetails?.roles?.filter((el) => el.id !== id); + await updateUserWorkspaceRole({ + projectId, + roles: updatedRoles.map( + ({ + role, + customRoleSlug, + isTemporary, + temporaryMode, + temporaryRange, + temporaryAccessStartTime, + temporaryAccessEndTime + }) => ({ + role: role === "custom" ? customRoleSlug : role, + ...(isTemporary + ? { + isTemporary, + temporaryMode, + temporaryRange, + temporaryAccessStartTime, + temporaryAccessEndTime + } + : { + isTemporary + }) + }) + ), + membershipId: membershipDetails.id + }); + createNotification({ type: "success", text: "Successfully removed role" }); + handlePopUpClose("deleteRole"); }; return ( diff --git a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx index 287ea3ad7..947424191 100644 --- a/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx +++ b/frontend/src/pages/project/MemberDetailsByIDPage/components/MemberRoleDetailsSection/MemberRoleModify.tsx @@ -126,16 +126,12 @@ export const MemberRoleModify = ({ projectMember, onOpenUpgradeModal }: Props) = return; } - try { - await updateMembershipRole.mutateAsync({ - projectId, - membershipId: projectMember.id, - roles: sanitizedRoles - }); - createNotification({ text: "Successfully updated roles", type: "success" }); - } catch { - createNotification({ text: "Failed to update roles", type: "error" }); - } + await updateMembershipRole.mutateAsync({ + projectId, + membershipId: projectMember.id, + roles: sanitizedRoles + }); + createNotification({ text: "Successfully updated roles", type: "success" }); }; if (isRolesLoading) diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx index 9650b3fb8..9a87266c4 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/RoleDetailsBySlugPage.tsx @@ -53,38 +53,27 @@ const Page = () => { ] as const); const onDeleteRoleSubmit = async () => { - try { - if (!currentProject?.slug || !data?.id) return; + if (!currentProject?.slug || !data?.id) return; - await deleteProjectRole({ - projectId, - id: data.id - }); + await deleteProjectRole({ + projectId, + id: data.id + }); - createNotification({ - text: "Successfully deleted project role", - type: "success" - }); - handlePopUpClose("deleteRole"); - navigate({ - to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, - params: { - projectId - }, - search: { - selectedTab: ProjectAccessControlTabs.Roles - } - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete project role"; - - createNotification({ - text, - type: "error" - }); - } + createNotification({ + text: "Successfully deleted project role", + type: "success" + }); + handlePopUpClose("deleteRole"); + navigate({ + to: `${getProjectBaseURL(currentProject.type)}/access-management` as const, + params: { + projectId + }, + search: { + selectedTab: ProjectAccessControlTabs.Roles + } + }); }; const isCustomRole = !Object.values(ProjectMembershipRole).includes( diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx index 43467876b..03525eac5 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RoleModal.tsx @@ -76,63 +76,54 @@ export const RoleModal = ({ popUp, handlePopUpToggle }: Props) => { }, [role]); const onFormSubmit = async ({ name, description, slug }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - if (role) { - // update - await updateProjectRole({ - id: role.id, - projectId, - name, - description, - slug - }); - - handlePopUpToggle("role", false); - if (slug) { - navigate({ - to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug` as const, - params: { - roleSlug: slug, - projectId - } - }); - } - } else { - // create - const newRole = await createProjectRole({ - projectId, - name, - description, - slug, - permissions: [] - }); + if (role) { + // update + await updateProjectRole({ + id: role.id, + projectId, + name, + description, + slug + }); + handlePopUpToggle("role", false); + if (slug) { navigate({ to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug` as const, params: { - roleSlug: newRole.slug, + roleSlug: slug, projectId } }); - handlePopUpToggle("role", false); } - - createNotification({ - text: `Successfully ${popUp?.role?.data ? "updated" : "created"} role`, - type: "success" + } else { + // create + const newRole = await createProjectRole({ + projectId, + name, + description, + slug, + permissions: [] }); - reset(); - } catch { - const text = `Failed to ${popUp?.role?.data ? "update" : "create"} role`; - - createNotification({ - text, - type: "error" + navigate({ + to: `${getProjectBaseURL(currentProject.type)}/roles/$roleSlug` as const, + params: { + roleSlug: newRole.slug, + projectId + } }); + handlePopUpToggle("role", false); } + + createNotification({ + text: `Successfully ${popUp?.role?.data ? "updated" : "created"} role`, + type: "success" + }); + + reset(); }; return ( diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx index fd95d2659..c93cea534 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection.tsx @@ -122,19 +122,14 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => { const { mutateAsync: updateRole } = useUpdateProjectRole(); const onSubmit = async (el: TFormSchema) => { - try { - if (!projectId || !role?.id) return; - await updateRole({ - id: role?.id as string, - projectId, - ...el, - permissions: formRolePermission2API(el.permissions) - }); - createNotification({ type: "success", text: "Successfully updated role" }); - } catch (err) { - console.log(err); - createNotification({ type: "error", text: "Failed to update role" }); - } + if (!projectId || !role?.id) return; + await updateRole({ + id: role?.id as string, + projectId, + ...el, + permissions: formRolePermission2API(el.permissions) + }); + createNotification({ type: "success", text: "Successfully updated role" }); }; const isCustomRole = !Object.values(ProjectMembershipRole).includes( diff --git a/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx b/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx index 2490648a2..cf093c21a 100644 --- a/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/AuditLogsRetentionSection/AuditLogsRetentionSection.tsx @@ -39,38 +39,31 @@ export const AuditLogsRetentionSection = () => { if (!currentProject) return null; const handleAuditLogsRetentionSubmit = async ({ auditLogsRetentionDays }: TForm) => { - try { - if (!subscription?.auditLogs) { - handlePopUpOpen("upgradePlan", { - text: "Configuring audit logs retention can be unlocked if you upgrade to Infisical Pro plan." - }); - - return; - } - - if (subscription && auditLogsRetentionDays > subscription?.auditLogsRetentionDays) { - handlePopUpOpen("upgradePlan", { - text: "Updating audit logs retention period to a higher value can be unlocked if you upgrade to Infisical Pro plan." - }); - - return; - } - - await updateAuditLogsRetention({ - auditLogsRetentionDays, - projectSlug: currentProject.slug + if (!subscription?.auditLogs) { + handlePopUpOpen("upgradePlan", { + text: "Configuring audit logs retention can be unlocked if you upgrade to Infisical Pro plan." }); - createNotification({ - text: "Successfully updated audit logs retention period", - type: "success" - }); - } catch { - createNotification({ - text: "Failed updating audit logs retention period", - type: "error" - }); + return; } + + if (subscription && auditLogsRetentionDays > subscription?.auditLogsRetentionDays) { + handlePopUpOpen("upgradePlan", { + text: "Updating audit logs retention period to a higher value can be unlocked if you upgrade to Infisical Pro plan." + }); + + return; + } + + await updateAuditLogsRetention({ + auditLogsRetentionDays, + projectSlug: currentProject.slug + }); + + createNotification({ + text: "Successfully updated audit logs retention period", + type: "success" + }); }; // render only for dedicated/self-hosted instances of Infisical diff --git a/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx b/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx index 7160e9adf..3cb0c9371 100644 --- a/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/DeleteProjectProtection/DeleteProjectProtection.tsx @@ -10,24 +10,16 @@ export const DeleteProjectProtection = () => { const { mutateAsync } = useUpdateProject(); const handleToggleDeleteProjectProtection = async (state: boolean) => { - try { - await mutateAsync({ - projectId, - hasDeleteProtection: state - }); + await mutateAsync({ + projectId, + hasDeleteProtection: state + }); - const text = `Successfully ${state ? "enabled" : "disabled"} delete protection`; - createNotification({ - text, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update delete protection", - type: "error" - }); - } + const text = `Successfully ${state ? "enabled" : "disabled"} delete protection`; + createNotification({ + text, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx b/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx index fb66f0a74..58db07484 100644 --- a/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx +++ b/frontend/src/pages/project/SettingsPage/components/DeleteProjectSection/DeleteProjectSection.tsx @@ -68,12 +68,6 @@ export const DeleteProjectSection = () => { to: "/organization/projects" }); handlePopUpClose("deleteWorkspace"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete project", - type: "error" - }); } finally { setIsDeleting.off(); } @@ -118,12 +112,6 @@ export const DeleteProjectSection = () => { navigate({ to: "/organization/projects" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to leave project", - type: "error" - }); } finally { setIsLeaving.off(); } diff --git a/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx b/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx index d6fd57d56..a07dd1674 100644 --- a/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx +++ b/frontend/src/pages/public/ErrorPage/components/ProjectAccessError.tsx @@ -2,7 +2,6 @@ import { faHome } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Link, useNavigate, useParams } from "@tanstack/react-router"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { RequestProjectAccessModal } from "@app/components/projects"; import { AccessRestrictedBanner, Button } from "@app/components/v2"; @@ -35,19 +34,12 @@ export const ProjectAccessError = () => { const handleAccessProject = async () => { if (!project) return; - try { - await orgAdminAccessProject.mutateAsync({ - projectId: project.id - }); - await navigate({ - to: "." - }); - } catch { - createNotification({ - text: "Failed to access project", - type: "error" - }); - } + await orgAdminAccessProject.mutateAsync({ + projectId: project.id + }); + await navigate({ + to: "." + }); }; return ( diff --git a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx index a34c08f9d..b8e93f0fe 100644 --- a/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx +++ b/frontend/src/pages/public/ShareSecretPage/components/ShareSecretForm.tsx @@ -131,52 +131,44 @@ export const ShareSecretForm = ({ emails, shouldLimitView }: FormData) => { - try { - const expiresAt = new Date(new Date().getTime() + Number(expiresIn)); + const expiresAt = new Date(new Date().getTime() + Number(expiresIn)); - const processedEmails = emails ? emails.split(",").map((e) => e.trim()) : undefined; + const processedEmails = emails ? emails.split(",").map((e) => e.trim()) : undefined; - const { id } = await createSharedSecret.mutateAsync({ - name, - password, - secretValue: secret, - expiresAt, - expiresAfterViews: shouldLimitView ? Number(viewLimit) : undefined, - accessType, - emails: processedEmails + const { id } = await createSharedSecret.mutateAsync({ + name, + password, + secretValue: secret, + expiresAt, + expiresAfterViews: shouldLimitView ? Number(viewLimit) : undefined, + accessType, + emails: processedEmails + }); + + if (processedEmails && processedEmails.length > 0) { + setSecretLink(""); + createNotification({ + text: `Shared secret link emailed to ${processedEmails.length} user(s).`, + type: "success" }); - - if (processedEmails && processedEmails.length > 0) { - setSecretLink(""); - createNotification({ - text: `Shared secret link emailed to ${processedEmails.length} user(s).`, - type: "success" - }); - } else { - const link = new URL(`${window.location.origin}/shared/secret/${id}`); - if (subOrganization) { - link.searchParams.set("subOrganization", subOrganization); - } - - setSecretLink(link.toString()); - - navigator.clipboard.writeText(link.toString()); - setCopyTextSecret("secret"); - - createNotification({ - text: "Shared secret link copied to clipboard.", - type: "success" - }); + } else { + const link = new URL(`${window.location.origin}/shared/secret/${id}`); + if (subOrganization) { + link.searchParams.set("subOrganization", subOrganization); } - reset(); - } catch (error) { - console.error(error); + setSecretLink(link.toString()); + + navigator.clipboard.writeText(link.toString()); + setCopyTextSecret("secret"); + createNotification({ - text: "Failed to create a shared secret.", - type: "error" + text: "Shared secret link copied to clipboard.", + type: "success" }); } + + reset(); }; if (secretLink === null) diff --git a/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx b/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx index 43992d73a..baf2b015b 100644 --- a/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx +++ b/frontend/src/pages/secret-manager/CommitDetailsPage/components/RollbackPreviewTab/RollbackPreviewTab.tsx @@ -140,22 +140,15 @@ export const RollbackPreviewTab = (): JSX.Element => { ); const handleRollback = async (): Promise => { - try { - await rollback(message); + await rollback(message); - createNotification({ - type: "success", - text: "Rollback completed successfully" - }); + createNotification({ + type: "success", + text: "Rollback completed successfully" + }); - handlePopUpClose("rollbackConfirm"); - goBackToHistory(); - } catch (error) { - createNotification({ - type: "error", - text: error instanceof Error ? error.message : "Failed to rollback changes" - }); - } + handlePopUpClose("rollbackConfirm"); + goBackToHistory(); }; const folderChanges: FolderChanges[] = rollbackChangesNested || []; diff --git a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx index 36f585850..ea6b604dc 100644 --- a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx +++ b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistModal.tsx @@ -64,39 +64,32 @@ export const IPAllowlistModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: }, [popUp?.trustedIp?.data]); const onIPAllowlistModalSubmit = async ({ ipAddress, comment }: FormData) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - if (popUp?.trustedIp?.data) { - await updateTrustedIp.mutateAsync({ - projectId: currentProject.id, - trustedIpId: (popUp?.trustedIp?.data as { trustedIpId: string })?.trustedIpId, - ipAddress, - comment, - isActive: true - }); - } else { - await addTrustedIp.mutateAsync({ - projectId: currentProject.id, - ipAddress, - comment, - isActive: true - }); - } - - createNotification({ - text: `Successfully ${popUp?.trustedIp?.data ? "updated" : "added"} trusted IP`, - type: "success" + if (popUp?.trustedIp?.data) { + await updateTrustedIp.mutateAsync({ + projectId: currentProject.id, + trustedIpId: (popUp?.trustedIp?.data as { trustedIpId: string })?.trustedIpId, + ipAddress, + comment, + isActive: true }); - - reset(); - handlePopUpClose("trustedIp"); - } catch { - createNotification({ - text: `Failed to ${popUp?.trustedIp?.data ? "update" : "add"} trusted IP`, - type: "error" + } else { + await addTrustedIp.mutateAsync({ + projectId: currentProject.id, + ipAddress, + comment, + isActive: true }); } + + createNotification({ + text: `Successfully ${popUp?.trustedIp?.data ? "updated" : "added"} trusted IP`, + type: "success" + }); + + reset(); + handlePopUpClose("trustedIp"); }; return ( diff --git a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx index 88c0afa4d..11d6ca8d9 100644 --- a/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx +++ b/frontend/src/pages/secret-manager/IPAllowlistPage/components/IPAllowlistSection.tsx @@ -29,27 +29,19 @@ export const IPAllowlistSection = () => { ] as const); const onDeleteTrustedIpSubmit = async (trustedIpId: string) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await mutateAsync({ - projectId: currentProject.id, - trustedIpId - }); + await mutateAsync({ + projectId: currentProject.id, + trustedIpId + }); - createNotification({ - text: "Successfully deleted IP access range", - type: "success" - }); + createNotification({ + text: "Successfully deleted IP access range", + type: "success" + }); - handlePopUpClose("deleteTrustedIp"); - } catch (err) { - console.log(err); - createNotification({ - text: "Failed to delete IP access range", - type: "error" - }); - } + handlePopUpClose("deleteTrustedIp"); }; return ( diff --git a/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx b/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx index 3b5600665..9043c35ef 100644 --- a/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/IntegrationsDetailsByIDPage.tsx @@ -53,28 +53,20 @@ export const IntegrationDetailsByIDPage = () => { const navigate = useNavigate(); const handleIntegrationDelete = async (shouldDeleteIntegrationSecrets: boolean) => { - try { - await deleteIntegration({ - id: integrationId, - workspaceId: currentProject.id, - shouldDeleteIntegrationSecrets - }); + await deleteIntegration({ + id: integrationId, + workspaceId: currentProject.id, + shouldDeleteIntegrationSecrets + }); - createNotification({ - type: "success", - text: "Deleted integration" - }); + createNotification({ + type: "success", + text: "Deleted integration" + }); - await navigate({ - to: `/${ProjectType.SecretManager}/${projectId}/integrations` - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to delete integration" - }); - } + await navigate({ + to: `/${ProjectType.SecretManager}/${projectId}/integrations` + }); }; const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx index 97ebfbee5..db4c6356e 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/NativeIntegrationsTab/NativeIntegrationsTab.tsx @@ -108,43 +108,27 @@ export const NativeIntegrationsTab = () => { shouldDeleteIntegrationSecrets: boolean, cb: () => void ) => { - try { - await deleteIntegration({ id: integrationId, workspaceId, shouldDeleteIntegrationSecrets }); - if (cb) cb(); - createNotification({ - type: "success", - text: "Deleted integration" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to delete integration" - }); - } + await deleteIntegration({ id: integrationId, workspaceId, shouldDeleteIntegrationSecrets }); + if (cb) cb(); + createNotification({ + type: "success", + text: "Deleted integration" + }); }; const handleIntegrationAuthRevoke = async (provider: string, cb?: () => void) => { const integrationAuthForProvider = integrationAuths?.[provider]; if (!integrationAuthForProvider) return; - try { - await deleteIntegrationAuths({ - integration: provider, - workspaceId - }); - if (cb) cb(); - createNotification({ - type: "success", - text: "Revoked provider authentication" - }); - } catch (err) { - console.error(err); - createNotification({ - type: "error", - text: "Failed to revoke provider authentication" - }); - } + await deleteIntegrationAuths({ + integration: provider, + workspaceId + }); + if (cb) cb(); + createNotification({ + type: "success", + text: "Revoked provider authentication" + }); }; const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx index bdf92ef7f..93a22feb8 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx @@ -235,46 +235,32 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => { const isAutoSyncEnabled = !secretSync.isAutoSyncEnabled; - try { - await updateSync.mutateAsync({ - syncId: secretSync.id, - destination: secretSync.destination, - isAutoSyncEnabled, - projectId: secretSync.projectId - }); + await updateSync.mutateAsync({ + syncId: secretSync.id, + destination: secretSync.destination, + isAutoSyncEnabled, + projectId: secretSync.projectId + }); - createNotification({ - text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to ${isAutoSyncEnabled ? "enable" : "disable"} auto-sync for ${destinationName} Sync`, - type: "error" - }); - } + createNotification({ + text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`, + type: "success" + }); }; const handleTriggerSync = async (secretSync: TSecretSync) => { const destinationName = SECRET_SYNC_MAP[secretSync.destination].name; - try { - await triggerSync.mutateAsync({ - syncId: secretSync.id, - destination: secretSync.destination, - projectId: secretSync.projectId - }); + await triggerSync.mutateAsync({ + syncId: secretSync.id, + destination: secretSync.destination, + projectId: secretSync.projectId + }); - createNotification({ - text: `Successfully triggered ${destinationName} Sync`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to trigger ${destinationName} Sync`, - type: "error" - }); - } + createNotification({ + text: `Successfully triggered ${destinationName} Sync`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx index 81cb5168f..591b7cf3d 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx @@ -488,55 +488,47 @@ export const OverviewPage = () => { }; const handleSecretCreate = async (env: string, key: string, value: string) => { - try { - // create folder if not existing - if (secretPath !== "/") { - // /hello/world -> [hello","world"] - const pathSegment = secretPath.split("/").filter(Boolean); - const parentPath = `/${pathSegment.slice(0, -1).join("/")}`; - const folderName = pathSegment.at(-1); - const canCreateFolder = permission.can( - ProjectPermissionActions.Create, - subject(ProjectPermissionSub.SecretFolders, { - environment: env, - secretPath: parentPath - }) - ); - if (folderName && parentPath && canCreateFolder) { - await getOrCreateFolder({ - projectId, - path: parentPath, - environment: env, - name: folderName - }); - } + // create folder if not existing + if (secretPath !== "/") { + // /hello/world -> [hello","world"] + const pathSegment = secretPath.split("/").filter(Boolean); + const parentPath = `/${pathSegment.slice(0, -1).join("/")}`; + const folderName = pathSegment.at(-1); + const canCreateFolder = permission.can( + ProjectPermissionActions.Create, + subject(ProjectPermissionSub.SecretFolders, { + environment: env, + secretPath: parentPath + }) + ); + if (folderName && parentPath && canCreateFolder) { + await getOrCreateFolder({ + projectId, + path: parentPath, + environment: env, + name: folderName + }); } - const result = await createSecretV3({ - environment: env, - projectId, - secretPath, - secretKey: key, - secretValue: value, - secretComment: "", - type: SecretType.Shared - }); + } + const result = await createSecretV3({ + environment: env, + projectId, + secretPath, + secretKey: key, + secretValue: value, + secretComment: "", + type: SecretType.Shared + }); - if ("approval" in result) { - createNotification({ - type: "info", - text: "Requested change has been sent for review" - }); - } else { - createNotification({ - type: "success", - text: "Successfully created secret" - }); - } - } catch (error) { - console.log(error); + if ("approval" in result) { createNotification({ - type: "error", - text: "Failed to create secret" + type: "info", + text: "Requested change has been sent for review" + }); + } else { + createNotification({ + type: "success", + text: "Successfully created secret" }); } }; @@ -565,63 +557,47 @@ export const OverviewPage = () => { secretValue = undefined; } - try { - const result = await updateSecretV3({ - environment: env, - projectId, - secretPath, - secretKey: key, - secretValue, - type - }); + const result = await updateSecretV3({ + environment: env, + projectId, + secretPath, + secretKey: key, + secretValue, + type + }); - if ("approval" in result) { - createNotification({ - type: "info", - text: "Requested change has been sent for review" - }); - } else { - createNotification({ - type: "success", - text: "Successfully updated secret" - }); - } - } catch (error) { - console.log(error); + if ("approval" in result) { createNotification({ - type: "error", - text: "Failed to update secret" + type: "info", + text: "Requested change has been sent for review" + }); + } else { + createNotification({ + type: "success", + text: "Successfully updated secret" }); } }; const handleSecretDelete = async (env: string, key: string, secretId?: string) => { - try { - const result = await deleteSecretV3({ - environment: env, - projectId, - secretPath, - secretKey: key, - secretId, - type: SecretType.Shared - }); + const result = await deleteSecretV3({ + environment: env, + projectId, + secretPath, + secretKey: key, + secretId, + type: SecretType.Shared + }); - if ("approval" in result) { - createNotification({ - type: "info", - text: "Requested change has been sent for review" - }); - } else { - createNotification({ - type: "success", - text: "Successfully deleted secret" - }); - } - } catch (error) { - console.log(error); + if ("approval" in result) { createNotification({ - type: "error", - text: "Failed to delete secret" + type: "info", + text: "Requested change has been sent for review" + }); + } else { + createNotification({ + type: "success", + text: "Successfully deleted secret" }); } }; diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/CreateSecretForm/CreateSecretForm.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/CreateSecretForm/CreateSecretForm.tsx index fede4e44d..977943330 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/CreateSecretForm/CreateSecretForm.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/CreateSecretForm/CreateSecretForm.tsx @@ -193,19 +193,12 @@ export const CreateSecretForm = ({ secretPath = "/", onClose }: Props) => { const slugSchema = z.string().trim().toLowerCase().min(1); const createNewTag = async (slug: string) => { // TODO: Replace with slugSchema generic - try { - const parsedSlug = slugSchema.parse(slug); - await createWsTag.mutateAsync({ - projectId, - tagSlug: parsedSlug, - tagColor: "" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to create new tag" - }); - } + const parsedSlug = slugSchema.parse(slug); + await createWsTag.mutateAsync({ + projectId, + tagSlug: parsedSlug, + tagColor: "" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx b/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx index 98feebb7e..79a658717 100644 --- a/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx +++ b/frontend/src/pages/secret-manager/OverviewPage/components/SecretV2MigrationSection/SecretV2MigrationSection.tsx @@ -65,20 +65,13 @@ export const SecretV2MigrationSection = () => { const didProjectUpgradeFailed = workspaceDetails?.upgradeStatus === ProjectUpgradeStatus.Failed; const handleMigrationSecretV2 = async () => { - try { - handlePopUpToggle("migrationInfo"); - await migrateProjectToV3.mutateAsync({ projectId: currentProject?.id || "" }); - refetch(); - createNotification({ - text: "Project upgrade started", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to upgrade project", - type: "error" - }); - } + handlePopUpToggle("migrationInfo"); + await migrateProjectToV3.mutateAsync({ projectId: currentProject?.id || "" }); + refetch(); + createNotification({ + text: "Project upgrade started", + type: "success" + }); }; const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/EditAccessRequestModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/EditAccessRequestModal.tsx index 430ba080e..922a5b8b1 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/EditAccessRequestModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/EditAccessRequestModal.tsx @@ -65,28 +65,20 @@ const Content = ({ accessRequest, onComplete, projectSlug }: ContentProps) => { }); const onSubmit = async (form: FormData) => { - try { - const request = await update.mutateAsync({ - requestId: accessRequest.id, - projectSlug, - ...form - }); - await queryClient.refetchQueries({ - queryKey: accessApprovalKeys.getAccessApprovalPolicies(projectSlug) - }); + const request = await update.mutateAsync({ + requestId: accessRequest.id, + projectSlug, + ...form + }); + await queryClient.refetchQueries({ + queryKey: accessApprovalKeys.getAccessApprovalPolicies(projectSlug) + }); - createNotification({ - type: "success", - text: "Access request updated successfully." - }); - onComplete(request); - } catch (e) { - console.error(e); - createNotification({ - type: "error", - text: "Failed to update access request" - }); - } + createNotification({ + type: "success", + text: "Access request updated successfully." + }); + onComplete(request); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx index 6a04225b4..a86ca8ebb 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx @@ -178,8 +178,7 @@ export const ReviewAccessRequestModal = ({ text: `The request has been ${status}`, type: status === "approved" ? "success" : "info" }); - } catch (error) { - console.error(error); + } catch { setIsLoading(null); return; } diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx index 30998aff4..67385a58b 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx @@ -237,48 +237,40 @@ const Form = ({ }: TFormSchema) => { if (!projectId) return; - try { - const bypassers = [...userBypassers, ...groupBypassers]; + const bypassers = [...userBypassers, ...groupBypassers]; - if (data.policyType === PolicyType.ChangePolicy) { - await createSecretApprovalPolicy({ - ...data, - approvers: [...userApprovers, ...groupApprovers], - bypassers: bypassers.length > 0 ? bypassers : undefined, - environments: environments.map((env) => env.slug), - projectId: currentProject?.id || "" - }); - } else { - await createAccessApprovalPolicy({ - ...data, - approvers: sequenceApprovers?.flatMap((approvers, index) => - approvers.user - .map( - (el) => ({ ...el, sequence: index + 1 }) as Omit - ) - .concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 }))) - ), - approvalsRequired: sequenceApprovers?.map((el, index) => ({ - stepNumber: index + 1, - numberOfApprovals: el.approvals - })), - bypassers: bypassers.length > 0 ? bypassers : undefined, - environments: environments.map((env) => env.slug), - projectSlug - }); - } - createNotification({ - type: "success", - text: "Successfully created policy" + if (data.policyType === PolicyType.ChangePolicy) { + await createSecretApprovalPolicy({ + ...data, + approvers: [...userApprovers, ...groupApprovers], + bypassers: bypassers.length > 0 ? bypassers : undefined, + environments: environments.map((env) => env.slug), + projectId: currentProject?.id || "" }); - onToggle(false); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to create policy" + } else { + await createAccessApprovalPolicy({ + ...data, + approvers: sequenceApprovers?.flatMap((approvers, index) => + approvers.user + .map( + (el) => ({ ...el, sequence: index + 1 }) as Omit + ) + .concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 }))) + ), + approvalsRequired: sequenceApprovers?.map((el, index) => ({ + stepNumber: index + 1, + numberOfApprovals: el.approvals + })), + bypassers: bypassers.length > 0 ? bypassers : undefined, + environments: environments.map((env) => env.slug), + projectSlug }); } + createNotification({ + type: "success", + text: "Successfully created policy" + }); + onToggle(false); }; const handleUpdatePolicy = async ({ @@ -293,50 +285,42 @@ const Form = ({ if (!projectId || !projectSlug) return; if (!editValues?.id) return; - try { - const bypassers = [...userBypassers, ...groupBypassers]; + const bypassers = [...userBypassers, ...groupBypassers]; - if (data.policyType === PolicyType.ChangePolicy) { - await updateSecretApprovalPolicy({ - id: editValues?.id, - ...data, - approvers: [...userApprovers, ...groupApprovers], - bypassers: bypassers.length > 0 ? bypassers : undefined, - projectId: currentProject?.id || "", - environments: environments.map((env) => env.slug) - }); - } else { - await updateAccessApprovalPolicy({ - id: editValues?.id, - ...data, - approvers: sequenceApprovers?.flatMap((approvers, index) => - approvers.user - .map( - (el) => ({ ...el, sequence: index + 1 }) as Omit - ) - .concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 }))) - ), - approvalsRequired: sequenceApprovers?.map((el, index) => ({ - stepNumber: index + 1, - numberOfApprovals: el.approvals - })), - bypassers: bypassers.length > 0 ? bypassers : undefined, - environments: environments.map((env) => env.slug), - projectSlug - }); - } - createNotification({ - type: "success", - text: "Successfully updated policy" + if (data.policyType === PolicyType.ChangePolicy) { + await updateSecretApprovalPolicy({ + id: editValues?.id, + ...data, + approvers: [...userApprovers, ...groupApprovers], + bypassers: bypassers.length > 0 ? bypassers : undefined, + projectId: currentProject?.id || "", + environments: environments.map((env) => env.slug) }); - onToggle(false); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "failed to update policy" + } else { + await updateAccessApprovalPolicy({ + id: editValues?.id, + ...data, + approvers: sequenceApprovers?.flatMap((approvers, index) => + approvers.user + .map( + (el) => ({ ...el, sequence: index + 1 }) as Omit + ) + .concat(approvers.group.map((el) => ({ ...el, sequence: index + 1 }))) + ), + approvalsRequired: sequenceApprovers?.map((el, index) => ({ + stepNumber: index + 1, + numberOfApprovals: el.approvals + })), + bypassers: bypassers.length > 0 ? bypassers : undefined, + environments: environments.map((env) => env.slug), + projectSlug }); } + createNotification({ + type: "success", + text: "Successfully updated policy" + }); + onToggle(false); }; const handleFormSubmit = async (data: TFormSchema) => { diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx index db2406335..96866ad31 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/ApprovalPolicyList/components/RemoveApprovalPolicyModal.tsx @@ -32,29 +32,22 @@ export const RemoveApprovalPolicyModal = ({ const { currentProject } = useProject(); const handleDeletePolicy = async () => { - try { - if (policyType === PolicyType.ChangePolicy) { - await deleteSecretApprovalPolicy({ - projectId: currentProject.id, - id: policyId - }); - } else { - await deleteAccessApprovalPolicy({ - projectSlug: currentProject.slug, - id: policyId - }); - } - createNotification({ - type: "success", - text: "Successfully deleted policy" + if (policyType === PolicyType.ChangePolicy) { + await deleteSecretApprovalPolicy({ + projectId: currentProject.id, + id: policyId }); - onOpenChange(false); - } catch { - createNotification({ - type: "error", - text: "Failed to delete policy" + } else { + await deleteAccessApprovalPolicy({ + projectSlug: currentProject.slug, + id: policyId }); } + createNotification({ + type: "success", + text: "Successfully deleted policy" + }); + onOpenChange(false); }; const deleteSecretApprovalData = useGetSecretApprovalRequestCount({ diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx index 0b8e6f03f..c730e4622 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/SecretApprovalRequest.tsx @@ -40,7 +40,7 @@ import { PreferenceKey, setUserTablePreference } from "@app/helpers/userTablePreferences"; -import { usePagination } from "@app/hooks"; +import { usePagination, useResetPageHelper } from "@app/hooks"; import { useGetSecretApprovalRequestCount, useGetSecretApprovalRequests, @@ -99,6 +99,12 @@ export const SecretApprovalRequest = () => { const totalApprovalCount = data?.totalCount ?? 0; const secretApprovalRequests = data?.approvals ?? []; + useResetPageHelper({ + totalCount: totalApprovalCount, + offset, + setPage + }); + const { data: secretApprovalRequestCount, isSuccess: isSecretApprovalReqCountSuccess } = useGetSecretApprovalRequestCount({ projectId }); const { user: userSession } = useUser(); @@ -107,7 +113,7 @@ export const SecretApprovalRequest = () => { }); const { permission } = useProjectPermission(); - const { data: members } = useGetWorkspaceUsers(projectId); + const { data: members } = useGetWorkspaceUsers(projectId, true); const isSecretApprovalScreen = Boolean(selectedApprovalId); const { requestId } = search; diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx index c2eb4c61f..709e312bb 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestAction.tsx @@ -59,43 +59,27 @@ export const SecretApprovalRequestAction = ({ }; const handleSecretApprovalRequestMerge = async () => { - try { - await performSecretApprovalMerge({ - id: approvalRequestId, - projectId, - bypassReason: byPassApproval ? bypassReason : undefined - }); - createNotification({ - type: "success", - text: "Successfully merged the request" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to update the request status" - }); - } + await performSecretApprovalMerge({ + id: approvalRequestId, + projectId, + bypassReason: byPassApproval ? bypassReason : undefined + }); + createNotification({ + type: "success", + text: "Successfully merged the request" + }); }; const handleSecretApprovalStatusChange = async (reqState: "open" | "close") => { - try { - await updateSecretStatusChange({ - id: approvalRequestId, - status: reqState, - projectId - }); - createNotification({ - type: "success", - text: "Successfully updated the request" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to update the request status" - }); - } + await updateSecretStatusChange({ + id: approvalRequestId, + status: reqState, + projectId + }); + createNotification({ + type: "success", + text: "Successfully updated the request" + }); }; const isSoftEnforcement = enforcementLevel === EnforcementLevel.Soft; diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx index c54ee8502..cbab034ed 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChangeItem.tsx @@ -221,9 +221,7 @@ export const SecretApprovalRequestChangeItem = ({
Multi-line Encoding
- {secretVersion?.skipMultilineEncoding?.toString() || ( - - - )}{" "} + {secretVersion?.skipMultilineEncoding?.toString() || "false"}
@@ -366,9 +364,8 @@ export const SecretApprovalRequestChangeItem = ({
Multi-line Encoding
{newVersion?.skipMultilineEncoding?.toString() ?? - secretVersion?.skipMultilineEncoding?.toString() ?? ( - - - )}{" "} + secretVersion?.skipMultilineEncoding?.toString() ?? + "false"}
diff --git a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx index 24d306314..bd4a04fa3 100644 --- a/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx +++ b/frontend/src/pages/secret-manager/SecretApprovalsPage/components/SecretApprovalRequest/components/SecretApprovalRequestChanges.tsx @@ -174,23 +174,15 @@ export const SecretApprovalRequestChanges = ({ approvalRequestId, onGoBack }: Pr ); const handleSecretApprovalStatusUpdate = async (status: ApprovalStatus, comment: string) => { - try { - await updateSecretApprovalRequestStatus({ - id: approvalRequestId, - status, - comment - }); - createNotification({ - type: "success", - text: `Successfully ${status} the request` - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to update the request status" - }); - } + await updateSecretApprovalRequestStatus({ + id: approvalRequestId, + status, + comment + }); + createNotification({ + type: "success", + text: `Successfully ${status} the request` + }); handlePopUpToggle("reviewChanges", false); reset({ diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx index f1a7ffba8..a2ee814f8 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/SecretDashboardPage.tsx @@ -338,45 +338,37 @@ const Page = () => { const isProtectedBranch = Boolean(boardPolicy); const handleCreateCommit = async (changes: PendingChanges, message: string) => { - try { - await createCommit({ - projectId, - environment, - secretPath, - pendingChanges: changes, - message - }); + await createCommit({ + projectId, + environment, + secretPath, + pendingChanges: changes, + message + }); - if (!isProtectedBranch) { - pendingChanges.secrets.forEach((secret) => { - if (secret.type === "update" && secret.secretValue !== undefined) { - queryClient.setQueryData( - dashboardKeys.getSecretValue({ - projectId, - environment, - secretPath, - secretKey: secret.newSecretName ?? secret.secretKey, - isOverride: false - }), - { value: secret.secretValue } - ); - } - }); - } - - createNotification({ - text: isProtectedBranch - ? "Requested changes have been sent for review" - : "Changes saved successfully", - type: "success" + if (!isProtectedBranch) { + pendingChanges.secrets.forEach((secret) => { + if (secret.type === "update" && secret.secretValue !== undefined) { + queryClient.setQueryData( + dashboardKeys.getSecretValue({ + projectId, + environment, + secretPath, + secretKey: secret.newSecretName ?? secret.secretKey, + isOverride: false + }), + { value: secret.secretValue } + ); + } }); - } catch (error) { - createNotification({ - text: "Failed to save changes", - type: "error" - }); - console.error(error); } + + createNotification({ + text: isProtectedBranch + ? "Requested changes have been sent for review" + : "Changes saved successfully", + type: "success" + }); }; const { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx index c3fb5159b..727903999 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx @@ -209,48 +209,40 @@ export const ActionBar = ({ const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin); const handleFolderCreate = async (folderName: string, description: string | null) => { - try { - if (isBatchMode) { - const folderId = `${folderName}`; - const pendingFolderCreate: PendingFolderCreate = { - id: folderId, - resourceType: "folder", - type: PendingAction.Create, - folderName, - description: description || undefined, - parentPath: secretPath, - timestamp: Date.now() - }; + if (isBatchMode) { + const folderId = `${folderName}`; + const pendingFolderCreate: PendingFolderCreate = { + id: folderId, + resourceType: "folder", + type: PendingAction.Create, + folderName, + description: description || undefined, + parentPath: secretPath, + timestamp: Date.now() + }; - addPendingChange(pendingFolderCreate, { - projectId, - environment, - secretPath - }); - - handlePopUpClose("addFolder"); - return; - } - - await createFolder({ - name: folderName, - path: secretPath, - environment, + addPendingChange(pendingFolderCreate, { projectId, - description + environment, + secretPath }); + handlePopUpClose("addFolder"); - createNotification({ - type: "success", - text: "Successfully created folder" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to create folder" - }); + return; } + + await createFolder({ + name: folderName, + path: secretPath, + environment, + projectId, + description + }); + handlePopUpClose("addFolder"); + createNotification({ + type: "success", + text: "Successfully created folder" + }); }; const handleSecretDownload = async () => { @@ -323,26 +315,18 @@ export const ActionBar = ({ const handleSecretBulkDelete = async () => { const bulkDeletedSecrets = Object.values(selectedSecrets); - try { - await deleteBatchSecretV3({ - secretPath, - projectId, - environment, - secrets: bulkDeletedSecrets.map(({ key }) => ({ secretKey: key, type: SecretType.Shared })) - }); - resetSelectedSecret(); - handlePopUpClose("bulkDeleteSecrets"); - createNotification({ - type: "success", - text: "Successfully deleted secrets" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to delete secrets" - }); - } + await deleteBatchSecretV3({ + secretPath, + projectId, + environment, + secrets: bulkDeletedSecrets.map(({ key }) => ({ secretKey: key, type: SecretType.Shared })) + }); + resetSelectedSecret(); + handlePopUpClose("bulkDeleteSecrets"); + createNotification({ + type: "success", + text: "Successfully deleted secrets" + }); }; const handleSecretsMove = async ({ @@ -678,35 +662,23 @@ export const ActionBar = ({ }; const handleVaultImport = async (vaultPath: string, namespace: string) => { - try { - const result = await importVaultSecrets({ - projectId, - environment, - secretPath, - vaultNamespace: namespace, - vaultSecretPath: vaultPath - }); - - if (result.status === VaultImportStatus.ApprovalRequired) { - createNotification({ - type: "info", - text: "Secret change request created successfully. Awaiting approval." - }); - } else { - createNotification({ - type: "success", - text: "Successfully imported secrets from HashiCorp Vault" - }); - } - } catch (err) { - console.error("Vault import error:", err); - const error = err as AxiosError<{ message?: string }>; - const errorMessage = - error.response?.data?.message || "Failed to import secrets from Vault. Please try again."; + const result = await importVaultSecrets({ + projectId, + environment, + secretPath, + vaultNamespace: namespace, + vaultSecretPath: vaultPath + }); + if (result.status === VaultImportStatus.ApprovalRequired) { createNotification({ - type: "error", - text: errorMessage + type: "info", + text: "Secret change request created successfully. Awaiting approval." + }); + } else { + createNotification({ + type: "success", + text: "Successfully imported secrets from HashiCorp Vault" }); } }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx index c58f2a754..48bd2012b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -111,25 +110,18 @@ export const AwsElastiCacheInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx index 35a416224..e4438d117 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -166,26 +165,19 @@ export const AwsIamInputForm = ({ // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.AwsIam, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.AwsIam, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx index 581c473b9..b4506c51a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx @@ -6,7 +6,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2"; import { DropdownMenu, @@ -112,34 +111,27 @@ export const AzureEntraIdInputForm = ({ }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - selectedUsers.map(async (user: { id: string; name: string; email: string }) => { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.AzureEntraId, - inputs: { - userId: user.id, - tenantId: provider.tenantId, - email: user.email, - applicationId: provider.applicationId, - clientSecret: provider.clientSecret - } - }, - maxTTL, - name: `${name}-${user.name}`, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug - }); + selectedUsers.map(async (user: { id: string; name: string; email: string }) => { + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.AzureEntraId, + inputs: { + userId: user.id, + tenantId: provider.tenantId, + email: user.email, + applicationId: provider.applicationId, + clientSecret: provider.clientSecret + } + }, + maxTTL, + name: `${name}-${user.name}`, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx index aece11f38..954aed3ba 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureSqlDatabaseInputForm.tsx @@ -5,7 +5,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Accordion, @@ -171,29 +170,22 @@ export const AzureSqlDatabaseInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.AzureSqlDatabase, - inputs: { ...provider, masterDatabase: "master" } - }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - metadata, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.AzureSqlDatabase, + inputs: { ...provider, masterDatabase: "master" } + }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + metadata, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx index 15f9387df..dd0c3e879 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -112,25 +111,18 @@ export const CassandraInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Cassandra, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Cassandra, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx index 17bfeddca..894b31c1c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CouchbaseInputForm.tsx @@ -7,7 +7,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -386,25 +385,18 @@ export const CouchbaseInputForm = ({ const { useAdvancedBuckets, ...finalProvider } = transformedProvider; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Couchbase, inputs: finalProvider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Couchbase, inputs: finalProvider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx index 1d0b6c268..d6477862b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx @@ -6,7 +6,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -131,25 +130,18 @@ export const ElasticSearchInputForm = ({ // wait till previous request is finished if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; const selectedAuthType = watch("provider.auth.type"); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx index a208b1973..a0134bfde 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GcpIamInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2"; import { useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; @@ -83,28 +82,21 @@ export const GcpIamInputForm = ({ }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.GcpIam, - inputs: { - ...provider - } - }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.GcpIam, + inputs: { + ...provider + } + }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx index 776511ac4..31256f5a1 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/GithubInputForm.tsx @@ -4,7 +4,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -71,27 +70,20 @@ export const GithubInputForm = ({ const handleCreateDynamicSecret = async ({ name, provider, environment }: TForm) => { if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.Github, - inputs: { - ...provider - } - }, - defaultTTL: "1h", // Github is limited to 1 hour tokens - name, - path: secretPath, - projectSlug, - environmentSlug: environment.slug - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.Github, + inputs: { + ...provider + } + }, + defaultTTL: "1h", // Github is limited to 1 hour tokens + name, + path: secretPath, + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx index d15bcc9b8..a439f1af2 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx @@ -283,33 +283,26 @@ export const KubernetesInputForm = ({ // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.Kubernetes, - inputs: { - ...provider, - url: provider.url || undefined - } - }, - maxTTL: rest.maxTTL, - name: rest.name, - path: secretPath, - defaultTTL: rest.defaultTTL, - projectSlug, - environmentSlug: rest.environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.Kubernetes, + inputs: { + ...provider, + url: provider.url || undefined + } + }, + maxTTL: rest.maxTTL, + name: rest.name, + path: secretPath, + defaultTTL: rest.defaultTTL, + projectSlug, + environmentSlug: rest.environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx index 7b7397625..61273fc41 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -139,25 +138,18 @@ export const LdapInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Ldap, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate, - environmentSlug: environment.slug - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Ldap, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate, + environmentSlug: environment.slug + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx index 3b0c7bb3b..317febae8 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx @@ -6,7 +6,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -143,25 +142,18 @@ export const MongoAtlasInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx index ac60a77b0..8099b123c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx @@ -6,7 +6,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -113,32 +112,25 @@ export const MongoDBDatabaseInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { - type: DynamicSecretProviders.MongoDB, - inputs: { - ...provider, - port: provider?.port ? provider.port : undefined, - roles: provider.roles.map((el) => el.roleName) - } - }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { + type: DynamicSecretProviders.MongoDB, + inputs: { + ...provider, + port: provider?.port ? provider.port : undefined, + roles: provider.roles.map((el) => el.roleName) + } + }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx index 7b2e56464..b142682dd 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx @@ -6,7 +6,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -121,25 +120,18 @@ export const RabbitMqInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; const selectedTags = watch("provider.tags"); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx index 472d30783..31745ae79 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -104,26 +103,19 @@ export const RedisInputForm = ({ }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Redis, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Redis, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx index 621451fda..42c06ebea 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -105,26 +104,19 @@ sp_droplogin '{{username}}';` }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.SapAse, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.SapAse, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx index 034afee2c..8984d1f49 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -105,26 +104,19 @@ DROP USER {{username}};`, }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.SapHana, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate, - environmentSlug: environment.slug - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.SapHana, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate, + environmentSlug: environment.slug + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx index 11357e26a..91f638f39 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx @@ -4,7 +4,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { Accordion, AccordionContent, @@ -102,26 +101,19 @@ export const SnowflakeInputForm = ({ }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Snowflake, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Snowflake, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx index 0a836fec2..aaf2e43d1 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx @@ -5,7 +5,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Accordion, @@ -215,26 +214,19 @@ export const SqlDatabaseInputForm = ({ if (createDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - metadata, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + metadata, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; const handleDatabaseChange = (type: SqlProviders) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx index 7f5693e33..f86ac4017 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx @@ -2,7 +2,6 @@ import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, @@ -100,23 +99,16 @@ export const TotpInputForm = ({ const handleCreateDynamicSecret = async ({ name, provider, environment }: TForm) => { // wait till previous request is finished if (createDynamicSecret.isPending) return; - try { - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Totp, inputs: provider }, - maxTTL: "24h", - name, - path: secretPath, - defaultTTL: "1m", - projectSlug, - environmentSlug: environment.slug - }); - onCompleted(); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to create dynamic secret" - }); - } + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Totp, inputs: provider }, + maxTTL: "24h", + name, + path: secretPath, + defaultTTL: "1m", + projectSlug, + environmentSlug: environment.slug + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx index b48eab850..7bd66a466 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VerticaInputForm.tsx @@ -5,7 +5,6 @@ import ms from "ms"; import { z } from "zod"; import { TtlFormLabel } from "@app/components/features"; -import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { Accordion, @@ -145,26 +144,19 @@ GRANT CREATE ON SCHEMA public TO {{username}};`, usernameTemplate }: TForm) => { if (createDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await createDynamicSecret.mutateAsync({ - provider: { type: DynamicSecretProviders.Vertica, inputs: provider }, - maxTTL, - name, - path: secretPath, - defaultTTL, - projectSlug, - environmentSlug: environment.slug, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate - }); - onCompleted(); - } catch { - createNotification({ - type: "error", - text: "Failed to create dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await createDynamicSecret.mutateAsync({ + provider: { type: DynamicSecretProviders.Vertica, inputs: provider }, + maxTTL, + name, + path: secretPath, + defaultTTL, + projectSlug, + environmentSlug: environment.slug, + usernameTemplate: + !usernameTemplate || isDefaultUsernameTemplate ? undefined : usernameTemplate + }); + onCompleted(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx index 68d2d8cd0..73a9d938e 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateSecretImportForm.tsx @@ -101,11 +101,6 @@ export const CreateSecretImportForm = ({ text: "You do not have access to the selected environment/path", type: "error" }); - } else { - createNotification({ - type: "error", - text: "Failed to link secrets" - }); } } }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CreateSecretForm/CreateSecretForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CreateSecretForm/CreateSecretForm.tsx index dd0561495..858fcde77 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/CreateSecretForm/CreateSecretForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/CreateSecretForm/CreateSecretForm.tsx @@ -77,70 +77,55 @@ export const CreateSecretForm = ({ const slugSchema = z.string().trim().toLowerCase().min(1); const createNewTag = async (slug: string) => { // TODO: Replace with slugSchema generic - try { - const parsedSlug = slugSchema.parse(slug); - await createWsTag.mutateAsync({ - projectId, - tagSlug: parsedSlug, - tagColor: "" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to create new tag" - }); - } + const parsedSlug = slugSchema.parse(slug); + await createWsTag.mutateAsync({ + projectId, + tagSlug: parsedSlug, + tagColor: "" + }); }; const handleFormSubmit = async ({ key, value, tags }: TFormSchema) => { - try { - if (isBatchMode) { - const pendingSecretCreate: PendingSecretCreate = { - id: key, - type: PendingAction.Create, - secretKey: key, - secretValue: value || "", - secretComment: "", - tags: tags?.map((el) => ({ id: el.value, slug: el.label })), - timestamp: Date.now(), - resourceType: "secret" - }; - addPendingChange(pendingSecretCreate, { - projectId, - - environment, - secretPath - }); - closePopUp(PopUpNames.CreateSecretForm); - reset(); - return; - } - await createSecretV3({ - environment, - projectId, - secretPath, + if (isBatchMode) { + const pendingSecretCreate: PendingSecretCreate = { + id: key, + type: PendingAction.Create, secretKey: key, secretValue: value || "", secretComment: "", - type: SecretType.Shared, - tagIds: tags?.map((el) => el.value) + tags: tags?.map((el) => ({ id: el.value, slug: el.label })), + timestamp: Date.now(), + resourceType: "secret" + }; + addPendingChange(pendingSecretCreate, { + projectId, + + environment, + secretPath }); closePopUp(PopUpNames.CreateSecretForm); reset(); - - createNotification({ - type: isProtectedBranch ? "info" : "success", - text: isProtectedBranch - ? "Requested changes have been sent for review" - : "Successfully created secret" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to create secret" - }); + return; } + await createSecretV3({ + environment, + projectId, + secretPath, + secretKey: key, + secretValue: value || "", + secretComment: "", + type: SecretType.Shared, + tagIds: tags?.map((el) => el.value) + }); + closePopUp(PopUpNames.CreateSecretForm); + reset(); + + createNotification({ + type: isProtectedBranch ? "info" : "success", + text: isProtectedBranch + ? "Requested changes have been sent for review" + : "Successfully created secret" + }); }; const handlePaste = (e: ClipboardEvent) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx index 1a16e6e4e..99e66e8b7 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/CreateDynamicSecretLease.tsx @@ -441,30 +441,22 @@ export const CreateKubernetesDynamicSecretLease = ({ const handleDynamicSecretLeaseCreate = async ({ ttl, namespace }: TKubernetesForm) => { if (createDynamicSecretLease.isPending) return; - try { - await createDynamicSecretLease.mutateAsync({ - environmentSlug: environment, - projectSlug, - path: secretPath, - ttl, - dynamicSecretName, - config: { - namespace: namespace || undefined - }, - provider - }); + await createDynamicSecretLease.mutateAsync({ + environmentSlug: environment, + projectSlug, + path: secretPath, + ttl, + dynamicSecretName, + config: { + namespace: namespace || undefined + }, + provider + }); - createNotification({ - type: "success", - text: "Successfully leased dynamic secret" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to lease dynamic secret" - }); - } + createNotification({ + type: "success", + text: "Successfully leased dynamic secret" + }); }; const handleLeaseRegeneration = async (data: { ttl?: string }) => { @@ -590,29 +582,21 @@ export const CreateDynamicSecretLease = ({ const handleDynamicSecretLeaseCreate = async ({ ttl }: TForm) => { if (createDynamicSecretLease.isPending) return; - try { - await createDynamicSecretLease.mutateAsync({ - environmentSlug: environment, - projectSlug, - path: secretPath, - ttl, - dynamicSecretName, - provider - }); + await createDynamicSecretLease.mutateAsync({ + environmentSlug: environment, + projectSlug, + path: secretPath, + ttl, + dynamicSecretName, + provider + }); - createNotification({ - type: "success", - text: "Successfully leased dynamic secret" - }); + createNotification({ + type: "success", + text: "Successfully leased dynamic secret" + }); - setIsPreloading.off(); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to lease dynamic secret" - }); - } + setIsPreloading.off(); }; const handleLeaseRegeneration = async (data: { ttl?: string }) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretLease.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretLease.tsx index 085a06169..1f734de32 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretLease.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretLease.tsx @@ -69,31 +69,23 @@ export const DynamicSecretLease = ({ const deleteDynamicSecretLease = useRevokeDynamicSecretLease(); const handleDynamicSecretDeleteLease = async () => { - try { - const { leaseId, isForced } = popUp.deleteSecret.data as { - leaseId: string; - isForced?: boolean; - }; - await deleteDynamicSecretLease.mutateAsync({ - environmentSlug: environment, - projectSlug, - path: secretPath, - dynamicSecretName, - leaseId, - isForced - }); - handlePopUpClose("deleteSecret"); - createNotification({ - type: "success", - text: "Successfully deleted lease" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to delete lease" - }); - } + const { leaseId, isForced } = popUp.deleteSecret.data as { + leaseId: string; + isForced?: boolean; + }; + await deleteDynamicSecretLease.mutateAsync({ + environmentSlug: environment, + projectSlug, + path: secretPath, + dynamicSecretName, + leaseId, + isForced + }); + handlePopUpClose("deleteSecret"); + createNotification({ + type: "success", + text: "Successfully deleted lease" + }); }; const canRenew = !DYNAMIC_SECRETS_WITHOUT_RENEWAL.includes(dynamicSecret.type); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretListView.tsx index 1ef140193..d444f8390 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/DynamicSecretListView.tsx @@ -54,29 +54,21 @@ export const DynamicSecretListView = ({ const deleteDynamicSecret = useDeleteDynamicSecret(); const handleDynamicSecretDelete = async () => { - try { - const { name, isForced } = popUp.deleteDynamicSecret.data as TDynamicSecret & { - isForced?: boolean; - }; - await deleteDynamicSecret.mutateAsync({ - environmentSlug: environment, - projectSlug, - path: secretPath, - name, - isForced - }); - handlePopUpClose("deleteDynamicSecret"); - createNotification({ - type: "success", - text: "Successfully deleted dynamic secret" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to delete dynamic secret" - }); - } + const { name, isForced } = popUp.deleteDynamicSecret.data as TDynamicSecret & { + isForced?: boolean; + }; + await deleteDynamicSecret.mutateAsync({ + environmentSlug: environment, + projectSlug, + path: secretPath, + name, + isForced + }); + handlePopUpClose("deleteDynamicSecret"); + createNotification({ + type: "success", + text: "Successfully deleted dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx index 5740e2061..0015c0c0c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsElastiCacheProviderForm.tsx @@ -101,31 +101,24 @@ export const EditDynamicSecretAwsElastiCacheProviderForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx index 555ed9eae..c9c9afb71 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAwsIamForm.tsx @@ -141,31 +141,24 @@ export const EditDynamicSecretAwsIamForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx index c9bfc92cc..2799a95ed 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureEntraIdForm.tsx @@ -76,30 +76,23 @@ export const EditDynamicSecretAzureEntraIdForm = ({ const handleUpdateDynamicSecret = async ({ maxTTL, defaultTTL, newName, inputs }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - newName: newName === dynamicSecret.name ? undefined : newName, - inputs - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + newName: newName === dynamicSecret.name ? undefined : newName, + inputs + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx index 77bb7aa1f..fd370d755 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretAzureSqlDatabaseForm.tsx @@ -162,33 +162,26 @@ export const EditDynamicSecretAzureSqlDatabaseForm = ({ }: TForm) => { if (updateDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await updateDynamicSecret.mutateAsync({ - projectSlug, - environmentSlug: environment, - path: secretPath, - name: dynamicSecret.name, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs: inputs ? { ...inputs, masterDatabase: "master" } : undefined, - newName: newName === dynamicSecret.name ? undefined : newName, - metadata, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await updateDynamicSecret.mutateAsync({ + projectSlug, + environmentSlug: environment, + path: secretPath, + name: dynamicSecret.name, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs: inputs ? { ...inputs, masterDatabase: "master" } : undefined, + newName: newName === dynamicSecret.name ? undefined : newName, + metadata, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx index 515ee19e3..fe1212330 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCassandraForm.tsx @@ -103,31 +103,24 @@ export const EditDynamicSecretCassandraForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx index e69b3460f..de38358f3 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretCouchbaseForm.tsx @@ -386,37 +386,30 @@ export const EditDynamicSecretCouchbaseForm = ({ })() : transformedInputs; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - defaultTTL, - maxTTL: maxTTL || undefined, - newName: newName === dynamicSecret.name ? undefined : newName, - metadata, - usernameTemplate: - !usernameTemplate || usernameTemplate === "{{randomUsername}}" - ? undefined - : usernameTemplate, - inputs: finalInputs - } - }); + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + defaultTTL, + maxTTL: maxTTL || undefined, + newName: newName === dynamicSecret.name ? undefined : newName, + metadata, + usernameTemplate: + !usernameTemplate || usernameTemplate === "{{randomUsername}}" + ? undefined + : usernameTemplate, + inputs: finalInputs + } + }); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); - onClose(); - } catch (err) { - createNotification({ - type: "error", - text: `Failed to update dynamic secret: ${err}` - }); - } + onClose(); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx index 000762bc5..015f394ea 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretElasticSearchForm.tsx @@ -123,31 +123,24 @@ export const EditDynamicSecretElasticSearchForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; const selectedAuthType = watch("inputs.auth.type"); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGcpIamForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGcpIamForm.tsx index fb313f730..a2755e9c8 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGcpIamForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGcpIamForm.tsx @@ -77,30 +77,23 @@ export const EditDynamicSecretGcpIamForm = ({ const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGithubForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGithubForm.tsx index 434bc9465..b5539d85d 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGithubForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretGithubForm.tsx @@ -61,28 +61,21 @@ export const EditDynamicSecretGithubForm = ({ const handleUpdateDynamicSecret = async ({ inputs, newName }: TForm) => { if (updateDynamicSecret.isPending) return; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - inputs, - newName: newName === dynamicSecret.name ? undefined : newName - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + inputs, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx index 95f40f9f5..e599c2d6a 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretKubernetesForm.tsx @@ -189,38 +189,29 @@ export const EditDynamicSecretKubernetesForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = formData.usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - inputs: { - ...formData.inputs, - url: formData.inputs.url || undefined - }, - newName: formData.newName === dynamicSecret.name ? undefined : formData.newName, - defaultTTL: formData.defaultTTL, - maxTTL: formData.maxTTL, - usernameTemplate: - !formData.usernameTemplate || isDefaultUsernameTemplate - ? null - : formData.usernameTemplate - } - }); + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + inputs: { + ...formData.inputs, + url: formData.inputs.url || undefined + }, + newName: formData.newName === dynamicSecret.name ? undefined : formData.newName, + defaultTTL: formData.defaultTTL, + maxTTL: formData.maxTTL, + usernameTemplate: + !formData.usernameTemplate || isDefaultUsernameTemplate ? null : formData.usernameTemplate + } + }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to update dynamic secret" - }); - } + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx index 09d345d3d..e95d89561 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretLdapForm.tsx @@ -119,31 +119,24 @@ export const EditDynamicSecretLdapForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx index 5b577192a..905e00660 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoAtlasForm.tsx @@ -142,31 +142,24 @@ export const EditDynamicSecretMongoAtlasForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx index 90c31e1f0..d5b63a1cf 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretMongoDBForm.tsx @@ -108,36 +108,28 @@ export const EditDynamicSecretMongoDBForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs: { - ...inputs, - port: inputs?.port ? inputs.port : undefined, - roles: inputs?.roles?.map((el) => el.roleName) - }, - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate, - newName: newName === dynamicSecret.name ? undefined : newName - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs: { + ...inputs, + port: inputs?.port ? inputs.port : undefined, + roles: inputs?.roles?.map((el) => el.roleName) + }, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx index 984e68831..dbf0d90d0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRabbitMqForm.tsx @@ -100,31 +100,24 @@ export const EditDynamicSecretRabbitMqForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; const selectedTags = watch("inputs.tags"); diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx index bc21a17d7..add8d6042 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretRedisProviderForm.tsx @@ -102,32 +102,24 @@ export const EditDynamicSecretRedisProviderForm = ({ // wait till previous request is finished if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - usernameTemplate: - !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate, - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate, + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx index 37dbae5ee..2f55cca2b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapAseForm.tsx @@ -101,31 +101,24 @@ export const EditDynamicSecretSapAseForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx index 430f13e83..d0f6a9d90 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSapHanaForm.tsx @@ -101,31 +101,24 @@ export const EditDynamicSecretSapHanaForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx index 1cbc20cfb..384278494 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSnowflakeForm.tsx @@ -97,32 +97,25 @@ export const EditDynamicSecretSnowflakeForm = ({ }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to update dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx index 929a1485d..d6da25f80 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretSqlProviderForm.tsx @@ -167,36 +167,29 @@ export const EditDynamicSecretSqlProviderForm = ({ }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; - try { - const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs: { - ...inputs, - gatewayId: isGatewayInActive ? null : inputs.gatewayId - }, - newName: newName === dynamicSecret.name ? undefined : newName, - metadata, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs: { + ...inputs, + gatewayId: isGatewayInActive ? null : inputs.gatewayId + }, + newName: newName === dynamicSecret.name ? undefined : newName, + metadata, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx index 7b4a4dac3..15dead8d0 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx @@ -87,28 +87,21 @@ export const EditDynamicSecretTotpForm = ({ const handleUpdateDynamicSecret = async ({ inputs, newName }: TForm) => { // wait till previous request is finished if (updateDynamicSecret.isPending) return; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - inputs, - newName: newName === dynamicSecret.name ? undefined : newName - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch (err) { - createNotification({ - type: "error", - text: err instanceof Error ? err.message : "Failed to update dynamic secret" - }); - } + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + inputs, + newName: newName === dynamicSecret.name ? undefined : newName + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx index e4ccda943..0579ba3b9 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretVertica.tsx @@ -146,34 +146,28 @@ export const EditDynamicSecretVerticaForm = ({ if (updateDynamicSecret.isPending) return; const isDefaultUsernameTemplate = usernameTemplate === "{{randomUsername}}"; - try { - await updateDynamicSecret.mutateAsync({ - name: dynamicSecret.name, - path: secretPath, - projectSlug, - environmentSlug: environment, - data: { - maxTTL: maxTTL || undefined, - defaultTTL, - inputs: { - ...inputs, - gatewayId: isGatewayInActive ? null : inputs.gatewayId - }, - newName: newName === dynamicSecret.name ? undefined : newName, - usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate - } - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully updated dynamic secret" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update dynamic secret" - }); - } + + await updateDynamicSecret.mutateAsync({ + name: dynamicSecret.name, + path: secretPath, + projectSlug, + environmentSlug: environment, + data: { + maxTTL: maxTTL || undefined, + defaultTTL, + inputs: { + ...inputs, + gatewayId: isGatewayInActive ? null : inputs.gatewayId + }, + newName: newName === dynamicSecret.name ? undefined : newName, + usernameTemplate: !usernameTemplate || isDefaultUsernameTemplate ? null : usernameTemplate + } + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully updated dynamic secret" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/RenewDynamicSecretLease.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/RenewDynamicSecretLease.tsx index 85528a5bc..bdb5f9add 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/RenewDynamicSecretLease.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/DynamicSecretListView/RenewDynamicSecretLease.tsx @@ -64,27 +64,19 @@ export const RenewDynamicSecretLease = ({ const handleDynamicSecretLeaseCreate = async ({ ttl }: TForm) => { if (renewDynamicSecretLease.isPending) return; - try { - await renewDynamicSecretLease.mutateAsync({ - environmentSlug: environment, - projectSlug, - path: secretPath, - ttl, - dynamicSecretName, - leaseId - }); - onClose(); - createNotification({ - type: "success", - text: "Successfully renewed lease" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to renew lease" - }); - } + await renewDynamicSecretLease.mutateAsync({ + environmentSlug: environment, + projectSlug, + path: secretPath, + ttl, + dynamicSecretName, + leaseId + }); + onClose(); + createNotification({ + type: "success", + text: "Successfully renewed lease" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx index bd812b837..6e39b8ffc 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/FolderListView/FolderListView.tsx @@ -69,73 +69,65 @@ export const FolderListView = ({ oldFolderName?: string, oldFolderDescription?: string ) => { - try { - const updateFolderData = popUp.updateFolder.data; - if (!updateFolderData) throw new Error("Update folder data is required"); - const { id: folderId, pendingAction, isPending } = updateFolderData as TSecretFolder; + const updateFolderData = popUp.updateFolder.data; + if (!updateFolderData) throw new Error("Update folder data is required"); + const { id: folderId, pendingAction, isPending } = updateFolderData as TSecretFolder; - if (isBatchMode) { - const isEditingPendingCreation = isPending && pendingAction === PendingAction.Create; + if (isBatchMode) { + const isEditingPendingCreation = isPending && pendingAction === PendingAction.Create; - if (isEditingPendingCreation) { - const updatedCreate: PendingFolderCreate = { - id: folderId, - type: PendingAction.Create, - folderName: newFolderName, - description: newFolderDescription || undefined, - parentPath: secretPath, - timestamp: Date.now(), - resourceType: "folder" - }; + if (isEditingPendingCreation) { + const updatedCreate: PendingFolderCreate = { + id: folderId, + type: PendingAction.Create, + folderName: newFolderName, + description: newFolderDescription || undefined, + parentPath: secretPath, + timestamp: Date.now(), + resourceType: "folder" + }; - addPendingChange(updatedCreate, { - projectId, - environment, - secretPath - }); - } else { - const updateChange: PendingFolderUpdate = { - id: folderId, - type: PendingAction.Update, - originalFolderName: oldFolderName || "", - folderName: newFolderName, - originalDescription: oldFolderDescription, - description: newFolderDescription || undefined, - timestamp: Date.now(), - resourceType: "folder" - }; + addPendingChange(updatedCreate, { + projectId, + environment, + secretPath + }); + } else { + const updateChange: PendingFolderUpdate = { + id: folderId, + type: PendingAction.Update, + originalFolderName: oldFolderName || "", + folderName: newFolderName, + originalDescription: oldFolderDescription, + description: newFolderDescription || undefined, + timestamp: Date.now(), + resourceType: "folder" + }; - addPendingChange(updateChange, { - projectId, - environment, - secretPath - }); - } - - handlePopUpClose("updateFolder"); - return; + addPendingChange(updateChange, { + projectId, + environment, + secretPath + }); } - await updateFolder({ - folderId, - name: newFolderName, - path: secretPath, - environment, - projectId, - description: newFolderDescription - }); handlePopUpClose("updateFolder"); - createNotification({ - type: "success", - text: "Successfully saved folder" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to save folder" - }); + return; } + + await updateFolder({ + folderId, + name: newFolderName, + path: secretPath, + environment, + projectId, + description: newFolderDescription + }); + handlePopUpClose("updateFolder"); + createNotification({ + type: "success", + text: "Successfully saved folder" + }); }; const handleDeletePending = (id: string) => { @@ -147,48 +139,40 @@ export const FolderListView = ({ }; const handleFolderDelete = async () => { - try { - const folderData = popUp.deleteFolder?.data as TSecretFolder; + const folderData = popUp.deleteFolder?.data as TSecretFolder; - if (isBatchMode) { - const pendingFolderDelete: PendingFolderDelete = { - id: folderData.id, - folderName: folderData.name, - folderPath: secretPath, - resourceType: "folder", - type: PendingAction.Delete, - timestamp: Date.now() - }; + if (isBatchMode) { + const pendingFolderDelete: PendingFolderDelete = { + id: folderData.id, + folderName: folderData.name, + folderPath: secretPath, + resourceType: "folder", + type: PendingAction.Delete, + timestamp: Date.now() + }; - addPendingChange(pendingFolderDelete, { - projectId, - environment, - secretPath - }); - - handlePopUpClose("deleteFolder"); - return; - } - - await deleteFolder({ - folderId: folderData.id, - path: secretPath, + addPendingChange(pendingFolderDelete, { + projectId, environment, - projectId + secretPath }); handlePopUpClose("deleteFolder"); - createNotification({ - type: "success", - text: "Successfully deleted folder" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to delete folder" - }); + return; } + + await deleteFolder({ + folderId: folderData.id, + path: secretPath, + environment, + projectId + }); + + handlePopUpClose("deleteFolder"); + createNotification({ + type: "success", + text: "Successfully deleted folder" + }); }; const handleFolderClick = (name: string, isPending?: boolean) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx index d4e7d42d5..6a127d6b5 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportItem.tsx @@ -118,24 +118,16 @@ export const SecretImportItem = ({ const handleResyncSecretReplication = async () => { if (resyncSecretReplication.isPending) return; - try { - await resyncSecretReplication.mutateAsync({ - id, - environment, - path: secretPath, - projectId: currentProject?.id || "" - }); - createNotification({ - text: "Please refresh the dashboard to view changes", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to resync replication", - type: "error" - }); - } + await resyncSecretReplication.mutateAsync({ + id, + environment, + path: secretPath, + projectId: currentProject?.id || "" + }); + createNotification({ + text: "Please refresh the dashboard to view changes", + type: "success" + }); }; const handleRowClick = () => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx index 763872e9e..794fce88f 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx @@ -147,25 +147,17 @@ export const SecretImportListView = ({ const handleSecretImportDelete = async () => { const { id: secretImportId } = popUp.deleteSecretImport?.data as { id: string }; - try { - await deleteSecretImport({ - projectId, - environment, - path: secretPath, - id: secretImportId - }); - handlePopUpClose("deleteSecretImport"); - createNotification({ - type: "success", - text: "Successfully removed secret link" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to remove secret link", - type: "error" - }); - } + await deleteSecretImport({ + projectId, + environment, + path: secretPath, + id: secretImportId + }); + handlePopUpClose("deleteSecretImport"); + createNotification({ + type: "success", + text: "Successfully removed secret link" + }); }; const handleSecretImportReorder = ({ over, active }: DragEndEvent) => { diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CreateReminderForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CreateReminderForm.tsx index 3f7727290..531401998 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CreateReminderForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/CreateReminderForm.tsx @@ -199,53 +199,37 @@ export const CreateReminderForm = ({ // Form submission handler const handleFormSubmit = async (data: TReminderFormSchema) => { - try { - await createReminder({ - repeatDays: data.repeatDays, - message: data.message, - recipients: data.recipients?.map((r) => r.value) || [], - secretId, - nextReminderDate: data.nextReminderDate, - fromDate: data.fromDate - }); + await createReminder({ + repeatDays: data.repeatDays, + message: data.message, + recipients: data.recipients?.map((r) => r.value) || [], + secretId, + nextReminderDate: data.nextReminderDate, + fromDate: data.fromDate + }); - invalidateQueries(); + invalidateQueries(); - createNotification({ - type: "success", - text: `Successfully ${isEditMode ? "updated" : "created"} secret reminder` - }); + createNotification({ + type: "success", + text: `Successfully ${isEditMode ? "updated" : "created"} secret reminder` + }); - reset(); - onOpenChange(); - } catch (error) { - console.error("Failed to save reminder:", error); - createNotification({ - type: "error", - text: "Failed to save reminder. Please try again." - }); - } + reset(); + onOpenChange(); }; // Delete reminder handler const handleDeleteReminder = async () => { - try { - await deleteReminder({ reminderId: reminder?.id || "", secretId }); - invalidateQueries(); - reset(); - onOpenChange(); + await deleteReminder({ reminderId: reminder?.id || "", secretId }); + invalidateQueries(); + reset(); + onOpenChange(); - createNotification({ - type: "success", - text: "Successfully deleted reminder" - }); - } catch (error) { - console.error("Failed to delete reminder:", error); - createNotification({ - type: "error", - text: "Failed to delete reminder. Please try again." - }); - } + createNotification({ + type: "success", + text: "Successfully deleted reminder" + }); }; // Handle reminder type change diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx index bd43e1271..be5dfaaed 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretListView.tsx @@ -293,174 +293,166 @@ export const SecretListView = ({ isSameTags && isSameRecipients; - try { - // personal secret change - let personalAction = false; - if (overrideAction === "deleted") { - await handleSecretOperation("delete", SecretType.Personal, oldKey, { - secretId: orgSecret.idOverride - }); - personalAction = true; - } else if (overrideAction && idOverride) { - await handleSecretOperation("update", SecretType.Personal, oldKey, { - value: valueOverride, - newKey: hasKeyChanged ? key : undefined, - secretId: orgSecret.idOverride, - skipMultilineEncoding: modSecret.skipMultilineEncoding - }); - personalAction = true; - } else if (overrideAction) { - await handleSecretOperation("create", SecretType.Personal, oldKey, { - value: valueOverride - }); - personalAction = true; - } + // personal secret change + let personalAction = false; + if (overrideAction === "deleted") { + await handleSecretOperation("delete", SecretType.Personal, oldKey, { + secretId: orgSecret.idOverride + }); + personalAction = true; + } else if (overrideAction && idOverride) { + await handleSecretOperation("update", SecretType.Personal, oldKey, { + value: valueOverride, + newKey: hasKeyChanged ? key : undefined, + secretId: orgSecret.idOverride, + skipMultilineEncoding: modSecret.skipMultilineEncoding + }); + personalAction = true; + } else if (overrideAction) { + await handleSecretOperation("create", SecretType.Personal, oldKey, { + value: valueOverride + }); + personalAction = true; + } - // shared secret change - if (!isSharedSecUnchanged && !personalAction) { - if (isBatchMode) { - const isEditingPendingCreation = isPending && pendingAction === PendingAction.Create; + // shared secret change + if (!isSharedSecUnchanged && !personalAction) { + if (isBatchMode) { + const isEditingPendingCreation = isPending && pendingAction === PendingAction.Create; - if (isEditingPendingCreation) { - const updatedCreate: PendingSecretCreate = { - id: orgSecret.id, - type: PendingAction.Create, - secretKey: key, - secretValue: value || "", - secretComment: comment || "", - skipMultilineEncoding: modSecret.skipMultilineEncoding || false, - tags: tags?.map((tag) => ({ id: tag.id, slug: tag.name || tag.slug || "" })) || [], - secretMetadata: secretMetadata || [], - timestamp: Date.now(), - resourceType: "secret", - originalKey: oldKey - }; + if (isEditingPendingCreation) { + const updatedCreate: PendingSecretCreate = { + id: orgSecret.id, + type: PendingAction.Create, + secretKey: key, + secretValue: value || "", + secretComment: comment || "", + skipMultilineEncoding: modSecret.skipMultilineEncoding || false, + tags: tags?.map((tag) => ({ id: tag.id, slug: tag.name || tag.slug || "" })) || [], + secretMetadata: secretMetadata || [], + timestamp: Date.now(), + resourceType: "secret", + originalKey: oldKey + }; - addPendingChange(updatedCreate, { - projectId, - environment, - secretPath - }); - } else { - const trueOriginalSecret = getTrueOriginalSecret( - orgSecret, - pendingChangesRef.current.secrets - ); + addPendingChange(updatedCreate, { + projectId, + environment, + secretPath + }); + } else { + const trueOriginalSecret = getTrueOriginalSecret( + orgSecret, + pendingChangesRef.current.secrets + ); - const updateChange: PendingSecretUpdate = { - id: orgSecret.id, - type: PendingAction.Update, - secretKey: trueOriginalSecret.key, - newSecretName: key, - originalValue: trueOriginalSecret.value, - secretValue: value, - originalComment: trueOriginalSecret.comment, - secretComment: comment, - originalSkipMultilineEncoding: trueOriginalSecret.skipMultilineEncoding, - skipMultilineEncoding: modSecret.skipMultilineEncoding, - originalTags: - trueOriginalSecret.tags?.map((tag) => ({ id: tag.id, slug: tag.slug })) || [], - tags: tags?.map((tag) => ({ id: tag.id, slug: tag.name || tag.slug || "" })) || [], - originalSecretMetadata: trueOriginalSecret.secretMetadata || [], - secretMetadata: secretMetadata || [], - timestamp: Date.now(), - resourceType: "secret", - existingSecret: orgSecret - }; + const updateChange: PendingSecretUpdate = { + id: orgSecret.id, + type: PendingAction.Update, + secretKey: trueOriginalSecret.key, + newSecretName: key, + originalValue: trueOriginalSecret.value, + secretValue: value, + originalComment: trueOriginalSecret.comment, + secretComment: comment, + originalSkipMultilineEncoding: trueOriginalSecret.skipMultilineEncoding, + skipMultilineEncoding: modSecret.skipMultilineEncoding, + originalTags: + trueOriginalSecret.tags?.map((tag) => ({ id: tag.id, slug: tag.slug })) || [], + tags: tags?.map((tag) => ({ id: tag.id, slug: tag.name || tag.slug || "" })) || [], + originalSecretMetadata: trueOriginalSecret.secretMetadata || [], + secretMetadata: secretMetadata || [], + timestamp: Date.now(), + resourceType: "secret", + existingSecret: orgSecret + }; - addPendingChange(updateChange, { - projectId, - environment, - secretPath - }); - } - - if (!isReminderEvent) { - handlePopUpClose("secretDetail"); - } - if (cb) cb(); - return; + addPendingChange(updateChange, { + projectId, + environment, + secretPath + }); } - await handleSecretOperation("update", SecretType.Shared, oldKey, { - value, - tags: tagIds, - comment, - reminderRepeatDays, - reminderNote, - reminderRecipients, - secretId: orgSecret.id, - newKey: hasKeyChanged ? key : undefined, - skipMultilineEncoding: modSecret.skipMultilineEncoding, - secretMetadata, - isRotatedSecret: orgSecret.isRotatedSecret, - secretValueHidden - }); + if (!isReminderEvent) { + handlePopUpClose("secretDetail"); + } if (cb) cb(); - } - queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ - projectId, - secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ - projectId, - environment, - directory: secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ - projectId, - environment, - directory: secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: commitKeys.history({ - projectId, - environment, - directory: secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: secretApprovalRequestKeys.count({ projectId }) - }); - if (!isReminderEvent) { - handlePopUpClose("secretDetail"); + return; } - let successMessage; - if (isReminderEvent) { - successMessage = reminderRepeatDays - ? "Successfully saved secret reminder" - : "Successfully deleted secret reminder"; - } else { - successMessage = "Successfully saved secrets"; - } - - createNotification({ - type: isProtectedBranch && !personalAction ? "info" : "success", - text: - isProtectedBranch && !personalAction - ? "Requested changes have been sent for review" - : successMessage - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to save secret" + await handleSecretOperation("update", SecretType.Shared, oldKey, { + value, + tags: tagIds, + comment, + reminderRepeatDays, + reminderNote, + reminderRecipients, + secretId: orgSecret.id, + newKey: hasKeyChanged ? key : undefined, + skipMultilineEncoding: modSecret.skipMultilineEncoding, + secretMetadata, + isRotatedSecret: orgSecret.isRotatedSecret, + secretValueHidden }); + if (cb) cb(); } + queryClient.invalidateQueries({ + queryKey: dashboardKeys.getDashboardSecrets({ + projectId, + secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: secretSnapshotKeys.list({ + projectId, + environment, + directory: secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: secretSnapshotKeys.count({ + projectId, + environment, + directory: secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: commitKeys.history({ + projectId, + environment, + directory: secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: secretApprovalRequestKeys.count({ projectId }) + }); + if (!isReminderEvent) { + handlePopUpClose("secretDetail"); + } + + let successMessage; + if (isReminderEvent) { + successMessage = reminderRepeatDays + ? "Successfully saved secret reminder" + : "Successfully deleted secret reminder"; + } else { + successMessage = "Successfully saved secrets"; + } + + createNotification({ + type: isProtectedBranch && !personalAction ? "info" : "success", + text: + isProtectedBranch && !personalAction + ? "Requested changes have been sent for review" + : successMessage + }); }, [environment, secretPath, isProtectedBranch, isBatchMode, projectId, addPendingChange] ); @@ -488,75 +480,67 @@ export const SecretListView = ({ value, secretValueHidden } = popUp.deleteSecret?.data as SecretV3RawSanitized; - try { - if (isBatchMode) { - const deleteChange: PendingSecretDelete = { - id: `${secretId}`, - type: PendingAction.Delete, - secretKey: key, - secretValue: value || "", - timestamp: Date.now(), - resourceType: "secret", - secretValueHidden - }; + if (isBatchMode) { + const deleteChange: PendingSecretDelete = { + id: `${secretId}`, + type: PendingAction.Delete, + secretKey: key, + secretValue: value || "", + timestamp: Date.now(), + resourceType: "secret", + secretValueHidden + }; - addPendingChange(deleteChange, { - projectId, - environment, - secretPath - }); + addPendingChange(deleteChange, { + projectId, + environment, + secretPath + }); - handlePopUpClose("deleteSecret"); - handlePopUpClose("secretDetail"); - return; - } - - await handleSecretOperation("delete", SecretType.Shared, key, { secretId }); - // wrap this in another function and then reuse - queryClient.invalidateQueries({ - queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.list({ - projectId, - environment, - directory: secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: secretSnapshotKeys.count({ - projectId, - environment, - directory: secretPath - }) - }); - queryClient.invalidateQueries({ - queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) - }); - queryClient.invalidateQueries({ - queryKey: secretApprovalRequestKeys.count({ projectId }) - }); handlePopUpClose("deleteSecret"); handlePopUpClose("secretDetail"); - createNotification({ - type: isProtectedBranch ? "info" : "success", - text: isProtectedBranch - ? "Requested changes have been sent for review" - : "Successfully deleted secret" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to delete secret" - }); + return; } + + await handleSecretOperation("delete", SecretType.Shared, key, { secretId }); + // wrap this in another function and then reuse + queryClient.invalidateQueries({ + queryKey: dashboardKeys.getDashboardSecrets({ projectId, secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: secretKeys.getProjectSecret({ projectId, environment, secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: secretSnapshotKeys.list({ + projectId, + environment, + directory: secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: secretSnapshotKeys.count({ + projectId, + environment, + directory: secretPath + }) + }); + queryClient.invalidateQueries({ + queryKey: commitKeys.count({ projectId, environment, directory: secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: commitKeys.history({ projectId, environment, directory: secretPath }) + }); + queryClient.invalidateQueries({ + queryKey: secretApprovalRequestKeys.count({ projectId }) + }); + handlePopUpClose("deleteSecret"); + handlePopUpClose("secretDetail"); + createNotification({ + type: isProtectedBranch ? "info" : "success", + text: isProtectedBranch + ? "Requested changes have been sent for review" + : "Successfully deleted secret" + }); }, [ (popUp.deleteSecret?.data as SecretV3RawSanitized)?.key, environment, diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretVersionItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretVersionItem.tsx index a2c25b13a..525b54444 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretVersionItem.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretVersionItem.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faArrowRotateRight, + faBan, faDesktop, faEyeSlash, faServer, @@ -68,10 +69,14 @@ export const SecretVersionItem = ({ const getLinkToModifyHistoryEntity = ( actorId: string, actorType: string, - membershipId: string | null = "" + membershipId: string | null = "", + groupId: string | null = "", + actorName: string | null = "" ) => { switch (actorType) { case ActorType.USER: + if (groupId) + return `/projects/secret-management/${currentProject.id}/groups/${groupId}?username=${actorName}`; return `/projects/secret-management/${currentProject.id}/members/${membershipId}`; case ActorType.IDENTITY: return `/projects/secret-management/${currentProject.id}/identities/${actorId}`; @@ -83,10 +88,26 @@ export const SecretVersionItem = ({ const onModifyHistoryClick = ( actorId: string | undefined | null, actorType: string | undefined | null, - membershipId: string | undefined | null + membershipId: string | undefined | null, + groupId: string | undefined | null, + actorName: string | undefined | null ) => { + if (!membershipId) { + createNotification({ + type: "info", + text: `This ${actorType === ActorType.USER ? "user" : "identity"} is no longer a member of this project.` + }); + return; + } + if (actorType && actorId && actorType !== ActorType.PLATFORM) { - const redirectLink = getLinkToModifyHistoryEntity(actorId, actorType, membershipId); + const redirectLink = getLinkToModifyHistoryEntity( + actorId, + actorType, + membershipId, + groupId, + actorName + ); if (redirectLink) { navigate({ to: redirectLink }); } @@ -157,15 +178,35 @@ export const SecretVersionItem = ({
Modified by: - + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
- onModifyHistoryClick(actor.actorId, actor.actorType, actor.membershipId) + onClick={ + actor.membershipId + ? () => + onModifyHistoryClick( + actor.actorId, + actor.actorType, + actor.membershipId, + actor.groupId, + actor.name + ) + : undefined } - className="cursor-pointer" + className={actor.membershipId ? "cursor-pointer" : undefined} > + {!actor.membershipId && + actor.actorType && + [ActorType.USER, ActorType.IDENTITY].includes( + actor.actorType as ActorType + ) && }
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SnapshotView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SnapshotView.tsx index eed335fa8..ca12e76a4 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SnapshotView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SnapshotView/SnapshotView.tsx @@ -128,25 +128,17 @@ export const SnapshotView = ({ }); return; } - try { - await performRollback({ - projectId, - snapshotId: snapshotData.id, - environment, - directory: secretPath - }); - createNotification({ - text: "Successfully rollback secrets", - type: "success" - }); - onGoBack(); - } catch (error) { - console.log(error); - createNotification({ - text: "Failed to rollback secrets", - type: "error" - }); - } + await performRollback({ + projectId, + snapshotId: snapshotData.id, + environment, + directory: secretPath + }); + createNotification({ + text: "Successfully rollback secrets", + type: "success" + }); + onGoBack(); }; if (isSnapshotLoading) { diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx index 5ccdd437d..12c264004 100644 --- a/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx +++ b/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx @@ -95,42 +95,26 @@ const Page = () => { const handleDeleteRotation = async () => { const { id } = popUp.deleteRotation.data as { id: string }; - try { - await deleteSecretRotation({ - id, - workspaceId - }); - handlePopUpClose("deleteRotation"); - createNotification({ - type: "success", - text: "Successfully removed rotation" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to remove rotation" - }); - } + await deleteSecretRotation({ + id, + workspaceId + }); + handlePopUpClose("deleteRotation"); + createNotification({ + type: "success", + text: "Successfully removed rotation" + }); }; const handleRestartRotation = async (id: string) => { - try { - await restartSecretRotation({ - id, - workspaceId - }); - createNotification({ - type: "success", - text: "Secret rotation initiated" - }); - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to restart rotation" - }); - } + await restartSecretRotation({ + id, + workspaceId + }); + createNotification({ + type: "success", + text: "Secret rotation initiated" + }); }; const handleCreateRotation = (provider: TSecretRotationProviderTemplate) => { diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx index 952c869db..cfc34df2c 100644 --- a/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx +++ b/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx @@ -1,7 +1,6 @@ import { useRef, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; -import { createNotification } from "@app/components/notifications"; import { Modal, ModalContent, Step, Stepper } from "@app/components/v2"; import { useCreateSecretRotation } from "@app/hooks/api"; import { TSecretRotationProviderTemplate } from "@app/hooks/api/types"; @@ -54,27 +53,19 @@ export const CreateRotationForm = ({ const handleFormSubmit = async () => { if (!wizardData.current.input || !wizardData.current.output) return; - try { - await createSecretRotation({ - workspaceId, - provider: provider.name, - customProvider, - secretPath: wizardData.current.output.secretPath, - environment: wizardData.current.output.environment, - interval: wizardData.current.output.interval, - inputs: wizardData.current.input, - outputs: wizardData.current.output.secrets - }); - setWizardStep(0); - onToggle(false); - wizardData.current = {}; - } catch (error) { - console.log(error); - createNotification({ - type: "error", - text: "Failed to create secret rotation" - }); - } + await createSecretRotation({ + workspaceId, + provider: provider.name, + customProvider, + secretPath: wizardData.current.output.secretPath, + environment: wizardData.current.output.environment, + interval: wizardData.current.output.interval, + inputs: wizardData.current.input, + outputs: wizardData.current.output.secrets + }); + setWizardStep(0); + onToggle(false); + wizardData.current = {}; }; return ( diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx index 78cdf899e..ad82460eb 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx @@ -89,44 +89,30 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => { const handleToggleEnableSync = async () => { const isAutoSyncEnabled = !secretSync.isAutoSyncEnabled; - try { - await updateSync.mutateAsync({ - syncId: secretSync.id, - destination: secretSync.destination, - isAutoSyncEnabled, - projectId: secretSync.projectId - }); + await updateSync.mutateAsync({ + syncId: secretSync.id, + destination: secretSync.destination, + isAutoSyncEnabled, + projectId: secretSync.projectId + }); - createNotification({ - text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to ${isAutoSyncEnabled ? "enable" : "disable"} auto-sync for ${destinationName} Sync`, - type: "error" - }); - } + createNotification({ + text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`, + type: "success" + }); }; const handleTriggerSync = async () => { - try { - await triggerSyncSecrets.mutateAsync({ - syncId: secretSync.id, - destination: secretSync.destination, - projectId: secretSync.projectId - }); + await triggerSyncSecrets.mutateAsync({ + syncId: secretSync.id, + destination: secretSync.destination, + projectId: secretSync.projectId + }); - createNotification({ - text: `Successfully triggered ${destinationName} Sync`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to trigger ${destinationName} Sync`, - type: "error" - }); - } + createNotification({ + text: `Successfully triggered ${destinationName} Sync`, + type: "success" + }); }; const permissionSubject = diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx index 73bec81b9..7c9d03904 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/AutoCapitalizationSection/AutoCapitalizationSection.tsx @@ -13,26 +13,18 @@ export const AutoCapitalizationSection = () => { const { mutateAsync } = useUpdateProject(); const handleToggleCapitalizationToggle = async (state: boolean) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await mutateAsync({ - projectId: currentProject.id, - autoCapitalization: state - }); + await mutateAsync({ + projectId: currentProject.id, + autoCapitalization: state + }); - const text = `Successfully ${state ? "enabled" : "disabled"} auto capitalization`; - createNotification({ - text, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update auto capitalization", - type: "error" - }); - } + const text = `Successfully ${state ? "enabled" : "disabled"} auto capitalization`; + createNotification({ + text, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx index 26f5c7fc9..9350698dc 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/BackfillSecretReferenceSection/BackfillSecretReferenceSection.tsx @@ -13,12 +13,8 @@ export const BackfillSecretReferenceSecretion = () => { const handleBackfill = async () => { if (backfillSecretReferences.isPending) return; - try { - await backfillSecretReferences.mutateAsync({ projectId: currentProject.id || "" }); - createNotification({ text: "Successfully re-indexed secret references", type: "success" }); - } catch { - createNotification({ text: "Failed to re-index secret references", type: "error" }); - } + await backfillSecretReferences.mutateAsync({ projectId: currentProject.id || "" }); + createNotification({ text: "Successfully re-indexed secret references", type: "success" }); }; const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EncryptionTab/EncryptionTab.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EncryptionTab/EncryptionTab.tsx index 9e4627d26..e22719c83 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EncryptionTab/EncryptionTab.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EncryptionTab/EncryptionTab.tsx @@ -124,17 +124,13 @@ const LoadBackupModal = ({ return; } - try { - await loadKmsBackup(backupContent); - createNotification({ - text: "Successfully loaded KMS backup", - type: "success" - }); + await loadKmsBackup(backupContent); + createNotification({ + text: "Successfully loaded KMS backup", + type: "success" + }); - onOpenChange(false); - } catch (err) { - console.error(err); - } + onOpenChange(false); }; const parseFile = (file?: File) => { @@ -245,20 +241,16 @@ export const EncryptionTab = () => { }); const onUpdateProjectKms = async (data: TForm) => { - try { - await updateProjectKms( - data.kmsKeyId === INTERNAL_KMS_KEY_ID - ? { type: KmsType.Internal } - : { type: KmsType.External, kmsId: data.kmsKeyId } - ); + await updateProjectKms( + data.kmsKeyId === INTERNAL_KMS_KEY_ID + ? { type: KmsType.Internal } + : { type: KmsType.External, kmsId: data.kmsKeyId } + ); - createNotification({ - text: "Successfully updated project KMS", - type: "success" - }); - } catch (err) { - console.error(err); - } + createNotification({ + text: "Successfully updated project KMS", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx index 611504a14..70c486616 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/AddEnvironmentModal.tsx @@ -36,28 +36,20 @@ const Content = ({ onComplete }: ContentProps) => { }); const onFormSubmit = async ({ environmentName, environmentSlug }: FormData) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - const env = await mutateAsync({ - projectId: currentProject.id, - name: environmentName, - slug: environmentSlug - }); + const env = await mutateAsync({ + projectId: currentProject.id, + name: environmentName, + slug: environmentSlug + }); - createNotification({ - text: "Successfully created environment", - type: "success" - }); + createNotification({ + text: "Successfully created environment", + type: "success" + }); - onComplete(env); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create environment", - type: "error" - }); - } + onComplete(env); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx index 09ea16f7f..b6276a8c0 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentSection.tsx @@ -39,27 +39,19 @@ export const EnvironmentSection = () => { ] as const); const onEnvDeleteSubmit = async (id: string) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await deleteWsEnvironment.mutateAsync({ - projectId: currentProject.id, - id - }); + await deleteWsEnvironment.mutateAsync({ + projectId: currentProject.id, + id + }); - createNotification({ - text: "Successfully deleted environment", - type: "success" - }); + createNotification({ + text: "Successfully deleted environment", + type: "success" + }); - handlePopUpClose("deleteEnv"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete environment", - type: "error" - }); - } + handlePopUpClose("deleteEnv"); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx index 0908d8377..6b5b3a8d2 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/EnvironmentTable.tsx @@ -46,26 +46,18 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => { const updateEnvironment = useUpdateWsEnvironment(); const handleReorderEnv = async (id: string, position: number) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await updateEnvironment.mutateAsync({ - projectId: currentProject.id, - id, - position - }); + await updateEnvironment.mutateAsync({ + projectId: currentProject.id, + id, + position + }); - createNotification({ - text: "Successfully re-ordered environments", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to re-order environments", - type: "error" - }); - } + createNotification({ + text: "Successfully re-ordered environments", + type: "success" + }); }; const isMoreEnvironmentsAllowed = diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx index e2d64ee72..1c9d09101 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/EnvironmentSection/UpdateEnvironmentModal.tsx @@ -33,29 +33,21 @@ export const UpdateEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpTog const oldEnvId = (popUp?.updateEnv?.data as { id: string })?.id; const onFormSubmit = async ({ name, slug }: FormData) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await mutateAsync({ - projectId: currentProject.id, - name, - slug, - id: oldEnvId - }); + await mutateAsync({ + projectId: currentProject.id, + name, + slug, + id: oldEnvId + }); - createNotification({ - text: "Successfully updated environment", - type: "success" - }); + createNotification({ + text: "Successfully updated environment", + type: "success" + }); - handlePopUpClose("updateEnv"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update environment", - type: "error" - }); - } + handlePopUpClose("updateEnv"); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx index e647ff481..6345301a0 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/PointInTimeVersionLimitSection/PointInTimeVersionLimitSection.tsx @@ -34,22 +34,15 @@ export const PointInTimeVersionLimitSection = () => { if (!currentProject) return null; const handleVersionLimitSubmit = async ({ pitVersionLimit }: TForm) => { - try { - await updateProject({ - pitVersionLimit, - projectId - }); + await updateProject({ + pitVersionLimit, + projectId + }); - createNotification({ - text: "Successfully updated version limit", - type: "success" - }); - } catch { - createNotification({ - text: "Failed updating project's version limit", - type: "error" - }); - } + createNotification({ + text: "Successfully updated version limit", + type: "success" + }); }; const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx index e086834a1..5667afa76 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretDetectionIgnoreValuesSection/SecretDetectionIgnoreValuesSection.tsx @@ -55,22 +55,15 @@ export const SecretDetectionIgnoreValuesSection = () => { }, [currentProject?.secretDetectionIgnoreValues, reset]); const handleIgnoreValuesSubmit = async ({ ignoreValues }: TForm) => { - try { - await updateProject({ - projectId: currentProject.id, - secretDetectionIgnoreValues: ignoreValues.map((item) => item.value) - }); + await updateProject({ + projectId: currentProject.id, + secretDetectionIgnoreValues: ignoreValues.map((item) => item.value) + }); - createNotification({ - text: "Successfully updated secret detection ignore values", - type: "success" - }); - } catch { - createNotification({ - text: "Failed updating secret detection ignore values", - type: "error" - }); - } + createNotification({ + text: "Successfully updated secret detection ignore values", + type: "success" + }); }; const isAdmin = hasProjectRole(ProjectMembershipRole.Admin); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx index bd8928823..ac1f735f9 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSharingSection/SecretSharingSection.tsx @@ -15,12 +15,12 @@ export const SecretSharingSection = () => { const handleToggle = async (state: boolean) => { setIsLoading(true); - try { - if (!currentProject?.id) { - setIsLoading(false); - return; - } + if (!currentProject?.id) { + setIsLoading(false); + return; + } + try { await updateProject({ projectId: currentProject.id, secretSharing: state @@ -30,12 +30,6 @@ export const SecretSharingSection = () => { text: `Successfully ${state ? "enabled" : "disabled"} secret sharing for this project`, type: "success" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update secret sharing for this project", - type: "error" - }); } finally { setIsLoading(false); } diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx index 553f17d54..466b950f1 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretSnapshotsLegacySection/SecretSnapshotsLegacySection.tsx @@ -30,12 +30,6 @@ export const SecretSnapshotsLegacySection = () => { text: `Successfully ${state ? "enabled" : "disabled"} secret snapshots legacy for this project`, type: "success" }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update secret snapshots legacy for this project", - type: "error" - }); } finally { setIsLoading(false); } diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx index 1d4c8e492..cfe7dff8e 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/AddSecretTagModal.tsx @@ -39,29 +39,21 @@ export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle } }); const onFormSubmit = async ({ slug }: FormData) => { - try { - if (!currentProject?.id) return; + if (!currentProject?.id) return; - await createWsTag.mutateAsync({ - projectId: currentProject?.id, - tagSlug: slug, - tagColor: "" - }); + await createWsTag.mutateAsync({ + projectId: currentProject?.id, + tagSlug: slug, + tagColor: "" + }); - handlePopUpClose("CreateSecretTag"); + handlePopUpClose("CreateSecretTag"); - createNotification({ - text: "Successfully created a tag", - type: "success" - }); - reset(); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create a tag", - type: "error" - }); - } + createNotification({ + text: "Successfully created a tag", + type: "success" + }); + reset(); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx index 508ef2867..2964467ad 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/SecretTagsSection/SecretTagsSection.tsx @@ -29,25 +29,17 @@ export const SecretTagsSection = (): JSX.Element => { const deleteWsTag = useDeleteWsTag(); const onDeleteApproved = async () => { - try { - await deleteWsTag.mutateAsync({ - projectId: currentProject?.id || "", - tagID: (popUp?.deleteTagConfirmation?.data as DeleteModalData)?.id - }); + await deleteWsTag.mutateAsync({ + projectId: currentProject?.id || "", + tagID: (popUp?.deleteTagConfirmation?.data as DeleteModalData)?.id + }); - createNotification({ - text: "Successfully deleted tag", - type: "success" - }); + createNotification({ + text: "Successfully deleted tag", + type: "success" + }); - handlePopUpClose("deleteTagConfirmation"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete the tag", - type: "error" - }); - } + handlePopUpClose("deleteTagConfirmation"); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx index 517f9e624..5296bc4ad 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WebhooksTab/WebhooksTab.tsx @@ -59,83 +59,51 @@ export const WebhooksTab = withProjectPermission( const { mutateAsync: deleteWebhook } = useDeleteWebhook(); const handleWebhookCreate = async (data: TFormSchema) => { - try { - await createWebhook({ - ...data, - projectId - }); - handlePopUpClose("addWebhook"); - createNotification({ - type: "success", - text: "Successfully created webhook" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to create webhook" - }); - } + await createWebhook({ + ...data, + projectId + }); + handlePopUpClose("addWebhook"); + createNotification({ + type: "success", + text: "Successfully created webhook" + }); }; const handleWebhookDisable = async (webhookId: string, isDisabled: boolean) => { - try { - await updateWebhook({ - webhookId, - projectId, - isDisabled - }); - createNotification({ - type: "success", - text: "Successfully updated webhook" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to update webhook" - }); - } + await updateWebhook({ + webhookId, + projectId, + isDisabled + }); + createNotification({ + type: "success", + text: "Successfully updated webhook" + }); }; const handleWebhookDelete = async () => { - try { - const webhookId = popUp?.deleteWebhook?.data as string; - await deleteWebhook({ - webhookId, - projectId - }); - handlePopUpClose("deleteWebhook"); - createNotification({ - type: "success", - text: "Successfully deleted webhook" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to delete webhook" - }); - } + const webhookId = popUp?.deleteWebhook?.data as string; + await deleteWebhook({ + webhookId, + projectId + }); + handlePopUpClose("deleteWebhook"); + createNotification({ + type: "success", + text: "Successfully deleted webhook" + }); }; const handleWebhookTest = async (webhookId: string) => { - try { - await testWebhook({ - webhookId, - projectId - }); - createNotification({ - type: "success", - text: "Successfully triggered webhook" - }); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to trigger webhook" - }); - } + await testWebhook({ + webhookId, + projectId + }); + createNotification({ + type: "success", + text: "Successfully triggered webhook" + }); }; return ( diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/WorkflowIntegrationTab.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/WorkflowIntegrationTab.tsx index fb48717cc..2dbb43fe6 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/WorkflowIntegrationTab.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/WorkflowIntegrationTab.tsx @@ -111,7 +111,7 @@ export const WorkflowIntegrationTab = () => { Provider Access Request Notifications Destination Secret Request Notifications Destination - + Secret Sync Error Notifications Destination diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsConfigRow.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsConfigRow.tsx index 6fd0097ad..da9c19a55 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsConfigRow.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsConfigRow.tsx @@ -99,7 +99,9 @@ export const MicrosoftTeamsConfigRow = ({ )} - + + Disabled + diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx index c638b65a7..707322264 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/MicrosoftTeamsIntegrationForm.tsx @@ -130,37 +130,30 @@ export const MicrosoftTeamsIntegrationForm = ({ onClose }: Props) => { }); const handleIntegrationSave = async (data: TMicrosoftTeamsConfigForm) => { - try { - if (!currentProject) { - return; - } - - await updateProjectMicrosoftTeamsConfig({ - projectId: currentProject.id, - isAccessRequestNotificationEnabled: data.isAccessRequestNotificationEnabled, - isSecretRequestNotificationEnabled: data.isSecretRequestNotificationEnabled, - ...(data.isAccessRequestNotificationEnabled && { - accessRequestChannels: data.accessRequestChannels - }), - ...(data.isSecretRequestNotificationEnabled && { - secretRequestChannels: data.secretRequestChannels - }), - integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS, - integrationId: data.microsoftTeamsIntegrationId - }); - - createNotification({ - type: "success", - text: "Successfully created microsoft teams integration" - }); - - onClose(); - } catch { - createNotification({ - type: "error", - text: "Failed to create microsoft teams integration" - }); + if (!currentProject) { + return; } + + await updateProjectMicrosoftTeamsConfig({ + projectId: currentProject.id, + isAccessRequestNotificationEnabled: data.isAccessRequestNotificationEnabled, + isSecretRequestNotificationEnabled: data.isSecretRequestNotificationEnabled, + ...(data.isAccessRequestNotificationEnabled && { + accessRequestChannels: data.accessRequestChannels + }), + ...(data.isSecretRequestNotificationEnabled && { + secretRequestChannels: data.secretRequestChannels + }), + integration: WorkflowIntegrationPlatform.MICROSOFT_TEAMS, + integrationId: data.microsoftTeamsIntegrationId + }); + + createNotification({ + type: "success", + text: "Successfully created microsoft teams integration" + }); + + onClose(); }; const selectedAccessRequestTeamId = watch("accessRequestChannels.teamId"); diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackConfigRow.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackConfigRow.tsx index bce21583f..afabe826c 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackConfigRow.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackConfigRow.tsx @@ -96,7 +96,25 @@ export const SlackConfigRow = ({ handlePopUpOpen, isSlackConfigLoading, slackCon )} - + + {slackConfig.isSecretSyncErrorNotificationEnabled && + !isLoadingConfig && + slackConfig.secretSyncErrorChannels.length > 0 ? ( +

+ {slackConfig.secretSyncErrorChannels + .split(", ") + .map((channel) => slackChannelIdToName[channel]) + .join(", ")} +

+ ) : isLoadingConfig ? ( + + ) : ( + + + Disabled + + )} + diff --git a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx index bcc2d81b5..a473e897e 100644 --- a/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx +++ b/frontend/src/pages/secret-manager/SettingsPage/components/WorkflowIntegrationSection/components/SlackIntegrationForm.tsx @@ -37,11 +37,76 @@ const formSchema = z.object({ isSecretRequestNotificationEnabled: z.boolean(), secretRequestChannels: z.string().array(), isAccessRequestNotificationEnabled: z.boolean(), - accessRequestChannels: z.string().array() + accessRequestChannels: z.string().array(), + isSecretSyncErrorNotificationEnabled: z.boolean(), + secretSyncErrorChannels: z.string().array() }); type TSlackConfigForm = z.infer; +type TChannelSelectorProps = { + value: string[]; + onChange: (value: string[]) => void; + error?: { message?: string }; + slackChannelIdToName: Record; + sortedSlackChannels?: { id: string; name: string }[]; + keyPrefix: string; +}; + +const ChannelSelector = ({ + value, + onChange, + error, + slackChannelIdToName, + sortedSlackChannels, + keyPrefix +}: TChannelSelectorProps) => ( + + + + slackChannelIdToName[entry]) + .join(", ")} + className="text-left" + /> + + + {sortedSlackChannels?.map((slackChannel) => { + const isChecked = value?.includes(slackChannel.id); + return ( + { + evt.preventDefault(); + onChange( + isChecked + ? value?.filter((el: string) => el !== slackChannel.id) + : [...(value || []), slackChannel.id] + ); + }} + key={`${keyPrefix}-slack-channel-${slackChannel.id}`} + iconPos="right" + icon={isChecked && } + > + {slackChannel.name} + + ); + })} + + + +); + type Props = { onClose: () => void; }; @@ -71,42 +136,39 @@ export const SlackIntegrationForm = ({ onClose }: Props) => { isAccessRequestNotificationEnabled: false, accessRequestChannels: [], isSecretRequestNotificationEnabled: false, - secretRequestChannels: [] + secretRequestChannels: [], + isSecretSyncErrorNotificationEnabled: false, + secretSyncErrorChannels: [] } }); const handleIntegrationSave = async (data: TSlackConfigForm) => { - try { - if (!currentProject) { - return; - } - - await updateProjectSlackConfig({ - ...data, - projectId: currentProject.id, - integration: WorkflowIntegrationPlatform.SLACK, - integrationId: data.slackIntegrationId, - accessRequestChannels: data.accessRequestChannels.filter(Boolean).join(", "), - secretRequestChannels: data.secretRequestChannels.filter(Boolean).join(", ") - }); - - createNotification({ - type: "success", - text: "Successfully created slack integration" - }); - - onClose(); - } catch { - createNotification({ - type: "error", - text: "Failed to create slack integration" - }); + if (!currentProject) { + return; } + + await updateProjectSlackConfig({ + ...data, + projectId: currentProject.id, + integration: WorkflowIntegrationPlatform.SLACK, + integrationId: data.slackIntegrationId, + accessRequestChannels: data.accessRequestChannels.filter(Boolean).join(", "), + secretRequestChannels: data.secretRequestChannels.filter(Boolean).join(", "), + secretSyncErrorChannels: data.secretSyncErrorChannels.filter(Boolean).join(", ") + }); + + createNotification({ + type: "success", + text: "Successfully created slack integration" + }); + + onClose(); }; const secretRequestNotifState = watch("isSecretRequestNotificationEnabled"); const selectedSlackIntegrationId = watch("slackIntegrationId"); const accessRequestNotifState = watch("isAccessRequestNotificationEnabled"); + const secretSyncErrorNotifState = watch("isSecretSyncErrorNotificationEnabled"); const { data: slackChannels } = useGetSlackIntegrationChannels(selectedSlackIntegrationId); const slackChannelIdToName = Object.fromEntries( @@ -117,7 +179,7 @@ export const SlackIntegrationForm = ({ onClose }: Props) => { ); useEffect(() => { - if (slackConfig) { + if (slackConfig && slackConfig.integration === WorkflowIntegrationPlatform.SLACK) { setValue("slackIntegrationId", slackConfig.integrationId); setValue( "isSecretRequestNotificationEnabled", @@ -127,22 +189,30 @@ export const SlackIntegrationForm = ({ onClose }: Props) => { "isAccessRequestNotificationEnabled", slackConfig.isAccessRequestNotificationEnabled ); + setValue( + "isSecretSyncErrorNotificationEnabled", + slackConfig.isSecretSyncErrorNotificationEnabled + ); - if (slackConfig.integration === WorkflowIntegrationPlatform.SLACK) { - if (slackChannels) { - setValue( - "secretRequestChannels", - slackConfig.secretRequestChannels - .split(", ") - .filter((channel) => channel in slackChannelIdToName) - ); - setValue( - "accessRequestChannels", - slackConfig.accessRequestChannels - .split(", ") - .filter((channel) => channel in slackChannelIdToName) - ); - } + if (slackChannels) { + setValue( + "secretRequestChannels", + (slackConfig.secretRequestChannels || "") + .split(", ") + .filter((channel) => channel in slackChannelIdToName) + ); + setValue( + "accessRequestChannels", + (slackConfig.accessRequestChannels || "") + .split(", ") + .filter((channel) => channel in slackChannelIdToName) + ); + setValue( + "secretSyncErrorChannels", + (slackConfig.secretSyncErrorChannels || "") + .split(", ") + .filter((channel) => channel in slackChannelIdToName) + ); } } }, [slackConfig, slackChannels]); @@ -215,54 +285,14 @@ export const SlackIntegrationForm = ({ onClose }: Props) => { control={control} name="secretRequestChannels" render={({ field: { value, onChange }, fieldState: { error } }) => ( - - - - slackChannelIdToName[entry]) - .join(", ")} - className="text-left" - /> - - - {sortedSlackChannels?.map((slackChannel) => { - const isChecked = value?.includes(slackChannel.id); - return ( - { - evt.preventDefault(); - onChange( - isChecked - ? value?.filter((el: string) => el !== slackChannel.id) - : [...(value || []), slackChannel.id] - ); - }} - key={`secret-requests-slack-channel-${slackChannel.id}`} - iconPos="right" - icon={isChecked && } - > - {slackChannel.name} - - ); - })} - - - + )} /> )} @@ -288,54 +318,47 @@ export const SlackIntegrationForm = ({ onClose }: Props) => { control={control} name="accessRequestChannels" render={({ field: { value, onChange }, fieldState: { error } }) => ( - - - - slackChannelIdToName[entry]) - .join(", ")} - className="text-left" - /> - - - {sortedSlackChannels?.map((slackChannel) => { - const isChecked = value?.includes(slackChannel.id); - return ( - { - evt.preventDefault(); - onChange( - isChecked - ? value?.filter((el: string) => el !== slackChannel.id) - : [...(value || []), slackChannel.id] - ); - }} - key={`access-requests-slack-channel-${slackChannel.id}`} - iconPos="right" - icon={isChecked && } - > - {slackChannel.name} - - ); - })} - - + + )} + /> + )} + { + return ( + + field.onChange(value)} + isChecked={field.value} + > +

Secret Sync Errors

+
+ ); + }} + /> + {secretSyncErrorNotifState && ( + ( + )} /> )} diff --git a/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx index 8d855f284..b66644bdc 100644 --- a/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx @@ -126,43 +126,35 @@ export const BitbucketConfigurePage = () => { }: TFormData) => { if (!targetRepo || !targetWorkspace) return; - try { - await createIntegration.mutateAsync({ - integrationAuthId, - isActive: true, - app: targetRepo.name, - appId: targetRepo.appId, - sourceEnvironment: sourceEnvironment.slug, - targetEnvironment: targetWorkspace.name, - targetEnvironmentId: targetWorkspace.slug, - ...(scope.value === BitbucketScope.Env && - targetEnvironment && { - targetService: targetEnvironment.name, - targetServiceId: targetEnvironment.uuid - }), - secretPath - }); + await createIntegration.mutateAsync({ + integrationAuthId, + isActive: true, + app: targetRepo.name, + appId: targetRepo.appId, + sourceEnvironment: sourceEnvironment.slug, + targetEnvironment: targetWorkspace.name, + targetEnvironmentId: targetWorkspace.slug, + ...(scope.value === BitbucketScope.Env && + targetEnvironment && { + targetService: targetEnvironment.name, + targetServiceId: targetEnvironment.uuid + }), + secretPath + }); - createNotification({ - type: "success", - text: "Successfully created integration" - }); - navigate({ - to: "/projects/secret-management/$projectId/integrations", - params: { - projectId: currentProject.id - }, - search: { - selectedTab: IntegrationsListPageTabs.NativeIntegrations - } - }); - } catch (err) { - createNotification({ - type: "error", - text: "Failed to create integration" - }); - console.error(err); - } + createNotification({ + type: "success", + text: "Successfully created integration" + }); + navigate({ + to: "/projects/secret-management/$projectId/integrations", + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } + }); }; useEffect(() => { diff --git a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx index 7df2ae35c..a7ecbca6a 100644 --- a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx @@ -73,51 +73,43 @@ export const CircleCIConfigurePage = () => { : undefined; const onSubmit = async (data: TFormData) => { - try { - if (data.scope === CircleCiScope.Context) { - await mutateAsync({ - scope: data.scope, - integrationAuthId, - isActive: true, - sourceEnvironment: data.sourceEnvironment.slug, - app: data.targetContext.name, - appId: data.targetContext.id, - owner: data.targetOrg.name, - secretPath: data.secretPath - }); - } else { - await mutateAsync({ - scope: data.scope, - integrationAuthId, - isActive: true, - app: data.targetProject.name, // project name - owner: data.targetOrg.name, // organization name - appId: data.targetProject.id, // project id (used for syncing) - sourceEnvironment: data.sourceEnvironment.slug, - secretPath: data.secretPath - }); - } - - createNotification({ - type: "success", - text: "Successfully created integration" + if (data.scope === CircleCiScope.Context) { + await mutateAsync({ + scope: data.scope, + integrationAuthId, + isActive: true, + sourceEnvironment: data.sourceEnvironment.slug, + app: data.targetContext.name, + appId: data.targetContext.id, + owner: data.targetOrg.name, + secretPath: data.secretPath }); - navigate({ - to: "/projects/secret-management/$projectId/integrations", - params: { - projectId: currentProject.id - }, - search: { - selectedTab: IntegrationsListPageTabs.NativeIntegrations - } + } else { + await mutateAsync({ + scope: data.scope, + integrationAuthId, + isActive: true, + app: data.targetProject.name, // project name + owner: data.targetOrg.name, // organization name + appId: data.targetProject.id, // project id (used for syncing) + sourceEnvironment: data.sourceEnvironment.slug, + secretPath: data.secretPath }); - } catch (err) { - createNotification({ - type: "error", - text: "Failed to create integration" - }); - console.error(err); } + + createNotification({ + type: "success", + text: "Successfully created integration" + }); + navigate({ + to: "/projects/secret-management/$projectId/integrations", + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } + }); }; if (isCircleCIOrganizationsLoading) diff --git a/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx index 0a0d08863..3948351c3 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx @@ -1,8 +1,6 @@ import { useEffect, useState } from "react"; import { useNavigate, useSearch } from "@tanstack/react-router"; -import axios from "axios"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, @@ -102,19 +100,7 @@ export const CloudflarePagesConfigurePage = () => { selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); - } catch (err) { - console.error(err); - - let errorMessage: string = "Something went wrong!"; - if (axios.isAxiosError(err)) { - const { message } = err?.response?.data as { message: string }; - errorMessage = message; - } - - createNotification({ - text: errorMessage, - type: "error" - }); + } catch { setIsLoading(false); } }; diff --git a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx index 58bc8596b..417f0fe84 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx @@ -1,8 +1,6 @@ import { useEffect, useState } from "react"; import { useNavigate, useSearch } from "@tanstack/react-router"; -import axios from "axios"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "@app/components/v2"; import { SecretPathInput } from "@app/components/v2/SecretPathInput"; import { ROUTE_PATHS } from "@app/const/routes"; @@ -75,19 +73,7 @@ export const CloudflareWorkersConfigurePage = () => { selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); - } catch (err) { - console.error(err); - - let errorMessage: string = "Something went wrong!"; - if (axios.isAxiosError(err)) { - const { message } = err?.response?.data as { message: string }; - errorMessage = message; - } - - createNotification({ - text: errorMessage, - type: "error" - }); + } catch { setIsLoading(false); } }; diff --git a/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx index 1f5a3c40a..05c943fc5 100644 --- a/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx @@ -55,49 +55,45 @@ export const DatabricksConfigurePage = () => { const [secretPath, setSecretPath] = useState("/"); const handleButtonClick = async () => { - try { - if (!integrationAuth?.id) return; + if (!integrationAuth?.id) return; - if (!targetScope) { - createNotification({ - type: "error", - text: "Please select a scope" - }); - return; - } - - const selectedScope = integrationAuthScopes?.find( - (integrationAuthScope) => integrationAuthScope.name === targetScope - ); - - if (!selectedScope) { - createNotification({ - type: "error", - text: "Invalid scope selected" - }); - return; - } - - await mutateAsync({ - integrationAuthId: integrationAuth?.id, - isActive: true, - app: selectedScope.name, // scope name - sourceEnvironment: selectedSourceEnvironment, - secretPath + if (!targetScope) { + createNotification({ + type: "error", + text: "Please select a scope" }); - - navigate({ - to: "/projects/secret-management/$projectId/integrations", - params: { - projectId: currentProject.id - }, - search: { - selectedTab: IntegrationsListPageTabs.NativeIntegrations - } - }); - } catch (err) { - console.error(err); + return; } + + const selectedScope = integrationAuthScopes?.find( + (integrationAuthScope) => integrationAuthScope.name === targetScope + ); + + if (!selectedScope) { + createNotification({ + type: "error", + text: "Invalid scope selected" + }); + return; + } + + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: selectedScope.name, // scope name + sourceEnvironment: selectedSourceEnvironment, + secretPath + }); + + navigate({ + to: "/projects/secret-management/$projectId/integrations", + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } + }); }; return integrationAuth && selectedSourceEnvironment && integrationAuthScopes ? ( diff --git a/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx index 5e9871ade..741c03032 100644 --- a/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx @@ -12,12 +12,10 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { useNavigate, useSearch } from "@tanstack/react-router"; -import axios from "axios"; import { motion } from "framer-motion"; import { twMerge } from "tailwind-merge"; import { z, ZodIssueCode } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, @@ -275,19 +273,7 @@ export const GithubConfigurePage = () => { selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); - } catch (err) { - console.error(err); - - let errorMessage: string = "Something went wrong!"; - if (axios.isAxiosError(err)) { - const { message } = err?.response?.data as { message: string }; - errorMessage = message; - } - - createNotification({ - text: errorMessage, - type: "error" - }); + } catch { setIsLoading(false); } }; diff --git a/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx index 4b922a9bc..93d892241 100644 --- a/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/HashicorpVaultAuthorizePage.tsx @@ -4,10 +4,8 @@ import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-sv import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { useNavigate } from "@tanstack/react-router"; -import axios from "axios"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, CardBody, CardTitle, FormControl, Input } from "@app/components/v2"; import { useProject } from "@app/context"; import { useSaveIntegrationAccessToken } from "@app/hooks/api"; @@ -41,37 +39,23 @@ export const HashicorpVaultAuthorizePage = () => { }); const handleFormSubmit = async (formData: TForm) => { - try { - const integrationAuth = await mutateAsync({ - workspaceId: currentProject.id, - integration: "hashicorp-vault", - accessId: formData.vaultRoleID, - accessToken: formData.vaultSecretID, - url: formData.vaultURL, - namespace: formData.vaultNamespace - }); - navigate({ - to: "/projects/secret-management/$projectId/integrations/hashicorp-vault/create", - params: { - projectId: currentProject.id - }, - search: { - integrationAuthId: integrationAuth.id - } - }); - } catch (err) { - console.error(err); - let errorMessage: string = "Something went wrong!"; - if (axios.isAxiosError(err)) { - const { message } = err?.response?.data as { message: string }; - errorMessage = message; + const integrationAuth = await mutateAsync({ + workspaceId: currentProject.id, + integration: "hashicorp-vault", + accessId: formData.vaultRoleID, + accessToken: formData.vaultSecretID, + url: formData.vaultURL, + namespace: formData.vaultNamespace + }); + navigate({ + to: "/projects/secret-management/$projectId/integrations/hashicorp-vault/create", + params: { + projectId: currentProject.id + }, + search: { + integrationAuthId: integrationAuth.id } - - createNotification({ - text: errorMessage, - type: "error" - }); - } + }); }; return ( diff --git a/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx index 0dfcbb0bf..0636625df 100644 --- a/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx @@ -10,10 +10,8 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { useNavigate, useSearch } from "@tanstack/react-router"; -import axios from "axios"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, @@ -90,38 +88,24 @@ export const HashicorpVaultConfigurePage = () => { }); const handleFormSubmit = async (formData: TForm) => { - try { - if (!integrationAuth?.id) return; - await mutateAsync({ - integrationAuthId: integrationAuth?.id, - isActive: true, - app: formData.vaultEnginePath, - sourceEnvironment: formData.selectedSourceEnvironment, - path: formData.vaultSecretPath, - secretPath: formData.secretPath - }); - navigate({ - to: "/projects/secret-management/$projectId/integrations", - params: { - projectId: currentProject.id - }, - search: { - selectedTab: IntegrationsListPageTabs.NativeIntegrations - } - }); - } catch (err) { - console.error(err); - let errorMessage: string = "Something went wrong!"; - if (axios.isAxiosError(err)) { - const { message } = err?.response?.data as { message: string }; - errorMessage = message; + if (!integrationAuth?.id) return; + await mutateAsync({ + integrationAuthId: integrationAuth?.id, + isActive: true, + app: formData.vaultEnginePath, + sourceEnvironment: formData.selectedSourceEnvironment, + path: formData.vaultSecretPath, + secretPath: formData.secretPath + }); + navigate({ + to: "/projects/secret-management/$projectId/integrations", + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } - - createNotification({ - text: errorMessage, - type: "error" - }); - } + }); }; return integrationAuth ? ( diff --git a/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/OctopusDeployAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/OctopusDeployAuthorizePage.tsx index 21851e4cd..9e50fd43d 100644 --- a/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/OctopusDeployAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/OctopusDeployAuthorizePage.tsx @@ -6,7 +6,6 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useNavigate } from "@tanstack/react-router"; import { z } from "zod"; -import { createNotification } from "@app/components/notifications"; import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2"; import { useProject } from "@app/context"; import { removeTrailingSlash } from "@app/helpers/string"; @@ -29,30 +28,22 @@ export const OctopusDeployAuthorizePage = () => { }); const onSubmit = async ({ instanceUrl, apiKey }: TForm) => { - try { - const integrationAuth = await mutateAsync({ - workspaceId: currentProject.id, - integration: "octopus-deploy", - url: removeTrailingSlash(instanceUrl), - accessToken: apiKey - }); + const integrationAuth = await mutateAsync({ + workspaceId: currentProject.id, + integration: "octopus-deploy", + url: removeTrailingSlash(instanceUrl), + accessToken: apiKey + }); - navigate({ - to: "/projects/secret-management/$projectId/integrations/octopus-deploy/create", - params: { - projectId: currentProject.id - }, - search: { - integrationAuthId: integrationAuth.id - } - }); - } catch (err: any) { - createNotification({ - type: "error", - text: err.message ?? "Error authorizing integration" - }); - console.error(err); - } + navigate({ + to: "/projects/secret-management/$projectId/integrations/octopus-deploy/create", + params: { + projectId: currentProject.id + }, + search: { + integrationAuthId: integrationAuth.id + } + }); }; return ( diff --git a/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx index 5b531ac2d..75f8f5031 100644 --- a/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx @@ -107,49 +107,41 @@ export const OctopusDeployConfigurePage = () => { targetRoles, scope }: TFormData) => { - try { - await createIntegration.mutateAsync({ - integrationAuthId, - isActive: true, - scope, - app: targetResource.name, - appId: targetResource.appId, - targetEnvironment: targetSpace.Name, - targetEnvironmentId: targetSpace.Id, - metadata: { - octopusDeployScopeValues: { - Environment: targetEnvironments?.map(({ Id }) => Id), - Action: targetActions?.map(({ Id }) => Id), - Channel: targetChannels?.map(({ Id }) => Id), - ProcessOwner: targetProcesses?.map(({ Id }) => Id), - Role: targetRoles?.map(({ Id }) => Id), - Machine: targetMachines?.map(({ Id }) => Id) - } - }, - sourceEnvironment: sourceEnvironment.slug, - secretPath - }); - - createNotification({ - type: "success", - text: "Successfully created integration" - }); - navigate({ - to: "/projects/secret-management/$projectId/integrations", - params: { - projectId: currentProject.id - }, - search: { - selectedTab: IntegrationsListPageTabs.NativeIntegrations + await createIntegration.mutateAsync({ + integrationAuthId, + isActive: true, + scope, + app: targetResource.name, + appId: targetResource.appId, + targetEnvironment: targetSpace.Name, + targetEnvironmentId: targetSpace.Id, + metadata: { + octopusDeployScopeValues: { + Environment: targetEnvironments?.map(({ Id }) => Id), + Action: targetActions?.map(({ Id }) => Id), + Channel: targetChannels?.map(({ Id }) => Id), + ProcessOwner: targetProcesses?.map(({ Id }) => Id), + Role: targetRoles?.map(({ Id }) => Id), + Machine: targetMachines?.map(({ Id }) => Id) } - }); - } catch (err) { - createNotification({ - type: "error", - text: "Failed to create integration" - }); - console.error(err); - } + }, + sourceEnvironment: sourceEnvironment.slug, + secretPath + }); + + createNotification({ + type: "success", + text: "Successfully created integration" + }); + navigate({ + to: "/projects/secret-management/$projectId/integrations", + params: { + projectId: currentProject.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } + }); }; useEffect(() => { diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx index 740cf6d71..b2021bf5c 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceRow.tsx @@ -81,24 +81,17 @@ export const SecretScanningResourceRow = ({ resource, dataSource }: Props) => { const navigate = useNavigate(); const handleTriggerScan = async () => { - try { - await triggerDataSourceScan.mutateAsync({ - dataSourceId: dataSource.id, - type: dataSource.type, - projectId: dataSource.projectId, - resourceId: id - }); + await triggerDataSourceScan.mutateAsync({ + dataSourceId: dataSource.id, + type: dataSource.type, + projectId: dataSource.projectId, + resourceId: id + }); - createNotification({ - text: `Successfully triggered scan for ${name}`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to trigger scan for ${name}`, - type: "error" - }); - } + createNotification({ + text: `Successfully triggered scan for ${name}`, + type: "success" + }); }; const [isIdCopied, setIsIdCopied] = useToggle(false); diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceSection.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceSection.tsx index 5616c956a..1e81357b8 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceSection.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourceByIdPage/components/SecretScanningResourceSection.tsx @@ -22,23 +22,16 @@ export const SecretScanningResourceSection = ({ dataSource }: Props) => { const triggerDataSourceScan = useTriggerSecretScanningDataSource(); const handleTriggerScan = async () => { - try { - await triggerDataSourceScan.mutateAsync({ - dataSourceId: dataSource.id, - type: dataSource.type, - projectId: dataSource.projectId - }); + await triggerDataSourceScan.mutateAsync({ + dataSourceId: dataSource.id, + type: dataSource.type, + projectId: dataSource.projectId + }); - createNotification({ - text: `Successfully triggered scan for ${dataSource.name}`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to trigger scan for ${dataSource.name}`, - type: "error" - }); - } + createNotification({ + text: `Successfully triggered scan for ${dataSource.name}`, + type: "success" + }); }; const resourceDetails = RESOURCE_DESCRIPTION_HELPER[dataSource.type]; diff --git a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx index 8e879acce..ac61c8f54 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningDataSourcesPage/components/SecretScanningDataSourcesTable.tsx @@ -184,44 +184,30 @@ export const SecretScanningDataSourcesTable = ({ dataSources }: Props) => { const isAutoScanEnabled = !dataSource.isAutoScanEnabled; - try { - await updateDataSource.mutateAsync({ - dataSourceId: dataSource.id, - type: dataSource.type, - isAutoScanEnabled, - projectId: dataSource.projectId - }); + await updateDataSource.mutateAsync({ + dataSourceId: dataSource.id, + type: dataSource.type, + isAutoScanEnabled, + projectId: dataSource.projectId + }); - createNotification({ - text: `Successfully ${isAutoScanEnabled ? "enabled" : "disabled"} auto-scan for ${destinationName} Data Source`, - type: "success" - }); - } catch { - createNotification({ - text: `Failed to ${isAutoScanEnabled ? "enable" : "disable"} auto-scan for ${destinationName} Data Source`, - type: "error" - }); - } + createNotification({ + text: `Successfully ${isAutoScanEnabled ? "enabled" : "disabled"} auto-scan for ${destinationName} Data Source`, + type: "success" + }); }; const handleTriggerScan = async (dataSource: TSecretScanningDataSource) => { - try { - await triggerDataSourceScan.mutateAsync({ - dataSourceId: dataSource.id, - type: dataSource.type, - projectId: dataSource.projectId - }); + await triggerDataSourceScan.mutateAsync({ + dataSourceId: dataSource.id, + type: dataSource.type, + projectId: dataSource.projectId + }); - createNotification({ - text: "Successfully triggered scan", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to trigger scan", - type: "error" - }); - } + createNotification({ + text: "Successfully triggered scan", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx index 484cbcf36..c4838673b 100644 --- a/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx +++ b/frontend/src/pages/secret-scanning/SecretScanningFindingsPage/components/SecretScanningUpdateFindingModal.tsx @@ -55,39 +55,32 @@ const Content = ({ findings, onComplete }: ContentProps) => { const onSubmit = async (data: FormType) => { if (!data.status) return; - try { - if (findings.length > 1) { - await updateMultipleFindings.mutateAsync( - findings.map((f) => ({ - ...data, - status: data.status!, - findingId: f.id, - projectId: f.projectId - })) - ); - } else { - await updateMultipleFindings.mutateAsync([ - { - ...data, - status: data.status, - findingId: findings[0].id, - projectId: findings[0].projectId - } - ]); - } - - createNotification({ - type: "success", - text: `Finding status${single ? "" : "es"} successfully updated` - }); - - onComplete(); - } catch { - createNotification({ - type: "error", - text: `Failed to update finding status${single ? "" : "es"}` - }); + if (findings.length > 1) { + await updateMultipleFindings.mutateAsync( + findings.map((f) => ({ + ...data, + status: data.status!, + findingId: f.id, + projectId: f.projectId + })) + ); + } else { + await updateMultipleFindings.mutateAsync([ + { + ...data, + status: data.status, + findingId: findings[0].id, + projectId: findings[0].projectId + } + ]); } + + createNotification({ + type: "success", + text: `Finding status${single ? "" : "es"} successfully updated` + }); + + onComplete(); }; return ( diff --git a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx index c9df333c4..7ff36964d 100644 --- a/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx +++ b/frontend/src/pages/secret-scanning/SettingsPage/components/ProjectScanningConfigTab/SecretScanningConfigForm.tsx @@ -38,22 +38,15 @@ export const SecretScanningConfigForm = ({ config }: Props) => { }); const onSubmit = async ({ content }: FormType) => { - try { - await updateConfig.mutateAsync({ - projectId: config.projectId, - content: content || null - }); + await updateConfig.mutateAsync({ + projectId: config.projectId, + content: content || null + }); - createNotification({ - type: "success", - text: "Configuration successfully updated" - }); - } catch { - createNotification({ - type: "error", - text: "Failed to update Configuration" - }); - } + createNotification({ + type: "success", + text: "Configuration successfully updated" + }); }; return ( diff --git a/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx b/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx index 86bdf3c34..aed25d46f 100644 --- a/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx +++ b/frontend/src/pages/ssh/SettingsPage/components/ProjectSshTab/components/ProjectSshConfigCasSection.tsx @@ -47,24 +47,16 @@ export const ProjectSshConfigCasSection = () => { }, [sshConfig]); const onFormSubmit = async ({ defaultUserSshCaId, defaultHostSshCaId }: FormData) => { - try { - await updateProjectSshConfig({ - projectId: currentProject.id, - defaultUserSshCaId: defaultUserSshCaId || undefined, - defaultHostSshCaId: defaultHostSshCaId || undefined - }); + await updateProjectSshConfig({ + projectId: currentProject.id, + defaultUserSshCaId: defaultUserSshCaId || undefined, + defaultHostSshCaId: defaultHostSshCaId || undefined + }); - createNotification({ - text: "Successfully updated SSH project settings", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to update SSH project settings", - type: "error" - }); - } + createNotification({ + text: "Successfully updated SSH project settings", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx b/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx index bb6790a92..41a69a7bd 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/SshCaByIDPage.tsx @@ -43,30 +43,22 @@ const Page = () => { ] as const); const onRemoveCaSubmit = async (caIdToDelete: string) => { - try { - if (!projectId) return; + if (!projectId) return; - await deleteSshCa({ caId: caIdToDelete }); + await deleteSshCa({ caId: caIdToDelete }); - createNotification({ - text: "Successfully deleted SSH CA", - type: "success" - }); + createNotification({ + text: "Successfully deleted SSH CA", + type: "success" + }); - handlePopUpClose("deleteSshCa"); - navigate({ - to: "/projects/ssh/$projectId/overview", - params: { - projectId - } - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH CA", - type: "error" - }); - } + handlePopUpClose("deleteSshCa"); + navigate({ + to: "/projects/ssh/$projectId/overview", + params: { + projectId + } + }); }; return ( diff --git a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx index 6e0baa502..32470308a 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateModal.tsx @@ -122,65 +122,57 @@ export const SshCertificateModal = ({ popUp, handlePopUpToggle }: Props) => { ttl, keyId }: FormData) => { - try { - if (!templateData) return; - if (!projectId) return; + if (!templateData) return; + if (!projectId) return; - switch (operation) { - case SshCertificateOperation.SIGN_SSH_KEY: { - const { serialNumber, signedKey } = await signSshKey({ - projectId, - certificateTemplateId: templateData.id, - publicKey: existingPublicKey, - certType, - principals: principals.split(",").map((user) => user.trim()), - ttl, - keyId - }); + switch (operation) { + case SshCertificateOperation.SIGN_SSH_KEY: { + const { serialNumber, signedKey } = await signSshKey({ + projectId, + certificateTemplateId: templateData.id, + publicKey: existingPublicKey, + certType, + principals: principals.split(",").map((user) => user.trim()), + ttl, + keyId + }); - setCertificateDetails({ - serialNumber, - signedKey - }); - break; - } - case SshCertificateOperation.ISSUE_SSH_CREDS: { - const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({ - projectId, - certificateTemplateId: templateData.id, - keyAlgorithm, - certType, - principals: principals.split(",").map((user) => user.trim()), - ttl, - keyId - }); - - setCertificateDetails({ - serialNumber, - privateKey, - publicKey, - signedKey - }); - break; - } - default: { - break; - } + setCertificateDetails({ + serialNumber, + signedKey + }); + break; } + case SshCertificateOperation.ISSUE_SSH_CREDS: { + const { serialNumber, publicKey, privateKey, signedKey } = await issueSshCreds({ + projectId, + certificateTemplateId: templateData.id, + keyAlgorithm, + certType, + principals: principals.split(",").map((user) => user.trim()), + ttl, + keyId + }); - reset(); - - createNotification({ - text: "Successfully created SSH certificate", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create SSH certificate", - type: "error" - }); + setCertificateDetails({ + serialNumber, + privateKey, + publicKey, + signedKey + }); + break; + } + default: { + break; + } } + + reset(); + + createNotification({ + text: "Successfully created SSH certificate", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx index 17c7a552e..926a1d022 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplateModal.tsx @@ -138,52 +138,44 @@ export const SshCertificateTemplateModal = ({ popUp, handlePopUpToggle, sshCaId allowedHosts, allowCustomKeyIds }: FormData) => { - try { - if (certTemplate) { - await updateSshCertTemplate({ - id: certTemplate.id, - name, - ttl, - maxTTL, - allowedUsers: allowedUsers ? allowedUsers.split(",").map((user) => user.trim()) : [], - allowedHosts: allowedHosts ? allowedHosts.split(",").map((host) => host.trim()) : [], - allowUserCertificates, - allowHostCertificates, - allowCustomKeyIds - }); + if (certTemplate) { + await updateSshCertTemplate({ + id: certTemplate.id, + name, + ttl, + maxTTL, + allowedUsers: allowedUsers ? allowedUsers.split(",").map((user) => user.trim()) : [], + allowedHosts: allowedHosts ? allowedHosts.split(",").map((host) => host.trim()) : [], + allowUserCertificates, + allowHostCertificates, + allowCustomKeyIds + }); - createNotification({ - text: "Successfully updated SSH certificate template", - type: "success" - }); - } else { - await createSshCertTemplate({ - sshCaId, - name, - ttl, - maxTTL, - allowedUsers: allowedUsers ? allowedUsers.split(",").map((user) => user.trim()) : [], - allowedHosts: allowedHosts ? allowedHosts.split(",").map((host) => host.trim()) : [], - allowUserCertificates, - allowHostCertificates, - allowCustomKeyIds - }); - - createNotification({ - text: "Successfully created SSH certificate template", - type: "success" - }); - } - - reset(); - handlePopUpToggle("sshCertificateTemplate", false); - } catch (err) { - console.error(err); createNotification({ - text: "Failed to save changes", - type: "error" + text: "Successfully updated SSH certificate template", + type: "success" + }); + } else { + await createSshCertTemplate({ + sshCaId, + name, + ttl, + maxTTL, + allowedUsers: allowedUsers ? allowedUsers.split(",").map((user) => user.trim()) : [], + allowedHosts: allowedHosts ? allowedHosts.split(",").map((host) => host.trim()) : [], + allowUserCertificates, + allowHostCertificates, + allowCustomKeyIds + }); + + createNotification({ + text: "Successfully created SSH certificate template", + type: "success" }); } + + reset(); + handlePopUpToggle("sshCertificateTemplate", false); }; return ( diff --git a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesSection.tsx b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesSection.tsx index 792c6ad3b..5b021715f 100644 --- a/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesSection.tsx +++ b/frontend/src/pages/ssh/SshCaByIDPage/components/SshCertificateTemplatesSection.tsx @@ -33,24 +33,16 @@ export const SshCertificateTemplatesSection = ({ caId }: Props) => { const { mutateAsync: updateSshCertTemplate } = useUpdateSshCertTemplate(); const onRemoveSshCertificateTemplateSubmit = async (id: string) => { - try { - await deleteSshCertTemplate({ - id - }); + await deleteSshCertTemplate({ + id + }); - await createNotification({ - text: "Successfully deleted SSH certificate template", - type: "success" - }); + createNotification({ + text: "Successfully deleted SSH certificate template", + type: "success" + }); - handlePopUpClose("deleteSshCertificateTemplate"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH certificate template", - type: "error" - }); - } + handlePopUpClose("deleteSshCertificateTemplate"); }; const onUpdateSshCaStatus = async ({ @@ -60,26 +52,16 @@ export const SshCertificateTemplatesSection = ({ caId }: Props) => { templateId: string; status: SshCertTemplateStatus; }) => { - try { - await updateSshCertTemplate({ id: templateId, status }); + await updateSshCertTemplate({ id: templateId, status }); - await createNotification({ - text: `Successfully ${ - status === SshCertTemplateStatus.ACTIVE ? "enabled" : "disabled" - } SSH certificate template`, - type: "success" - }); + createNotification({ + text: `Successfully ${ + status === SshCertTemplateStatus.ACTIVE ? "enabled" : "disabled" + } SSH certificate template`, + type: "success" + }); - handlePopUpClose("sshCertificateTemplateStatus"); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${ - status === SshCertTemplateStatus.ACTIVE ? "enabled" : "disabled" - } SSH certificate template`, - type: "error" - }); - } + handlePopUpClose("sshCertificateTemplateStatus"); }; return ( diff --git a/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx b/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx index 4c8ce4e19..53e0298ff 100644 --- a/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx +++ b/frontend/src/pages/ssh/SshCasPage/components/SshCaModal.tsx @@ -106,47 +106,39 @@ export const SshCaModal = ({ popUp, handlePopUpToggle }: Props) => { publicKey, privateKey }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - if (ca) { - await updateMutateAsync({ - caId: ca.id, - friendlyName - }); - } else { - const { id: newCaId } = await createMutateAsync({ - projectId, - friendlyName, - keySource, - keyAlgorithm, - publicKey, - privateKey - }); - - navigate({ - to: "/projects/ssh/$projectId/ca/$caId", - params: { - projectId, - caId: newCaId - } - }); - } - - reset(); - handlePopUpToggle("sshCa", false); - - createNotification({ - text: `Successfully ${ca ? "updated" : "created"} SSH CA`, - type: "success" + if (ca) { + await updateMutateAsync({ + caId: ca.id, + friendlyName }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${ca ? "update" : "create"} SSH CA`, - type: "error" + } else { + const { id: newCaId } = await createMutateAsync({ + projectId, + friendlyName, + keySource, + keyAlgorithm, + publicKey, + privateKey + }); + + navigate({ + to: "/projects/ssh/$projectId/ca/$caId", + params: { + projectId, + caId: newCaId + } }); } + + reset(); + handlePopUpToggle("sshCa", false); + + createNotification({ + text: `Successfully ${ca ? "updated" : "created"} SSH CA`, + type: "success" + }); }; return ( diff --git a/frontend/src/pages/ssh/SshCasPage/components/SshCaSection.tsx b/frontend/src/pages/ssh/SshCasPage/components/SshCaSection.tsx index 016dc1306..11942795c 100644 --- a/frontend/src/pages/ssh/SshCasPage/components/SshCaSection.tsx +++ b/frontend/src/pages/ssh/SshCasPage/components/SshCaSection.tsx @@ -22,41 +22,25 @@ export const SshCaSection = () => { ] as const); const onRemoveSshCaSubmit = async (caId: string) => { - try { - await deleteSshCa({ caId }); + await deleteSshCa({ caId }); - createNotification({ - text: "Successfully deleted SSH CA", - type: "success" - }); + createNotification({ + text: "Successfully deleted SSH CA", + type: "success" + }); - handlePopUpClose("deleteSshCa"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH CA", - type: "error" - }); - } + handlePopUpClose("deleteSshCa"); }; const onUpdateSshCaStatus = async ({ caId, status }: { caId: string; status: SshCaStatus }) => { - try { - await updateSshCa({ caId, status }); + await updateSshCa({ caId, status }); - createNotification({ - text: `Successfully ${status === SshCaStatus.ACTIVE ? "enabled" : "disabled"} SSH CA`, - type: "success" - }); + createNotification({ + text: `Successfully ${status === SshCaStatus.ACTIVE ? "enabled" : "disabled"} SSH CA`, + type: "success" + }); - handlePopUpClose("sshCaStatus"); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${status === SshCaStatus.ACTIVE ? "enabled" : "disabled"} SSH CA`, - type: "error" - }); - } + handlePopUpClose("sshCaStatus"); }; return ( diff --git a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx index d093003ac..ffb596ac2 100644 --- a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx +++ b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/SshHostGroupDetailsByIDPage.tsx @@ -44,30 +44,22 @@ const Page = () => { ] as const); const onRemoveSshGroupSubmit = async (groupIdToDelete: string) => { - try { - if (!projectId) return; + if (!projectId) return; - await deleteSshHostGroup({ sshHostGroupId: groupIdToDelete }); + await deleteSshHostGroup({ sshHostGroupId: groupIdToDelete }); - createNotification({ - text: "Successfully deleted SSH group", - type: "success" - }); + createNotification({ + text: "Successfully deleted SSH group", + type: "success" + }); - handlePopUpClose("deleteSshHostGroup"); - navigate({ - to: "/projects/ssh/$projectId/overview", - params: { - projectId - } - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH group", - type: "error" - }); - } + handlePopUpClose("deleteSshHostGroup"); + navigate({ + to: "/projects/ssh/$projectId/overview", + params: { + projectId + } + }); }; return ( diff --git a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/AddHostGroupMemberModal.tsx b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/AddHostGroupMemberModal.tsx index 2374492a5..e45266fa0 100644 --- a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/AddHostGroupMemberModal.tsx +++ b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/AddHostGroupMemberModal.tsx @@ -42,30 +42,23 @@ export const AddHostGroupMemberModal = ({ popUp, handlePopUpToggle }: Props) => useAddHostToSshHostGroup(); const handleAddHost = async (sshHostId: string) => { - try { - if (!popUpData?.sshHostGroupId) { - createNotification({ - text: "Some data is missing, please refresh the page and try again", - type: "error" - }); - return; - } - - await addHostToSshHostGroup({ - sshHostGroupId: popUpData.sshHostGroupId, - sshHostId - }); - + if (!popUpData?.sshHostGroupId) { createNotification({ - text: "Successfully added host to the group", - type: "success" - }); - } catch { - createNotification({ - text: "Failed to add host to the group", + text: "Some data is missing, please refresh the page and try again", type: "error" }); + return; } + + await addHostToSshHostGroup({ + sshHostGroupId: popUpData.sshHostGroupId, + sshHostId + }); + + createNotification({ + text: "Successfully added host to the group", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx index 6f45fe510..189ec6e56 100644 --- a/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx +++ b/frontend/src/pages/ssh/SshHostGroupDetailsByIDPage/components/SshHostGroupHostsSection.tsx @@ -40,25 +40,17 @@ export const SshHostGroupHostsSection = ({ sshHostGroupId }: Props) => { }; const onRemoveSshHostSubmit = async (sshHostId: string) => { - try { - await removeHostFromGroup({ - sshHostId, - sshHostGroupId - }); + await removeHostFromGroup({ + sshHostId, + sshHostGroupId + }); - await createNotification({ - text: "Successfully removed host from SSH group", - type: "success" - }); + createNotification({ + text: "Successfully removed host from SSH group", + type: "success" + }); - handlePopUpClose("removeHostFromSshHostGroup"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to remove host from SSH group", - type: "error" - }); - } + handlePopUpClose("removeHostFromSshHostGroup"); }; return ( diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx index 6e65dd532..d7411f889 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupModal.tsx @@ -121,67 +121,59 @@ export const SshHostGroupModal = ({ popUp, handlePopUpToggle }: Props) => { }, [sshHostGroup]); const onFormSubmit = async ({ name, loginMappings }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - // check if there is already a different host group with the same name - const existingNames = - sshHostGroups?.filter((h) => h.id !== sshHostGroup?.id).map((h) => h.name) || []; - - if (existingNames.includes(name.trim())) { - createNotification({ - text: "A host group with this name already exists.", - type: "error" - }); - return; - } - - const transformedLoginMappings = loginMappings.map(({ loginUser, allowedPrincipals }) => { - const usernames = allowedPrincipals - .filter((p) => p.type === "user" && p.value) - .map((p) => p.value); - - const groupNames = allowedPrincipals - .filter((p) => p.type === "group" && p.value) - .map((p) => p.value); - - return { - loginUser, - allowedPrincipals: { - usernames, - groups: groupNames - } - }; - }); - - if (sshHostGroup) { - await updateMutateAsync({ - sshHostGroupId: sshHostGroup.id, - name, - loginMappings: transformedLoginMappings - }); - } else { - await createMutateAsync({ - projectId, - name, - loginMappings: transformedLoginMappings - }); - } - - reset(); - handlePopUpToggle("sshHostGroup", false); + // check if there is already a different host group with the same name + const existingNames = + sshHostGroups?.filter((h) => h.id !== sshHostGroup?.id).map((h) => h.name) || []; + if (existingNames.includes(name.trim())) { createNotification({ - text: `Successfully ${sshHostGroup ? "updated" : "created"} SSH host group`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${sshHostGroup ? "update" : "create"} SSH host group`, + text: "A host group with this name already exists.", type: "error" }); + return; } + + const transformedLoginMappings = loginMappings.map(({ loginUser, allowedPrincipals }) => { + const usernames = allowedPrincipals + .filter((p) => p.type === "user" && p.value) + .map((p) => p.value); + + const groupNames = allowedPrincipals + .filter((p) => p.type === "group" && p.value) + .map((p) => p.value); + + return { + loginUser, + allowedPrincipals: { + usernames, + groups: groupNames + } + }; + }); + + if (sshHostGroup) { + await updateMutateAsync({ + sshHostGroupId: sshHostGroup.id, + name, + loginMappings: transformedLoginMappings + }); + } else { + await createMutateAsync({ + projectId, + name, + loginMappings: transformedLoginMappings + }); + } + + reset(); + handlePopUpToggle("sshHostGroup", false); + + createNotification({ + text: `Successfully ${sshHostGroup ? "updated" : "created"} SSH host group`, + type: "success" + }); }; const toggleMapping = (index: number) => { diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx index efffbd3a2..5febe22b7 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostGroupsSection.tsx @@ -34,22 +34,14 @@ export const SshHostGroupsSection = () => { }; const onRemoveSshHostGroupSubmit = async (sshHostGroupId: string) => { - try { - const hostGroup = await deleteSshHostGroup({ sshHostGroupId }); + const hostGroup = await deleteSshHostGroup({ sshHostGroupId }); - createNotification({ - text: `Successfully deleted SSH host group: ${hostGroup.name}`, - type: "success" - }); + createNotification({ + text: `Successfully deleted SSH host group: ${hostGroup.name}`, + type: "success" + }); - handlePopUpClose("deleteSshHostGroup"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH host group", - type: "error" - }); - } + handlePopUpClose("deleteSshHostGroup"); }; return ( diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx index e9b0d870a..afcde908f 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostModal.tsx @@ -140,93 +140,84 @@ export const SshHostModal = ({ popUp, handlePopUpToggle }: Props) => { }, [sshHost]); const onFormSubmit = async ({ hostname, alias, userCertTtl, loginMappings }: FormData) => { - try { - if (!projectId) return; + if (!projectId) return; - // Filter out login mappings that are from host groups - const hostLoginMappings = loginMappings.filter( - (mapping) => mapping.source === LoginMappingSource.HOST - ); + // Filter out login mappings that are from host groups + const hostLoginMappings = loginMappings.filter( + (mapping) => mapping.source === LoginMappingSource.HOST + ); - // check if there is already a different host with the same hostname - const existingHostnames = - sshHosts?.filter((h) => h.id !== sshHost?.id).map((h) => h.hostname) || []; + // check if there is already a different host with the same hostname + const existingHostnames = + sshHosts?.filter((h) => h.id !== sshHost?.id).map((h) => h.hostname) || []; - if (existingHostnames.includes(hostname.trim())) { + if (existingHostnames.includes(hostname.trim())) { + createNotification({ + text: "A host with this hostname already exists.", + type: "error" + }); + return; + } + + const trimmedAlias = alias.trim(); + + // check if there is already a different host with the same non-null alias + if (trimmedAlias) { + const existingAliases = + sshHosts?.filter((h) => h.id !== sshHost?.id && h.alias !== null).map((h) => h.alias) || []; + + if (existingAliases.includes(trimmedAlias)) { createNotification({ - text: "A host with this hostname already exists.", + text: "A host with this alias already exists.", type: "error" }); return; } + } - const trimmedAlias = alias.trim(); + const transformedLoginMappings = hostLoginMappings.map(({ loginUser, allowedPrincipals }) => { + const usernames = allowedPrincipals + .filter((p) => p.type === "user" && p.value) + .map((p) => p.value); - // check if there is already a different host with the same non-null alias - if (trimmedAlias) { - const existingAliases = - sshHosts?.filter((h) => h.id !== sshHost?.id && h.alias !== null).map((h) => h.alias) || - []; + const groupNames = allowedPrincipals + .filter((p) => p.type === "group" && p.value) + .map((p) => p.value); - if (existingAliases.includes(trimmedAlias)) { - createNotification({ - text: "A host with this alias already exists.", - type: "error" - }); - return; + return { + loginUser, + allowedPrincipals: { + usernames, + groups: groupNames } - } + }; + }); - const transformedLoginMappings = hostLoginMappings.map(({ loginUser, allowedPrincipals }) => { - const usernames = allowedPrincipals - .filter((p) => p.type === "user" && p.value) - .map((p) => p.value); - - const groupNames = allowedPrincipals - .filter((p) => p.type === "group" && p.value) - .map((p) => p.value); - - return { - loginUser, - allowedPrincipals: { - usernames, - groups: groupNames - } - }; + if (sshHost) { + await updateMutateAsync({ + sshHostId: sshHost.id, + hostname, + alias: trimmedAlias, + userCertTtl, + loginMappings: transformedLoginMappings }); - - if (sshHost) { - await updateMutateAsync({ - sshHostId: sshHost.id, - hostname, - alias: trimmedAlias, - userCertTtl, - loginMappings: transformedLoginMappings - }); - } else { - await createMutateAsync({ - projectId, - hostname, - alias: trimmedAlias, - userCertTtl, - loginMappings: transformedLoginMappings - }); - } - - reset(); - handlePopUpToggle("sshHost", false); - - createNotification({ - text: `Successfully ${sshHost ? "updated" : "added"} SSH host`, - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: `Failed to ${sshHost ? "update" : "add"} SSH host`, - type: "error" + } else { + await createMutateAsync({ + projectId, + hostname, + alias: trimmedAlias, + userCertTtl, + loginMappings: transformedLoginMappings }); } + + reset(); + handlePopUpToggle("sshHost", false); + + createNotification({ + text: `Successfully ${sshHost ? "updated" : "added"} SSH host`, + type: "success" + }); }; const toggleMapping = (index: number) => { diff --git a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx index 0f57cbeb2..53e8062ab 100644 --- a/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx +++ b/frontend/src/pages/ssh/SshHostsPage/components/SshHostsSection.tsx @@ -20,22 +20,14 @@ export const SshHostsSection = () => { ] as const); const onRemoveSshHostSubmit = async (sshHostId: string) => { - try { - const host = await deleteSshHost({ sshHostId }); + const host = await deleteSshHost({ sshHostId }); - createNotification({ - text: `Successfully deleted SSH host: ${host.hostname}`, - type: "success" - }); + createNotification({ + text: `Successfully deleted SSH host: ${host.hostname}`, + type: "success" + }); - handlePopUpClose("deleteSshHost"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete SSH host", - type: "error" - }); - } + handlePopUpClose("deleteSshHost"); }; return ( diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/APIKeyTable.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/APIKeyTable.tsx index 6fe6afd43..cf2960ada 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/APIKeyTable.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/APIKeyTable.tsx @@ -22,19 +22,11 @@ export const APIKeyTable = () => { const { mutateAsync } = useDeleteAPIKey(); const handleDeleteAPIKeyDataClick = async (apiKeyDataId: string) => { - try { - await mutateAsync(apiKeyDataId); - createNotification({ - text: "Successfully deleted API key", - type: "success" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete API key", - type: "error" - }); - } + await mutateAsync(apiKeyDataId); + createNotification({ + text: "Successfully deleted API key", + type: "success" + }); }; return ( diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/AddAPIKeyModal.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/AddAPIKeyModal.tsx index 213479b47..f28c39339 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/AddAPIKeyModal.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/APIKeySection/AddAPIKeyModal.tsx @@ -76,27 +76,19 @@ export const AddAPIKeyModal = ({ popUp, handlePopUpToggle }: Props) => { }; const onFormSubmit = async ({ name, expiresIn }: FormData) => { - try { - const { apiKey } = await mutateAsync({ - name, - expiresIn: expirationMapping[expiresIn] - }); + const { apiKey } = await mutateAsync({ + name, + expiresIn: expirationMapping[expiresIn] + }); - setNewAPIKey(apiKey); + setNewAPIKey(apiKey); - createNotification({ - text: "Successfully created API key", - type: "success" - }); + createNotification({ + text: "Successfully created API key", + type: "success" + }); - reset(); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to create API key", - type: "error" - }); - } + reset(); }; const hasAPIKey = Boolean(newAPIKey); diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/ChangeEmailSection/ChangeEmailSection.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/ChangeEmailSection/ChangeEmailSection.tsx index c9d457e44..bd37e5620 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/ChangeEmailSection/ChangeEmailSection.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/ChangeEmailSection/ChangeEmailSection.tsx @@ -83,23 +83,14 @@ export const ChangeEmailSection = () => { return; } - try { - await requestEmailChangeOTP({ newEmail }); - setPendingEmail(newEmail); - setIsOTPModalOpen(true); + await requestEmailChangeOTP({ newEmail }); + setPendingEmail(newEmail); + setIsOTPModalOpen(true); - createNotification({ - text: "Verification code sent to your new email address. Check your inbox!", - type: "success" - }); - } catch (err: any) { - console.error(err); - const errorMessage = err?.response?.data?.message || "Failed to send verification code"; - createNotification({ - text: errorMessage, - type: "error" - }); - } + createNotification({ + text: "Verification code sent to your new email address. Check your inbox!", + type: "success" + }); }; const [typedOTP, setTypedOTP] = useState(""); @@ -135,8 +126,6 @@ export const ChangeEmailSection = () => { navigate({ to: "/login" }); }, 2000); } catch (err: any) { - console.error(err); - const errorMessage = err?.response?.data?.message || "Invalid verification code"; if (errorMessage.includes("Invalid verification code")) { // Reset to email step so user must request new OTP @@ -149,11 +138,6 @@ export const ChangeEmailSection = () => { text: "Invalid verification code. Please request a new one.", type: "error" }); - } else { - createNotification({ - text: errorMessage, - type: "error" - }); } } }; diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/ChangePasswordSection/ChangePasswordSection.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/ChangePasswordSection/ChangePasswordSection.tsx index 552eab741..1ae492162 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/ChangePasswordSection/ChangePasswordSection.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/ChangePasswordSection/ChangePasswordSection.tsx @@ -97,21 +97,13 @@ export const ChangePasswordSection = () => { }; const onSetupPassword = async () => { - try { - await sendSetupPasswordEmail.mutateAsync(); + await sendSetupPasswordEmail.mutateAsync(); - createNotification({ - title: "Password setup verification email sent", - text: "Check your email to confirm password setup", - type: "info" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to send password setup email", - type: "error" - }); - } + createNotification({ + title: "Password setup verification email sent", + text: "Check your email to confirm password setup", + type: "info" + }); }; return ( diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/DeleteAccountSection/DeleteAccountSection.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/DeleteAccountSection/DeleteAccountSection.tsx index 5761a772a..301942346 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/DeleteAccountSection/DeleteAccountSection.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/DeleteAccountSection/DeleteAccountSection.tsx @@ -15,23 +15,15 @@ export const DeleteAccountSection = () => { const { mutateAsync: deleteUserMutateAsync, isPending } = useDeleteMe(); const handleDeleteAccountSubmit = async () => { - try { - await deleteUserMutateAsync(); + await deleteUserMutateAsync(); - createNotification({ - text: "Successfully deleted account", - type: "success" - }); + createNotification({ + text: "Successfully deleted account", + type: "success" + }); - navigate({ to: "/login" }); - handlePopUpClose("deleteAccount"); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to delete account", - type: "error" - }); - } + navigate({ to: "/login" }); + handlePopUpClose("deleteAccount"); }; return ( diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/SecuritySection/MFASection.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/SecuritySection/MFASection.tsx index ef29f3ff6..74f4ed99b 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/SecuritySection/MFASection.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/SecuritySection/MFASection.tsx @@ -84,49 +84,27 @@ export const MFASection = () => { }, [totpRegistration, showMobileAuthSetup]); const handleTotpDeletion = async () => { - try { - await deleteTotpConfiguration(); + await deleteTotpConfiguration(); - await mutateAsync({ - selectedMfaMethod: MfaMethod.EMAIL - }); + await mutateAsync({ + selectedMfaMethod: MfaMethod.EMAIL + }); - createNotification({ - text: "Successfully deleted mobile authenticator and switched to email authentication", - type: "success" - }); + createNotification({ + text: "Successfully deleted mobile authenticator and switched to email authentication", + type: "success" + }); - handlePopUpClose("deleteTotpConfig"); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to delete mobile authenticator"; - - createNotification({ - text, - type: "error" - }); - } + handlePopUpClose("deleteTotpConfig"); }; const handleGenerateMoreRecoveryCodes = async () => { - try { - await createTotpRecoveryCodes(); + await createTotpRecoveryCodes(); - createNotification({ - text: "Successfully generated new recovery codes", - type: "success" - }); - } catch (err) { - console.error(err); - const error = err as any; - const text = error?.response?.data?.message ?? "Failed to generate new recovery codes"; - - createNotification({ - text, - type: "error" - }); - } + createNotification({ + text: "Successfully generated new recovery codes", + type: "success" + }); }; const handleFormDataChange = async (field: string, value: any) => { @@ -200,10 +178,6 @@ export const MFASection = () => { await queryClient.invalidateQueries({ queryKey: userKeys.totpConfiguration }); } catch { - createNotification({ - text: "Failed to verify TOTP code. Please try again.", - type: "error" - }); setIsLoading(false); return; } @@ -249,12 +223,6 @@ export const MFASection = () => { setShowMobileAuthSetup(false); setTotpCode(""); setShouldShowRecoveryCodes.off(); - } catch (err) { - createNotification({ - text: "Something went wrong while updating two-factor authentication settings.", - type: "error" - }); - console.error(err); } finally { setIsLoading(false); } diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx index 027f2ed46..f37be42a1 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/SessionsSection/SessionsTable.tsx @@ -40,19 +40,11 @@ export const SessionsTable = () => { ] as const); const handleSignOut = async (sessionId: string) => { - try { - await revokeMySessionById(sessionId); - createNotification({ - text: "Session revoked successfully", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to revoke session", - type: "error" - }); - } + await revokeMySessionById(sessionId); + createNotification({ + text: "Session revoked successfully", + type: "success" + }); handlePopUpClose("deleteSession"); }; diff --git a/frontend/src/pages/user/PersonalSettingsPage/components/UserNameSection/UserNameSection.tsx b/frontend/src/pages/user/PersonalSettingsPage/components/UserNameSection/UserNameSection.tsx index df0dae9cb..1e06d298f 100644 --- a/frontend/src/pages/user/PersonalSettingsPage/components/UserNameSection/UserNameSection.tsx +++ b/frontend/src/pages/user/PersonalSettingsPage/components/UserNameSection/UserNameSection.tsx @@ -27,22 +27,14 @@ export const UserNameSection = (): JSX.Element => { }, [user]); const onFormSubmit = async ({ name }: FormData) => { - try { - if (!user?.id) return; - if (name === "") return; + if (!user?.id) return; + if (name === "") return; - await mutateAsync({ newName: name }); - createNotification({ - text: "Successfully renamed user", - type: "success" - }); - } catch (error) { - console.error(error); - createNotification({ - text: "Failed to rename user", - type: "error" - }); - } + await mutateAsync({ newName: name }); + createNotification({ + text: "Successfully renamed user", + type: "success" + }); }; return (