diff --git a/authentik/core/templates/base/header_js.html b/authentik/core/templates/base/header_js.html index 9e75d96c4d..39dc374cf0 100644 --- a/authentik/core/templates/base/header_js.html +++ b/authentik/core/templates/base/header_js.html @@ -1,11 +1,13 @@ {% load i18n %} {% get_current_language as LANGUAGE_CODE %} - {% block head %} diff --git a/authentik/core/templates/base/theme.html b/authentik/core/templates/base/theme.html index ddd12dc58d..25466eb099 100644 --- a/authentik/core/templates/base/theme.html +++ b/authentik/core/templates/base/theme.html @@ -1,10 +1,45 @@ +{% load static %} +{% load authentik_core %} + + + {% if ui_theme == "dark" %} -{% elif ui_theme == "light" %} + + {% elif ui_theme == "light" %} {% else %} + + diff --git a/authentik/core/templates/if/error.html b/authentik/core/templates/if/error.html index 6abc12e3e9..6918bf9feb 100644 --- a/authentik/core/templates/if/error.html +++ b/authentik/core/templates/if/error.html @@ -12,10 +12,13 @@ {% endblock %} {% block card %} -
+

{% trans message %}

- - {% trans 'Go home' %} - - + + +
{% endblock %} diff --git a/authentik/core/templates/login/base_full.html b/authentik/core/templates/login/base_full.html index 703014c13d..4b89409576 100644 --- a/authentik/core/templates/login/base_full.html +++ b/authentik/core/templates/login/base_full.html @@ -2,82 +2,67 @@ {% load static %} {% load i18n %} +{% load authentik_core %} {% block head_before %} - - {% include "base/header_js.html" %} {% endblock %} {% block head %} - + + {% endblock %} {% block body %} -
-
-
-
-
-
- +
{% endblock %} diff --git a/authentik/flows/templates/if/flow-sfe.html b/authentik/flows/templates/if/flow-sfe.html index 1dc99a80d7..39f528d416 100644 --- a/authentik/flows/templates/if/flow-sfe.html +++ b/authentik/flows/templates/if/flow-sfe.html @@ -17,7 +17,7 @@ {% include "base/header_js.html" %} - {% endblock %} diff --git a/authentik/policies/templates/policies/denied.html b/authentik/policies/templates/policies/denied.html index 9a2da6e2b8..5f16ac4b21 100644 --- a/authentik/policies/templates/policies/denied.html +++ b/authentik/policies/templates/policies/denied.html @@ -12,8 +12,7 @@ {% endblock %} {% block card %} -
- {% csrf_token %} +
{% if user.is_authenticated %}
@@ -29,7 +28,7 @@ {% endif %}

- + {% trans 'Request has been denied.' %}

{% if error %} @@ -71,5 +70,11 @@ {% endif %} {% endif %}
- + + +
{% endblock %} diff --git a/tests/e2e/sources_oauth2_dex/dex.yaml b/tests/e2e/sources_oauth2_dex/dex.yaml index c1f59d5fbd..0dc1c22a94 100644 --- a/tests/e2e/sources_oauth2_dex/dex.yaml +++ b/tests/e2e/sources_oauth2_dex/dex.yaml @@ -1,6 +1,6 @@ --- enablePasswordDB: true -issuer: http://127.0.0.1:5556/dex +issuer: http://{{ .Env.AK_HOST }}:5556/dex logger: level: debug staticClients: diff --git a/tests/e2e/test_flows_enroll.py b/tests/e2e/test_flows_enroll.py index 836c922973..226a34fad4 100644 --- a/tests/e2e/test_flows_enroll.py +++ b/tests/e2e/test_flows_enroll.py @@ -110,8 +110,8 @@ class TestFlowsEnroll(SeleniumTestCase): identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor) wait = WebDriverWait(identification_stage, self.wait_timeout) - wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "#enroll"))) - identification_stage.find_element(By.CSS_SELECTOR, "#enroll").click() + wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "a[name='enroll']"))) + identification_stage.find_element(By.CSS_SELECTOR, "a[name='enroll']").click() # First prompt stage flow_executor = self.get_shadow_root("ak-flow-executor") @@ -193,7 +193,7 @@ class TestFlowsEnroll(SeleniumTestCase): self.assertEqual( "Continue to confirm this email address.", - consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + consent_stage.find_element(By.CSS_SELECTOR, "[data-test-id='stage-heading']").text, ) # Back on the main tab, confirm diff --git a/tests/e2e/test_flows_recovery.py b/tests/e2e/test_flows_recovery.py index 15a60278bd..e0e068e50c 100644 --- a/tests/e2e/test_flows_recovery.py +++ b/tests/e2e/test_flows_recovery.py @@ -26,8 +26,8 @@ class TestFlowsRecovery(SeleniumTestCase): identification_stage = self.get_shadow_root("ak-stage-identification", flow_executor) wait = WebDriverWait(identification_stage, self.wait_timeout) - wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "#recovery"))) - identification_stage.find_element(By.CSS_SELECTOR, "#recovery").click() + wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "a[name='recovery']"))) + identification_stage.find_element(By.CSS_SELECTOR, "a[name='recovery']").click() # First prompt stage flow_executor = self.get_shadow_root("ak-flow-executor") diff --git a/tests/e2e/test_provider_oauth2_github.py b/tests/e2e/test_provider_oauth2_github.py index 71f41f3448..5d737f2b79 100644 --- a/tests/e2e/test_provider_oauth2_github.py +++ b/tests/e2e/test_provider_oauth2_github.py @@ -158,7 +158,7 @@ class TestProviderOAuth2Github(SeleniumTestCase): self.assertIn( app.name, - consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + consent_stage.find_element(By.CSS_SELECTOR, "[data-test-id='stage-heading']").text, ) self.assertEqual( "GitHub Compatibility: Access you Email addresses", @@ -228,8 +228,10 @@ class TestProviderOAuth2Github(SeleniumTestCase): self.driver.find_element(By.CLASS_NAME, "btn-service--github").click() self.login() - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "header > h1"))) + self.wait.until( + ec.presence_of_element_located((By.CSS_SELECTOR, "[data-test-id='card-title']")) + ) self.assertEqual( - self.driver.find_element(By.CSS_SELECTOR, "header > h1").text, + self.driver.find_element(By.CSS_SELECTOR, "[data-test-id='card-title']").text, "Permission denied", ) diff --git a/tests/e2e/test_provider_oauth2_grafana.py b/tests/e2e/test_provider_oauth2_grafana.py index 7900dd12a5..569a47b63f 100644 --- a/tests/e2e/test_provider_oauth2_grafana.py +++ b/tests/e2e/test_provider_oauth2_grafana.py @@ -327,7 +327,7 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): self.assertIn( app.name, - consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + consent_stage.find_element(By.CSS_SELECTOR, "[data-test-id='stage-heading']").text, ) consent_stage.find_element( By.CSS_SELECTOR, @@ -407,9 +407,11 @@ class TestProviderOAuth2OAuth(SeleniumTestCase): self.driver.find_element(By.CLASS_NAME, "btn-service--oauth").click() self.login() - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "header > h1"))) + self.wait.until( + ec.presence_of_element_located((By.CSS_SELECTOR, "[data-test-id='card-title']")) + ) self.assertEqual( - self.driver.find_element(By.CSS_SELECTOR, "header > h1").text, + self.driver.find_element(By.CSS_SELECTOR, "[data-test-id='card-title']").text, "Permission denied", ) diff --git a/tests/e2e/test_provider_oidc.py b/tests/e2e/test_provider_oidc.py index d1bf7d3573..05a0023201 100644 --- a/tests/e2e/test_provider_oidc.py +++ b/tests/e2e/test_provider_oidc.py @@ -1,6 +1,6 @@ """test OAuth2 OpenID Provider flow""" -from json import loads +from json import dumps from time import sleep from selenium.webdriver.common.by import By @@ -146,6 +146,7 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): self.driver.get("http://localhost:9009") self.login() + self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-flow-executor"))) flow_executor = self.get_shadow_root("ak-flow-executor") @@ -153,26 +154,63 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): self.assertIn( app.name, - consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + consent_stage.find_element(By.CSS_SELECTOR, "[data-test-id='stage-heading']").text, ) + + current_url = self.driver.current_url + consent_stage.find_element( By.CSS_SELECTOR, "[type=submit]", ).click() - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "pre"))) - self.wait.until(ec.text_to_be_present_in_element((By.CSS_SELECTOR, "pre"), "{")) - body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) + self.wait.until(ec.url_changes(current_url)) - self.assertEqual(body["IDTokenClaims"]["nickname"], self.user.username) - self.assertEqual(body["IDTokenClaims"]["amr"], ["pwd"]) - self.assertEqual(body["UserInfo"]["nickname"], self.user.username) + body = self.parse_json_content() + snippet = dumps(body, indent=2)[:500].replace("\n", " ") - self.assertEqual(body["IDTokenClaims"]["name"], self.user.name) - self.assertEqual(body["UserInfo"]["name"], self.user.name) + self.assertEqual( + body.get("IDTokenClaims", {}).get("nickname"), + self.user.username, + f"IDTokenClaims.nickname mismatch at {self.driver.current_url}: {snippet}", + ) - self.assertEqual(body["IDTokenClaims"]["email"], self.user.email) - self.assertEqual(body["UserInfo"]["email"], self.user.email) + self.assertEqual( + body.get("IDTokenClaims", {}).get("amr"), + ["pwd"], + f"IDTokenClaims.amr mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + body.get("IDTokenClaims", {}).get("name"), + self.user.name, + f"IDTokenClaims.name mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + body.get("IDTokenClaims", {}).get("email"), + self.user.email, + f"IDTokenClaims.email mismatch at {self.driver.current_url}: {snippet}", + ) + + # UserInfo assertions + self.assertEqual( + body.get("UserInfo", {}).get("nickname"), + self.user.username, + f"UserInfo.nickname mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + body.get("UserInfo", {}).get("name"), + self.user.name, + f"UserInfo.name mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + body.get("UserInfo", {}).get("email"), + self.user.email, + f"UserInfo.email mismatch at {self.driver.current_url}: {snippet}", + ) @retry() @apply_blueprint( @@ -227,25 +265,56 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): self.assertIn( app.name, - consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + consent_stage.find_element(By.CSS_SELECTOR, "[data-test-id='stage-heading']").text, ) + + current_url = self.driver.current_url + consent_stage.find_element( By.CSS_SELECTOR, "[type=submit]", ).click() - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "pre"))) - self.wait.until(ec.text_to_be_present_in_element((By.CSS_SELECTOR, "pre"), "{")) - body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) + self.wait.until(ec.url_changes(current_url)) - self.assertEqual(body["IDTokenClaims"]["nickname"], self.user.username) - self.assertEqual(body["UserInfo"]["nickname"], self.user.username) + body = self.parse_json_content() + snippet = dumps(body, indent=2)[:500].replace("\n", " ") - self.assertEqual(body["IDTokenClaims"]["name"], self.user.name) - self.assertEqual(body["UserInfo"]["name"], self.user.name) + id_token_claims = body.get("IDTokenClaims", {}) + user_info = body.get("UserInfo", {}) - self.assertEqual(body["IDTokenClaims"]["email"], self.user.email) - self.assertEqual(body["UserInfo"]["email"], self.user.email) + self.assertEqual( + id_token_claims.get("nickname"), + self.user.username, + f"IDTokenClaims.nickname mismatch at {self.driver.current_url}: {snippet}", + ) + self.assertEqual( + user_info.get("nickname"), + self.user.username, + f"UserInfo.nickname mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + id_token_claims.get("name"), + self.user.name, + f"IDTokenClaims.name mismatch at {self.driver.current_url}: {snippet}", + ) + self.assertEqual( + user_info.get("name"), + self.user.name, + f"UserInfo.name mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + id_token_claims.get("email"), + self.user.email, + f"IDTokenClaims.email mismatch at {self.driver.current_url}: {snippet}", + ) + self.assertEqual( + user_info.get("email"), + self.user.email, + f"UserInfo.email mismatch at {self.driver.current_url}: {snippet}", + ) @retry() @apply_blueprint( @@ -297,8 +366,10 @@ class TestProviderOAuth2OIDC(SeleniumTestCase): self.setup_client() self.driver.get("http://localhost:9009") self.login() - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "header > h1"))) + self.wait.until( + ec.presence_of_element_located((By.CSS_SELECTOR, "[data-test-id='card-title']")) + ) self.assertEqual( - self.driver.find_element(By.CSS_SELECTOR, "header > h1").text, + self.driver.find_element(By.CSS_SELECTOR, "[data-test-id='card-title']").text, "Permission denied", ) diff --git a/tests/e2e/test_provider_oidc_implicit.py b/tests/e2e/test_provider_oidc_implicit.py index 3856fb81fe..d3bf5844a7 100644 --- a/tests/e2e/test_provider_oidc_implicit.py +++ b/tests/e2e/test_provider_oidc_implicit.py @@ -1,6 +1,6 @@ """test OAuth2 OpenID Provider flow""" -from json import loads +from json import dumps from time import sleep from selenium.webdriver.common.by import By @@ -149,12 +149,29 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): self.driver.get("http://localhost:9009/implicit/") self.wait.until(ec.title_contains("authentik")) self.login() - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "pre"))) - self.wait.until(ec.text_to_be_present_in_element((By.CSS_SELECTOR, "pre"), "{")) - body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) - self.assertEqual(body["profile"]["nickname"], self.user.username) - self.assertEqual(body["profile"]["name"], self.user.name) - self.assertEqual(body["profile"]["email"], self.user.email) + + body = self.parse_json_content() + snippet = dumps(body, indent=2)[:500].replace("\n", " ") + + profile = body.get("profile", {}) + + self.assertEqual( + profile.get("nickname"), + self.user.username, + f"Nickname mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + profile.get("name"), + self.user.name, + f"Name mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + profile.get("email"), + self.user.email, + f"Email mismatch at {self.driver.current_url}: {snippet}", + ) @retry() @apply_blueprint( @@ -211,20 +228,40 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): self.assertIn( app.name, - consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + consent_stage.find_element(By.CSS_SELECTOR, "[data-test-id='stage-heading']").text, ) + + current_url = self.driver.current_url + consent_stage.find_element( By.CSS_SELECTOR, "[type=submit]", ).click() - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "pre"))) - self.wait.until(ec.text_to_be_present_in_element((By.CSS_SELECTOR, "pre"), "{")) - body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) + self.wait.until(ec.url_changes(current_url)) - self.assertEqual(body["profile"]["nickname"], self.user.username) - self.assertEqual(body["profile"]["name"], self.user.name) - self.assertEqual(body["profile"]["email"], self.user.email) + body = self.parse_json_content() + snippet = dumps(body, indent=2)[:500].replace("\n", " ") + + profile = body.get("profile", {}) + + self.assertEqual( + profile.get("nickname"), + self.user.username, + f"Nickname mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + profile.get("name"), + self.user.name, + f"Name mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + profile.get("email"), + self.user.email, + f"Email mismatch at {self.driver.current_url}: {snippet}", + ) @retry() @apply_blueprint( @@ -278,8 +315,10 @@ class TestProviderOAuth2OIDCImplicit(SeleniumTestCase): self.driver.get("http://localhost:9009/implicit/") self.wait.until(ec.title_contains("authentik")) self.login() - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "header > h1"))) + self.wait.until( + ec.presence_of_element_located((By.CSS_SELECTOR, "[data-test-id='card-title']")) + ) self.assertEqual( - self.driver.find_element(By.CSS_SELECTOR, "header > h1").text, + self.driver.find_element(By.CSS_SELECTOR, "[data-test-id='card-title']").text, "Permission denied", ) diff --git a/tests/e2e/test_provider_proxy.py b/tests/e2e/test_provider_proxy.py index 186cb48f87..12b76ffb6d 100644 --- a/tests/e2e/test_provider_proxy.py +++ b/tests/e2e/test_provider_proxy.py @@ -2,7 +2,7 @@ from base64 import b64encode from dataclasses import asdict -from json import loads +from json import dumps from sys import platform from time import sleep from unittest.case import skip, skipUnless @@ -94,24 +94,39 @@ class TestProviderProxy(SeleniumTestCase): self.login() sleep(1) - full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text - body = loads(full_body_text) + body = self.parse_json_content() + headers = body.get("headers", {}) + snippet = dumps(body, indent=2)[:500].replace("\n", " ") - self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) - self.assertEqual(body["headers"]["X-Foo"], ["bar"]) - raw_jwt: str = body["headers"]["X-Authentik-Jwt"][0] + self.assertEqual( + headers.get("X-Authentik-Username"), + [self.user.username], + f"X-Authentik-Username header mismatch at {self.driver.current_url}: {snippet}", + ) + self.assertEqual( + headers.get("X-Foo"), + ["bar"], + f"X-Foo header mismatch at {self.driver.current_url}: {snippet}", + ) + raw_jwt: str = headers.get("X-Authentik-Jwt", [None])[0] jwt = decode(raw_jwt, options={"verify_signature": False}) - self.assertIsNotNone(jwt["sid"]) - self.assertIsNotNone(jwt["ak_proxy"]) + self.assertIsNotNone(jwt["sid"], "Missing 'sid' in JWT") + self.assertIsNotNone(jwt["ak_proxy"], "Missing 'ak_proxy' in JWT") self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out") sleep(2) + flow_executor = self.get_shadow_root("ak-flow-executor") session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) flow_card = self.get_shadow_root("ak-flow-card", session_end_stage) title = flow_card.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text - self.assertIn("You've logged out of", title) + + self.assertIn( + "You've logged out of", + title, + f"Logout title mismatch at {self.driver.current_url}: {title}", + ) @retry() @apply_blueprint( @@ -167,20 +182,37 @@ class TestProviderProxy(SeleniumTestCase): self.login() sleep(1) - full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text - body = loads(full_body_text) + body = self.parse_json_content() + headers = body.get("headers", {}) + snippet = dumps(body, indent=2)[:500].replace("\n", " ") + + self.assertEqual( + headers.get("X-Authentik-Username"), + [self.user.username], + f"X-Authentik-Username header mismatch at {self.driver.current_url}: {snippet}", + ) - self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) auth_header = b64encode(f"{cred}:{cred}".encode()).decode() - self.assertEqual(body["headers"]["Authorization"], [f"Basic {auth_header}"]) + + self.assertEqual( + headers.get("Authorization"), + [f"Basic {auth_header}"], + f"Authorization header mismatch at {self.driver.current_url}: {snippet}", + ) self.driver.get("http://localhost:9000/outpost.goauthentik.io/sign_out") sleep(2) + flow_executor = self.get_shadow_root("ak-flow-executor") session_end_stage = self.get_shadow_root("ak-stage-session-end", flow_executor) flow_card = self.get_shadow_root("ak-flow-card", session_end_stage) title = flow_card.find_element(By.CSS_SELECTOR, ".pf-c-title.pf-m-3xl").text - self.assertIn("You've logged out of", title) + + self.assertIn( + "You've logged out of", + title, + f"Logout title mismatch at {self.driver.current_url}: {title}", + ) # TODO: Fix flaky test diff --git a/tests/e2e/test_provider_proxy_forward.py b/tests/e2e/test_provider_proxy_forward.py index 757454d5a4..1d96496a0a 100644 --- a/tests/e2e/test_provider_proxy_forward.py +++ b/tests/e2e/test_provider_proxy_forward.py @@ -1,6 +1,6 @@ """Proxy and Outpost e2e tests""" -from json import loads +from json import dumps from pathlib import Path from time import sleep from unittest import skip @@ -101,10 +101,14 @@ class TestProviderProxyForward(SeleniumTestCase): self.login() sleep(1) - full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text - body = loads(full_body_text) + body_json = self.parse_json_content() + snippet = dumps(body_json, indent=2)[:500].replace("\n", " ") - self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) + self.assertEqual( + body_json.get("headers", {}).get("X-Authentik-Username"), + [self.user.username], + f"X-Authentik-Username header mismatch at {self.driver.current_url}: {snippet}", + ) self.driver.get("http://localhost/outpost.goauthentik.io/sign_out") sleep(2) @@ -137,10 +141,14 @@ class TestProviderProxyForward(SeleniumTestCase): self.login() sleep(1) - full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text - body = loads(full_body_text) + body_json = self.parse_json_content() + snippet = dumps(body_json, indent=2)[:500].replace("\n", " ") - self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) + self.assertEqual( + body_json.get("headers", {}).get("X-Authentik-Username"), + [self.user.username], + f"X-Authentik-Username header mismatch at {self.driver.current_url}: {snippet}", + ) self.driver.get("http://localhost/outpost.goauthentik.io/sign_out") sleep(2) @@ -171,10 +179,14 @@ class TestProviderProxyForward(SeleniumTestCase): self.login() sleep(1) - full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text - body = loads(full_body_text) + body_json = self.parse_json_content() + snippet = dumps(body_json, indent=2)[:500].replace("\n", " ") - self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) + self.assertEqual( + body_json.get("headers", {}).get("X-Authentik-Username"), + [self.user.username], + f"X-Authentik-Username header mismatch at {self.driver.current_url}: {snippet}", + ) self.driver.get("http://localhost/outpost.goauthentik.io/sign_out") sleep(2) @@ -208,10 +220,14 @@ class TestProviderProxyForward(SeleniumTestCase): self.login() sleep(1) - full_body_text = self.driver.find_element(By.CSS_SELECTOR, "pre").text - body = loads(full_body_text) + body_json = self.parse_json_content() + snippet = dumps(body_json, indent=2)[:500].replace("\n", " ") - self.assertEqual(body["headers"]["X-Authentik-Username"], [self.user.username]) + self.assertEqual( + body_json.get("headers", {}).get("X-Authentik-Username"), + [self.user.username], + f"X-Authentik-Username header mismatch at {self.driver.current_url}: {snippet}", + ) self.driver.get("http://localhost/outpost.goauthentik.io/sign_out") sleep(2) diff --git a/tests/e2e/test_provider_saml.py b/tests/e2e/test_provider_saml.py index 68a15d6e70..e7f48ed690 100644 --- a/tests/e2e/test_provider_saml.py +++ b/tests/e2e/test_provider_saml.py @@ -1,6 +1,6 @@ """test SAML Provider flow""" -from json import loads +from json import dumps from time import sleep from selenium.webdriver.common.by import By @@ -86,33 +86,44 @@ class TestProviderSAML(SeleniumTestCase): self.login() self.wait_for_url("http://localhost:9009/") - body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) + body = self.parse_json_content() + snippet = dumps(body, indent=2)[:500].replace("\n", " ") + attrs = body.get("attr", {}) self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"), [self.user.name], + f"Claim 'name' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"][ - "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" - ], + attrs.get("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"), [self.user.username], + f"Claim 'windowsaccountname' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/username"), [self.user.username], + f"Claim 'saml/username' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/uid"), [str(self.user.pk)], + f"Claim 'saml/uid' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"), [self.user.email], + f"Claim 'emailaddress' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"), [self.user.email], + f"Claim 'upn' mismatch at {self.driver.current_url}: {snippet}", ) @retry() @@ -154,33 +165,44 @@ class TestProviderSAML(SeleniumTestCase): self.login() self.wait_for_url("http://localhost:9009/") - body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) + body = self.parse_json_content() + snippet = dumps(body, indent=2)[:500].replace("\n", " ") + attrs = body.get("attr", {}) self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"), [self.user.name], + f"Claim 'name' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"][ - "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" - ], + attrs.get("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"), [self.user.username], + f"Claim 'windowsaccountname' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/username"), [self.user.username], + f"Claim 'saml/username' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/uid"), [str(self.user.pk)], + f"Claim 'saml/uid' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"), [self.user.email], + f"Claim 'emailaddress' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"), [self.user.email], + f"Claim 'upn' mismatch at {self.driver.current_url}: {snippet}", ) @retry() @@ -228,7 +250,8 @@ class TestProviderSAML(SeleniumTestCase): self.assertIn( app.name, - consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + consent_stage.find_element(By.CSS_SELECTOR, "[data-test-id='stage-heading']").text, + "Consent stage header mismatch", ) consent_stage.find_element( By.CSS_SELECTOR, @@ -237,33 +260,44 @@ class TestProviderSAML(SeleniumTestCase): self.wait_for_url("http://localhost:9009/") - body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) + body = self.parse_json_content() + snippet = dumps(body, indent=2)[:500].replace("\n", " ") + attrs = body.get("attr", {}) self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"), [self.user.name], + f"Claim 'name' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"][ - "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" - ], + attrs.get("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"), [self.user.username], + f"Claim 'windowsaccountname' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/username"), [self.user.username], + f"Claim 'saml/username' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/uid"), [str(self.user.pk)], + f"Claim 'saml/uid' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"), [self.user.email], + f"Claim 'emailaddress' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"), [self.user.email], + f"Claim 'upn' mismatch at {self.driver.current_url}: {snippet}", ) @retry() @@ -311,7 +345,7 @@ class TestProviderSAML(SeleniumTestCase): self.assertIn( app.name, - consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + consent_stage.find_element(By.CSS_SELECTOR, "[data-test-id='stage-heading']").text, ) consent_stage.find_element( By.CSS_SELECTOR, @@ -320,33 +354,44 @@ class TestProviderSAML(SeleniumTestCase): self.wait_for_url("http://localhost:9009/") - body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) + body = self.parse_json_content() + snippet = dumps(body, indent=2)[:500].replace("\n", " ") + attrs = body.get("attr", {}) self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"), [self.user.name], + f"Claim 'name' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"][ - "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" - ], + attrs.get("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"), [self.user.username], + f"Claim 'windowsaccountname' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/username"), [self.user.username], + f"Claim 'username' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/uid"), [str(self.user.pk)], + f"Claim 'uid' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"), [self.user.email], + f"Claim 'emailaddress' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"), [self.user.email], + f"Claim 'upn' mismatch at {self.driver.current_url}: {snippet}", ) @retry() @@ -394,33 +439,44 @@ class TestProviderSAML(SeleniumTestCase): sleep(1) self.wait_for_url("http://localhost:9009/") - body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) + body = self.parse_json_content() + snippet = dumps(body, indent=2)[:500].replace("\n", " ") + attrs = body.get("attr", {}) self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"), [self.user.name], + f"Claim 'name' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"][ - "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" - ], + attrs.get("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"), [self.user.username], + f"Claim 'windowsaccountname' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/username"), [self.user.username], + f"Claim 'username' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/uid"), [str(self.user.pk)], + f"Claim 'uid' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"), [self.user.email], + f"Claim 'emailaddress' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"), [self.user.email], + f"Claim 'upn' mismatch at {self.driver.current_url}: {snippet}", ) @retry() @@ -465,9 +521,12 @@ class TestProviderSAML(SeleniumTestCase): self.driver.get("http://localhost:9009/") self.login() - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "header > h1"))) + self.wait.until( + ec.presence_of_element_located((By.CSS_SELECTOR, "[data-test-id='card-title']")) + ) + self.assertEqual( - self.driver.find_element(By.CSS_SELECTOR, "header > h1").text, + self.driver.find_element(By.CSS_SELECTOR, "[data-test-id='card-title']").text, "Permission denied", ) @@ -589,31 +648,42 @@ class TestProviderSAML(SeleniumTestCase): self.wait_for_url(f"http://{self.host}:9009/") - body = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) + body = self.parse_json_content() + snippet = dumps(body, indent=2)[:500].replace("\n", " ") + attrs = body.get("attr", {}) self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"), [self.user.name], + f"Claim 'name' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"][ - "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" - ], + attrs.get("http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"), [self.user.username], + f"Claim 'windowsaccountname' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/username"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/username"), [self.user.username], + f"Claim 'username' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.goauthentik.io/2021/02/saml/uid"], + attrs.get("http://schemas.goauthentik.io/2021/02/saml/uid"), [str(self.user.pk)], + f"Claim 'uid' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"), [self.user.email], + f"Claim 'emailaddress' mismatch at {self.driver.current_url}: {snippet}", ) + self.assertEqual( - body["attr"]["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"], + attrs.get("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"), [self.user.email], + f"Claim 'upn' mismatch at {self.driver.current_url}: {snippet}", ) diff --git a/tests/e2e/test_source_oauth_oauth1.py b/tests/e2e/test_source_oauth_oauth1.py index 6236d1a374..012c485f3b 100644 --- a/tests/e2e/test_source_oauth_oauth1.py +++ b/tests/e2e/test_source_oauth_oauth1.py @@ -109,11 +109,11 @@ class TestSourceOAuth1(SeleniumTestCase): wait.until( ec.presence_of_element_located( - (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") + (By.CSS_SELECTOR, "fieldset[name='login-sources'] button") ) ) identification_stage.find_element( - By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" + By.CSS_SELECTOR, "fieldset[name='login-sources'] button" ).click() # Now we should be at the IDP, wait for the login field diff --git a/tests/e2e/test_source_oauth_oauth2.py b/tests/e2e/test_source_oauth_oauth2.py index 3766c23d48..bef591742b 100644 --- a/tests/e2e/test_source_oauth_oauth2.py +++ b/tests/e2e/test_source_oauth_oauth2.py @@ -1,6 +1,5 @@ """test OAuth Source""" -from json import loads from pathlib import Path from time import sleep @@ -16,7 +15,10 @@ from authentik.flows.models import Flow from authentik.lib.generators import generate_id from authentik.sources.oauth.models import OAuthSource from authentik.stages.identification.models import IdentificationStage -from tests.e2e.utils import SeleniumTestCase, retry +from tests.e2e.utils import NoSuchElementException, SeleniumTestCase, TimeoutException, retry + +MAX_REFRESH_RETRIES = 5 +INTERFACE_TIMEOUT = 10 class TestSourceOAuth2(SeleniumTestCase): @@ -27,7 +29,7 @@ class TestSourceOAuth2(SeleniumTestCase): self.slug = generate_id() super().setUp() self.run_container( - image="ghcr.io/dexidp/dex:v2.28.1", + image="ghcr.io/dexidp/dex:v2.44.0", ports={"5556": "5556"}, healthcheck=Healthcheck( test=["CMD", "wget", "--spider", "http://localhost:5556/dex/healthz"], @@ -35,6 +37,7 @@ class TestSourceOAuth2(SeleniumTestCase): start_period=1 * 1_000 * 1_000_000, ), environment={ + "AK_HOST": self.host, "AK_REDIRECT_URL": self.url( "authentik_sources_oauth:oauth-client-callback", source_slug=self.slug, @@ -48,6 +51,69 @@ class TestSourceOAuth2(SeleniumTestCase): }, ) + def find_settings_tab_panel(self, tab_name: str, panel_content_selector: str): + """Find a settings tab panel by name""" + url_after_login = self.driver.current_url + + user_settings_url = self.if_user_url("/settings") + hash_route = ';%7B"page"%3A"page-' + tab_name + '"%7D' + + self.driver.get(user_settings_url + hash_route) + + # A refresh is required because the hash change doesn't always trigger a reload. + self.driver.refresh() + + try: + self.wait.until(ec.url_contains(user_settings_url)) + except TimeoutException: + self.fail( + f"Timed out waiting for user settings page" + f"Initial URL after OAuth linking: {url_after_login} " + f"Current URL: {self.driver.current_url} " + f"Expected URL: {user_settings_url})" + ) + + try: + self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-interface-user"))) + except TimeoutException: + context = self.driver.find_element(By.TAG_NAME, "body") + inner_html = context.get_attribute("innerHTML") or "" + + snippet = context.text.strip()[:1000].replace("\n", " ") + + self.fail( + f"Timed out waiting for element text to appear at {self.driver.current_url}. " + f"Current content: {snippet or ''}" + f"{inner_html or ''}" + ) + + interface = self.driver.find_element(By.CSS_SELECTOR, "ak-interface-user").shadow_root + + interface_wait = WebDriverWait(interface, INTERFACE_TIMEOUT) + + try: + interface_wait.until( + ec.presence_of_element_located((By.CSS_SELECTOR, "ak-interface-user-presentation")) + ) + except TimeoutException: + snippet = context.text.strip()[:1000].replace("\n", " ") + self.fail( + f"Timed out waiting for element text to appear at {self.driver.current_url}. " + f"Current content: {snippet or ''}" + ) + + interface_presentation = interface.find_element( + By.CSS_SELECTOR, "ak-interface-user-presentation" + ).shadow_root + + user_settings = interface_presentation.find_element( + By.CSS_SELECTOR, "ak-user-settings" + ).shadow_root + + tab_panel = user_settings.find_element(By.CSS_SELECTOR, panel_content_selector).shadow_root + + return tab_panel + def create_objects(self): """Create required objects""" # Bootstrap all needed objects @@ -60,9 +126,9 @@ class TestSourceOAuth2(SeleniumTestCase): authentication_flow=authentication_flow, enrollment_flow=enrollment_flow, provider_type="openidconnect", - authorization_url="http://127.0.0.1:5556/dex/auth", - access_token_url="http://127.0.0.1:5556/dex/token", - profile_url="http://127.0.0.1:5556/dex/userinfo", + authorization_url=f"http://{self.host}:5556/dex/auth", + access_token_url=f"http://{self.host}:5556/dex/token", + profile_url=f"http://{self.host}:5556/dex/userinfo", consumer_key="example-app", consumer_secret=self.client_secret, ) @@ -70,6 +136,28 @@ class TestSourceOAuth2(SeleniumTestCase): ident_stage.sources.set([source]) ident_stage.save() + def login_via_oauth_provider(self): + """Perform login at the OAuth provider (Dex)""" + self.wait.until(ec.presence_of_element_located((By.ID, "login"))) + + initial_provider_url = self.driver.current_url + + self.driver.find_element(By.ID, "login").send_keys("admin@example.com") + self.driver.find_element(By.ID, "password").send_keys("password") + self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) + + self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "button[type=submit]"))) + + self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click() + + self.wait.until(ec.url_changes(initial_provider_url)) + + self.assertNotEqual( + initial_provider_url, + self.driver.current_url, + "Expected to be redirected after login at OAuth provider", + ) + @retry() @apply_blueprint( "default/flow-default-authentication-flow.yaml", @@ -91,22 +179,14 @@ class TestSourceOAuth2(SeleniumTestCase): wait.until( ec.presence_of_element_located( - (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") + (By.CSS_SELECTOR, "fieldset[name='login-sources'] button") ) ) identification_stage.find_element( - By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" + By.CSS_SELECTOR, "fieldset[name='login-sources'] button" ).click() - # Now we should be at the IDP, wait for the login field - self.wait.until(ec.presence_of_element_located((By.ID, "login"))) - self.driver.find_element(By.ID, "login").send_keys("admin@example.com") - self.driver.find_element(By.ID, "password").send_keys("password") - self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) - - # Wait until we're logged in - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "button[type=submit]"))) - self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click() + self.login_via_oauth_provider() # At this point we've been redirected back # and we're asked for the username @@ -135,25 +215,16 @@ class TestSourceOAuth2(SeleniumTestCase): wait.until( ec.presence_of_element_located( - (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") + (By.CSS_SELECTOR, "fieldset[name='login-sources'] button") ) ) identification_stage.find_element( - By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" + By.CSS_SELECTOR, "fieldset[name='login-sources'] button" ).click() - # Now we should be at the IDP, wait for the login field - self.wait.until(ec.presence_of_element_located((By.ID, "login"))) - self.driver.find_element(By.ID, "login").send_keys("admin@example.com") - self.driver.find_element(By.ID, "password").send_keys("password") - self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) + self.login_via_oauth_provider() - # Wait until we're logged in - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "button[type=submit]"))) - self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click() - - # Wait until we've logged in - self.wait_for_url(self.if_user_url()) + self.wait.until(ec.url_matches(self.if_user_url())) self.assert_user(User(username="foo", name="admin", email="admin@example.com")) @@ -167,30 +238,93 @@ class TestSourceOAuth2(SeleniumTestCase): "default/flow-default-source-enrollment.yaml", "default/flow-default-source-pre-authentication.yaml", ) - def test_oauth_link(self): - """test OAuth Source link OIDC""" + def test_oauth_link(self) -> None: + """ + Test OAuth Source link OIDC + + This test will enroll the user via OAuth, then log in as admin and link the OAuth + source to the admin user. + """ self.create_objects() self.driver.get(self.live_server_url) self.login() + # Ensure that a stable session is created before linking. + sleep(3) + self.driver.get( self.url("authentik_sources_oauth:oauth-client-login", source_slug=self.slug) ) - # Now we should be at the IDP, wait for the login field - self.wait.until(ec.presence_of_element_located((By.ID, "login"))) - self.driver.find_element(By.ID, "login").send_keys("admin@example.com") - self.driver.find_element(By.ID, "password").send_keys("password") - self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) + self.login_via_oauth_provider() - # Wait until we're logged in - self.wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "button[type=submit]"))) - self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click() + post_login_expected_url = self.if_user_url("/settings;page-sources") - self.driver.get(self.url("authentik_api:usersourceconnection-list") + "?format=json") - body_json = loads(self.driver.find_element(By.CSS_SELECTOR, "pre").text) - results = body_json["results"] - self.assertEqual(len(results), 1) - connection = results[0] - self.assertEqual(connection["source_obj"]["slug"], self.slug) - self.assertEqual(connection["user"], self.user.pk) + self.assertEqual( + self.driver.current_url, + post_login_expected_url, + "Expected to be redirected to user settings after linking OAuth source", + ) + + selector = f"[data-test-id=source-settings-list-item][data-slug='{self.slug}']" + sourceElement = None + + for attempt in range(MAX_REFRESH_RETRIES): + source_settings_tab_panel = self.find_settings_tab_panel( + "sources", "ak-user-settings-source" + ) + + try: + sourceElement = source_settings_tab_panel.find_element(By.CSS_SELECTOR, selector) + except NoSuchElementException: + sourceElement = None + + if sourceElement: + break + + if attempt < MAX_REFRESH_RETRIES - 1: + self.logger.debug( + f"[Attempt {attempt + 1}/{MAX_REFRESH_RETRIES}] No results yet, sleeping 1s… " + f"(Current URL: {self.driver.current_url})" + ) + + sleep(1) + + if not sourceElement: + context = self.driver.find_element(By.TAG_NAME, "body") + inner_html = context.get_attribute("innerHTML") or "" + + snippet = context.text.strip()[:1000].replace("\n", " ") + + self.fail( + f"Selector '{selector}' not found at {self.driver.current_url}" + f" after {MAX_REFRESH_RETRIES} retries. " + f"Current content: {snippet or ''}" + f"{inner_html or ''}" + ) + + data_source_component_attribute = sourceElement.get_attribute("data-source-component") + + self.assertIsNotNone( + data_source_component_attribute, + f"Source Component not found in source element at {self.driver.current_url}", + ) + + self.assertEqual( + data_source_component_attribute, + "ak-user-settings-source-oauth", + "Unexpected source component", + ) + + connection_user_pk_attribute = sourceElement.get_attribute("data-connection-user-pk") + + self.assertIsNotNone( + connection_user_pk_attribute, + f"Connection User PK not found in source element at {self.driver.current_url}", + ) + + self.assertEqual( + int(connection_user_pk_attribute), + self.user.pk, + f"Unexpected user {self.driver.current_url}", + ) diff --git a/tests/e2e/test_source_saml.py b/tests/e2e/test_source_saml.py index da01fd2ab2..0294431c3d 100644 --- a/tests/e2e/test_source_saml.py +++ b/tests/e2e/test_source_saml.py @@ -104,6 +104,15 @@ class TestSourceSAML(SeleniumTestCase): }, ) + def login_via_saml_source(self): + """Perform login at the SAML IDP""" + self.wait.until(ec.presence_of_element_located((By.ID, "username"))) + self.driver.find_element(By.ID, "username").send_keys("user1") + self.driver.find_element(By.ID, "password").send_keys("user1pass") + self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) + + self.wait_for_url(self.if_user_url()) + @retry() @apply_blueprint( "default/flow-default-authentication-flow.yaml", @@ -149,21 +158,14 @@ class TestSourceSAML(SeleniumTestCase): wait.until( ec.presence_of_element_located( - (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") + (By.CSS_SELECTOR, "fieldset[name='login-sources'] button") ) ) identification_stage.find_element( - By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" + By.CSS_SELECTOR, "fieldset[name='login-sources'] button" ).click() - # Now we should be at the IDP, wait for the username field - self.wait.until(ec.presence_of_element_located((By.ID, "username"))) - self.driver.find_element(By.ID, "username").send_keys("user1") - self.driver.find_element(By.ID, "password").send_keys("user1pass") - self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) - - # Wait until we're logged in - self.wait_for_url(self.if_user_url()) + self.login_via_saml_source() self.assert_user( User.objects.exclude(username="akadmin") @@ -218,11 +220,11 @@ class TestSourceSAML(SeleniumTestCase): wait.until( ec.presence_of_element_located( - (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") + (By.CSS_SELECTOR, "fieldset[name='login-sources'] button") ) ) identification_stage.find_element( - By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" + By.CSS_SELECTOR, "fieldset[name='login-sources'] button" ).click() sleep(1) @@ -231,21 +233,14 @@ class TestSourceSAML(SeleniumTestCase): self.assertIn( source.name, - consent_stage.find_element(By.CSS_SELECTOR, "#header-text").text, + consent_stage.find_element(By.CSS_SELECTOR, "[data-test-id='stage-heading']").text, ) consent_stage.find_element( By.CSS_SELECTOR, "[type=submit]", ).click() - # Now we should be at the IDP, wait for the username field - self.wait.until(ec.presence_of_element_located((By.ID, "username"))) - self.driver.find_element(By.ID, "username").send_keys("user1") - self.driver.find_element(By.ID, "password").send_keys("user1pass") - self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) - - # Wait until we're logged in - self.wait_for_url(self.if_user_url()) + self.login_via_saml_source() self.assert_user( User.objects.exclude(username="akadmin") @@ -300,21 +295,14 @@ class TestSourceSAML(SeleniumTestCase): wait.until( ec.presence_of_element_located( - (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") + (By.CSS_SELECTOR, "fieldset[name='login-sources'] button") ) ) identification_stage.find_element( - By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" + By.CSS_SELECTOR, "fieldset[name='login-sources'] button" ).click() - # Now we should be at the IDP, wait for the username field - self.wait.until(ec.presence_of_element_located((By.ID, "username"))) - self.driver.find_element(By.ID, "username").send_keys("user1") - self.driver.find_element(By.ID, "password").send_keys("user1pass") - self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) - - # Wait until we're logged in - self.wait_for_url(self.if_user_url()) + self.login_via_saml_source() self.assert_user( User.objects.exclude(username="akadmin") @@ -369,21 +357,14 @@ class TestSourceSAML(SeleniumTestCase): wait.until( ec.presence_of_element_located( - (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") + (By.CSS_SELECTOR, "fieldset[name='login-sources'] button") ) ) identification_stage.find_element( - By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" + By.CSS_SELECTOR, "fieldset[name='login-sources'] button" ).click() - # Now we should be at the IDP, wait for the username field - self.wait.until(ec.presence_of_element_located((By.ID, "username"))) - self.driver.find_element(By.ID, "username").send_keys("user1") - self.driver.find_element(By.ID, "password").send_keys("user1pass") - self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) - - # Wait until we're logged in - self.wait_for_url(self.if_user_url()) + self.login_via_saml_source() self.assert_user( User.objects.exclude(username="akadmin") @@ -403,21 +384,14 @@ class TestSourceSAML(SeleniumTestCase): wait.until( ec.presence_of_element_located( - (By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button") + (By.CSS_SELECTOR, "fieldset[name='login-sources'] button") ) ) identification_stage.find_element( - By.CSS_SELECTOR, ".pf-c-login__main-footer-links-item > button" + By.CSS_SELECTOR, "fieldset[name='login-sources'] button" ).click() - # Now we should be at the IDP, wait for the username field - self.wait.until(ec.presence_of_element_located((By.ID, "username"))) - self.driver.find_element(By.ID, "username").send_keys("user1") - self.driver.find_element(By.ID, "password").send_keys("user1pass") - self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER) - - # Wait until we're logged in - self.wait_for_url(self.if_user_url()) + self.login_via_saml_source() # sleep(999999) self.assert_user( diff --git a/tests/e2e/utils.py b/tests/e2e/utils.py index 35061853e0..b1f7561296 100644 --- a/tests/e2e/utils.py +++ b/tests/e2e/utils.py @@ -1,9 +1,9 @@ """authentik e2e testing utilities""" -import json import socket from collections.abc import Callable from functools import lru_cache, wraps +from json import JSONDecodeError, dumps, loads from os import environ, getenv from sys import stderr from time import sleep @@ -22,7 +22,12 @@ from docker.errors import DockerException from docker.models.containers import Container from docker.models.networks import Network from selenium import webdriver -from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException +from selenium.common.exceptions import ( + NoSuchElementException, + NoSuchShadowRootException, + TimeoutException, + WebDriverException, +) from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.remote.command import Command @@ -40,6 +45,9 @@ from authentik.root.test_runner import get_docker_tag IS_CI = "CI" in environ RETRIES = int(environ.get("RETRIES", "3")) if IS_CI else 1 +SHADOW_ROOT_RETRIES = 5 + +JSONType = dict[str, Any] | list[Any] | str | int | float | bool | None def get_local_ip() -> str: @@ -206,14 +214,16 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): print("::endgroup::") self.driver.quit() - def wait_for_url(self, desired_url): + def wait_for_url(self, desired_url: str): """Wait until URL is `desired_url`.""" + self.wait.until( lambda driver: driver.current_url == desired_url, - f"URL {self.driver.current_url} doesn't match expected URL {desired_url}", + f"URL {self.driver.current_url} doesn't match expected URL {desired_url}. " + f"HTML: {self.driver.page_source[:1000]}", ) - def url(self, view, query: dict | None = None, **kwargs) -> str: + def url(self, view: str, query: dict | None = None, **kwargs) -> str: """reverse `view` with `**kwargs` into full URL using live_server_url""" url = self.live_server_url + reverse(view, kwargs=kwargs) if query: @@ -227,14 +237,112 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): return f"{url}#{path}" return url + def parse_json_content( + self, context: WebElement | None = None, timeout: float | None = 10 + ) -> JSONType: + """ + Parse JSON from a Selenium element's text content. + + If `context` is not provided, defaults to the element. + Raises a clear test failure if the element isn't found, the text doesn't appear + within `timeout` seconds, or the text is not valid JSON. + """ + + try: + if context is None: + context = self.driver.find_element(By.TAG_NAME, "body") + except NoSuchElementException: + self.fail( + f"No element found (defaulted to ). Current URL: {self.driver.current_url}" + ) + + wait_timeout = timeout or self.wait_timeout + wait = WebDriverWait(context, wait_timeout) + + try: + wait.until(lambda d: len(d.text.strip()) != 0) + except TimeoutException: + snippet = context.text.strip()[:500].replace("\n", " ") + self.fail( + f"Timed out waiting for element text to appear at {self.driver.current_url}. " + f"Current content: {snippet or ''}" + ) + + body_text = context.text.strip() + inner_html = context.get_attribute("innerHTML") or "" + + if "redirecting" in inner_html.lower(): + try: + wait.until(lambda d: "redirecting" not in d.get_attribute("innerHTML").lower()) + except TimeoutException: + snippet = context.text.strip()[:500].replace("\n", " ") + inner_html = context.get_attribute("innerHTML") or "" + + self.fail( + f"Timed out waiting for redirect to finish at {self.driver.current_url}. " + f"Current content: {snippet or ''}" + f"{inner_html or ''}" + ) + + inner_html = context.get_attribute("innerHTML") or "" + body_text = context.text.strip() + + snippet = body_text[:500].replace("\n", " ") + + if not body_text.startswith("{") and not body_text.startswith("["): + self.fail( + f"Expected JSON content but got non-JSON text at {self.driver.current_url}: " + f"{snippet or ''}" + f"{inner_html or ''}" + ) + + try: + body_json = loads(body_text) + except JSONDecodeError as e: + self.fail( + f"Expected JSON but got invalid content at {self.driver.current_url}: " + f"{snippet or ''}" + f"{inner_html or ''}" + f"(JSON error: {e})" + ) + + return body_json + def get_shadow_root( - self, selector: str, container: WebElement | WebDriver | None = None + self, selector: str, container: WebElement | WebDriver | None = None, timeout: float = 10 ) -> WebElement: - """Get shadow root element's inner shadowRoot""" + """Get the shadow root of a web component specified by `selector`.""" if not container: container = self.driver - el = container.find_element(By.CSS_SELECTOR, selector) - return el.shadow_root + wait = WebDriverWait(container, timeout) + host: WebElement | None = None + + try: + host = wait.until(lambda c: c.find_element(By.CSS_SELECTOR, selector)) + except TimeoutException: + self.fail(f"Timed out waiting for shadow host {selector} to appear") + + attempts = 0 + + while attempts < SHADOW_ROOT_RETRIES: + try: + return host.shadow_root + except NoSuchShadowRootException: + attempts += 1 + sleep(0.2) + # re-find host in case it was re-attached + try: + host = container.find_element(By.CSS_SELECTOR, selector) + except NoSuchElementException: + # loop and retry finding host + pass + + inner_html = host.get_attribute("innerHTML") or "" + + raise RuntimeError( + f"Failed to obtain shadow root for {selector} after {attempts} attempts. " + f"Host innerHTML: {inner_html}" + ) def shady_dom(self) -> WebElement: class wrapper: @@ -249,7 +357,7 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): return wrapper(self.driver) def login(self, shadow_dom=True): - """Do entire login flow""" + """Perform the entire authentik login flow.""" if shadow_dom: flow_executor = self.get_shadow_root("ak-flow-executor") @@ -287,13 +395,42 @@ class SeleniumTestCase(DockerTestCase, StaticLiveServerTestCase): def assert_user(self, expected_user: User): """Check users/me API and assert it matches expected_user""" - self.driver.get(self.url("authentik_api:user-me") + "?format=json") - user_json = self.driver.find_element(By.CSS_SELECTOR, "pre").text - user = UserSerializer(data=json.loads(user_json)["user"]) + + expected_url = self.url("authentik_api:user-me") + "?format=json" + self.driver.get(expected_url) + + self.wait.until(lambda d: d.current_url == expected_url) + + user_json = self.parse_json_content() + data = user_json.get("user") + snippet = dumps(user_json, indent=2)[:500].replace("\n", " ") + + self.assertIsNotNone( + data, + f"Missing 'user' key in response at {self.driver.current_url}: {snippet}", + ) + + user = UserSerializer(data=data) + user.is_valid() - self.assertEqual(user["username"].value, expected_user.username) - self.assertEqual(user["name"].value, expected_user.name) - self.assertEqual(user["email"].value, expected_user.email) + + self.assertEqual( + user["username"].value, + expected_user.username, + f"Username mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + user["name"].value, + expected_user.name, + f"Name mismatch at {self.driver.current_url}: {snippet}", + ) + + self.assertEqual( + user["email"].value, + expected_user.email, + f"Email mismatch at {self.driver.current_url}: {snippet}", + ) @lru_cache diff --git a/web/.storybook/preview.js b/web/.storybook/preview.js index 999ac74356..6d7a3eb91d 100644 --- a/web/.storybook/preview.js +++ b/web/.storybook/preview.js @@ -4,7 +4,8 @@ * @import { Preview } from "@storybook/web-components"; */ -import "#common/styles/storybook.css"; +import "#styles/authentik/interface.global.css"; +import "#styles/authentik/storybook.css"; import { ThemedDocsContainer } from "./DocsContainer.tsx"; import { extendStorybookTheme } from "./theme.js"; @@ -18,6 +19,8 @@ import { const base = resolveUITheme(); const theme = extendStorybookTheme(base); +applyDocumentTheme(base); + createUIThemeEffect(applyDocumentTheme); /** diff --git a/web/bundler/mdx-plugin/node.js b/web/bundler/mdx-plugin/node.js index fe33b8f63e..0a0c619a94 100644 --- a/web/bundler/mdx-plugin/node.js +++ b/web/bundler/mdx-plugin/node.js @@ -1,6 +1,3 @@ -import * as fs from "node:fs/promises"; -import * as path from "node:path"; - /** * @file MDX plugin for ESBuild. * @@ -13,6 +10,10 @@ import * as path from "node:path"; * PluginBuild * } from "esbuild" */ + +import * as fs from "node:fs/promises"; +import * as path from "node:path"; + import { MonoRepoRoot } from "@goauthentik/core/paths/node"; /** diff --git a/web/bundler/style-loader-plugin/node.js b/web/bundler/style-loader-plugin/node.js new file mode 100644 index 0000000000..893c9a4fed --- /dev/null +++ b/web/bundler/style-loader-plugin/node.js @@ -0,0 +1,161 @@ +/** + * @file MDX plugin for ESBuild. + * + * @import { Plugin, PluginBuild, BuildContext } from "esbuild" + */ + +import { readFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; + +import { resolvePackage } from "@goauthentik/core/paths/node"; + +const CSSNamespace = /** @type {const} */ ({ + Global: "css-global", + Process: "css-process", + Bundled: "css-bundled", +}); + +/** + * Selectively apply the ESBuild `css` loader. + * + * @returns {Plugin} + */ +export function styleLoaderPlugin() { + const patternflyPath = resolvePackage("@patternfly/patternfly", import.meta); + const require = createRequire(import.meta.url); + + /** + * Apply custom resolution for Patternfly font files. + * + * This is necessary because Patternfly's CSS references fonts via relative paths + * that ESBuild cannot resolve automatically. + * @type {Parameters} + */ + const fontResolverArgs = [ + { filter: /\.woff2?$/ }, + async (args) => { + if (!args.resolveDir.startsWith(patternflyPath)) { + return; + } + + return { + path: join(patternflyPath, args.path), + }; + }, + ]; + + return { + name: "global-css-plugin", + setup(build) { + const { absWorkingDir = process.cwd() } = build.initialOptions; + + /** + * @type {Map} + */ + const disposables = new Map(); + + build.onDispose(async () => { + for (const ctx of disposables.values()) { + await ctx.dispose(); + } + }); + + build.onLoad({ filter: /patternfly-base.css/ }, () => { + return { + contents: "", + loader: "text", + }; + }); + + build.onResolve(...fontResolverArgs); + + /** + * Files which should be processed as CSS throughout ESBuild's loader chain. + */ + build.onResolve({ filter: /\.css$/ }, (args) => { + if (args.path.endsWith(".global.css") || args.namespace === CSSNamespace.Process) { + return { + path: require.resolve(args.path, { paths: [args.resolveDir] }), + namespace: CSSNamespace.Process, + }; + } + + /** + * Files imported via `with { type: "bundled-text" }` + */ + if (args.with.type === "bundled-text") { + return { + path: require.resolve(args.path, { paths: [args.resolveDir] }), + namespace: CSSNamespace.Bundled, + }; + } + }); + + /** + * Handle `.global.css` files + * + * Load as normal CSS... + */ + build.onLoad({ filter: /.*/, namespace: CSSNamespace.Process }, async (args) => { + return { + contents: await readFile(args.path, "utf8"), + loader: "css", + resolveDir: dirname(args.path), + }; + }); + + /** + * Handle `with { type: "bundled-text" }` imports. + * + * Bundle the CSS and return as text... + */ + build.onLoad({ filter: /.*/, namespace: CSSNamespace.Bundled }, async (args) => { + const cssContent = await readFile(args.path, "utf8"); + let context = disposables.get(args.path); + + if (!context) { + context = await build.esbuild.context({ + stdin: { + contents: cssContent, + resolveDir: dirname(args.path), + loader: "css", + }, + metafile: true, + bundle: true, + write: false, + minify: build.initialOptions.minify || false, + logLevel: "silent", + loader: { ".woff": "empty", ".woff2": "empty" }, + plugins: [ + { + name: "font-resolver", + setup(fontBuild) { + fontBuild.onResolve(...fontResolverArgs); + }, + }, + ], + }); + + disposables.set(args.path, context); + } + + await context.cancel(); + + // Resolve the CSS content by bundling it with ESBuild. + const result = await context.rebuild(); + + const bundledCSS = result.outputFiles?.[0].text; + const { inputs = {} } = result.metafile || {}; + const relativePaths = Object.keys(inputs).filter((path) => path !== ""); + const watchFiles = relativePaths.map((path) => join(absWorkingDir, path)); + + return { + contents: bundledCSS, + loader: "text", + watchFiles, + }; + }); + }, + }; +} diff --git a/web/package-lock.json b/web/package-lock.json index 2f121221bd..38450b4b07 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -44,7 +44,6 @@ "@patternfly/patternfly": "^4.224.2", "@playwright/test": "^1.56.1", "@sentry/browser": "^10.24.0", - "@spotlightjs/spotlight": "^4.5.1", "@storybook/addon-docs": "^10.0.6", "@storybook/addon-links": "^10.0.6", "@storybook/web-components": "^10.0.6", @@ -230,23 +229,6 @@ "swagger-client": "^3.32.2" } }, - "node_modules/@apm-js-collab/code-transformer": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.8.2.tgz", - "integrity": "sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA==", - "license": "Apache-2.0" - }, - "node_modules/@apm-js-collab/tracing-hooks": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.3.1.tgz", - "integrity": "sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==", - "license": "Apache-2.0", - "dependencies": { - "@apm-js-collab/code-transformer": "^0.8.0", - "debug": "^4.4.1", - "module-details-from-path": "^1.0.4" - } - }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1450,28 +1432,6 @@ "integrity": "sha512-bR8W/2kgdCt2IaQU7AeRA4TAZbFuEyiDqUbTlLqCbZqp3GyzrAKthACDJ6WQ2K9IWBQCuA2fPj9ikfp98+bvSA==", "license": "MIT" }, - "node_modules/@hono/mcp": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@hono/mcp/-/mcp-0.1.5.tgz", - "integrity": "sha512-q6Yurx9VUwVEpqnwVXtzIYaq4kgQgWWq9lYLM7NFS2W0sg1RzL+RdKh6jO4/dGyvBLKrahPd2v+NC6rr0XWBvQ==", - "license": "MIT", - "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.12.0", - "hono": ">=4.0.0" - } - }, - "node_modules/@hono/node-server": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.6.tgz", - "integrity": "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==", - "license": "MIT", - "engines": { - "node": ">=18.14.1" - }, - "peerDependencies": { - "hono": "^4" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1945,72 +1905,6 @@ "langium": "3.3.1" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.21.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.21.1.tgz", - "integrity": "sha512-UyLFcJLDvUuZbGnaQqXFT32CpPpGj7VS19roLut6gkQVhb439xUzYWbsUvdI3ZPL+2hnFosuugtYWE0Mcs1rmQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@cfworker/json-schema": "^4.1.1" - }, - "peerDependenciesMeta": { - "@cfworker/json-schema": { - "optional": true - } - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, "node_modules/@mrmarble/djangoql-completion": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/@mrmarble/djangoql-completion/-/djangoql-completion-0.8.3.tgz", @@ -2365,518 +2259,6 @@ "ol": "^7.5.0" } }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.204.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.204.0.tgz", - "integrity": "sha512-DqxY8yoAaiBPivoJD4UtgrMS8gEmzZ5lnaxzPojzLVHBGqPxgWm4zcuvcUHZiqQ6kRX2Klel2r9y8cA2HAtqpw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.2.0.tgz", - "integrity": "sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==", - "license": "Apache-2.0", - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", - "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/instrumentation": { - "version": "0.204.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.204.0.tgz", - "integrity": "sha512-vV5+WSxktzoMP8JoYWKeopChy6G3HKk4UQ2hESCRDUUTZqQ3+nM3u8noVG0LmNfRWwcFBnbZ71GKC7vaYYdJ1g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.204.0", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-amqplib": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.51.0.tgz", - "integrity": "sha512-XGmjYwjVRktD4agFnWBWQXo9SiYHKBxR6Ag3MLXwtLE4R99N3a08kGKM5SC1qOFKIELcQDGFEFT9ydXMH00Luw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-connect": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.48.0.tgz", - "integrity": "sha512-OMjc3SFL4pC16PeK+tDhwP7MRvDPalYCGSvGqUhX5rASkI2H0RuxZHOWElYeXkV0WP+70Gw6JHWac/2Zqwmhdw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/connect": "3.4.38" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-dataloader": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.22.0.tgz", - "integrity": "sha512-bXnTcwtngQsI1CvodFkTemrrRSQjAjZxqHVc+CJZTDnidT0T6wt3jkKhnsjU/Kkkc0lacr6VdRpCu2CUWa0OKw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-express": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.53.0.tgz", - "integrity": "sha512-r/PBafQmFYRjuxLYEHJ3ze1iBnP2GDA1nXOSS6E02KnYNZAVjj6WcDA1MSthtdAUUK0XnotHvvWM8/qz7DMO5A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-fs": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.24.0.tgz", - "integrity": "sha512-HjIxJ6CBRD770KNVaTdMXIv29Sjz4C1kPCCK5x1Ujpc6SNnLGPqUVyJYZ3LUhhnHAqdbrl83ogVWjCgeT4Q0yw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-generic-pool": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.48.0.tgz", - "integrity": "sha512-TLv/On8pufynNR+pUbpkyvuESVASZZKMlqCm4bBImTpXKTpqXaJJ3o/MUDeMlM91rpen+PEv2SeyOKcHCSlgag==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-graphql": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.52.0.tgz", - "integrity": "sha512-3fEJ8jOOMwopvldY16KuzHbRhPk8wSsOTSF0v2psmOCGewh6ad+ZbkTx/xyUK9rUdUMWAxRVU0tFpj4Wx1vkPA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-hapi": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.51.0.tgz", - "integrity": "sha512-qyf27DaFNL1Qhbo/da+04MSCw982B02FhuOS5/UF+PMhM61CcOiu7fPuXj8TvbqyReQuJFljXE6UirlvoT/62g==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-http": { - "version": "0.204.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.204.0.tgz", - "integrity": "sha512-1afJYyGRA4OmHTv0FfNTrTAzoEjPQUYgd+8ih/lX0LlZBnGio/O80vxA0lN3knsJPS7FiDrsDrWq25K7oAzbkw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.1.0", - "@opentelemetry/instrumentation": "0.204.0", - "@opentelemetry/semantic-conventions": "^1.29.0", - "forwarded-parse": "2.1.2" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.1.0.tgz", - "integrity": "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/instrumentation-ioredis": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.52.0.tgz", - "integrity": "sha512-rUvlyZwI90HRQPYicxpDGhT8setMrlHKokCtBtZgYxQWRF5RBbG4q0pGtbZvd7kyseuHbFpA3I/5z7M8b/5ywg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/redis-common": "^0.38.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-kafkajs": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.14.0.tgz", - "integrity": "sha512-kbB5yXS47dTIdO/lfbbXlzhvHFturbux4EpP0+6H78Lk0Bn4QXiZQW7rmZY1xBCY16mNcCb8Yt0mhz85hTnSVA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.30.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-knex": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.49.0.tgz", - "integrity": "sha512-NKsRRT27fbIYL4Ix+BjjP8h4YveyKc+2gD6DMZbr5R5rUeDqfC8+DTfIt3c3ex3BIc5Vvek4rqHnN7q34ZetLQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.33.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-koa": { - "version": "0.52.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.52.0.tgz", - "integrity": "sha512-JJSBYLDx/mNSy8Ibi/uQixu2rH0bZODJa8/cz04hEhRaiZQoeJ5UrOhO/mS87IdgVsHrnBOsZ6vDu09znupyuA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-lru-memoizer": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.49.0.tgz", - "integrity": "sha512-ctXu+O/1HSadAxtjoEg2w307Z5iPyLOMM8IRNwjaKrIpNAthYGSOanChbk1kqY6zU5CrpkPHGdAT6jk8dXiMqw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.57.0.tgz", - "integrity": "sha512-KD6Rg0KSHWDkik+qjIOWoksi1xqSpix8TSPfquIK1DTmd9OTFb5PHmMkzJe16TAPVEuElUW8gvgP59cacFcrMQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mongoose": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.51.0.tgz", - "integrity": "sha512-gwWaAlhhV2By7XcbyU3DOLMvzsgeaymwP/jktDC+/uPkCmgB61zurwqOQdeiRq9KAf22Y2dtE5ZLXxytJRbEVA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mysql": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.50.0.tgz", - "integrity": "sha512-duKAvMRI3vq6u9JwzIipY9zHfikN20bX05sL7GjDeLKr2qV0LQ4ADtKST7KStdGcQ+MTN5wghWbbVdLgNcB3rA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/mysql": "2.15.27" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-mysql2": { - "version": "0.51.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.51.0.tgz", - "integrity": "sha512-zT2Wg22Xn43RyfU3NOUmnFtb5zlDI0fKcijCj9AcK9zuLZ4ModgtLXOyBJSSfO+hsOCZSC1v/Fxwj+nZJFdzLQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@opentelemetry/sql-common": "^0.41.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-pg": { - "version": "0.57.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.57.0.tgz", - "integrity": "sha512-dWLGE+r5lBgm2A8SaaSYDE3OKJ/kwwy5WLyGyzor8PLhUL9VnJRiY6qhp4njwhnljiLtzeffRtG2Mf/YyWLeTw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.34.0", - "@opentelemetry/sql-common": "^0.41.0", - "@types/pg": "8.15.5", - "@types/pg-pool": "2.0.6" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-redis": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.53.0.tgz", - "integrity": "sha512-WUHV8fr+8yo5RmzyU7D5BIE1zwiaNQcTyZPwtxlfr7px6NYYx7IIpSihJK7WA60npWynfxxK1T67RAVF0Gdfjg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/redis-common": "^0.38.0", - "@opentelemetry/semantic-conventions": "^1.27.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-tedious": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.23.0.tgz", - "integrity": "sha512-3TMTk/9VtlRonVTaU4tCzbg4YqW+Iq/l5VnN2e5whP6JgEg/PKfrGbqQ+CxQWNLfLaQYIUgEZqAn5gk/inh1uQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/semantic-conventions": "^1.27.0", - "@types/tedious": "^4.0.14" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation-undici": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.15.0.tgz", - "integrity": "sha512-sNFGA/iCDlVkNjzTzPRcudmI11vT/WAfAguRdZY9IspCw02N4WSC72zTuQhSMheh2a1gdeM9my1imnKRvEEvEg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0", - "@opentelemetry/instrumentation": "^0.204.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.7.0" - } - }, - "node_modules/@opentelemetry/redis-common": { - "version": "0.38.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz", - "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==", - "license": "Apache-2.0", - "engines": { - "node": "^18.19.0 || >=20.6.0" - } - }, - "node_modules/@opentelemetry/resources": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", - "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.2.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", - "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.2.0", - "@opentelemetry/resources": "2.2.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", - "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sql-common": { - "version": "0.41.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", - "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "^2.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0" - } - }, "node_modules/@oxc-resolver/binding-android-arm-eabi": { "version": "11.8.4", "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.8.4.tgz", @@ -3226,50 +2608,6 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@prisma/instrumentation": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.15.0.tgz", - "integrity": "sha512-6TXaH6OmDkMOQvOxwLZ8XS51hU2v4A3vmE2pSijCIiGRJYyNeMcL6nMHQMyYdZRD8wl7LF3Wzc+AMPMV/9Oo7A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.8" - } - }, - "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.57.2.tgz", - "integrity": "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.57.2.tgz", - "integrity": "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.57.2", - "@types/shimmer": "^1.2.0", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, "node_modules/@rollup/plugin-commonjs": { "version": "29.0.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.0.tgz", @@ -3742,95 +3080,6 @@ "node": ">=18" } }, - "node_modules/@sentry/node": { - "version": "10.24.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.24.0.tgz", - "integrity": "sha512-OsyMzemG+a1QHe9BXDduA0bL4r5dlViOpIocSL3atPNupYTxoSZqOP/wFwqTGE+M/2oIv0/VIIWoXJUd8BLUAg==", - "license": "MIT", - "dependencies": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.1.0", - "@opentelemetry/core": "^2.1.0", - "@opentelemetry/instrumentation": "^0.204.0", - "@opentelemetry/instrumentation-amqplib": "0.51.0", - "@opentelemetry/instrumentation-connect": "0.48.0", - "@opentelemetry/instrumentation-dataloader": "0.22.0", - "@opentelemetry/instrumentation-express": "0.53.0", - "@opentelemetry/instrumentation-fs": "0.24.0", - "@opentelemetry/instrumentation-generic-pool": "0.48.0", - "@opentelemetry/instrumentation-graphql": "0.52.0", - "@opentelemetry/instrumentation-hapi": "0.51.0", - "@opentelemetry/instrumentation-http": "0.204.0", - "@opentelemetry/instrumentation-ioredis": "0.52.0", - "@opentelemetry/instrumentation-kafkajs": "0.14.0", - "@opentelemetry/instrumentation-knex": "0.49.0", - "@opentelemetry/instrumentation-koa": "0.52.0", - "@opentelemetry/instrumentation-lru-memoizer": "0.49.0", - "@opentelemetry/instrumentation-mongodb": "0.57.0", - "@opentelemetry/instrumentation-mongoose": "0.51.0", - "@opentelemetry/instrumentation-mysql": "0.50.0", - "@opentelemetry/instrumentation-mysql2": "0.51.0", - "@opentelemetry/instrumentation-pg": "0.57.0", - "@opentelemetry/instrumentation-redis": "0.53.0", - "@opentelemetry/instrumentation-tedious": "0.23.0", - "@opentelemetry/instrumentation-undici": "0.15.0", - "@opentelemetry/resources": "^2.1.0", - "@opentelemetry/sdk-trace-base": "^2.1.0", - "@opentelemetry/semantic-conventions": "^1.37.0", - "@prisma/instrumentation": "6.15.0", - "@sentry/core": "10.24.0", - "@sentry/node-core": "10.24.0", - "@sentry/opentelemetry": "10.24.0", - "import-in-the-middle": "^1.14.2", - "minimatch": "^9.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@sentry/node-core": { - "version": "10.24.0", - "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.24.0.tgz", - "integrity": "sha512-OTvJSrPstEc0NydMDpdmyYeuOcOQxZ0ZT8rmdKkrw4odYs56pYS4euMHNler8Tw9j8mZxqyI/wjzl//xGI+F0w==", - "license": "MIT", - "dependencies": { - "@apm-js-collab/tracing-hooks": "^0.3.1", - "@sentry/core": "10.24.0", - "@sentry/opentelemetry": "10.24.0", - "import-in-the-middle": "^1.14.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", - "@opentelemetry/core": "^1.30.1 || ^2.1.0", - "@opentelemetry/instrumentation": ">=0.57.1 <1", - "@opentelemetry/resources": "^1.30.1 || ^2.1.0", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", - "@opentelemetry/semantic-conventions": "^1.37.0" - } - }, - "node_modules/@sentry/opentelemetry": { - "version": "10.24.0", - "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.24.0.tgz", - "integrity": "sha512-yOqeAUTnikx1eG8XMWvY4FWEU/aBp24sKlejxE0k7jmw5X2vCBd+4FUgDAwKsHwvEGOeD2XVfMqgLYjrNkm+Vg==", - "license": "MIT", - "dependencies": { - "@sentry/core": "10.24.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", - "@opentelemetry/core": "^1.30.1 || ^2.1.0", - "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", - "@opentelemetry/semantic-conventions": "^1.37.0" - } - }, "node_modules/@sindresorhus/is": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", @@ -3843,72 +3092,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@spotlightjs/overlay": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@spotlightjs/overlay/-/overlay-4.5.0.tgz", - "integrity": "sha512-Pn8qnVI68082y/RCuZXX6lUctMRFxH5v48IctbC8WogOCWZ3VSdZoKqCAcDQpafZwTgoGADGPp/vIeYyqfNCaQ==", - "license": "Apache-2.0" - }, - "node_modules/@spotlightjs/sidecar": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@spotlightjs/sidecar/-/sidecar-2.5.0.tgz", - "integrity": "sha512-KCkADnSMqt65LpfX99clXrUqG+ROPjbQfXxSLkZ5uPZ0t0NwOVd+CjYPH7siNl1CBQGCrLm0vgJ2USbaWzGk3g==", - "license": "Apache-2.0", - "dependencies": { - "@hono/mcp": "^0.1.5", - "@hono/node-server": "^1.19.3", - "@jridgewell/trace-mapping": "^0.3.25", - "@modelcontextprotocol/sdk": "^1.16.0", - "@sentry/core": "^10", - "@sentry/node": "^10.22.0", - "chalk": "^5.6.2", - "eventsource": "^4.0.0", - "fast-fuzzy": "^1.12.0", - "hono": "^4.10.3", - "launch-editor": "^2.9.1", - "logfmt": "^1.4.0", - "mcp-proxy": "^5.6.0", - "uuidv7": "^1.0.2", - "yaml": "^2.8.1", - "zod": "^3" - }, - "bin": { - "spotlight-sidecar": "dist/run.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@spotlightjs/sidecar/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@spotlightjs/spotlight": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@spotlightjs/spotlight/-/spotlight-4.5.1.tgz", - "integrity": "sha512-PtoYz7ov/owm2PBrV9DY11CTNkQWsC6eiWKOM0e+M40E1/AfgtlhMTYLFA4+qk1Ifh8AazDD0tcfPFUQuDZDxQ==", - "license": "Apache-2.0", - "dependencies": { - "@sentry/node": "^10.22.0", - "@spotlightjs/overlay": "4.5.0", - "@spotlightjs/sidecar": "2.5.0", - "import-meta-resolve": "^4.1.0" - }, - "bin": { - "spotlight": "dist/run.js" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/@storybook/addon-docs": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.0.6.tgz", @@ -5136,15 +4319,6 @@ "@types/tern": "*" } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -5519,15 +4693,6 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, - "node_modules/@types/mysql": { - "version": "2.15.27", - "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", - "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { "version": "24.10.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", @@ -5543,26 +4708,6 @@ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, - "node_modules/@types/pg": { - "version": "8.15.5", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.5.tgz", - "integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "pg-protocol": "*", - "pg-types": "^2.2.0" - } - }, - "node_modules/@types/pg-pool": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.6.tgz", - "integrity": "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==", - "license": "MIT", - "dependencies": { - "@types/pg": "*" - } - }, "node_modules/@types/ramda": { "version": "0.30.2", "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", @@ -5601,21 +4746,6 @@ "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", "license": "MIT" }, - "node_modules/@types/shimmer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", - "license": "MIT" - }, - "node_modules/@types/tedious": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", - "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/tern": { "version": "0.23.9", "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", @@ -6518,40 +5648,6 @@ "node": ">=10.0.0" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -6564,15 +5660,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -6607,45 +5694,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -7170,26 +6218,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/bootstrap": { "version": "5.3.8", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", @@ -7269,15 +6297,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -7546,12 +6565,6 @@ "node": ">=20" } }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "license": "MIT" - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -7733,33 +6746,6 @@ "node": ">= 0.6" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, "node_modules/core-js": { "version": "3.46.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz", @@ -7782,19 +6768,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", @@ -8613,15 +7586,6 @@ "node": ">=0.4.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -8753,21 +7717,6 @@ "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", "license": "ISC" }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -9206,12 +8155,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -9863,36 +8806,6 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-4.0.0.tgz", - "integrity": "sha512-fvIkb9qZzdMxgZrEQDyll+9oJsyaVvY92I2Re+qK0qEJ+w5s0X3dtz+M0VAPOjP1gtU3iqWyjQ0G3nvd5CLZ2g==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/expect-type": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", @@ -9902,96 +8815,6 @@ "node": ">=12.0.0" } }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/express/node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/exsolve": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", @@ -10081,15 +8904,6 @@ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" }, - "node_modules/fast-fuzzy": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/fast-fuzzy/-/fast-fuzzy-1.12.0.tgz", - "integrity": "sha512-sXxGgHS+ubYpsdLnvOvJ9w5GYYZrtL9mkosG3nfuD446ahvoWEsSKBP7ieGmWIKVLnaxRDgUJkZMdxRgA2Ni+Q==", - "license": "ISC", - "dependencies": { - "graphemesplit": "^2.4.1" - } - }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -10140,22 +8954,6 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -10301,23 +9099,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/find-free-ports": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/find-free-ports/-/find-free-ports-3.1.1.tgz", @@ -10528,30 +9309,6 @@ "node": ">=12.20.0" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/forwarded-parse": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", - "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", - "license": "MIT" - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -10874,16 +9631,6 @@ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, - "node_modules/graphemesplit": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/graphemesplit/-/graphemesplit-2.6.0.tgz", - "integrity": "sha512-rG9w2wAfkpg0DILa1pjnjNfucng3usON360shisqIMUBw/87pojcBSrHmeE4UwryAuBih7g8m1oilf5/u8EWdQ==", - "license": "MIT", - "dependencies": { - "js-base64": "^3.6.0", - "unicode-trie": "^2.0.0" - } - }, "node_modules/guacamole-common-js": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/guacamole-common-js/-/guacamole-common-js-1.5.0.tgz", @@ -11238,15 +9985,6 @@ "node": ">=12.0.0" } }, - "node_modules/hono": { - "version": "4.10.4", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.4.tgz", - "integrity": "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -11268,31 +10006,6 @@ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -11394,27 +10107,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-in-the-middle": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", - "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", - "license": "Apache-2.0", - "dependencies": { - "acorn": "^8.14.0", - "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^1.2.2", - "module-details-from-path": "^1.0.3" - } - }, - "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -11508,15 +10200,6 @@ "node": ">=8" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -11855,12 +10538,6 @@ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -12097,12 +10774,6 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, - "node_modules/js-base64": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", - "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", - "license": "BSD-3-Clause" - }, "node_modules/js-levenshtein-esm": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-2.0.0.tgz", @@ -12332,16 +11003,6 @@ "node": ">=16.0.0" } }, - "node_modules/launch-editor": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", - "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", - "license": "MIT", - "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" - } - }, "node_modules/layout-base": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", @@ -12510,19 +11171,6 @@ "node": ">=8.0" } }, - "node_modules/logfmt": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/logfmt/-/logfmt-1.4.0.tgz", - "integrity": "sha512-p1Ow0C2dDJYaQBhRHt+HVMP6ELuBm4jYSYNHPMfz0J5wJ9qA6/7oBOlBZBfT1InqguTYcvJzNea5FItDxTcbyw==", - "license": "MIT", - "dependencies": { - "split": "0.2.x", - "through": "2.3.x" - }, - "bin": { - "logfmt": "bin/logfmt" - } - }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -12659,15 +11307,6 @@ "node": ">= 0.4" } }, - "node_modules/mcp-proxy": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/mcp-proxy/-/mcp-proxy-5.10.0.tgz", - "integrity": "sha512-mbNKRWlh89gg8c659phTqCx6CyRseESzzvxRynFXkdRKoTLRWSn7PQKYfgGyw6j4gR2SxBod5IDGYjNSrF0UPw==", - "license": "MIT", - "bin": { - "mcp-proxy": "dist/bin/mcp-proxy.js" - } - }, "node_modules/md-front-matter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/md-front-matter/-/md-front-matter-1.0.4.tgz", @@ -13026,15 +11665,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -13043,18 +11673,6 @@ "node": ">= 0.10.0" } }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -14025,12 +12643,6 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" - }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -14083,15 +12695,6 @@ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/neotraverse": { "version": "0.6.18", "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", @@ -14501,18 +13104,6 @@ "node": ">=14.0.0" } }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -14713,12 +13304,6 @@ "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", "license": "MIT" }, - "node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "license": "MIT" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -14786,15 +13371,6 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/path-data-parser": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", @@ -14829,16 +13405,6 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/pathval": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", @@ -14879,37 +13445,6 @@ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "license": "ISC", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", - "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", - "license": "MIT" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "license": "MIT", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -15034,15 +13569,6 @@ "@napi-rs/nice": "^1.0.1" } }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, "node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", @@ -15153,45 +13679,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -15330,19 +13817,6 @@ "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", "license": "MIT" }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -15422,21 +13896,6 @@ "resolved": "https://registry.npmjs.org/qrjs/-/qrjs-0.2.0.tgz", "integrity": "sha512-6tOePfihDByEXDULYlT/FmV27m5rX6IehCeZ82LouBD5kzSNqNXuVog8m1KGuGNyQovVOb0nKOB2ybHRRRgKJw==" }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/quansync": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", @@ -15534,15 +13993,6 @@ "node": ">=4" } }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/rapidoc": { "version": "9.3.8", "resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.3.8.tgz", @@ -15573,37 +14023,6 @@ "node": ">= 12" } }, - "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.7.0", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/rbush": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", @@ -16112,29 +14531,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-in-the-middle": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", - "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -16390,22 +14786,6 @@ "points-on-path": "^0.2.1" } }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -16608,64 +14988,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/send/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/send/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -16710,12 +15032,6 @@ "node": ">= 0.4" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -16747,12 +15063,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", - "license": "BSD-2-Clause" - }, "node_modules/short-unique-id": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.2.2.tgz", @@ -17082,17 +15392,6 @@ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==" }, - "node_modules/split": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", - "integrity": "sha512-e0pKq+UUH2Xq/sXbYpZBZc3BawsfDZ7dgv+JtRTUPNcvF5CMR4Y9cvJqkMY0MoxWzTHvZuz1beg6pNEKlszPiQ==", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -17114,15 +15413,6 @@ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "license": "MIT" }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/std-env": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", @@ -17686,12 +15976,6 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "license": "MIT" - }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", @@ -17754,15 +16038,6 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/token-types": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz", @@ -17957,41 +16232,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -18184,16 +16424,6 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, - "node_modules/unicode-trie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", - "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", - "license": "MIT", - "dependencies": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -18342,15 +16572,6 @@ "node": ">= 10.0.0" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/unplugin": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz", @@ -18390,15 +16611,6 @@ "node": ">= 4" } }, - "node_modules/uuidv7": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uuidv7/-/uuidv7-1.0.2.tgz", - "integrity": "sha512-8JQkH4ooXnm1JCIhqTMbtmdnYEn6oKukBxHn1Ic9878jMkL7daTI7anTExfY18VRCX7tcdn5quzvCb6EWrR8PA==", - "license": "Apache-2.0", - "bin": { - "uuidv7": "cli.js" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -18417,15 +16629,6 @@ "node": ">= 0.10" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -19150,15 +17353,6 @@ "integrity": "sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==", "license": "CC0-1.0" }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -19263,24 +17457,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, "node_modules/zstddec": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.1.0.tgz", diff --git a/web/package.json b/web/package.json index 1ea33c922f..37d73174c4 100644 --- a/web/package.json +++ b/web/package.json @@ -32,6 +32,7 @@ "type": "module", "exports": { "./package.json": "./package.json", + "./styles/*": "./src/styles/*", "./elements/*": "./src/elements/*", "./common/*": "./src/common/*", "./components/*": "./src/components/*", @@ -53,7 +54,7 @@ } }, "imports": { - "#common/*.css": "./src/common/*.css", + "#styles/*.css": "./src/styles/*.css", "#common/*": "./src/common/*.js", "#elements/*.css": "./src/elements/*.css", "#elements/*": "./src/elements/*.js", @@ -224,9 +225,7 @@ "./dist/enterprise/**", "./dist/poly-*.js", "./dist/poly-*.js.map", - "./dist/theme-dark.css", - "./dist/one-dark.css", - "./dist/patternfly.min.css" + "./dist/styles/**" ], "dependencies": [ "build-locales", diff --git a/web/paths/node.js b/web/paths/node.js index de7bf9ba3b..07fcb7d152 100644 --- a/web/paths/node.js +++ b/web/paths/node.js @@ -9,6 +9,8 @@ import { fileURLToPath } from "node:url"; import { DistDirectoryName } from "#paths"; +import { resolvePackage } from "@goauthentik/core/paths/node"; + const relativeDirname = dirname(fileURLToPath(import.meta.url)); //#region Base paths @@ -46,6 +48,8 @@ export const DistDirectory = /** @type {`${WebPackageIdentifier}/${DistDirectory * Matches the type defined in the ESBuild context. */ +const patternflyPath = resolvePackage("@patternfly/patternfly", import.meta); + /** * Entry points available for building. * @@ -82,6 +86,14 @@ export const EntryPoint = /** @type {const} */ ({ in: resolve(PackageRoot, "src", "polyfill", "index.entrypoint.ts"), out: resolve(DistDirectory, "poly"), }, + InterfaceStyles: { + in: resolve(PackageRoot, "src", "styles", "authentik", "interface.global.css"), + out: resolve(DistDirectory, "styles", "interface"), + }, + StaticStyles: { + in: resolve(PackageRoot, "src", "styles", "authentik", "static.global.css"), + out: resolve(DistDirectory, "styles", "static"), + }, }); //#endregion diff --git a/web/scripts/build-web.mjs b/web/scripts/build-web.mjs index 4525dbae90..424c0a5103 100644 --- a/web/scripts/build-web.mjs +++ b/web/scripts/build-web.mjs @@ -13,6 +13,7 @@ import * as path from "node:path"; * @import { BuildOptions } from "esbuild"; */ import { mdxPlugin } from "#bundler/mdx-plugin/node"; +import { styleLoaderPlugin } from "#bundler/style-loader-plugin/node"; import { createBundleDefinitions } from "#bundler/utils/node"; import { ConsoleLogger } from "#logger/node"; import { DistDirectory, EntryPoint, PackageRoot } from "#paths/node"; @@ -53,10 +54,14 @@ const BASE_ESBUILD_OPTIONS = { legalComments: "external", splitting: true, treeShaking: true, - external: ["*.woff", "*.woff2"], tsconfig: path.resolve(PackageRoot, "tsconfig.build.json"), loader: { ".css": "text", + ".woff": "file", + ".woff2": "file", + ".jpg": "file", + ".png": "file", + ".svg": "file", }, plugins: [ copy({ @@ -65,19 +70,6 @@ const BASE_ESBUILD_OPTIONS = { from: path.join(path.dirname(EntryPoint.StandaloneLoading.in), "startup", "**"), to: path.dirname(EntryPoint.StandaloneLoading.out), }, - - { - from: path.join(patternflyPath, "patternfly.min.css"), - to: ".", - }, - { - from: path.join(patternflyPath, "assets", "**"), - to: "./assets", - }, - { - from: path.resolve(PackageRoot, "src", "common", "styles", "**"), - to: ".", - }, { from: path.resolve(PackageRoot, "src", "assets", "images", "**"), to: "./assets/images", @@ -88,6 +80,7 @@ const BASE_ESBUILD_OPTIONS = { }, ], }), + styleLoaderPlugin(), mdxPlugin({ root: MonoRepoRoot, }), @@ -170,7 +163,6 @@ async function doWatch() { const buildContext = await esbuild.context(buildOptions); - await buildContext.rebuild(); await buildContext.watch(); const httpURL = new URL("http://localhost"); @@ -256,8 +248,7 @@ await cleanDistDirectory() .then(() => { process.exit(0); }) - .catch((error) => { - logger.error(error); + .catch(() => { process.exit(1); }), ); diff --git a/web/src/admin/AdminInterface/AboutModal.ts b/web/src/admin/AdminInterface/AboutModal.ts index 111ecac9aa..0dc9d32e10 100644 --- a/web/src/admin/AdminInterface/AboutModal.ts +++ b/web/src/admin/AdminInterface/AboutModal.ts @@ -23,6 +23,10 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton)) ...ModalButton.styles, PFAbout, css` + .pf-c-about-modal-box { + --pf-c-about-modal-box--BackgroundColor: var(--pf-global--palette--black-900); + } + .pf-c-about-modal-box__hero { background-image: url("/static/dist/assets/images/flow_background.jpg"); } diff --git a/web/src/admin/AdminInterface/styles.css b/web/src/admin/AdminInterface/index.entrypoint.css similarity index 63% rename from web/src/admin/AdminInterface/styles.css rename to web/src/admin/AdminInterface/index.entrypoint.css index 05a4a00649..78717ed660 100644 --- a/web/src/admin/AdminInterface/styles.css +++ b/web/src/admin/AdminInterface/index.entrypoint.css @@ -1,3 +1,7 @@ +.pf-c-page { + height: 100dvh; +} + .pf-c-page__main { scrollbar-gutter: stable; } @@ -13,17 +17,6 @@ display: none; } -.pf-c-page { - background-color: var(--pf-c-page--BackgroundColor) !important; -} - -:host([theme="dark"]) { - /* Global page background colour */ - .pf-c-page { - --pf-c-page--BackgroundColor: var(--ak-dark-background); - } -} - ak-page-navbar { grid-area: header; } diff --git a/web/src/admin/AdminInterface/index.entrypoint.ts b/web/src/admin/AdminInterface/index.entrypoint.ts index abea423fde..064eaa725f 100644 --- a/web/src/admin/AdminInterface/index.entrypoint.ts +++ b/web/src/admin/AdminInterface/index.entrypoint.ts @@ -27,7 +27,7 @@ import { getURLParam, updateURLParams } from "#elements/router/RouteMatch"; import { PageNavMenuToggle } from "#components/ak-page-navbar"; import type { AboutModal } from "#admin/AdminInterface/AboutModal"; -import Styles from "#admin/AdminInterface/styles.css"; +import Styles from "#admin/AdminInterface/index.entrypoint.css"; import { ROUTES } from "#admin/Routes"; import { CapabilitiesEnum, SessionUser, UiThemeEnum } from "@goauthentik/api"; diff --git a/web/src/admin/common/ak-license-notice.ts b/web/src/admin/common/ak-license-notice.ts index 3d727d200a..26451d0313 100644 --- a/web/src/admin/common/ak-license-notice.ts +++ b/web/src/admin/common/ak-license-notice.ts @@ -1,7 +1,5 @@ import "#elements/Alert"; -import { $PFBase } from "#common/theme"; - import { AKElement } from "#elements/Base"; import { WithLicenseSummary } from "#elements/mixins/license"; @@ -11,8 +9,6 @@ import { customElement, property } from "lit/decorators.js"; @customElement("ak-license-notice") export class AKLicenceNotice extends WithLicenseSummary(AKElement) { - static styles = [$PFBase]; - @property() public label = msg("Enterprise only"); diff --git a/web/src/admin/providers/ProviderWizard.ts b/web/src/admin/providers/ProviderWizard.ts index cb824cf484..2cdf8d002d 100644 --- a/web/src/admin/providers/ProviderWizard.ts +++ b/web/src/admin/providers/ProviderWizard.ts @@ -82,6 +82,7 @@ export class ProviderWizard extends AKElement { aria-label=${msg("New Provider")} aria-description="${msg("Open the wizard to create a new provider.")}" type="button" + part="button trigger" slot="trigger" class="pf-c-button pf-m-primary" > diff --git a/web/src/admin/sources/utils.ts b/web/src/admin/sources/utils.ts index 6f0e94f78d..ea49f2f946 100644 --- a/web/src/admin/sources/utils.ts +++ b/web/src/admin/sources/utils.ts @@ -4,13 +4,18 @@ import { msg } from "@lit/localize"; import { html, TemplateResult } from "lit"; export function renderSourceIcon(name: string, iconUrl: string | undefined | null): TemplateResult { - const icon = html``; + const icon = html``; if (iconUrl) { if (iconUrl.startsWith("fa://")) { const url = iconUrl.replaceAll("fa://", ""); - return html``; + return html``; } - return html`${name}`; + return html`${name}`; } return icon; } diff --git a/web/src/admin/users/UserViewPage.ts b/web/src/admin/users/UserViewPage.ts index bb665eb1df..808278318d 100644 --- a/web/src/admin/users/UserViewPage.ts +++ b/web/src/admin/users/UserViewPage.ts @@ -356,7 +356,7 @@ export class UserViewPage extends WithCapabilitiesConfig(AKElement) { class="pf-c-page__main-section pf-m-no-padding-mobile" >
- +
diff --git a/web/src/common/styles/authentik.css b/web/src/common/styles/authentik.css deleted file mode 100644 index f516c881dc..0000000000 --- a/web/src/common/styles/authentik.css +++ /dev/null @@ -1,771 +0,0 @@ -/** - * @file authentik base UI theme. - */ - -/* Defined to better identify the base theme when debugging constructed stylesheets. */ -.__AK_UI_BASE__ { - --__AK_UI_BASE__: 1; -} - -/* #region Global */ - -:root { - --ak-accent: #fd4b2d; - - --ak-dark-foreground: #fafafa; - --ak-dark-foreground-darker: #bebebe; - --ak-dark-foreground-link: #5a5cb9; - --ak-dark-background: #18191a; - --ak-dark-background-darker: #000000; - --ak-dark-background-light: #1c1e21; - --ak-dark-background-light-ish: #212427; - --ak-dark-background-lighter: #2b2e33; - - --ak-flow-background-color-contrast: var(--pf-global--Color--100); - --ak-flow-footer-color: var(--pf-global--Color--light-100); - - /* PatternFly likes to override global variables for some reason */ - --ak-global--Color--100: var(--pf-global--Color--100); - - /* Minimum width after which the sidebar becomes automatic */ - --ak-sidebar--minimum-auto-width: 80rem; - - /** - * The height of the navbar and branded sidebar. - * @todo This shouldn't be necessary. The sidebar can instead use a grid layout - * ensuring they share the same height. - */ - --ak-navbar--height: 7rem; - - --pf-global--disabled-color--100: GrayText; - --pf-global--disabled-color--200: color-mix(in srgb, GrayText 100%, CanvasText 75%); - --pf-global--disabled-color--300: color-mix(in srgb, GrayText 100%, CanvasText 100%); -} - -/* #endregion */ - -/* #region Scrollbars */ - -/** - * Scrollbar colors derived from default user agent styles. - * - * @remarks - * - * Scrollbar colors may be ignored by the browser depending on a few factors: - * - * - A preference for increased contrast - * - A preference for scrollbar visibility - * - The pointer precision available - * - Operating system differences - */ -:root { - --ak-scrollbar-background-color: hsl(0 0% 98%); - --ak-scrollbar-thumb-background-color: hsl(0 0% 76%); -} - -/* Applicable to browsers with a WebKit lineage (Chrome, Edge, Safari) */ -::-webkit-scrollbar { - background: var(--ak-scrollbar-background-color); - - /* Emulate thin scrollbar sizing. */ - @media (pointer: fine) { - max-height: 12px; - max-width: 12px; - } -} - -/* Necessary to avoid background color mismatch. */ -::-webkit-scrollbar-track { - background: var(--ak-scrollbar-background-color); -} - -::-webkit-scrollbar-thumb { - background: var(--ak-scrollbar-thumb-background-color); - /* Applies consistent shape across browsers and platforms */ - border: 3px solid transparent; - border-radius: 8px; - background-clip: content-box; - - /* Emulate thin scrollbar sizing. */ - @media (pointer: fine) { - border: 3px solid transparent; - border-radius: 8px; - } -} - -/** - * Hides arrow buttons on the scrollbar. - * Not applicable to Edge when scrollbars are set to always visible. - */ -::-webkit-scrollbar-button { - display: none; - height: 0; - width: 0; -} - -@supports (scrollbar-color: auto) { - :root { - /** - * Applies consistent colors across browsers and platforms. - * Not applicable when thin scrollbars are preferred. - */ - scrollbar-color: var(--ak-scrollbar-thumb-background-color) - var(--ak-scrollbar-background-color); - } -} - -@supports (scrollbar-width: thin) { - .pf-c-page__main, - .pf-c-nav__list, - .pf-c-card__body { - scrollbar-width: thin; - - @media (pointer: coarse) { - /** - * Avoids issues on touch devices where thin scrollbars - * are difficult to interact with. - */ - scrollbar-width: auto; - } - } -} - -/* #endregion */ - -.pf-c-form__group { - column-gap: var(--pf-global--spacer--md); -} - -.pf-c-form__group-label { - display: flex; - user-select: none; - padding-top: var(--pf-c-form--m-horizontal__group-label--md--PaddingTop); - - /* Increase the pressable area of the label. */ - .pf-c-form__label { - display: block; - flex: 1 1 auto; - } -} - -.pf-c-form__label[aria-required="true"] .pf-c-form__label-text::after { - content: "*" / ""; - user-select: none; - margin-left: var(--pf-c-form__label-required--MarginLeft); - font-size: var(--pf-c-form__label-required--FontSize); - color: var(--pf-c-form__label-required--Color); -} - -html { - --pf-c-nav__link--PaddingTop: 0.5rem; - --pf-c-nav__link--PaddingRight: 0.5rem; - --pf-c-nav__link--PaddingBottom: 0.5rem; - --pf-c-nav__link--PaddingLeft: 0.5rem; -} - -html > form > input { - position: absolute; - top: -2000px; - left: -2000px; -} - -/* #endregion */ - -/* #region Screen readers */ - -.sr-only { - position: absolute; - left: -10000px; - top: auto; - width: 1px; - height: 1px; - overflow: hidden; -} - -@media not (prefers-contrast: more) { - .less-contrast-sr-only { - position: absolute; - left: -10000px; - top: auto; - width: 1px; - height: 1px; - overflow: hidden; - } -} - -/* #endregion */ - -/* #region Icons */ - -.pf-icon { - display: inline-block; - font-style: normal; - font-variant: normal; - text-rendering: auto; - line-height: 1; - vertical-align: middle; - - &:has(+ span) { - margin-inline-end: 0.5em; - } -} - -/* #endregion */ - -/* #region Page */ - -.pf-c-page { - --pf-c-page__header--BackgroundColor: transparent; -} - -/** - * We reverse the page header colors because PatternFly assumes a dark header - * on light themes. The opposite must be done for dark themes. - */ -.pf-c-page__header { - --pf-global--Color--100: var(--pf-global--Color--dark-100); - --pf-global--Color--200: var(--pf-global--Color--dark-200); - --pf-global--BorderColor--100: var(--pf-global--BorderColor--dark-100); - --pf-global--primary-color--100: var(--pf-global--primary-color--dark-100); - --pf-global--link--Color: var(--pf-global--link--Color--dark); - --pf-global--link--Color--hover: var(--pf-global--link--Color--dark); - --pf-global--BackgroundColor--100: transparent; -} - -/* #endregion */ - -/* #region Form controls */ - -/* TODO: Move after PF5 */ - -:root { - --ak-c-form-control-BackgroundColor: transparent; - --ak-c-form-control-BackgroundColor--hover: color-mix(in srgb, transparent 100%, Field 50%); - --ak-c-form-control-BackgroundColor--focus: color-mix(in srgb, transparent 100%, Field 75%); -} - -@media (prefers-contrast: more) { - :root { - --ak-c-form-control-BackgroundColor: color-mix(in srgb, transparent 100%, Field 50%); - --ak-c-form-control-BackgroundColor--hover: color-mix(in srgb, transparent 100%, Field 75%); - --ak-c-form-control-BackgroundColor--focus: Field; - } -} - -.pf-c-form-control { - /* !important is required to ensure priority when in compatibility mode. */ - - --pf-c-form-control--readonly--BackgroundColor: var( - --pf-global--BackgroundColor--200 - ) !important; - --pf-c-form-control--disabled--BackgroundColor: var( - --pf-global--BackgroundColor--200 - ) !important; - - --pf-c-form-control--BorderTopColor: transparent; - --pf-c-form-control--BorderRightColor: transparent; - --pf-c-form-control--BorderLeftColor: transparent; - - --pf-c-form-control--Color: FieldText !important; - --pf-c-form-control--BackgroundColor: var(--ak-c-form-control-BackgroundColor) !important; - - &:hover { - --pf-c-form-control--BackgroundColor: var( - --ak-c-form-control-BackgroundColor--hover - ) !important; - } - - &:focus { - --pf-c-form-control--BackgroundColor: var( - --ak-c-form-control-BackgroundColor--focus - ) !important; - - @media not (prefers-contrast: more) { - outline: none; - } - - @media (prefers-contrast: more) { - outline-offset: 0.25em; - } - } - - @media (prefers-contrast: more) { - --pf-c-text-input-group--placeholder--Color: GrayText; - --pf-c-form-control--placeholder--Color: GrayText; - } - - /* #region Caps Lock */ - - --pf-c-form-control--m-caps-lock--BackgroundUrl: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23aaabac' viewBox='0 0 56 56' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M 20.7812 37.6211 L 35.2421 37.6211 C 38.5233 37.6211 40.2577 35.6992 40.2577 32.6055 L 40.2577 28.4570 L 49.1404 28.4570 C 51.0859 28.4570 52.6329 27.3086 52.6329 25.5039 C 52.6329 24.4024 52.0703 23.5351 51.0158 22.6211 L 30.9062 4.8789 C 29.9452 4.0351 29.0546 3.4727 27.9999 3.4727 C 26.9687 3.4727 26.0780 4.0351 25.1171 4.8789 L 4.9843 22.6445 C 3.8828 23.6055 3.3671 24.4024 3.3671 25.5039 C 3.3671 27.3086 4.9140 28.4570 6.8828 28.4570 L 15.7421 28.4570 L 15.7421 32.6055 C 15.7421 35.6992 17.4999 37.6211 20.7812 37.6211 Z M 21.1562 34.0820 C 20.2655 34.0820 19.6562 33.4961 19.6562 32.6055 L 19.6562 25.7149 C 19.6562 25.1524 19.4452 24.9180 18.8828 24.9180 L 8.6640 24.9180 C 8.4999 24.9180 8.4296 24.8476 8.4296 24.7305 C 8.4296 24.6367 8.4530 24.5430 8.5702 24.4492 L 27.5077 7.9961 C 27.7187 7.8086 27.8359 7.7383 27.9999 7.7383 C 28.1640 7.7383 28.3046 7.8086 28.4921 7.9961 L 47.4532 24.4492 C 47.5703 24.5430 47.5939 24.6367 47.5939 24.7305 C 47.5939 24.8476 47.4998 24.9180 47.3356 24.9180 L 37.1406 24.9180 C 36.5780 24.9180 36.3671 25.1524 36.3671 25.7149 L 36.3671 32.6055 C 36.3671 33.4727 35.7109 34.0820 34.8671 34.0820 Z M 19.7733 52.5273 L 36.0624 52.5273 C 38.7577 52.5273 40.3046 51.0273 40.3046 48.3086 L 40.3046 44.9336 C 40.3046 42.2148 38.7577 40.6680 36.0624 40.6680 L 19.7733 40.6680 C 17.0546 40.6680 15.5077 42.2383 15.5077 44.9336 L 15.5077 48.3086 C 15.5077 51.0039 17.0546 52.5273 19.7733 52.5273 Z M 20.3124 49.2227 C 19.4921 49.2227 19.0468 48.8008 19.0468 47.9805 L 19.0468 45.2617 C 19.0468 44.4414 19.4921 43.9727 20.3124 43.9727 L 35.5233 43.9727 C 36.3202 43.9727 36.7655 44.4414 36.7655 45.2617 L 36.7655 47.9805 C 36.7655 48.8008 36.3202 49.2227 35.5233 49.2227 Z'/%3E%3C/svg%3E"); - - &.pf-m-icon.pf-m-caps-lock { - --pf-c-form-control--m-icon--BackgroundUrl: var( - --pf-c-form-control--m-caps-lock--BackgroundUrl - ); - } - - /* #endregion */ -} - -input.pf-c-form-control { - &[type="text"], - &[type="email"], - &[type="password"], - &[type="search"], - textarea { - @media not (prefers-contrast: more) { - border-inline: 0; - border-block-start-color: transparent; - } - - @media (prefers-contrast: more) { - border: 1px solid ButtonBorder !important; - - &:focus { - border: 1px solid Highlight !important; - } - } - } -} - -@media (prefers-contrast: less) { - input { - outline: none !important; - } -} - -.pf-c-input-group { - --pf-c-input-group--BackgroundColor: transparent !important; -} - -/* #region Buttons */ - -.pf-c-button { - --pf-c-button--disabled--BackgroundColor: var(--pf-global--Color--light-200); -} - -/* #endregion */ - -/* #region Select */ - -.pf-c-select { - --pf-c-select__toggle--BackgroundColor: transparent; - --pf-c-select__toggle-typeahead--BackgroundColor: var(--ak-c-form-control-BackgroundColor); -} - -.pf-c-select__toggle-typeahead { - &:hover { - --pf-c-select__toggle-typeahead--BackgroundColor: var( - --ak-c-form-control-BackgroundColor--hover - ); - } - - &:focus { - --pf-c-select__toggle-typeahead--BackgroundColor: var( - --ak-c-form-control-BackgroundColor--focus - ); - } -} - -.pf-c-select__toggle::before { - --pf-c-select__toggle--before--BorderTopColor: transparent; - --pf-c-select__toggle--before--BorderRightColor: transparent; - --pf-c-select__toggle--before--BorderLeftColor: transparent; -} - -/* #endregion */ - -/* #region Dropdown */ - -.pf-c-dropdown__menu-item[role="contentinfo"] { - &, - &:hover, - &:focus { - --pf-c-dropdown__menu-item--hover--Color: initial; - --pf-c-dropdown__menu-item--hover--BackgroundColor: initial; - } -} - -/* #endregion */ - -/* #region Tabs */ - -.pf-c-tabs { - --pf-c-tabs--inset: var(--pf-global--spacer--lg); - --pf-c-tabs--m-vertical--m-box--inset: var(--pf-global--spacer--lg); - - margin-block-start: var(--pf-global--spacer--xs); - background-color: transparent; - - &.pf-m-box.pf-m-vertical { - .pf-c-tabs__list::before { - border-color: transparent; - } - } - - &.pf-m-box .pf-c-tabs__item.pf-m-current:first-child .pf-c-tabs__link::before { - border-color: transparent; - } - - &.pf-m-vertical { - .pf-c-tabs__link { - background-color: transparent; - } - } - - &:not(.pf-m-vertical) .pf-c-tabs__item { - &.pf-m-current { - --pf-c-tabs__link--after--BorderColor: var(--ak-accent); - } - } -} - -ak-tabs[vertical] { - [role="tabpanel"] { - padding-inline-start: 0 !important; - } - - .pf-c-card__body > *::part(table-container) { - overflow-x: auto; - } -} - -.pf-c-tabs__link { - --pf-c-tabs__link--Color: var(--pf-global--Color--100); - - &::before { - border-color: transparent; - } -} - -/* #endregion */ - -/* #region Tables */ - -.pf-c-table { - --pf-c-table__sort__button__text--Color: var(--pf-global--Color--100); - --pf-c-table__button--hover--Color: var(--pf-global--link--Color--hover); - - /* Fixes the issue of an inner active element applying the active state to the row */ - tr.pf-m-hoverable { - &:has(:not(td):active) { - --pf-c-table--tr--m-hoverable--active--BackgroundColor: var( - --pf-c-table--tr--m-hoverable--hover--BackgroundColor - ); - } - } -} - -.pf-c-table__sort.pf-m-selected { - text-decoration: underline; - - .pf-c-table__button .pf-c-table__text { - --pf-c-table__sort__button__text--Color: var(--pf-global--Color--100); - - --pf-c-table__button--hover--Color: var(--pf-global--link--Color--hover); - } -} - -/* #endregion */ - -/* #region Login adjustments */ - -/* Ensure card is displayed on small screens */ -.pf-c-login__main { - display: block; - position: relative; - width: 100%; - flex: 1 1 auto; - place-content: center; -} - -@media (max-width: 1199px) { - .pf-c-login__container { - display: flex; - flex-direction: column; - } -} - -.ak-login-container { - max-width: 35rem; - width: 100%; - display: flex; - flex-direction: column; - height: calc(100vh - var(--pf-global--spacer--lg) - var(--pf-global--spacer--lg)); -} - -.pf-c-login__footer { - color: var(--ak-flow-footer-color); - flex: 250 0 auto; - display: flex; - justify-content: end; - flex-direction: column; -} - -@media (max-width: 768px) { - :root { - --ak-flow-footer-color: var(--ak-flow-background-color-contrast); - } -} - -.pf-c-login__footer ul.pf-c-list.pf-m-inline { - justify-content: center; - padding: 2rem 0; -} - -/* #endregion */ - -.pf-c-content h1 { - display: flex; - align-items: flex-start; -} - -.pf-c-content h1 i { - font-style: normal; -} - -.pf-c-content h1 :first-child { - margin-right: var(--pf-global--spacer--sm); -} - -/* ensure background on non-flow pages match */ -.pf-c-background-image::before { - background-image: var(--ak-flow-background); - background-position: center; -} - -.pf-m-success { - color: var(--pf-global--success-color--100) !important; -} - -.pf-m-warning { - color: var(--pf-global--warning-color--100); -} - -.pf-m-danger { - color: var(--pf-global--danger-color--100); -} - -/* #region Fields */ - -fieldset { - --ak-fieldset-border-width: thin; - --ak-fieldset-border-color: var(--pf-global--BackgroundColor--light-100); - --ak-legend-margin-inline-base: var(--pf-global--spacer--sm); - --ak-legend-padding-inline-base: var(--pf-global--spacer--sm); - - border-color: var(--ak-fieldset-border-color); - border-width: var(--ak-fieldset-border-width); - - padding: var(--ak-legend-padding-inline-base) !important; - - @media (prefers-contrast: more) { - --ak-fieldset-border-color: var(--pf-global--BorderColor--200); - } - - @media (prefers-contrast: less) { - --ak-fieldset-border-color: transparent; - } - - & > legend { - line-height: 1; - padding: var(--ak-legend-padding-inline-base) !important; - margin-inline-start: var( - --ak-legend-margin-inline-start, - var(--ak-legend-margin-inline-base) - ) !important; - margin-inline-end: var( - --ak-legend-margin-inline-end, - var(--ak-legend-margin-inline-base) - ) !important; - } - - &:has(legend.sr-only) { - border-width: 0; - } - - &.pf-c-form__group { - border-radius: var(--pf-global--BorderRadius--sm); - } - - &.pf-c-form__group { - display: flex; - flex-wrap: wrap; - - &.pf-m-action { - gap: var(--pf-global--spacer--md) var(--pf-global--spacer--sm); - margin-block-start: 0; - } - } - - &.pf-c-modal-box__footer { - --ak-legend-padding-inline-base: var(--pf-global--spacer--md); - padding-block: calc(var(--ak-legend-padding-inline-base) / 2); - border-inline: none; - border-block-end: none; - - --pf-c-modal-box__footer--c-button--sm--MarginRight: var( - --pf-c-modal-box__footer--c-button--MarginRight - ); - - & > ak-spinner-button:not(:last-child) { - margin-right: var(--pf-c-modal-box__footer--c-button--MarginRight); - } - } -} - -.pf-c-card > fieldset { - margin-inline: var(--pf-global--spacer--md); - margin-block-end: var(--pf-global--spacer--md); - - @media not (prefers-contrast: more) { - --ak-legend-margin-inline-start: calc( - var(--pf-c-card--child--PaddingLeft) - var(--ak-legend-padding-inline-base) - ); - --ak-legend-margin-inline-end: calc( - var(--pf-c-card--child--PaddingRight) - var(--ak-legend-padding-inline-base) - ); - - border-width: 0; - padding: 0 !important; - margin: 0 !important; - - legend { - padding-block-start: var(--pf-c-card--first-child--PaddingTop) !important; - padding-block-end: var(--pf-c-card__title--not--last-child--PaddingBottom) !important; - line-height: var(--pf-global--LineHeight--md); - } - } -} - -/* #endregion */ - -/* #region Notifications */ - -.pf-c-notification-drawer { - --pf-c-notification-drawer--BackgroundColor: var(--pf-global--BackgroundColor--150); -} - -/* #endregion */ - -/* #region Radio */ - -.pf-c-radio { - cursor: pointer; - user-select: none; - --pf-c-radio__label--Color: FieldText; - --pf-c-radio__description--Color: GrayText; - - @media (prefers-contrast: more) { - --pf-c-radio__description--Color: color-mix(in srgb, GrayText 100%, FieldText 75%); - } -} - -/* #endregion */ - -/* #region Switch */ - -.pf-c-switch { - --pf-c-switch__input--focus__toggle--OutlineWidth: 0; - - &:hover { - --pf-c-switch__toggle--before--BackgroundColor: var(--pf-global--BackgroundColor--200); - } -} - -.pf-c-switch__label { - --pf-c-switch__input--not-checked__label--Color: var(--pf-global--Color--100); - user-select: none; -} - -.pf-c-form__helper-text { - text-wrap: balance; - text-wrap: pretty; /* Supporting browsers. */ -} - -::placeholder { - font-style: italic; -} - -/* #endregion */ - -/* #region Fonts */ - -.pf-m-monospace { - font-family: var(--pf-global--FontFamily--monospace); - - &:not([inputmode="url"])::placeholder { - font-family: var(--pf-global--FontFamily--sans-serif); - } -} - -/* #endregion */ - -.pf-c-description-list__description .pf-c-button { - margin-right: 6px; - margin-bottom: 6px; -} - -.pf-m-pressable { - cursor: pointer; - user-select: none; -} - -.pf-m-link { - color: var(--pf-global--link--Color); - text-decoration: none; - - &:hover, - &:focus { - color: var(--pf-global--link--Color--hover); - text-decoration: underline; - } -} - -a.pf-m-block { - display: block; -} - -/* Flow-card adjustments for static pages */ -.pf-c-brand { - padding-top: calc( - var(--pf-c-login__main-footer-links--PaddingTop) + - var(--pf-c-login__main-footer-links--PaddingBottom) + - var(--pf-c-login__main-body--PaddingBottom) - ); - max-height: 9rem; -} - -.ak-brand { - display: flex; - justify-content: center; - width: 100%; -} - -.ak-brand img { - padding: 0 2rem; - max-height: inherit; -} - -@media (min-height: 60rem) { - .pf-c-login[data-layout="stacked"] .pf-c-login__main { - margin-top: 13rem; - } -} - -.pf-c-login[data-layout="sidebar_left"], -.pf-c-login[data-layout="sidebar_right"] { - --ak-flow-footer-color: var(--ak-flow-background-color-contrast); -} - -.pf-c-data-list { - padding-inline-start: 0; -} - -/* #region Code blocks */ - -pre:has(.hljs) { - padding: var(--pf-global--spacer--md); -} - -/* #endregion */ diff --git a/web/src/common/styles/theme-dark.css b/web/src/common/styles/theme-dark.css deleted file mode 100644 index 271efa53e4..0000000000 --- a/web/src/common/styles/theme-dark.css +++ /dev/null @@ -1,479 +0,0 @@ -/** - * @file authentik dark UI theme. - */ - -/* Defined to better identify the dark theme when debugging constructed stylesheets. */ -.__AK_UI_DARK__ { - --__AK_UI_DARK__: 1; -} - -/* #region Global */ - -:root { - --pf-global--Color--100: var(--ak-dark-foreground) !important; - --ak-global--Color--100: var(--ak-dark-foreground) !important; - --pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker); - --pf-global--BorderColor--100: var(--ak-dark-background-lighter) !important; - --pf-global--link--Color: var(--pf-global--link--Color--light); - --pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover); -} - -body { - background-color: var(--ak-dark-background) !important; - color-scheme: dark; -} - -/* #region Scrollbars */ - -:root { - /** - * Scrollbar colors derived from default user agent styles. - */ - --ak-scrollbar-background-color: hsl(0 0% 18%); - --ak-scrollbar-thumb-background-color: hsl(0 0% 42%); -} - -/* #endregion */ - -/* Global page background colour */ -.pf-c-page { - --pf-c-page--BackgroundColor: var(--ak-dark-background); -} - -.pf-c-drawer__content { - --pf-c-drawer__content--BackgroundColor: var(--ak-dark-background); -} - -.pf-c-title { - color: var(--ak-dark-foreground); -} - -.pf-u-mb-xl { - color: var(--ak-dark-foreground); -} - -/* #endregion */ - -/* Header sections */ - -.pf-c-page__main-section { - --pf-c-page__main-section--BackgroundColor: var(--ak-dark-background); -} - -.sidebar-trigger, -.notification-trigger { - background-color: transparent !important; -} - -.pf-c-content { - color: var(--ak-dark-foreground); -} - -/* #region Card */ - -.pf-c-card { - --pf-c-card--BackgroundColor: var(--ak-dark-background-light); - color: var(--ak-dark-foreground); -} - -.pf-c-card.pf-m-non-selectable-raised { - --pf-c-card--BackgroundColor: var(--ak-dark-background-lighter); -} - -.pf-c-card__title, -.pf-c-card__body { - color: var(--ak-dark-foreground); -} - -/* #endregion */ - -/* #region Fields */ - -fieldset { - --ak-fieldset-border-color: var(--pf-global--BackgroundColor--dark-transparent-200); - - @media (prefers-contrast: more) { - --ak-fieldset-border-color: var(--pf-global--BorderColor--300); - } - - @media (prefers-contrast: less) { - --ak-fieldset-border-color: transparent; - } -} - -/* #endregion */ - -.pf-c-toolbar { - --pf-c-toolbar--BackgroundColor: var(--ak-dark-background-light); -} - -.pf-c-pagination.pf-m-bottom { - background-color: var(--ak-dark-background-light); -} - -/* #region Tables */ - -.pf-c-table { - --pf-global--Color--100: var(--ak-dark-foreground); - --pf-c-table--BackgroundColor: var(--ak-dark-background-light); - --pf-c-table--BorderColor: var(--ak-dark-background-lighter); - --pf-c-table--cell--Color: var(--ak-dark-foreground); - --pf-c-table--tr--m-hoverable--hover--BackgroundColor: var(--ak-dark-background-light-ish); - --pf-c-table--tr--m-hoverable--active--BackgroundColor: var(--ak-dark-background-lighter); - --pf-global--link--Color: var(--pf-global--link--Color--light); - --pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover); -} - -.pf-c-table__text { - color: var(--ak-dark-foreground); -} - -.pf-c-table__sort-indicator i { - color: var(--ak-dark-foreground) !important; -} - -.pf-c-table__expandable-row.pf-m-expanded { - --pf-c-table__expandable-row--m-expanded--BorderBottomColor: var(--ak-dark-background-lighter); -} - -/* #endregion */ - -/* #Region Mobile Tables */ -@media screen and (max-width: 1200px) { - .pf-m-grid-xl.pf-c-table tbody:first-of-type { - border-top-color: var(--ak-dark-background); - } - .pf-m-grid-xl.pf-c-table tr:not(.pf-c-table__expandable-row) { - border-bottom-color: var(--ak-dark-background); - } -} - -/* #endregion */ - -/* #region Page layout */ - -/** - * Our reversal of the page header on the light theme requires us to set the colors - * back to their PatternFly defaults. - */ -.pf-c-page__header { - --pf-global--Color--100: var(--pf-global--Color--light-100); - --pf-global--Color--200: var(--pf-global--Color--light-200); - --pf-global--BorderColor--100: var(--pf-global--BorderColor--light-100); - --pf-global--primary-color--100: var(--pf-global--primary-color--light-100); - --pf-global--link--Color: var(--pf-global--link--Color--light); - --pf-global--link--Color--hover: var(--pf-global--link--Color--light); -} - -/* #endregion */ - -/* class for pagination text */ -.pf-c-options-menu__toggle { - color: var(--ak-dark-foreground); -} - -/* table icon used for expanding rows */ -.pf-c-table__toggle-icon { - color: var(--ak-dark-foreground); -} - -/* expandable elements */ -.pf-c-expandable-section__toggle-text { - color: var(--ak-dark-foreground); -} - -.pf-c-expandable-section__toggle-icon { - color: var(--ak-dark-foreground); -} - -.pf-c-expandable-section.pf-m-display-lg { - background-color: var(--ak-dark-background-light-ish); -} - -/* header for form group */ -.pf-c-form__field-group-header-title-text { - color: var(--ak-dark-foreground); -} - -.pf-c-form__field-group { - border-bottom: 0; -} - -/* #region Inputs */ -optgroup, -option { - color: var(--ak-dark-foreground); -} -select[multiple] optgroup:checked, -select[multiple] option:checked { - color: var(--ak-dark-background); -} - -select.pf-c-form-control { - --pf-c-form-control__select--BackgroundUrl: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath fill='%23fafafa' d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'/%3E%3C/svg%3E"); -} - -.pf-c-form-control { - /* !important is required to ensure priority when in compatibility mode. */ - - --pf-c-form-control--readonly--BackgroundColor: var(--ak-dark-background-light) !important; - --pf-c-form-control--disabled--BackgroundColor: var(--ak-dark-background-light) !important; -} - -/* #endregion */ - -/* #region Switch */ -.pf-c-switch__label { - --pf-c-switch__input--not-checked__label--Color: var(--ak-dark-foreground); - --pf-c-switch__input--checked__label--Color: var(--ak-dark-foreground); -} - -/* #endregion */ - -/* #region Select */ - -.pf-c-select__menu { - --pf-c-select__menu--BackgroundColor: var(--ak-dark-background-light-ish); - color: var(--ak-dark-foreground); -} - -.pf-c-select__menu-item { - color: var(--ak-dark-foreground); -} - -.pf-c-select__menu-wrapper:hover, -.pf-c-select__menu-item:hover { - --pf-c-select__menu-item--hover--BackgroundColor: var(--ak-dark-background-lighter); -} - -.pf-c-select__menu-wrapper:focus-within, -.pf-c-select__menu-wrapper.pf-m-focus, -.pf-c-select__menu-item:focus, -.pf-c-select__menu-item.pf-m-focus { - --pf-c-select__menu-item--focus--BackgroundColor: var(--ak-dark-background-light-ish); -} - -/* #region Buttons */ - -.pf-c-button { - --pf-c-button--disabled--BackgroundColor: var(--pf-global--Color--300); - - &.pf-m-plain { - --pf-c-button--m-plain--focus--Color: var(--pf-global--Color--200); - --pf-c-button--m-plain--hover--Color: var(--ak-dark-foreground); - - &:focus:hover { - color: var(--pf-c-button--m-plain--hover--Color); - } - } -} - -.pf-c-button.pf-m-control { - --pf-c-button--after--BorderColor: var(--ak-dark-background-lighter) - var(--ak-dark-background-lighter) var(--pf-c-button--m-control--after--BorderBottomColor) - var(--ak-dark-background-lighter); - background-color: var(--ak-dark-background-light); - color: var(--ak-dark-foreground); -} - -.pf-m-tertiary, -.pf-c-button.pf-m-tertiary { - --pf-c-button--after--BorderColor: var(--ak-dark-foreground-darker); - --pf-c-button--hover--after--BorderWidth: var(--pf-global--BorderWidth--sm); - --pf-c-button--active--after--BorderWidth: var(--pf-global--BorderWidth--sm); - - color: var(--ak-dark-foreground-darker); - - &:hover { - --pf-c-button--after--BorderColor: var(--ak-dark-foreground); - } -} - -/* #endregion */ - -.pf-c-form__label-text { - color: var(--ak-dark-foreground); -} - -.pf-c-check__label { - color: var(--ak-dark-foreground); -} - -.pf-c-dropdown__toggle::before { - border-color: transparent; -} - -.pf-c-dropdown__menu { - --pf-c-dropdown__menu--BackgroundColor: var(--pf-global--BackgroundColor--dark-100); - --pf-c-dropdown__menu-item--Color: var(--pf-global--Color--light-100); - --pf-c-dropdown__menu-item--hover--BackgroundColor: var(--pf-global--BackgroundColor--dark-300); - --pf-c-dropdown__menu-item--hover--Color: var(--pf-global--Color--light-100); -} - -.pf-c-toggle-group__button { - color: var(--ak-dark-foreground) !important; -} - -.pf-c-toggle-group__button:not(.pf-m-selected) { - background-color: var(--ak-dark-background-light) !important; -} - -.pf-c-toggle-group__button.pf-m-selected { - color: var(--ak-dark-foreground) !important; - background-color: var(--pf-global--primary-color--100) !important; -} - -/* #endregion */ - -/* #region Modal */ - -.pf-c-modal-box, -.pf-c-modal-box__header, -.pf-c-modal-box__footer, -.pf-c-modal-box__body { - background-color: var(--ak-dark-background); -} - -/* #endregion */ - -/* #region Sidebar */ - -.pf-c-nav { - background-color: var(--ak-dark-background-light); -} - -/* #endregion */ - -/* #region Flows */ - -.pf-c-login__main { - --pf-c-login__main--BackgroundColor: var(--ak-dark-background); -} - -.pf-c-login__main-body, -.pf-c-login__main-header, -.pf-c-login__main-header-desc { - color: var(--ak-dark-foreground); -} - -.pf-c-login__main-footer-links-item img, -.pf-c-login__main-footer-links-item .fas { - filter: invert(1); -} - -.pf-c-login__main-footer-band { - --pf-c-login__main-footer-band--BackgroundColor: var(--ak-dark-background-lighter); - color: var(--ak-dark-foreground); -} - -.form-control-static { - color: var(--ak-dark-foreground); -} - -/* #endregion */ - -/* #region Notifications */ - -.pf-c-drawer__panel { - background-color: var(--ak-dark-background); -} - -.pf-c-notification-drawer { - --pf-c-notification-drawer--BackgroundColor: var(--ak-dark-background) !important; - --pf-c-notification-drawer__header--BackgroundColor: var( - --ak-dark-background-lighter - ) !important; -} - -.pf-c-notification-drawer__header { - color: var(--ak-dark-foreground); -} - -.pf-c-notification-drawer__list-item { - background-color: var(--ak-dark-background-light-ish); - color: var(--ak-dark-foreground); - --pf-c-notification-drawer__list-item--BorderBottomColor: var( - --ak-dark-background-lighter - ) !important; -} - -/* #endregion */ - -/* #region Data List */ - -.pf-c-data-list { - padding-inline-start: 0; - border-top-color: var(--ak-dark-background-lighter); -} - -.pf-c-data-list__item { - --pf-c-data-list__item--BackgroundColor: transparent; - --pf-c-data-list__item--BorderBottomColor: var(--ak-dark-background-lighter); - color: var(--ak-dark-foreground); -} - -/* #endregion */ - -/* #region Wizards */ - -.pf-c-wizard__nav { - --pf-c-wizard__nav--BackgroundColor: var(--ak-dark-background-lighter); - --pf-c-wizard__nav--lg--BorderRightColor: transparent; -} - -.pf-c-wizard__main { - background-color: var(--ak-dark-background-light-ish); -} - -.pf-c-wizard__footer { - --pf-c-wizard__footer--BackgroundColor: var(--ak-dark-background-light-ish); -} - -.pf-c-wizard__toggle-num, -.pf-c-wizard__nav-link::before { - --pf-c-wizard__nav-link--before--BackgroundColor: transparent; -} - -/* #endregion */ - -/* #region Tree view */ - -.pf-c-tree-view__node { - --pf-c-tree-view__node--Color: var(--ak-dark-foreground); -} - -.pf-c-tree-view__node-toggle { - --pf-c-tree-view__node-toggle--Color: var(--ak-dark-foreground); -} - -.pf-c-tree-view__node:focus { - --pf-c-tree-view__node--focus--BackgroundColor: var(--ak-dark-background-light-ish); -} - -.pf-c-tree-view__content:hover, -.pf-c-tree-view__content:focus-within { - --pf-c-tree-view__node--hover--BackgroundColor: var(--ak-dark-background-light-ish); -} - -/* #endregion */ - -/* #region Stepper */ -.pf-c-progress-stepper__step-title { - --pf-c-progress-stepper__step-title--Color: var(--ak-dark-foreground); -} - -/* #endregion */ - -/* #region Mermaid */ - -svg[id^="mermaid-svg-"] { - line[class^="messageLine"] { - /* - Mermaid's support for dynamic palette changes leaves a lot to be desired. - This is a workaround to keep content readable while not breaking the rest of the theme. - */ - filter: invert(1) !important; - } -} - -/* #endregion */ diff --git a/web/src/common/theme.ts b/web/src/common/theme.ts index af986e8d2b..afbe1300ac 100644 --- a/web/src/common/theme.ts +++ b/web/src/common/theme.ts @@ -1,42 +1,11 @@ -import AKBase from "#common/styles/authentik.css"; -import AKBaseDark from "#common/styles/theme-dark.css"; /** * @file Theme utilities. */ -import { createStyleSheetUnsafe, setAdoptedStyleSheets, type StyleRoot } from "#common/stylesheets"; + +import { setAdoptedStyleSheets, type StyleRoot } from "#common/stylesheets"; import { UiThemeEnum } from "@goauthentik/api"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; - -//#region Stylesheet Exports - -/** - * A global style sheet for the Patternfly base styles. - * - * @remarks - * - * While a component *may* import its own instance of the PFBase style sheet, - * this instance ensures referential identity. - */ -export const $PFBase = createStyleSheetUnsafe(PFBase); - -/** - * A global style sheet for the authentik base styles. - * - * @see {@linkcode $PFBase} for details. - */ -export const $AKBase = createStyleSheetUnsafe(AKBase); - -/** - * A global style sheet for the authentik dark theme. - * - * @see {@linkcode $PFBase} for details. - */ -export const $AKBaseDark = createStyleSheetUnsafe(AKBaseDark); - -//#endregion - //#region Scheme Types /** @@ -112,8 +81,8 @@ export function formatColorScheme(theme: ResolvedUITheme): ResolvedCSSColorSchem export function formatColorScheme( colorScheme: ResolvedCSSColorSchemeValue, ): ResolvedCSSColorSchemeValue; -export function formatColorScheme(hint?: UIThemeHint): CSSColorSchemeValue; -export function formatColorScheme(hint?: UIThemeHint): CSSColorSchemeValue { +export function formatColorScheme(hint?: string): CSSColorSchemeValue; +export function formatColorScheme(hint?: string): CSSColorSchemeValue { if (!hint) return "auto"; switch (hint) { @@ -144,7 +113,7 @@ export function formatColorScheme(hint?: UIThemeHint): CSSColorSchemeValue { * @category CSS */ export function resolveUITheme( - hint?: UIThemeHint, + hint?: string, defaultUITheme: ResolvedUITheme = UiThemeEnum.Light, ): ResolvedUITheme { const colorScheme = formatColorScheme(hint); @@ -181,35 +150,59 @@ export function createUIThemeEffect( effect: UIThemeListener, listenerOptions?: AddEventListenerOptions, ): UIThemeDestructor { - const colorSchemeTarget = resolveUITheme(); - const invertedColorSchemeTarget = UIThemeInversion[colorSchemeTarget]; + const colorSchemeTarget: ResolvedUITheme = "light"; + const inversionTarget = UIThemeInversion[colorSchemeTarget]; - let previousUITheme: ResolvedUITheme | undefined; + const mediaQueryList = createColorSchemeTarget(colorSchemeTarget); // First, wrap the effect to ensure we can abort it. - const changeListener = (event: MediaQueryListEvent) => { + const mediaChangeListener = (event: MediaQueryListEvent) => { if (listenerOptions?.signal?.aborted) return; - const currentUITheme = event.matches ? colorSchemeTarget : invertedColorSchemeTarget; + const { themeChoice, theme: previousTheme } = document.documentElement.dataset; - if (previousUITheme === currentUITheme) return; + if (themeChoice && themeChoice !== "auto") { + console.debug( + `authentik/theme (document): skipping media query change due to explicit choice (${themeChoice})`, + ); + return; + } - previousUITheme = currentUITheme; + const currentUITheme = event.matches ? colorSchemeTarget : inversionTarget; + + if (previousTheme === currentUITheme) return; effect(currentUITheme); }; - const mediaQueryList = createColorSchemeTarget(colorSchemeTarget); + const themeChoiceListener = () => { + let theme = formatColorScheme(document.documentElement.dataset.themeChoice); - // Trigger the effect immediately. - effect(colorSchemeTarget); + if (theme === "auto") { + theme = mediaQueryList.matches + ? colorSchemeTarget + : UIThemeInversion[colorSchemeTarget]; + } + + document.documentElement.dataset.theme = theme; + + effect(theme); + }; + + const documentObserver = new MutationObserver(themeChoiceListener); + + documentObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme-choice"], + }); // Listen for changes to the color scheme... - mediaQueryList.addEventListener("change", changeListener, listenerOptions); + mediaQueryList.addEventListener("change", mediaChangeListener); // Finally, allow the caller to remove the effect. const cleanup = () => { - mediaQueryList.removeEventListener("change", changeListener); + documentObserver.disconnect(); + mediaQueryList.removeEventListener("change", mediaChangeListener); }; listenerOptions?.signal?.addEventListener("abort", cleanup); @@ -233,57 +226,96 @@ export function createUIThemeEffect( */ export function applyUITheme( styleRoot: StyleRoot, - currentUITheme: ResolvedUITheme = resolveUITheme(), ...additionalStyleSheets: Array ): void { setAdoptedStyleSheets(styleRoot, (currentStyleSheets) => { const appendedSheets = additionalStyleSheets.filter(Boolean) as CSSStyleSheet[]; - if (currentUITheme === UiThemeEnum.Dark) { - return [...currentStyleSheets, $AKBaseDark, ...appendedSheets]; - } - - return [ - ...currentStyleSheets.filter((styleSheet) => styleSheet !== $AKBaseDark), - ...appendedSheets, - ]; + return [...currentStyleSheets, ...appendedSheets]; }); } +export class ThemeChangeEvent extends Event { + static readonly eventName = "ak-theme-change"; + + public readonly theme: ResolvedUITheme; + + constructor(hint?: string) { + super(ThemeChangeEvent.eventName, { bubbles: true, composed: true }); + + this.theme = resolveUITheme(hint); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + [ThemeChangeEvent.eventName]: ThemeChangeEvent; + } +} + /** * Applies the given theme to the document, i.e. the `` element. * * @param hint The color scheme hint to use. */ -export function applyDocumentTheme(hint: CSSColorSchemeValue | UIThemeHint = "auto"): void { - const preferredColorScheme = formatColorScheme(hint); +export const applyDocumentTheme = ((currentUITheme = resolveUITheme()): void => { + console.debug(`authentik/theme (document): want to switch to ${currentUITheme} theme`); - if (document.documentElement.dataset.theme === preferredColorScheme) return; + const { themeChoice } = document.documentElement.dataset; - const applyStyleSheets: UIThemeListener = (currentUITheme) => { - console.debug(`authentik/theme (document): switching to ${currentUITheme} theme`); + if (themeChoice && themeChoice !== "auto") { + console.debug( + `authentik/theme (document): skipping theme application due to explicit choice (${themeChoice})`, + ); - setAdoptedStyleSheets(document, (currentStyleSheets) => { - if (currentUITheme === "dark") { - return [...currentStyleSheets, $PFBase, $AKBase, $AKBaseDark]; - } + document.dispatchEvent(new ThemeChangeEvent(themeChoice)); - return [ - ...currentStyleSheets.filter((styleSheet) => styleSheet !== $AKBaseDark), - $PFBase, - $AKBase, - ]; - }); - - document.documentElement.dataset.theme = currentUITheme; - }; - - if (preferredColorScheme === "auto") { - createUIThemeEffect(applyStyleSheets); return; } - applyStyleSheets(preferredColorScheme); + document.documentElement.dataset.theme = currentUITheme; + + console.debug(`authentik/theme (document): switching to ${currentUITheme} theme`); + + document.dispatchEvent(new ThemeChangeEvent(currentUITheme)); +}) satisfies UIThemeListener; + +/** + * A CSS variable representing the global background image. + */ +export const AKBackgroundImageProperty = "--ak-global--background-image"; + +/** + * Applies the given background image URL to the document body. + * + * This method is very defensive to avoid unnecessary DOM repaints. + */ +export function applyBackgroundImageProperty(value?: string | null): void { + const fallbackOrigin = window.location.origin; + + if (!value || !URL.canParse(value, fallbackOrigin)) { + return; + } + + const nextBackgroundURL = new URL(value, fallbackOrigin); + + const currentBackgroundImage = getComputedStyle(document.body, "::before").backgroundImage; + let currentBackgroundImageURL: URL | null = null; + + if (currentBackgroundImage && currentBackgroundImage !== "none") { + // Extract URL from background-image property + const [, urlMatch] = currentBackgroundImage.match(/url\(["']?([^"']*)["']?\)/) || []; + + if (URL.canParse(urlMatch)) { + currentBackgroundImageURL = new URL(urlMatch, fallbackOrigin); + } + } + + if (currentBackgroundImageURL && currentBackgroundImageURL.href === nextBackgroundURL.href) { + return; + } + + document.body.style.setProperty(AKBackgroundImageProperty, `url("${nextBackgroundURL.href}")`); } /** diff --git a/web/src/components/ak-hint/ak-hint.ts b/web/src/components/ak-hint/ak-hint.ts index 2c9ea5d7e8..eacf67a6c1 100644 --- a/web/src/components/ak-hint/ak-hint.ts +++ b/web/src/components/ak-hint/ak-hint.ts @@ -33,7 +33,7 @@ const styles = css` } :host([theme="dark"]) { - --ak-hint--BackgroundColor: var(--ak-dark-background-darker); + --ak-hint--BackgroundColor: var(--pf-global--palette--black-1000); --ak-hint--BorderColor: var(--ak-dark-background-lighter); --ak-hint--Color: var(--ak-dark-foreground); } diff --git a/web/src/components/ak-nav-button.css b/web/src/components/ak-nav-button.css new file mode 100644 index 0000000000..53aa904cf0 --- /dev/null +++ b/web/src/components/ak-nav-button.css @@ -0,0 +1,42 @@ +.pf-c-page__header-tools { + display: flex; +} + +.pf-c-avatar { + background-color: var(--pf-global--BackgroundColor--light-200); + overflow: hidden; + + img { + width: 100%; + height: 100%; + aspect-ratio: 1 / 1; + } +} + +:host([theme="dark"]) { + --pf-global--primary-color--100: var(--pf-global--primary-color--light-100) !important; + --pf-global--primary-color--200: var(--pf-global--primary-color--light-200) !important; + --pf-global--primary-color--300: var(--pf-global--primary-color--light-300) !important; + + .pf-c-page__header-tools { + color: var(--ak-dark-foreground) !important; + } + + .pf-c-avatar { + background-color: var(--pf-global--BackgroundColor--dark-300); + } +} + +.pf-c-page__header-tools-item .fas, +.pf-c-notification-badge__count, +.pf-c-page__header-tools-group .pf-c-button { + --pf-c-button--m-plain--Color: var(--pf-global--Color--300); + + .pf-c-button.pf-m-secondary { + --pf-c-button--m-secondary--hover--Color: var(--pf-global--primary-color--200); + --pf-c-button--m-secondary--hover--after--BorderColor: var(--pf-global--primary-color--200); + + --pf-c-button--m-secondary--Color: red; + --pf-c-button--m-secondary--Color: var(--pf-global--primary-color--100); + } +} diff --git a/web/src/components/ak-nav-buttons.ts b/web/src/components/ak-nav-buttons.ts index 13526a0f23..46e34fb552 100644 --- a/web/src/components/ak-nav-buttons.ts +++ b/web/src/components/ak-nav-buttons.ts @@ -8,12 +8,14 @@ import { me } from "#common/users"; import { AKElement } from "#elements/Base"; +import Styles from "#components/ak-nav-button.css"; + import { CoreApi, EventsApi, SessionUser } from "@goauthentik/api"; import { match } from "ts-pattern"; import { msg } from "@lit/localize"; -import { css, html, nothing } from "lit"; +import { html, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; @@ -53,40 +55,7 @@ export class NavigationButtons extends AKElement { PFDrawer, PFDropdown, PFNotificationBadge, - css` - .pf-c-page__header-tools { - display: flex; - } - - .pf-c-avatar { - background-color: var(--pf-global--BackgroundColor--light-200); - overflow: hidden; - - img { - width: 100%; - height: 100%; - aspect-ratio: 1 / 1; - } - } - - :host([theme="dark"]) { - .pf-c-page__header-tools { - color: var(--ak-dark-foreground) !important; - } - - .pf-c-avatar { - background-color: var(--pf-global--BackgroundColor--dark-300); - } - } - - :host([theme="light"]) { - .pf-c-page__header-tools-item .fas, - .pf-c-notification-badge__count, - .pf-c-page__header-tools-group .pf-c-button { - color: var(--ak-global--Color--100) !important; - } - } - `, + Styles, ]; async firstUpdated() { diff --git a/web/src/components/ak-page-navbar.ts b/web/src/components/ak-page-navbar.ts index 0c073767a6..5b9693b628 100644 --- a/web/src/components/ak-page-navbar.ts +++ b/web/src/components/ak-page-navbar.ts @@ -68,6 +68,11 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageHead --ak-brand-background-color: var(--pf-c-page__sidebar--BackgroundColor); --pf-c-page__sidebar--BackgroundColor: var(--ak-dark-background-light); color: var(--ak-dark-foreground); + + .sidebar-trigger, + .notification-trigger { + background-color: transparent !important; + } } .main-content { @@ -360,7 +365,7 @@ export class AKPageNavbar extends WithBrandConfig(AKElement) implements PageHead
-
diff --git a/web/src/flow/stages/authenticator_email/AuthenticatorEmailStage.ts b/web/src/flow/stages/authenticator_email/AuthenticatorEmailStage.ts index 2114f821f6..f0aef82b27 100644 --- a/web/src/flow/stages/authenticator_email/AuthenticatorEmailStage.ts +++ b/web/src/flow/stages/authenticator_email/AuthenticatorEmailStage.ts @@ -75,7 +75,11 @@ export class AuthenticatorEmailStage extends BaseStage< ${this.renderNonFieldErrors()}
${msg("Form actions")} -
@@ -118,7 +122,11 @@ export class AuthenticatorEmailStage extends BaseStage< ${this.renderNonFieldErrors()}
${msg("Form actions")} -
diff --git a/web/src/flow/stages/authenticator_sms/AuthenticatorSMSStage.ts b/web/src/flow/stages/authenticator_sms/AuthenticatorSMSStage.ts index 48b7adf388..c626a5ce89 100644 --- a/web/src/flow/stages/authenticator_sms/AuthenticatorSMSStage.ts +++ b/web/src/flow/stages/authenticator_sms/AuthenticatorSMSStage.ts @@ -75,7 +75,11 @@ export class AuthenticatorSMSStage extends BaseStage< ${this.renderNonFieldErrors()}
${msg("Form actions")} -
@@ -116,7 +120,11 @@ export class AuthenticatorSMSStage extends BaseStage< ${this.renderNonFieldErrors()}
${msg("Form actions")} -
diff --git a/web/src/flow/stages/authenticator_static/AuthenticatorStaticStage.ts b/web/src/flow/stages/authenticator_static/AuthenticatorStaticStage.ts index d6f674150a..7e0218a34b 100644 --- a/web/src/flow/stages/authenticator_static/AuthenticatorStaticStage.ts +++ b/web/src/flow/stages/authenticator_static/AuthenticatorStaticStage.ts @@ -76,7 +76,11 @@ export class AuthenticatorStaticStage extends BaseStage<
${msg("Form actions")} -
diff --git a/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.ts b/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.ts index efda1f3342..d9dcd6f1e0 100644 --- a/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.ts +++ b/web/src/flow/stages/authenticator_totp/AuthenticatorTOTPStage.ts @@ -141,7 +141,11 @@ export class AuthenticatorTOTPStage extends BaseStage<
${msg("Form actions")} -
diff --git a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.css b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.css new file mode 100644 index 0000000000..af8c375218 --- /dev/null +++ b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.css @@ -0,0 +1,22 @@ +.authenticator-button { + /* compatibility-mode-fix */ + & { + align-items: center; + width: 100%; + display: grid; + grid-template-columns: minmax(auto, 2rem) minmax(33%, max-content); + gap: var(--pf-global--spacer--lg); + } + + &:hover { + background-color: var(--pf-global--BackgroundColor--200); + } +} + +i { + font-size: var(--pf-global--icon--FontSize--lg); +} + +.content { + text-align: left; +} diff --git a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts index e473ea7a91..4c7ad4b41d 100644 --- a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts +++ b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStage.ts @@ -3,6 +3,8 @@ import "#flow/stages/authenticator_validate/AuthenticatorValidateStageCode"; import "#flow/stages/authenticator_validate/AuthenticatorValidateStageDuo"; import "#flow/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn"; +import Styles from "./AuthenticatorValidateStage.css"; + import { DEFAULT_CONFIG } from "#common/api/config"; import { BaseStage, StageHost, SubmitOptions } from "#flow/stages/base"; @@ -19,8 +21,9 @@ import { } from "@goauthentik/api"; import { msg } from "@lit/localize"; -import { css, CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit"; +import { CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit"; import { customElement, state } from "lit/decorators.js"; +import { repeat } from "lit/directives/repeat.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFForm from "@patternfly/patternfly/components/Form/form.css"; @@ -29,39 +32,6 @@ import PFLogin from "@patternfly/patternfly/components/Login/login.css"; import PFTitle from "@patternfly/patternfly/components/Title/title.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; -const customCSS = css` - .authenticator-button { - /* compatibility-mode-fix */ - & { - align-items: center; - width: 100%; - display: grid; - grid-template-columns: auto 1fr; - gap: var(--pf-global--spacer--md); - } - - &:hover { - background-color: var(--pf-global--Color--light-200); - } - } - :host([theme="dark"]) .authenticator-button { - color: var(--ak-dark-foreground) !important; - - &:hover { - background-color: var(--pf-global--Color--300); - } - } - - i { - font-size: 1.5rem; - padding: 1rem 0; - width: 3rem; - } - .content { - text-align: left; - } -`; - interface DevicePickerProps { icon?: string; label: string; @@ -121,7 +91,7 @@ export class AuthenticatorValidateStage PFFormControl, PFTitle, PFButton, - customCSS, + Styles, ]; flowSlug = ""; @@ -227,36 +197,42 @@ export class AuthenticatorValidateStage return nothing; } - const deviceChallengeButtons = this.challenge.deviceChallenges.map((challenges, idx) => { - const buttonID = `device-challenge-${idx}`; - const labelID = `${buttonID}-label`; - const descriptionID = `${buttonID}-description`; + const { deviceChallenges } = this.challenge; - const { icon, label, description } = DevicePickerPropMap[challenges.deviceClass]; + const deviceChallengeButtons = repeat( + deviceChallenges, + (challenges) => challenges.deviceUid, + (challenges, idx) => { + const buttonID = `device-challenge-${idx}`; + const labelID = `${buttonID}-label`; + const descriptionID = `${buttonID}-description`; - return html` - - `; - }); + const { icon, label, description } = DevicePickerPropMap[challenges.deviceClass]; + + return html` + + `; + }, + ); return html`
${msg("Select an authentication method")} - ${deviceChallengeButtons.length + ${deviceChallenges.length ? deviceChallengeButtons : msg("No authentication methods available.")}
`; @@ -267,23 +243,27 @@ export class AuthenticatorValidateStage return nothing; } - const stageButtons = this.challenge.configurationStages.map((stage) => { - return html``; - }); + const stageButtons = repeat( + this.challenge.configurationStages, + (stage) => stage.pk, + (stage) => { + return html``; + }, + ); return html`
${msg("Select a configuration stage")} diff --git a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageCode.ts b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageCode.ts index 0653fb4379..3726ed66c5 100644 --- a/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageCode.ts +++ b/web/src/flow/stages/authenticator_validate/AuthenticatorValidateStageCode.ts @@ -64,7 +64,7 @@ export class AuthenticatorValidateStageWebCode extends BaseDeviceStage<
${msg("Form actions")} - ${this.renderReturnToDevicePicker()} diff --git a/web/src/flow/stages/base.ts b/web/src/flow/stages/base.ts index aeb54f7739..11354dab3f 100644 --- a/web/src/flow/stages/base.ts +++ b/web/src/flow/stages/base.ts @@ -62,7 +62,9 @@ export abstract class BaseStage< delegatesFocus: true, }; - protected host!: StageHost; + // TODO: Should have a property but this needs some refactoring first. + // @property({ attribute: false }) + public host!: StageHost; @property({ attribute: false }) public challenge!: Tin; diff --git a/web/src/flow/stages/captcha/CaptchaStage.ts b/web/src/flow/stages/captcha/CaptchaStage.ts index 8f5a7480d1..c625dab16e 100644 --- a/web/src/flow/stages/captcha/CaptchaStage.ts +++ b/web/src/flow/stages/captcha/CaptchaStage.ts @@ -59,7 +59,7 @@ export class CaptchaStage extends BaseStage -

+

${this.challenge.headerText}

${this.challenge.permissions.length > 0 @@ -74,7 +74,7 @@ export class ConsentStage extends BaseStage -

+

${this.challenge.headerText}

${this.challenge.permissions.length > 0 @@ -130,7 +130,11 @@ export class ConsentStage extends BaseStage ${msg("Form actions")} -
diff --git a/web/src/flow/stages/dummy/DummyStage.ts b/web/src/flow/stages/dummy/DummyStage.ts index a52a7800fb..d199b2b6fe 100644 --- a/web/src/flow/stages/dummy/DummyStage.ts +++ b/web/src/flow/stages/dummy/DummyStage.ts @@ -26,7 +26,11 @@ export class DummyStage extends BaseStage${msg(str`Stage name: ${this.challenge.name}`)}

${msg("Form actions")} -
diff --git a/web/src/flow/stages/email/EmailStage.ts b/web/src/flow/stages/email/EmailStage.ts index bc6d8536c5..ad5e6b89cc 100644 --- a/web/src/flow/stages/email/EmailStage.ts +++ b/web/src/flow/stages/email/EmailStage.ts @@ -28,7 +28,11 @@ export class EmailStage extends BaseStage ${msg("Form actions")} -
diff --git a/web/src/flow/stages/identification/IdentificationStage.ts b/web/src/flow/stages/identification/IdentificationStage.ts index 0d5f013f59..bdbd82fef7 100644 --- a/web/src/flow/stages/identification/IdentificationStage.ts +++ b/web/src/flow/stages/identification/IdentificationStage.ts @@ -11,6 +11,7 @@ import { renderSourceIcon } from "#admin/sources/utils"; import { BaseStage } from "#flow/stages/base"; import { AkRememberMeController } from "#flow/stages/identification/RememberMeController"; +import Styles from "#flow/stages/identification/styles.css"; import { FlowDesignationEnum, @@ -20,10 +21,13 @@ import { UserFieldsEnum, } from "@goauthentik/api"; +import { kebabCase } from "change-case"; + import { msg, str } from "@lit/localize"; -import { css, CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit"; +import { CSSResult, html, nothing, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { createRef, ref } from "lit/directives/ref.js"; +import { repeat } from "lit/directives/repeat.js"; import PFAlert from "@patternfly/patternfly/components/Alert/alert.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; @@ -59,37 +63,7 @@ export class IdentificationStage extends BaseStage< PFTitle, PFButton, ...AkRememberMeController.styles, - css` - /* login page's icons */ - .pf-c-login__main-footer-links-item button { - background-color: transparent; - border: 0; - display: flex; - align-items: stretch; - } - .pf-c-login__main-footer-links-item img { - fill: var(--pf-c-login__main-footer-links-item-link-svg--Fill); - width: 100px; - max-width: var(--pf-c-login__main-footer-links-item-link-svg--Width); - height: 100%; - max-height: var(--pf-c-login__main-footer-links-item-link-svg--Height); - } - - .captcha-container { - /* compatibility-mode-fix */ - & { - position: relative; - } - - .faux-input { - position: absolute; - bottom: 0; - left: 0; - opacity: 0; - pointer-events: none; - } - } - `, + Styles, ]; /** @@ -270,40 +244,53 @@ export class IdentificationStage extends BaseStage< renderSource(source: LoginSource): TemplateResult { const icon = renderSourceIcon(source.name, source.iconUrl); - return html``; + + return html``; } renderFooter() { - if (!this.challenge?.enrollUrl && !this.challenge?.recoveryUrl) { + const { enrollUrl, recoveryUrl } = this.challenge || {}; + + const enrollmentItem = enrollUrl + ? html`` + : null; + + const recoveryItem = recoveryUrl + ? html`` + : null; + + if (!enrollmentItem && !recoveryItem) { return nothing; } - return html``; + + return html``; } renderInput(): TemplateResult { @@ -404,7 +391,7 @@ export class IdentificationStage extends BaseStage< } render(): TemplateResult { - return html` + return html`
${this.challenge.applicationPre ? html`

@@ -425,13 +412,22 @@ export class IdentificationStage extends BaseStage< ` : nothing}

- ${(this.challenge.sources || []).length > 0 - ? html` ` - : nothing} + ${this.challenge.sources?.length + ? html`
+ ${msg("Login sources")} + ${repeat( + this.challenge.sources, + (source, idx) => source.name + idx, + (source) => this.renderSource(source), + )} +
` + : null} ${this.renderFooter()}
`; } diff --git a/web/src/flow/stages/identification/styles.css b/web/src/flow/stages/identification/styles.css new file mode 100644 index 0000000000..34101693db --- /dev/null +++ b/web/src/flow/stages/identification/styles.css @@ -0,0 +1,61 @@ +fieldset[name="login-sources"] { + --ak-login-sources-padding-inline: var(--pf-global--spacer--xl); + + flex: 1 1 auto; + display: flex; + flex-flow: row wrap; + gap: var(--pf-global--spacer--sm); + + /* compatibility-mode-fix */ + + & { + padding-inline: var(--ak-login-sources-padding-inline) !important; + padding-block-start: var(--pf-global--spacer--lg) !important; + } + + .source-button { + display: flex; + align-items: center; + justify-content: center; + gap: var(--pf-global--spacer--sm); + + &:hover { + background-color: var(--pf-c-button--m-plain--hover--BackgroundColor); + } + + .pf-c-button__icon { + line-height: 1; + } + } + + .pf-c-button__icon img { + height: var(--pf-global--FontSize--2xl); + fill: var(--pf-global--icon--Color--light); + } + + @media (min-width: 768px) { + --ak-login-sources-padding-inline: var(--pf-global--spacer--2xl); + } +} + +.captcha-container { + /* compatibility-mode-fix */ + & { + position: relative; + } + + .faux-input { + position: absolute; + bottom: 0; + left: 0; + opacity: 0; + pointer-events: none; + } +} + +:host([theme="dark"]) fieldset[name="login-sources"] .pf-c-button__icon { + img, + .pf-c-button__icon .fas { + filter: invert(1); + } +} diff --git a/web/src/flow/stages/password/PasswordStage.ts b/web/src/flow/stages/password/PasswordStage.ts index 3e9d6da337..3b82d25934 100644 --- a/web/src/flow/stages/password/PasswordStage.ts +++ b/web/src/flow/stages/password/PasswordStage.ts @@ -10,7 +10,7 @@ import { PasswordManagerPrefill } from "#flow/stages/identification/Identificati import { PasswordChallenge, PasswordChallengeResponseRequest } from "@goauthentik/api"; import { msg } from "@lit/localize"; -import { CSSResult, html, nothing, TemplateResult } from "lit"; +import { CSSResult, html, TemplateResult } from "lit"; import { customElement } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; @@ -73,18 +73,29 @@ export class PasswordStage extends BaseStage
${msg("Form actions")} -
${this.challenge.recoveryUrl - ? html`` - : nothing} + ? html`` + : null}
`; } } diff --git a/web/src/flow/stages/prompt/PromptStage.ts b/web/src/flow/stages/prompt/PromptStage.ts index a8fce421ba..1eac58c71e 100644 --- a/web/src/flow/stages/prompt/PromptStage.ts +++ b/web/src/flow/stages/prompt/PromptStage.ts @@ -300,7 +300,7 @@ ${prompt.initialValue} ${msg("Form actions")} - `; diff --git a/web/src/flow/stages/user_login/UserLoginStage.ts b/web/src/flow/stages/user_login/UserLoginStage.ts index cedfa7a621..d83422de3e 100644 --- a/web/src/flow/stages/user_login/UserLoginStage.ts +++ b/web/src/flow/stages/user_login/UserLoginStage.ts @@ -40,10 +40,11 @@ export class PasswordStage extends BaseStage< @submit=${(event: SubmitEvent) => { event.preventDefault(); - const rememberMe = typeof event.submitter?.dataset.rememberMe === "string"; + const submitter = + event.submitter instanceof HTMLButtonElement ? event.submitter : null; this.submitForm(event, { - rememberMe, + rememberMe: submitter?.name === "remember-me", }); }} > @@ -59,7 +60,7 @@ export class PasswordStage extends BaseStage<
-

+

${msg("Stay signed in?")}

@@ -69,10 +70,12 @@ export class PasswordStage extends BaseStage<

${msg("Form actions")} - - +
`; diff --git a/web/src/rac/index.entrypoint.css b/web/src/rac/index.entrypoint.css new file mode 100644 index 0000000000..ccb15d26b2 --- /dev/null +++ b/web/src/rac/index.entrypoint.css @@ -0,0 +1,20 @@ +:host { + cursor: none; +} + +canvas { + z-index: unset !important; +} + +.container { + overflow: hidden; + height: 100vh; + background-color: black; + display: flex; + justify-content: center; + align-items: center; +} + +ak-loading-overlay { + z-index: 5; +} diff --git a/web/src/rac/index.entrypoint.ts b/web/src/rac/index.entrypoint.ts index 391c71f6c3..dc9bad17ab 100644 --- a/web/src/rac/index.entrypoint.ts +++ b/web/src/rac/index.entrypoint.ts @@ -1,6 +1,6 @@ import "#elements/LoadingOverlay"; -import AKGlobal from "#common/styles/authentik.css"; +import Styles from "./index.entrypoint.css"; import { Interface } from "#elements/Interface"; import { WithBrandConfig } from "#elements/mixins/branding"; @@ -8,7 +8,7 @@ import { WithBrandConfig } from "#elements/mixins/branding"; import Guacamole from "guacamole-common-js"; import { msg, str } from "@lit/localize"; -import { css, CSSResult, html, nothing, TemplateResult } from "lit"; +import { CSSResult, html, nothing, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; @@ -48,29 +48,11 @@ const RECONNECT_ATTEMPTS = 5; @customElement("ak-rac") export class RacInterface extends WithBrandConfig(Interface) { static styles: CSSResult[] = [ + // --- PFBase, PFPage, PFContent, - AKGlobal, - css` - :host { - cursor: none; - } - canvas { - z-index: unset !important; - } - .container { - overflow: hidden; - height: 100vh; - background-color: black; - display: flex; - justify-content: center; - align-items: center; - } - ak-loading-overlay { - z-index: 5; - } - `, + Styles, ]; client?: Guacamole.Client; diff --git a/web/src/standalone/api-browser/index.entrypoint.css b/web/src/standalone/api-browser/index.entrypoint.css new file mode 100644 index 0000000000..5cbfb9756f --- /dev/null +++ b/web/src/standalone/api-browser/index.entrypoint.css @@ -0,0 +1,18 @@ +:host { + display: block; + height: 100dvh; +} + +img.logo { + width: 100%; + padding: 1rem 0.5rem 1.5rem 0.5rem; + min-height: 48px; +} + +[part="rapi-doc"] { + height: 100%; +} + +rapi-doc::part(section-main-content) { + place-content: center; +} diff --git a/web/src/standalone/api-browser/index.entrypoint.ts b/web/src/standalone/api-browser/index.entrypoint.ts index bb8310d3e0..d01ec46ac3 100644 --- a/web/src/standalone/api-browser/index.entrypoint.ts +++ b/web/src/standalone/api-browser/index.entrypoint.ts @@ -2,35 +2,42 @@ import "rapidoc"; import "#elements/ak-locale-context/index"; +import styles from "./index.entrypoint.css"; + import { CSRFHeaderName } from "#common/api/middleware"; -import { EVENT_THEME_CHANGE } from "#common/constants"; +import { createUIThemeEffect } from "#common/theme"; import { getCookie } from "#common/utils"; import { Interface } from "#elements/Interface"; import { WithBrandConfig } from "#elements/mixins/branding"; import { themeImage } from "#elements/utils/images"; -import { UiThemeEnum } from "@goauthentik/api"; - import { msg } from "@lit/localize"; -import { css, CSSResult, html, TemplateResult } from "lit"; +import { CSSResult, html, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; +function rgba2hex(cssValue: string) { + const matches = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/.exec(cssValue); + if (!matches) return ""; + + return `#${matches + .slice(1) + .map((n, i) => + (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)) + .toString(16) + .padStart(2, "0") + .replace("NaN", ""), + ) + .join("")}`; +} + @customElement("ak-api-browser") export class APIBrowser extends WithBrandConfig(Interface) { @property() schemaPath?: string; - static styles: CSSResult[] = [ - css` - img.logo { - width: 100%; - padding: 1rem 0.5rem 1.5rem 0.5rem; - min-height: 48px; - } - `, - ]; + static styles: CSSResult[] = [styles]; @state() bgColor = "#000000"; @@ -38,32 +45,25 @@ export class APIBrowser extends WithBrandConfig(Interface) { @state() textColor = "#000000"; - firstUpdated(): void { - this.addEventListener(EVENT_THEME_CHANGE, ((ev: CustomEvent) => { - const style = getComputedStyle(document.documentElement); - if (ev.detail === UiThemeEnum.Light) { - this.bgColor = style - .getPropertyValue("--pf-global--BackgroundColor--light-300") - .trim(); - this.textColor = style.getPropertyValue("--pf-global--Color--300").trim(); - } else { - this.bgColor = style.getPropertyValue("--ak-dark-background").trim(); - this.textColor = style.getPropertyValue("--ak-dark-foreground").trim(); - } - }) as EventListener); - this.dispatchEvent( - new CustomEvent(EVENT_THEME_CHANGE, { - bubbles: true, - composed: true, - detail: UiThemeEnum.Automatic, - }), - ); + #synchronizeTheme = () => { + const style = getComputedStyle(document.body); + + this.bgColor = rgba2hex(style.backgroundColor.trim()); + this.textColor = rgba2hex(style.color.trim()); + }; + + public override connectedCallback(): void { + super.connectedCallback(); + + this.#synchronizeTheme(); + createUIThemeEffect(this.#synchronizeTheme); } render(): TemplateResult { return html`
diff --git a/web/src/standalone/loading/index.entrypoint.ts b/web/src/standalone/loading/index.entrypoint.ts index 9946e1bbc3..f6c89ea0ad 100644 --- a/web/src/standalone/loading/index.entrypoint.ts +++ b/web/src/standalone/loading/index.entrypoint.ts @@ -1,14 +1,9 @@ -import { globalAK } from "#common/global"; -import { applyDocumentTheme } from "#common/theme"; - import { AKElement } from "#elements/Base"; import { msg } from "@lit/localize"; import { css, html, TemplateResult } from "lit"; import { customElement } from "lit/decorators.js"; -import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; -import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFSpinner from "@patternfly/patternfly/components/Spinner/spinner.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css"; @@ -16,46 +11,44 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css"; export class Loading extends AKElement { static styles = [ PFBase, - PFPage, PFSpinner, - PFEmptyState, css` - :host([theme="dark"]) h1 { - color: var(--ak-dark-foreground); + :host { + position: absolute; + inset: 0; + display: flex; + flex-flow: column; + place-items: center; + justify-content: center; + text-align: center; + gap: var(--pf-global--spacer--md); + } + + label { + font-size: var(--pf-global--FontSize--xl); + font-weight: var(--pf-global--FontWeight--normal); + font-family: var(--pf-global--FontFamily--heading--sans-serif); } `, ]; - constructor() { - super(); - - applyDocumentTheme(globalAK().brand.uiTheme); - } - public connectedCallback(): void { super.connectedCallback(); this.dataset.akInterfaceRoot = this.tagName.toLowerCase(); } render(): TemplateResult { - return html`
-
-
- - - - - -

${msg("Loading...")}

-
-
-
`; + return html` + + `; } } diff --git a/web/src/common/styles/one-dark.css b/web/src/styles/atom/one-dark.css similarity index 90% rename from web/src/common/styles/one-dark.css rename to web/src/styles/atom/one-dark.css index a30856d781..3f757fcb20 100644 --- a/web/src/common/styles/one-dark.css +++ b/web/src/styles/atom/one-dark.css @@ -4,11 +4,6 @@ * @see https://github.com/atom/one-dark-syntax */ -/* Defined to better identify the One Dark theme when debugging constructed stylesheets. */ -.__HIGHLIGHT_THEME_ONE_DARK__ { - --__HIGHLIGHT_THEME_ONE_DARK__: 1; -} - :root { --one-dark-base: #282c34; --one-dark-mono-1: #abb2bf; diff --git a/web/src/styles/authentik/base.css b/web/src/styles/authentik/base.css new file mode 100644 index 0000000000..6124b52f0c --- /dev/null +++ b/web/src/styles/authentik/base.css @@ -0,0 +1,20 @@ +@import "./base/common.css"; +@import "./base/scrollbars.css"; +@import "./components/Alert/alert.css"; +@import "./components/Icon/icon.css"; +@import "./components/Tabs/tabs.css"; +@import "./components/Page/page.css"; +@import "./components/Button/button.css"; +@import "./components/Dropdown/dropdown.css"; +@import "./components/Drawer/drawer.css"; +@import "./components/Description/description.css"; +@import "./components/Card/card.css"; +@import "./components/Modal/modal.css"; +@import "./components/Content/content.css"; +@import "./components/Table/table.css"; +@import "./components/Form/form.css"; +@import "./components/Switch/switch.css"; +@import "./components/Select/select.css"; +@import "./components/Nav/nav.css"; +@import "./components/Notification/notification.css"; +@import "./components/Wizard/wizard.css"; diff --git a/web/src/styles/authentik/base/common.css b/web/src/styles/authentik/base/common.css new file mode 100644 index 0000000000..9d08fe89f2 --- /dev/null +++ b/web/src/styles/authentik/base/common.css @@ -0,0 +1,76 @@ +/* #region Screen readers */ + +.sr-only { + position: absolute; + left: -10000px; + top: auto; + width: 1px; + height: 1px; + overflow: hidden; +} + +/* #endregion */ + +/* #region Modifiers */ + +.pf-m-dark { + --pf-global--Color--100: var(--pf-global--Color--dark-100); + --pf-global--Color--200: var(--pf-global--Color--dark-200); + --pf-global--BorderColor--100: var(--pf-global--BorderColor--dark-100); + --pf-global--primary-color--100: var(--pf-global--primary-color--dark-100); + --pf-global--link--Color: var(--pf-global--link--Color--dark); + --pf-global--link--Color--hover: var(--pf-global--link--Color--dark); + --pf-global--BackgroundColor--100: var(--pf-global--BackgroundColor--light-100); +} + +.pf-m-monospace { + font-family: var(--pf-global--FontFamily--monospace); + + &::placeholder { + font-family: var(--pf-global--FontFamily--sans-serif); + } +} + +.pf-m-pressable { + cursor: pointer; + user-select: none; +} + +.pf-m-link { + color: var(--pf-global--link--Color); + text-decoration: none; + + &:hover, + &:focus { + color: var(--pf-global--link--Color--hover); + text-decoration: underline; + } +} + +::placeholder { + font-style: italic; +} + +/* #endregion */ + +/* #region Dark Theme */ + +/** + * Our reversal of Patternfly's light variables requires us to set the colors + * back to their defaults when in dark mode. + */ +:host([theme="dark"]) .pf-m-dark { + --pf-global--Color--100: var(--pf-global--Color--light-100); + --pf-global--Color--200: var(--pf-global--Color--light-200); + --pf-global--BorderColor--100: var(--pf-global--BorderColor--light-100); + --pf-global--primary-color--100: var(--pf-global--primary-color--light-100); + --pf-global--link--Color: var(--pf-global--link--Color--light); + --pf-global--link--Color--hover: var(--pf-global--link--Color--light); + --pf-global--BackgroundColor--100: var(--pf-global--BackgroundColor--dark-100); +} + +:host([theme="dark"]) .pf-u-mb-xl { + color: var(--ak-dark-foreground); +} + +/* #endregion */ diff --git a/web/src/styles/authentik/base/globals.css b/web/src/styles/authentik/base/globals.css new file mode 100644 index 0000000000..154360deee --- /dev/null +++ b/web/src/styles/authentik/base/globals.css @@ -0,0 +1,60 @@ +/* #region Document */ + +html, +body { + height: auto; + min-height: 100dvh; +} + +body { + background-color: var( + --ak-global--background-color, + var(--pf-global--BackgroundColor--150, Canvas) + ); + color: var(--pf-global--Color--100, CanvasText); + &::before { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + + background-size: cover; + background-repeat: no-repeat; + background-position: center; + background-attachment: local; + background-image: none; + z-index: -1; + + @media (min-width: 35rem) and (min-height: 17.5rem) { + background-image: var(--ak-global--background-image, none); + } + } +} + +/* #endregion */ + +html > form > input { + position: absolute; + top: -2000px; + left: -2000px; +} + +/* #region Light Theme */ + +html[data-theme="light"] body { + color-scheme: light; +} + +/* #endregion */ + +/* #region Dark Theme */ + +html[data-theme="dark"] body { + color-scheme: dark; + + &:has(ak-flow-executor) { + background-color: var(--pf-global--BackgroundColor--150); + } +} + +/* #endregion */ diff --git a/web/src/styles/authentik/base/scrollbars.css b/web/src/styles/authentik/base/scrollbars.css new file mode 100644 index 0000000000..ba16c7876c --- /dev/null +++ b/web/src/styles/authentik/base/scrollbars.css @@ -0,0 +1,105 @@ +/** + * Scrollbar colors derived from default user agent styles. + * + * @remarks + * + * Scrollbar colors may be ignored by the browser depending on a few factors: + * + * - A preference for increased contrast + * - A preference for scrollbar visibility + * - The pointer precision available + * - Operating system differences + */ + +/* #region Light theme */ + +:root { + --ak-scrollbar-background-color: hsl(0 0% 98%); + --ak-scrollbar-thumb-background-color: hsl(0 0% 76%); +} + +/* #endregion */ + +/* #region Dark theme */ + +html[data-theme="dark"], +:host([theme="dark"]) { + /** + * Scrollbar colors derived from default user agent styles. + */ + --ak-scrollbar-background-color: hsl(0 0% 18%); + --ak-scrollbar-thumb-background-color: hsl(0 0% 42%); + + /* #endregion */ +} + +/* #endregion */ + +/* #region Scrollbar Styles */ + +/* Applicable to browsers with a WebKit lineage (Chrome, Edge, Safari) */ +::-webkit-scrollbar { + background: var(--ak-scrollbar-background-color); + + /* Emulate thin scrollbar sizing. */ + @media (pointer: fine) { + max-height: 12px; + max-width: 12px; + } +} + +/* Necessary to avoid background color mismatch. */ +::-webkit-scrollbar-track { + background: var(--ak-scrollbar-background-color); +} + +::-webkit-scrollbar-thumb { + background: var(--ak-scrollbar-thumb-background-color); + /* Applies consistent shape across browsers and platforms */ + border: 3px solid transparent; + border-radius: 8px; + background-clip: content-box; + + /* Emulate thin scrollbar sizing. */ + @media (pointer: fine) { + border: 3px solid transparent; + border-radius: 8px; + } +} + +/** + * Hides arrow buttons on the scrollbar. + * Not applicable to Edge when scrollbars are set to always visible. + */ +::-webkit-scrollbar-button { + display: none; + height: 0; + width: 0; +} + +@supports (scrollbar-color: auto) { + :root { + /** + * Applies consistent colors across browsers and platforms. + * Not applicable when thin scrollbars are preferred. + */ + scrollbar-color: var(--ak-scrollbar-thumb-background-color) + var(--ak-scrollbar-background-color); + } +} + +@supports (scrollbar-width: thin) { + .pf-c-page__main, + .pf-c-nav__list, + .pf-c-card__body { + scrollbar-width: thin; + + @media (pointer: coarse) { + /** + * Avoids issues on touch devices where thin scrollbars + * are difficult to interact with. + */ + scrollbar-width: auto; + } + } +} diff --git a/web/src/styles/authentik/base/variables.css b/web/src/styles/authentik/base/variables.css new file mode 100644 index 0000000000..23a38aa49c --- /dev/null +++ b/web/src/styles/authentik/base/variables.css @@ -0,0 +1,69 @@ +/* #region Root */ +:root { + --ak-accent: #fd4b2d; + + --ak-dark-foreground: #fafafa; + --ak-dark-background: #18191a; + --ak-dark-background-light: #1c1e21; + --ak-dark-background-lighter: #2b2e33; + + --ak-global--background-contrast: var(--pf-global--Color--100); + + /* Minimum width after which the sidebar becomes automatic */ + --ak-sidebar--minimum-auto-width: 80rem; + + --pf-global--disabled-color--100: GrayText; + --pf-global--disabled-color--200: color-mix(in srgb, GrayText 100%, transparent 100%); + --pf-global--disabled-color--300: color-mix(in srgb, GrayText 75%, Canvas 100%); +} + +/* #endregion */ + +/* #region Dark theme */ + +html[data-theme="dark"] { + --pf-global--Color--100: var(--pf-global--Color--light-100) !important; + --pf-global--Color--200: var(--pf-global--Color--light-200) !important; + --pf-global--Color--300: var(--pf-global--Color--light-300) !important; + + /* TODO: We've seemed to have drifted from PF's dark-100 usage. Revisit this after PF 5. */ + --pf-global--BackgroundColor--100: var(--pf-global--BackgroundColor--dark-100) !important; + + --pf-global--BackgroundColor--dark-100: #18191a !important; + --pf-global--BackgroundColor--150: var(--pf-global--BackgroundColor--dark-100) !important; + --pf-global--BackgroundColor--200: var(--pf-global--BackgroundColor--dark-200) !important; + --pf-global--BackgroundColor--300: var(--pf-global--BackgroundColor--dark-300) !important; + + --pf-global--BorderColor--100: var(--pf-global--palette--black-800) !important; + --pf-global--BorderColor--200: var(--pf-global--palette--black-500) !important; + --pf-global--BorderColor--300: var(--pf-global--palette--black-300) !important; + + --pf-global--link--Color: var(--pf-global--link--Color--light) !important; + --pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover) !important; + + --pf-global--disabled-color--100: color-mix(in srgb, GrayText 100%, CanvasText 25%); + --pf-global--disabled-color--200: color-mix(in srgb, GrayText 10%, transparent 100%); + + /* #region Dispositions */ + + /* TODO: Revisit this after PF 5. */ + + --pf-global--default-color--100: var(--pf-global--palette--cyan-100); + --pf-global--default-color--300: var(--pf-global--palette--cyan-200); + + --pf-global--info-color--100: var(--pf-global--palette--blue-200); + --pf-global--info-color--200: var(--pf-global--palette--blue-50); + + --pf-global--warning-color--100: var(--pf-global--palette--gold-300); + --pf-global--warning-color--200: var(--pf-global--palette--gold-50); + + --pf-global--danger-color--100: var(--pf-global--palette--red-100); + --pf-global--danger-color--200: var(--pf-global--palette--red-50); + + --pf-global--success-color--100: var(--pf-global--palette--green-300); + --pf-global--success-color--200: var(--pf-global--palette--green-50); + + /* #endregion */ +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Alert/alert.css b/web/src/styles/authentik/components/Alert/alert.css new file mode 100644 index 0000000000..b54ebb0b9a --- /dev/null +++ b/web/src/styles/authentik/components/Alert/alert.css @@ -0,0 +1,11 @@ +:host([theme="dark"]) .pf-c-alert { + --pf-global--Color--100: var(--pf-global--Color--light-100); + --pf-global--Color--200: var(--pf-global--Color--light-200); + --pf-global--BorderColor--100: var(--pf-global--BorderColor--light-100); + --pf-global--primary-color--100: var(--pf-global--primary-color--light-100); + --pf-global--link--Color: var(--pf-global--link--Color--light); + --pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover); + --pf-global--BackgroundColor--100: var(--pf-global--BackgroundColor--dark-100); + + --pf-c-alert--BackgroundColor: var(--pf-global--BackgroundColor--300); +} diff --git a/web/src/styles/authentik/components/Button/button.css b/web/src/styles/authentik/components/Button/button.css new file mode 100644 index 0000000000..f6846f913e --- /dev/null +++ b/web/src/styles/authentik/components/Button/button.css @@ -0,0 +1,82 @@ +.pf-c-button { + --pf-c-button--hover--after--BorderWidth: var(--pf-global--BorderWidth--sm); + --pf-c-button--active--after--BorderWidth: var(--pf-global--BorderWidth--sm); + --pf-c-button--focus--after--BorderWidth: var(--pf-global--BorderWidth--sm); + + &.pf-m-danger { + /** + * TODO: PF4 doesn't seem to have disabled styles for danger buttons in the + * way that we've come to expect. This is a workaround until PF5 is adopted. + */ + --pf-global--disabled-color--100: var(--pf-global--danger-color--100); + --pf-global--disabled-color--200: var(--pf-global--palette--red-50); + --pf-global--disabled-color--300: var(--pf-global--danger-color--300); + } +} + +a.pf-c-button { + &.pf-m-plain, + &.pf-m-secondary { + &:hover { + text-decoration: underline; + } + } +} + +/* #region Dark Theme */ + +[data-theme="dark"] .pf-c-button, +:host([theme="dark"]) .pf-c-button { + --pf-c-button--m-secondary--hover--Color: var(--pf-global--primary-color--200); + --pf-c-button--m-secondary--hover--after--BorderColor: var(--pf-global--primary-color--200); + + /* --pf-c-button--disabled--BackgroundColor: var(--pf-global--Color--300); */ + --pf-c-button--m-plain--hover--BackgroundColor: var(--ak-dark-background-lighter); + --pf-c-button--m-plain--active--BackgroundColor: var(--ak-dark-background-lighter); + --pf-c-button--m-plain--focus--BackgroundColor: var(--ak-dark-background-lighter); + + &.pf-m-plain { + /* --pf-c-button--disabled--BackgroundColor: transparent; */ + --pf-c-button--m-plain--hover--Color: var(--ak-dark-foreground); + --pf-c-button--m-plain--active--BackgroundColor: var(--ak-dark-background-lighter); + --pf-c-button--m-plain--focus--Color: var(--pf-global--Color--200); + + &:focus:hover { + color: var(--pf-c-button--m-plain--hover--Color); + } + } + + &.pf-m-secondary { + --pf-global--primary-color--200: var(--pf-global--primary-color--light-100); + } + + &.pf-m-control { + --pf-c-button--after--BorderColor: var(--ak-dark-background-lighter) + var(--ak-dark-background-lighter) + var(--pf-c-button--m-control--after--BorderBottomColor) + var(--ak-dark-background-lighter); + background-color: var(--ak-dark-background-light); + color: var(--ak-dark-foreground); + } + + &.pf-m-tertiary { + --pf-c-button--m-tertiary--Color: var(--pf-global--Color--400); + --pf-c-button--hover--after--BorderWidth: var(--pf-global--BorderWidth--sm); + --pf-c-button--active--after--BorderWidth: var(--pf-global--BorderWidth--sm); + + --pf-c-button--m-tertiary--after--BorderColor: var(--pf-global--Color--200); + --pf-c-button--m-tertiary--hover--after--BorderColor: var(--pf-global--Color--300); + --pf-c-button--m-tertiary--hover--Color: var(--pf-global--Color--100); + + &:hover { + --pf-c-button--m-tertiary--Color: var(--pf-c-button--m-tertiary--hover--Color); + } + } + + &.pf-m-danger { + --pf-global--danger-color--200: var(--pf-global--palette--red-200); + + --pf-global--disabled-color--200: var(--pf-global--palette--red-400); + --pf-global--disabled-color--100: var(--pf-global--palette--red-200); + } +} diff --git a/web/src/styles/authentik/components/Card/card.css b/web/src/styles/authentik/components/Card/card.css new file mode 100644 index 0000000000..ceeb388e68 --- /dev/null +++ b/web/src/styles/authentik/components/Card/card.css @@ -0,0 +1,37 @@ +.pf-c-card > fieldset { + margin-inline: var(--pf-global--spacer--md); + margin-block-end: var(--pf-global--spacer--md); + + @media not (prefers-contrast: more) { + --ak-legend-margin-inline-start: calc( + var(--pf-c-card--child--PaddingLeft) - var(--ak-legend-padding-inline-base) + ); + --ak-legend-margin-inline-end: calc( + var(--pf-c-card--child--PaddingRight) - var(--ak-legend-padding-inline-base) + ); + + border-width: 0; + padding: 0 !important; + margin: 0 !important; + + legend { + padding-block-start: var(--pf-c-card--first-child--PaddingTop) !important; + padding-block-end: var(--pf-c-card__title--not--last-child--PaddingBottom) !important; + line-height: var(--pf-global--LineHeight--md); + } + } +} + +/* #region Dark Theme */ + +[data-theme="dark"] .pf-c-card, +:host([theme="dark"]) .pf-c-card { + --pf-c-card--BackgroundColor: var(--ak-dark-background-light); + --pf-c-card--m-non-selectable-raised--BackgroundColor: var( + --pf-global--BackgroundColor--dark-200 + ); + + --pf-c-card--m-non-selectable-raised--before--BackgroundColor: var( + --pf-global--info-color--100 + ); +} diff --git a/web/src/styles/authentik/components/Content/content.css b/web/src/styles/authentik/components/Content/content.css new file mode 100644 index 0000000000..a87ae8158a --- /dev/null +++ b/web/src/styles/authentik/components/Content/content.css @@ -0,0 +1,12 @@ +.pf-c-content h1 { + display: flex; + align-items: flex-start; +} + +.pf-c-content h1 i { + font-style: normal; +} + +.pf-c-content h1 :first-child { + margin-right: var(--pf-global--spacer--sm); +} diff --git a/web/src/styles/authentik/components/Description/description.css b/web/src/styles/authentik/components/Description/description.css new file mode 100644 index 0000000000..5855b3f253 --- /dev/null +++ b/web/src/styles/authentik/components/Description/description.css @@ -0,0 +1,4 @@ +.pf-c-description-list__description .pf-c-button { + margin-right: 6px; + margin-bottom: 6px; +} diff --git a/web/src/styles/authentik/components/Drawer/drawer.css b/web/src/styles/authentik/components/Drawer/drawer.css new file mode 100644 index 0000000000..b28e2eb18c --- /dev/null +++ b/web/src/styles/authentik/components/Drawer/drawer.css @@ -0,0 +1,19 @@ +.pf-c-drawer__body { + display: flex; + flex-flow: column; +} + +.pf-c-drawer__content { + --pf-c-drawer__content--BackgroundColor: transparent; +} + +/* #region Dark Theme */ + +[data-theme="dark"] .pf-c-drawer, +:host([theme="dark"]) .pf-c-drawer { + .pf-c-drawer__panel { + background-color: var(--ak-dark-background); + } +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Dropdown/dropdown.css b/web/src/styles/authentik/components/Dropdown/dropdown.css new file mode 100644 index 0000000000..f24da5fb2e --- /dev/null +++ b/web/src/styles/authentik/components/Dropdown/dropdown.css @@ -0,0 +1,28 @@ +.pf-c-dropdown__menu-item[role="contentinfo"] { + &, + &:hover, + &:focus { + --pf-c-dropdown__menu-item--hover--Color: initial; + --pf-c-dropdown__menu-item--hover--BackgroundColor: initial; + } +} + +/* #region Dark Theme */ + +[data-theme="dark"] .pf-c-dropdown, +:host([theme="dark"]) .pf-c-dropdown { + .pf-c-dropdown__toggle::before { + border-color: transparent; + } + + .pf-c-dropdown__menu { + --pf-c-dropdown__menu--BackgroundColor: var(--pf-global--BackgroundColor--dark-100); + --pf-c-dropdown__menu-item--Color: var(--pf-global--Color--light-100); + --pf-c-dropdown__menu-item--hover--BackgroundColor: var( + --pf-global--BackgroundColor--dark-300 + ); + --pf-c-dropdown__menu-item--hover--Color: var(--pf-global--Color--light-100); + } +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Form/form.css b/web/src/styles/authentik/components/Form/form.css new file mode 100644 index 0000000000..e920163a0f --- /dev/null +++ b/web/src/styles/authentik/components/Form/form.css @@ -0,0 +1,229 @@ +.pf-c-form__group { + column-gap: var(--pf-global--spacer--md); +} + +.pf-c-form__group-label { + display: flex; + user-select: none; + padding-top: var(--pf-c-form--m-horizontal__group-label--md--PaddingTop); + + /* Increase the pressable area of the label. */ + .pf-c-form__label { + display: block; + flex: 1 1 auto; + } +} + +.pf-c-form__label[aria-required="true"] .pf-c-form__label-text::after { + content: "*" / ""; + user-select: none; + margin-left: var(--pf-c-form__label-required--MarginLeft); + font-size: var(--pf-c-form__label-required--FontSize); + color: var(--pf-c-form__label-required--Color); +} + +/* #endregion */ + +/* #region Form controls */ + +.pf-c-form-control { + --pf-c-form-control--BorderTopColor: transparent !important; + --pf-c-form-control--BorderRightColor: transparent !important; + --pf-c-form-control--BorderLeftColor: transparent !important; + + --pf-c-form-control--readonly--BackgroundColor: var(--pf-global--BackgroundColor--200); + --pf-c-form-control--disabled--BackgroundColor: var(--pf-global--BackgroundColor--200); + + /* #region Caps Lock */ + + --pf-c-form-control--m-caps-lock--BackgroundUrl: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23aaabac' viewBox='0 0 56 56' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M 20.7812 37.6211 L 35.2421 37.6211 C 38.5233 37.6211 40.2577 35.6992 40.2577 32.6055 L 40.2577 28.4570 L 49.1404 28.4570 C 51.0859 28.4570 52.6329 27.3086 52.6329 25.5039 C 52.6329 24.4024 52.0703 23.5351 51.0158 22.6211 L 30.9062 4.8789 C 29.9452 4.0351 29.0546 3.4727 27.9999 3.4727 C 26.9687 3.4727 26.0780 4.0351 25.1171 4.8789 L 4.9843 22.6445 C 3.8828 23.6055 3.3671 24.4024 3.3671 25.5039 C 3.3671 27.3086 4.9140 28.4570 6.8828 28.4570 L 15.7421 28.4570 L 15.7421 32.6055 C 15.7421 35.6992 17.4999 37.6211 20.7812 37.6211 Z M 21.1562 34.0820 C 20.2655 34.0820 19.6562 33.4961 19.6562 32.6055 L 19.6562 25.7149 C 19.6562 25.1524 19.4452 24.9180 18.8828 24.9180 L 8.6640 24.9180 C 8.4999 24.9180 8.4296 24.8476 8.4296 24.7305 C 8.4296 24.6367 8.4530 24.5430 8.5702 24.4492 L 27.5077 7.9961 C 27.7187 7.8086 27.8359 7.7383 27.9999 7.7383 C 28.1640 7.7383 28.3046 7.8086 28.4921 7.9961 L 47.4532 24.4492 C 47.5703 24.5430 47.5939 24.6367 47.5939 24.7305 C 47.5939 24.8476 47.4998 24.9180 47.3356 24.9180 L 37.1406 24.9180 C 36.5780 24.9180 36.3671 25.1524 36.3671 25.7149 L 36.3671 32.6055 C 36.3671 33.4727 35.7109 34.0820 34.8671 34.0820 Z M 19.7733 52.5273 L 36.0624 52.5273 C 38.7577 52.5273 40.3046 51.0273 40.3046 48.3086 L 40.3046 44.9336 C 40.3046 42.2148 38.7577 40.6680 36.0624 40.6680 L 19.7733 40.6680 C 17.0546 40.6680 15.5077 42.2383 15.5077 44.9336 L 15.5077 48.3086 C 15.5077 51.0039 17.0546 52.5273 19.7733 52.5273 Z M 20.3124 49.2227 C 19.4921 49.2227 19.0468 48.8008 19.0468 47.9805 L 19.0468 45.2617 C 19.0468 44.4414 19.4921 43.9727 20.3124 43.9727 L 35.5233 43.9727 C 36.3202 43.9727 36.7655 44.4414 36.7655 45.2617 L 36.7655 47.9805 C 36.7655 48.8008 36.3202 49.2227 35.5233 49.2227 Z'/%3E%3C/svg%3E"); + + &.pf-m-icon.pf-m-caps-lock { + --pf-c-form-control--m-icon--BackgroundUrl: var( + --pf-c-form-control--m-caps-lock--BackgroundUrl + ); + } + + /* #endregion */ +} + +.pf-c-form__helper-text { + text-wrap: pretty; +} + +/* #region Fields */ + +fieldset { + --ak-fieldset-border-width: thin; + --ak-fieldset-border-color: var(--pf-global--BackgroundColor--light-100); + --ak-legend-margin-inline-base: var(--pf-global--spacer--sm); + --ak-legend-padding-inline-base: var(--pf-global--spacer--sm); + + border-color: var(--ak-fieldset-border-color); + border-width: var(--ak-fieldset-border-width); + + padding: var(--ak-legend-padding-inline-base) !important; + + @media (prefers-contrast: more) { + --ak-fieldset-border-color: var(--pf-global--BorderColor--200); + } + + @media (prefers-contrast: less) { + --ak-fieldset-border-color: transparent; + } + + & > legend { + line-height: 1; + padding: var(--ak-legend-padding-inline-base) !important; + margin-inline-start: var( + --ak-legend-margin-inline-start, + var(--ak-legend-margin-inline-base) + ) !important; + margin-inline-end: var( + --ak-legend-margin-inline-end, + var(--ak-legend-margin-inline-base) + ) !important; + } + + &:has(legend.sr-only) { + border-width: 0; + + &:not(.pf-c-modal-box__footer) { + --ak-legend-padding-inline-base: 0; + --ak-legend-margin-inline-base: 0; + } + } + + &.pf-c-form__group { + border-radius: var(--pf-global--BorderRadius--sm); + } + + &.pf-c-form__group { + display: flex; + flex-wrap: wrap; + + &.pf-m-action { + gap: var(--pf-global--spacer--md) var(--pf-global--spacer--sm); + margin-block-start: 0; + } + } + + &.pf-c-login__main-footer-band { + & > *:last-child { + padding-block-end: var(--pf-c-login__main-footer-band-item--PaddingTop); + } + } + + &.pf-c-modal-box__footer { + --ak-legend-padding-inline-base: var(--pf-global--spacer--md); + padding-block: calc(var(--ak-legend-padding-inline-base) / 2); + border-inline: none; + border-block-end: none; + + --pf-c-modal-box__footer--c-button--sm--MarginRight: var( + --pf-c-modal-box__footer--c-button--MarginRight + ); + + & > ak-spinner-button:not(:last-child) { + margin-right: var(--pf-c-modal-box__footer--c-button--MarginRight); + } + } +} + +/* #region Dark Theme */ + +[data-theme="dark"], +:host([theme="dark"]) { + /* #region Inputs */ + + optgroup, + option { + color: var(--ak-dark-foreground); + } + select[multiple] optgroup:checked, + select[multiple] option:checked { + color: var(--ak-dark-background); + } + + .pf-c-input-group:not(:has(.pf-c-switch)) { + --pf-c-input-group--BackgroundColor: transparent; + + --pf-global--Color--100: var(--pf-global--Color--light-100); + --pf-global--Color--200: var(--pf-global--Color--light-200); + --pf-global--BorderColor--100: var(--pf-global--BorderColor--light-100); + --pf-global--primary-color--100: var(--pf-global--primary-color--light-100); + --pf-global--link--Color: var(--pf-global--link--Color--light); + --pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover); + --pf-global--BackgroundColor--100: var(--pf-global--BackgroundColor--dark-100); + } + + select.pf-c-form-control { + --pf-c-form-control__select--BackgroundUrl: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3Cpath fill='%23fafafa' d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'/%3E%3C/svg%3E"); + } + + .pf-c-form-control { + /* !important is required to ensure priority when in compatibility mode. */ + + --pf-global--Color--100: var(--pf-global--Color--light-100) !important; + --pf-global--Color--200: var(--pf-global--Color--light-200) !important; + --pf-global--BackgroundColor--100: var(--pf-global--BackgroundColor--dark-100) !important; + --pf-global--BackgroundColor--200: var(--pf-global--BackgroundColor--dark-200) !important; + --pf-global--BorderColor--100: var(--pf-global--BorderColor--light-100) !important; + --pf-global--primary-color--100: var(--pf-global--primary-color--light-100) !important; + --pf-global--link--Color: var(--pf-global--link--Color--light) !important; + --pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover) !important; + + --pf-c-form-control--BackgroundColor: var(--ak-dark-background-light) !important; + + --pf-c-form-control--readonly--BackgroundColor: var(--ak-dark-background-light) !important; + --pf-c-form-control--disabled--BackgroundColor: var(--ak-dark-background-light) !important; + } + + .form-control-static { + color: var(--ak-dark-foreground); + } + + /* #endregion */ + + /* #region Fields */ + + .pf-c-form__field-group-header-title-text { + color: var(--ak-dark-foreground); + } + + .pf-c-form__field-group { + border-bottom: 0; + } + + fieldset { + --ak-fieldset-border-color: var(--pf-global--BackgroundColor--dark-transparent-200); + + @media (prefers-contrast: more) { + --ak-fieldset-border-color: var(--pf-global--BorderColor--300); + } + + @media (prefers-contrast: less) { + --ak-fieldset-border-color: transparent; + } + } + + /* #endregion */ + + /* #region Radio */ + + .pf-c-radio { + --pf-c-radio__label--Color: var(--ak-dark-foreground); + } + + /* #endregion */ + + .pf-c-form__label-text { + color: var(--ak-dark-foreground); + } + + .pf-c-check__label { + color: var(--ak-dark-foreground); + } +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Icon/icon.css b/web/src/styles/authentik/components/Icon/icon.css new file mode 100644 index 0000000000..e415c5b6ec --- /dev/null +++ b/web/src/styles/authentik/components/Icon/icon.css @@ -0,0 +1,27 @@ +.pf-icon { + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; + vertical-align: middle; + + &:has(+ span) { + margin-inline-end: 0.5em; + } +} +.pf-icon, +.fa, +.fas { + &.pf-m-success { + color: var(--pf-global--success-color--100); + } + + &.pf-m-warning { + color: var(--pf-global--warning-color--100); + } + + &.pf-m-danger { + color: var(--pf-global--danger-color--100); + } +} diff --git a/web/src/styles/authentik/components/Login/login.css b/web/src/styles/authentik/components/Login/login.css new file mode 100644 index 0000000000..cbcb8e888a --- /dev/null +++ b/web/src/styles/authentik/components/Login/login.css @@ -0,0 +1,289 @@ +/* #region Login Component */ + +/* compatibility-mode-fix */ +.pf-c-login.pf-c-login { + --ak-login--padding-max: 8dvw; + --ak-login--padding: clamp( + var(--pf-global--spacer--md), + var(--pf-global--spacer--2xl), + var(--ak-login--padding-max) + ); + + --ak-login--header-min-height: var(--pf-global--spacer--lg); + --ak-login--header-max-height: clamp(0, 45%, 15dvh); + + --ak-login--footer-padding-block: var(--pf-global--spacer--md); + + --ak-login--max-width: 35rem; + --ak-login--main-column-width: minmax( + min(100dvw, var(--ak-login--max-width)), + var(--ak-login--max-width) + ); + + --pf-c-login__main-body--PaddingBottom: 0; + --pf-c-login__main-footer--PaddingBottom: clamp( + var(--pf-global--spacer--xs), + 6dvw, + var(--pf-global--spacer--xl) + ); + + flex: 1 1 auto; + + padding: 0; + + position: relative; + display: grid; + justify-content: space-between; + min-height: 100dvh; + + grid-template-rows: + [header] minmax(clamp(0.5rem, 15dvh, 3dvh), auto) + [main] minmax(auto, min-content) + [footer] minmax(min-content, auto); + + grid-template-columns: + 1fr + [main] var(--ak-login--main-column-width) + 1fr; + + grid-template-areas: + "header header header" + " . main . " + "footer footer footer"; + + &::before { + display: block; + content: ""; + background-color: var(--ak-login--background-color-overlay, transparent); + z-index: -1; + height: 100%; + pointer-events: none; + } + + &::before, + .pf-c-login__overlay { + grid-row: header / footer; + grid-column: header; + } + + &::after { + display: block; + content: ""; + grid-area: main; + z-index: -1; + height: 75dvh; + pointer-events: none; + } + + @media (max-width: 35rem) or (max-height: 17.5rem) { + --ak-login--background-color-overlay: var(--pf-c-login__main--BackgroundColor); + } +} + +[data-theme="dark"] .pf-c-login, +:host([theme="dark"]) .pf-c-login { + --pf-c-login__main--BackgroundColor: var(--pf-global--BackgroundColor--150); +} + +/* #region Page Header */ + +.pf-c-login__header { + grid-area: header; + padding-inline: calc(var(--ak-login--padding) / 2); + align-self: start; + + display: grid; + grid-template-columns: 1fr; +} + +/* #endregion */ + +/* #region Page Footer */ + +/* compatibility-mode-fix */ +.pf-c-login__main-footer .pf-c-button__icon { + vertical-align: middle; +} + +/* compatibility-mode-fix */ +.pf-c-login__main-footer .source-button { + display: flex; + align-items: center; + justify-content: center; + gap: var(--pf-global--spacer--sm); +} + +/* #region Card Main */ + +.pf-c-login__main { + --pf-c-login__container--PaddingLeft: 0 !important; + --pf-c-login__container--PaddingRight: 0 !important; + + grid-area: main; + margin: 0; + + position: relative; + max-width: var(--ak-login--max-width); + min-height: calc(var(--ak-login--max-width) * 0.8); + + display: flex; + flex-flow: column; + justify-content: space-between; + + .slotted-content { + position: relative; + flex: 1 1 auto; + } +} + +/* #endregion */ + +/* #region Main Header */ + +.pf-c-login__main-header { + padding-inline: var(--ak-login--padding); + padding-block: clamp(var(--pf-global--spacer--xs), 6dvw, var(--pf-global--spacer--lg)); + + .pf-c-title { + font-size: clamp(1rem, var(--pf-c-title--m-3xl--FontSize), 7dvw); + } +} + +.pf-c-login__main-header.pf-c-brand { + --ak-login--main-padding-block-start: clamp( + var(--pf-global--spacer--xs), + 7dvw, + var(--pf-global--spacer--2xl) + ); + + padding-inline: calc(var(--ak-login--padding) / 4); + padding-block-start: calc( + var(--ak-login--main-padding-block-start) - var(--ak-login--footer-padding-block) + ); + + padding-block-end: calc(var(--ak-login--padding) / 2); + + display: flex; + justify-content: center; + + .branding-logo { + display: block; + width: clamp(75%, calc(var(--ak-login--max-width) / 2), 90%); + min-height: 4rem; + } +} + +.pf-c-login__main-body { + flex: 1 1 auto; + padding-inline: var(--ak-login--padding); +} + +/* #endregion */ + +/* #region Main Footer */ + +.pf-c-login__main-footer-band { + @media (max-width: 35rem) or (max-height: 17.5rem) { + --pf-c-login__main-footer-band--BackgroundColor: transparent !important; + } +} + +[data-theme="dark"] .pf-c-login__main-footer-band, +:host([theme="dark"]) .pf-c-login__main-footer-band { + --pf-c-login__main-footer-band--BackgroundColor: var(--pf-global--BackgroundColor--dark-300); +} + +/* #endregion */ + +/* #region Layout variations */ + +@media (min-width: 70rem) and (min-height: 17.5rem) { + .pf-c-login[data-layout="content_left"], + .pf-c-login[data-layout="content_right"] { + display: flex; + flex-flow: row wrap; + + place-content: space-between; + gap: var(--pf-global--spacer--lg); + + .pf-c-login__main, + .pf-c-login__footer { + align-self: center; + } + } + + .pf-c-login[data-layout="content_right"] { + flex-direction: row-reverse; + } + + .pf-c-login[data-layout="sidebar_left"], + .pf-c-login[data-layout="sidebar_right"] { + --ak-login--max-width: 36rem; + --ak-login--background-color-overlay: var(--pf-c-login__main--BackgroundColor); + .pf-c-login__main { + height: 100%; + justify-content: normal; + } + + .pf-c-login__footer { + color: inherit; + flex: 1 1 auto; + justify-content: end; + width: 100%; + } + } + + .pf-c-login[data-layout="sidebar_left"] { + grid-template-columns: [main footer] var(--ak-login--main-column-width) repeat(2, 1fr); + + grid-template-areas: + "header . ." + "main . ." + "footer . ."; + } + + .pf-c-login[data-layout="sidebar_right"] { + grid-template-columns: repeat(2, 1fr) var(--ak-login--main-column-width) [main footer]; + + grid-template-areas: + ". . header" + ". . main " + ". . footer"; + } + + .pf-c-login__main-footer-band { + --pf-c-login__main-footer-band--BackgroundColor: transparent !important; + } +} + +/* #endregion */ + +.pf-c-login__footer { + --pf-global--Color--100: var(--pf-global--Color--light-100) !important; + min-height: var(--pf-global--spacer--2xl); + grid-area: footer; + flex: 0 0 auto; + display: flex; + flex-direction: column; + align-self: end; + justify-content: center; + padding-inline: var(--pf-global--spacer--2xl); + padding-block: var(--ak-login--footer-padding-block) !important; + + ul.pf-c-list.pf-m-inline { + justify-content: center; + padding: 0; + padding-block-start: calc(var(--pf-global--spacer--xs) / 2); + + column-gap: var(--pf-global--spacer--xl); + row-gap: var(--pf-global--spacer--md); + } + + @media (max-width: 35rem) { + color: var(--pf-global--Color--300); + } +} + +/* #endregion */ + +/* #region Dark Theme */ diff --git a/web/src/styles/authentik/components/Modal/modal.css b/web/src/styles/authentik/components/Modal/modal.css new file mode 100644 index 0000000000..523b4c1e46 --- /dev/null +++ b/web/src/styles/authentik/components/Modal/modal.css @@ -0,0 +1,14 @@ +/* #region Dark Theme */ + +[data-theme="dark"] .pf-c-modal-box, +:host([theme="dark"]) .pf-c-modal-box { + background-color: var(--ak-dark-background); + + .pf-c-modal-box__header, + .pf-c-modal-box__footer, + .pf-c-modal-box__body { + background-color: var(--ak-dark-background); + } +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Nav/nav.css b/web/src/styles/authentik/components/Nav/nav.css new file mode 100644 index 0000000000..4c28e281bd --- /dev/null +++ b/web/src/styles/authentik/components/Nav/nav.css @@ -0,0 +1,14 @@ +/* #region Dark Theme */ + +.pf-c-nav { + --pf-c-nav__link--PaddingTop: 0.5rem; + --pf-c-nav__link--PaddingRight: 0.5rem; + --pf-c-nav__link--PaddingBottom: 0.5rem; + --pf-c-nav__link--PaddingLeft: 0.5rem; +} + +:host([theme="dark"]) .pf-c-nav { + background-color: var(--ak-dark-background-light); +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Notification/notification.css b/web/src/styles/authentik/components/Notification/notification.css new file mode 100644 index 0000000000..1ee14b6f6d --- /dev/null +++ b/web/src/styles/authentik/components/Notification/notification.css @@ -0,0 +1,24 @@ +.pf-c-notification-drawer { + --pf-c-notification-drawer--BackgroundColor: var(--pf-global--BackgroundColor--150); +} + +/* #region Dark Theme */ + +[data-theme="dark"] .pf-c-notification-drawer, +:host([theme="dark"]) .pf-c-notification-drawer { + --pf-c-notification-drawer--BackgroundColor: var(--ak-dark-background) !important; + --pf-c-notification-drawer__header--BackgroundColor: var( + --ak-dark-background-lighter + ) !important; +} + +.pf-c-notification-drawer__header { + color: var(--ak-dark-foreground); +} + +.pf-c-notification-drawer__list-item { + color: var(--ak-dark-foreground); + --pf-c-notification-drawer__list-item--BorderBottomColor: var(--pf-global--Color--dark-200); +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Page/page.css b/web/src/styles/authentik/components/Page/page.css new file mode 100644 index 0000000000..793d3e06aa --- /dev/null +++ b/web/src/styles/authentik/components/Page/page.css @@ -0,0 +1,35 @@ +/** + * We reverse the page header colors because PatternFly assumes a dark header + * on light themes. The opposite must be done for dark themes. + */ +.pf-c-page__header { + --pf-global--Color--100: var(--pf-global--Color--dark-100); + --pf-global--Color--200: var(--pf-global--Color--dark-200); + --pf-global--BorderColor--100: var(--pf-global--BorderColor--dark-100); + --pf-global--primary-color--100: var(--pf-global--primary-color--dark-100); + --pf-global--link--Color: var(--pf-global--link--Color--dark); + --pf-global--link--Color--hover: var(--pf-global--link--Color--dark); + --pf-global--BackgroundColor--100: transparent; +} + +/* #region Dark Theme */ + +[data-theme="dark"] .pf-c-page, +:host([theme="dark"]) .pf-c-page { + --pf-c-page--BackgroundColor: var(--pf-global--BackgroundColor--dark-100); + --pf-c-page__main-section--BackgroundColor: var(--pf-global--BackgroundColor--dark-100); +} + +/** + * cf. Color reversal. + */ +.pf-c-page__header { + --pf-global--Color--100: var(--pf-global--Color--light-100); + --pf-global--Color--200: var(--pf-global--Color--light-200); + --pf-global--BorderColor--100: var(--pf-global--BorderColor--light-100); + --pf-global--primary-color--100: var(--pf-global--primary-color--light-100); + --pf-global--link--Color: var(--pf-global--link--Color--light); + --pf-global--link--Color--hover: var(--pf-global--link--Color--light); +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Select/select.css b/web/src/styles/authentik/components/Select/select.css new file mode 100644 index 0000000000..f24496ecef --- /dev/null +++ b/web/src/styles/authentik/components/Select/select.css @@ -0,0 +1,13 @@ +.pf-c-select { + --pf-c-select__toggle--before--BorderTopColor: transparent; + --pf-c-select__toggle--before--BorderRightColor: transparent; + --pf-c-select__toggle--before--BorderLeftColor: transparent; +} + +/* #region Dark Theme */ + +:host([theme="dark"]) .pf-c-select { + --pf-global--BackgroundColor--100: var(--ak-dark-background-light); +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Switch/switch.css b/web/src/styles/authentik/components/Switch/switch.css new file mode 100644 index 0000000000..ecc6dba228 --- /dev/null +++ b/web/src/styles/authentik/components/Switch/switch.css @@ -0,0 +1,22 @@ +.pf-c-switch { + --pf-c-switch__input--focus__toggle--OutlineWidth: 0; + + &:hover { + --pf-c-switch__toggle--before--BackgroundColor: var(--pf-global--BackgroundColor--200); + } +} + +.pf-c-switch__label { + --pf-c-switch__input--not-checked__label--Color: var(--pf-global--Color--100); + user-select: none; +} + +/* #region Dark Theme */ + +[data-theme="dark"] .pf-c-switch__label, +:host([theme="dark"]) .pf-c-switch__label { + --pf-c-switch__input--not-checked__label--Color: var(--ak-dark-foreground); + --pf-c-switch__input--checked__label--Color: var(--ak-dark-foreground); +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Table/table.css b/web/src/styles/authentik/components/Table/table.css new file mode 100644 index 0000000000..89051ac5ed --- /dev/null +++ b/web/src/styles/authentik/components/Table/table.css @@ -0,0 +1,69 @@ +.pf-c-table { + --pf-c-table__sort__button__text--Color: var(--pf-global--Color--100); + --pf-c-table__button--hover--Color: var(--pf-global--link--Color--hover); +} + +.pf-c-toolbar { + .pf-c-button.pf-m-danger:disabled { + opacity: var(--ak-table--toolbar--bulk-delete-disabled-opacity, 0.5); + } + + @media (prefers-contrast: more) { + --ak-table--toolbar--bulk-delete-disabled-opacity: 0.75; + } +} + +.pf-c-table__sort.pf-m-selected { + text-decoration: underline; + + .pf-c-table__button .pf-c-table__text { + --pf-c-table__sort__button__text--Color: var(--pf-global--Color--100); + + --pf-c-table__button--hover--Color: var(--pf-global--link--Color--hover); + } +} + +/* #region Dark Theme */ + +:host([theme="dark"]) .pf-c-toolbar { + --pf-c-toolbar--BackgroundColor: var(--ak-dark-background-light); +} + +:host([theme="dark"]) .pf-c-pagination { + --pf-c-pagination--m-bottom--BackgroundColor: var(--ak-dark-background-light); +} + +:host([theme="dark"]) .pf-c-table { + --pf-global--BackgroundColor--100: var(--pf-global--BackgroundColor--dark-300); + --pf-global--Color--100: var(--ak-dark-foreground); + --pf-c-table--BackgroundColor: var(--ak-dark-background-light); + --pf-c-table--BorderColor: var(--ak-dark-background-lighter); + --pf-c-table--cell--Color: var(--ak-dark-foreground); + --pf-c-table--tr--m-hoverable--active--BackgroundColor: var(--ak-dark-background-lighter); + --pf-global--link--Color: var(--pf-global--link--Color--light); + --pf-global--link--Color--hover: var(--pf-global--link--Color--light--hover); + + .pf-c-table__text { + color: var(--ak-dark-foreground); + } + + .pf-c-table__sort-indicator i { + color: var(--ak-dark-foreground) !important; + } + + .pf-c-table__expandable-row.pf-m-expanded { + --pf-c-table__expandable-row--m-expanded--BorderBottomColor: var( + --ak-dark-background-lighter + ); + } + + .pf-c-button:has(.pf-c-table__toggle-icon) { + background-color: transparent !important; + + &:hover { + color: var(--pf-c-button--m-plain--hover--Color) !important; + } + } +} + +/* #endregion */ diff --git a/web/src/styles/authentik/components/Tabs/tabs.css b/web/src/styles/authentik/components/Tabs/tabs.css new file mode 100644 index 0000000000..d70bcb31a6 --- /dev/null +++ b/web/src/styles/authentik/components/Tabs/tabs.css @@ -0,0 +1,47 @@ +.pf-c-tabs { + --pf-c-tabs--inset: var(--pf-global--spacer--lg); + --pf-c-tabs--m-vertical--m-box--inset: var(--pf-global--spacer--lg); + + margin-block-start: var(--pf-global--spacer--xs); + background-color: transparent; + + &.pf-m-box.pf-m-vertical { + .pf-c-tabs__list::before { + border-color: transparent; + } + } + + &.pf-m-box .pf-c-tabs__item.pf-m-current:first-child .pf-c-tabs__link::before { + border-color: transparent; + } + + &.pf-m-vertical { + .pf-c-tabs__link { + background-color: transparent; + } + } + + &:not(.pf-m-vertical) .pf-c-tabs__item { + &.pf-m-current { + --pf-c-tabs__link--after--BorderColor: var(--ak-accent); + } + } +} + +ak-tabs[vertical] { + [role="tabpanel"] { + padding-inline-start: 0 !important; + } + + .pf-c-card__body > *::part(table-container) { + overflow-x: auto; + } +} + +.pf-c-tabs__link { + --pf-c-tabs__link--Color: var(--pf-global--Color--100); + + &::before { + border-color: transparent; + } +} diff --git a/web/src/styles/authentik/components/Wizard/wizard.css b/web/src/styles/authentik/components/Wizard/wizard.css new file mode 100644 index 0000000000..0ee49143b6 --- /dev/null +++ b/web/src/styles/authentik/components/Wizard/wizard.css @@ -0,0 +1,10 @@ +/* #region Dark Theme */ + +:host([theme="dark"]) .pf-c-wizard { + --pf-c-wizard__outer-wrap--BackgroundColor: var(--pf-global--BackgroundColor--300); + --pf-c-wizard__footer--BackgroundColor: var(--pf-global--BackgroundColor--300); + --pf-c-wizard__nav--BackgroundColor: var(--ak-dark-background-lighter); + --pf-c-wizard__nav--lg--BorderRightColor: transparent; +} + +/* #endregion */ diff --git a/web/src/styles/authentik/interface.global.css b/web/src/styles/authentik/interface.global.css new file mode 100644 index 0000000000..a9e1345f8b --- /dev/null +++ b/web/src/styles/authentik/interface.global.css @@ -0,0 +1,11 @@ +@import "@patternfly/patternfly/base/patternfly-variables.css"; +@import "@patternfly/patternfly/base/patternfly-fonts.css"; +@import "@patternfly/patternfly/base/patternfly-common.css"; +@import "@patternfly/patternfly/base/patternfly-globals.css"; +@import "@patternfly/patternfly/base/patternfly-themes.css"; +@import "@patternfly/patternfly/base/patternfly-fa-icons.css"; +@import "@patternfly/patternfly/base/patternfly-pf-icons.css"; +@import "./base/variables.css"; +@import "./base/scrollbars.css"; +@import "./base/globals.css"; +@import "./base/common.css"; diff --git a/web/src/styles/authentik/static.global.css b/web/src/styles/authentik/static.global.css new file mode 100644 index 0000000000..7ad1bb5163 --- /dev/null +++ b/web/src/styles/authentik/static.global.css @@ -0,0 +1,47 @@ +@import "@patternfly/patternfly/components/BackgroundImage/background-image.css"; +@import "@patternfly/patternfly/components/Button/button.css"; +@import "@patternfly/patternfly/components/Form/form.css"; +@import "@patternfly/patternfly/components/Drawer/drawer.css"; +@import "@patternfly/patternfly/components/List/list.css"; +@import "@patternfly/patternfly/components/Login/login.css"; +@import "@patternfly/patternfly/components/Title/title.css"; +@import "@patternfly/patternfly/components/Avatar/avatar.css"; +@import "@patternfly/patternfly/utilities/Text/text.css"; +@import "./components/Drawer/drawer.css"; +@import "./components/Form/form.css"; +@import "./components/Login/login.css"; +@import "./components/Icon/icon.css"; + +.pf-c-login__main-body { + display: flex; + flex-flow: column; + + .pf-c-form { + display: flex; + flex-flow: column; + flex: 1 1 auto; + justify-content: end; + } +} + +.form-control-static { + margin-top: var(--pf-global--spacer--sm); + display: flex; + align-items: center; + justify-content: space-between; + + .avatar { + display: flex; + align-items: center; + } + + img { + margin-right: var(--pf-global--spacer--xs); + } + + a { + padding-top: var(--pf-global--spacer--xs); + padding-bottom: var(--pf-global--spacer--xs); + line-height: var(--pf-global--spacer--xl); + } +} diff --git a/web/src/common/styles/storybook.css b/web/src/styles/authentik/storybook.css similarity index 100% rename from web/src/common/styles/storybook.css rename to web/src/styles/authentik/storybook.css diff --git a/web/src/styles/patternfly/base.css b/web/src/styles/patternfly/base.css new file mode 100644 index 0000000000..14f38dfd55 --- /dev/null +++ b/web/src/styles/patternfly/base.css @@ -0,0 +1,24 @@ +@import "@patternfly/patternfly/base/patternfly-common.css"; +@import "@patternfly/patternfly/base/patternfly-globals.css"; +@import "@patternfly/patternfly/base/patternfly-fa-icons.css"; +@import "@patternfly/patternfly/base/patternfly-pf-icons.css"; + +a { + font-weight: var(--pf-global--link--FontWeight); + color: var(--pf-global--link--Color); + text-decoration: var(--pf-global--link--TextDecoration); +} + +a:hover { + --pf-global--link--Color: var(--pf-global--link--Color--hover); + --pf-global--link--TextDecoration: var(--pf-global--link--TextDecoration--hover); +} + +button, +a { + cursor: pointer; +} + +.pf-m-overpass-font a { + font-weight: var(--pf-global--FontWeight--semi-bold); +} diff --git a/web/src/user/LibraryPage/ak-library-impl.css b/web/src/user/LibraryPage/ak-library-impl.css index e07a1ddb77..06452dfe27 100644 --- a/web/src/user/LibraryPage/ak-library-impl.css +++ b/web/src/user/LibraryPage/ak-library-impl.css @@ -3,7 +3,13 @@ padding: 3% 5%; } +.pf-c-page { + --pf-c-page--BackgroundColor: transparent; + --pf-c-page__main-section--BackgroundColor: transparent; +} + .pf-c-page__header { + --pf-c-page__header--BackgroundColor: transparent; display: flex; flex-direction: row; justify-content: space-between; diff --git a/web/src/user/index.entrypoint.css b/web/src/user/index.entrypoint.css new file mode 100644 index 0000000000..2192637347 --- /dev/null +++ b/web/src/user/index.entrypoint.css @@ -0,0 +1,63 @@ +.pf-c-drawer__panel { + z-index: var(--pf-global--ZIndex--md); +} + +.pf-c-page__header { + background-color: transparent !important; + box-shadow: none !important; + color: black !important; + + .pf-c-button { + --pf-c-button--m-secondary--Color: var(--pf-global--primary-color--100); + --pf-c-button--m-secondary--hover--Color: var(--pf-global--primary-color--100); + --pf-c-button--m-secondary--active--Color: var(--pf-global--primary-color--100); + --pf-c-button--m-secondary--focus--Color: var(--pf-global--primary-color--100); + } +} + +.pf-c-page { + background-color: transparent; +} + +.display-none { + display: none; +} + +.pf-c-brand { + min-height: 32px; + height: 32px; +} + +.has-notifications { + color: #2b9af3; +} + +.background-wrapper { + height: 100vh; + width: 100%; + position: fixed; + z-index: -1; + top: 0; + left: 0; + background-color: var(--pf-c-page--BackgroundColor) !important; +} + +.background-default-slant { + background-color: white; /*var(--ak-accent);*/ + clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 5vw)); + height: 50vh; +} + +:host([theme="dark"]) .background-default-slant { + background-color: black; +} + +ak-locale-context { + display: flex; + flex-direction: column; +} + +.pf-c-drawer__main { + min-height: calc(100vh - 76px); + max-height: calc(100vh - 76px); +} diff --git a/web/src/user/index.entrypoint.ts b/web/src/user/index.entrypoint.ts index 158de69f07..b33285a693 100644 --- a/web/src/user/index.entrypoint.ts +++ b/web/src/user/index.entrypoint.ts @@ -26,14 +26,16 @@ import { AuthenticatedInterface } from "#elements/AuthenticatedInterface"; import { AKElement } from "#elements/Base"; import { WithBrandConfig } from "#elements/mixins/branding"; import { getURLParam, updateURLParams } from "#elements/router/RouteMatch"; +import { ifPresent } from "#elements/utils/attributes"; import { themeImage } from "#elements/utils/images"; +import Styles from "#user/index.entrypoint.css"; import { ROUTES } from "#user/Routes"; import { EventsApi, SessionUser } from "@goauthentik/api"; import { msg } from "@lit/localize"; -import { css, html, nothing } from "lit"; +import { html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; @@ -43,68 +45,12 @@ import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css"; import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css"; import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; if (process.env.NODE_ENV === "development") { await import("@goauthentik/esbuild-plugin-live-reload/client"); } -const customStyles = css` - .pf-c-page__main, - .pf-c-drawer__content, - .pf-c-page__drawer { - z-index: auto !important; - background-color: transparent !important; - } - .pf-c-page__header { - background-color: transparent !important; - box-shadow: none !important; - color: black !important; - } - :host([theme="light"]) .pf-c-button.pf-m-secondary { - color: var(--ak-global--Color--100) !important; - } - .pf-c-page { - background-color: transparent; - } - .display-none { - display: none; - } - .pf-c-brand { - min-height: 32px; - height: 32px; - } - .has-notifications { - color: #2b9af3; - } - .background-wrapper { - height: 100vh; - width: 100%; - position: fixed; - z-index: -1; - top: 0; - left: 0; - background-color: var(--pf-c-page--BackgroundColor) !important; - } - .background-default-slant { - background-color: white; /*var(--ak-accent);*/ - clip-path: polygon(0 0, 100% 0, 100% 100%, 0 calc(100% - 5vw)); - height: 50vh; - } - :host([theme="dark"]) .background-default-slant { - background-color: black; - } - ak-locale-context { - display: flex; - flex-direction: column; - } - .pf-c-drawer__main { - min-height: calc(100vh - 76px); - max-height: calc(100vh - 76px); - } -`; - // ___ _ _ _ // | _ \_ _ ___ ___ ___ _ _| |_ __ _| |_(_)___ _ _ // | _/ '_/ -_|_-
-
- ${(this.uiConfig.theme.background || "") === "" +
+ ${!backgroundStyles ? html`
` : nothing}
@@ -204,7 +151,7 @@ class UserInterfacePresentation extends WithBrandConfig(AKElement) { ${this.brandingTitle} diff --git a/web/src/user/user-settings/UserSettingsPage.ts b/web/src/user/user-settings/UserSettingsPage.ts index aaaa073dfb..3ffb1030e8 100644 --- a/web/src/user/user-settings/UserSettingsPage.ts +++ b/web/src/user/user-settings/UserSettingsPage.ts @@ -13,13 +13,15 @@ import { rootInterface } from "#common/theme"; import { AKSkipToContent } from "#elements/a11y/ak-skip-to-content"; import { AKElement } from "#elements/Base"; +import { ifPresent } from "#elements/utils/attributes"; import type { UserInterface } from "#user/index.entrypoint"; +import Styles from "#user/user-settings/styles.css"; import { StagesApi, UserSetting } from "@goauthentik/api"; import { localized, msg } from "@lit/localize"; -import { css, CSSResult, html, nothing, TemplateResult } from "lit"; +import { CSSResult, html, nothing, TemplateResult } from "lit"; import { customElement, state } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; @@ -31,7 +33,6 @@ import PFFormControl from "@patternfly/patternfly/components/FormControl/form-co import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css"; import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css"; import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css"; @@ -39,7 +40,6 @@ import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css"; @customElement("ak-user-settings") export class UserSettingsPage extends AKElement { static styles: CSSResult[] = [ - PFBase, PFPage, PFDisplay, PFGallery, @@ -50,36 +50,7 @@ export class UserSettingsPage extends AKElement { PFForm, PFFormControl, PFStack, - css` - .pf-c-page { - --pf-c-page--BackgroundColor: transparent; - } - .pf-c-page__main-section { - --pf-c-page__main-section--BackgroundColor: transparent; - } - :host([theme="dark"]) .pf-c-page { - --pf-c-page--BackgroundColor: transparent; - } - :host([theme="dark"]) .pf-c-page__main-section { - --pf-c-page__main-section--BackgroundColor: transparent; - } - - .pf-c-page__main { - min-height: 100vh; - } - .pf-c-page__main, - .pf-c-page__main { - overflow: unset; - } - - @media screen and (min-width: 1200px) { - :host { - width: 90rem; - max-width: 100%; - align-self: center; - } - } - `, + Styles, ]; @state() @@ -194,7 +165,8 @@ export class UserSettingsPage extends AKElement { )}
()?.me?.user.pk)} + allow-configuration + user-id=${ifPresent(rootInterface()?.me?.user.pk)} >
diff --git a/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts b/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts index ed0409ec74..bbc9b3ce9b 100644 --- a/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts +++ b/web/src/user/user-settings/details/UserSettingsFlowExecutor.ts @@ -32,7 +32,6 @@ import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; -import PFBase from "@patternfly/patternfly/patternfly-base.css"; @customElement("ak-user-settings-flow-executor") export class UserSettingsFlowExecutor @@ -57,7 +56,7 @@ export class UserSettingsFlowExecutor @property({ type: Boolean }) loading = false; - static styles: CSSResult[] = [PFBase, PFCard, PFPage, PFButton, PFContent]; + static styles: CSSResult[] = [PFCard, PFPage, PFButton, PFContent]; submit(payload?: FlowChallengeResponseRequest): Promise { if (!payload) return Promise.reject(); diff --git a/web/src/user/user-settings/details/stages/prompt/PromptStage.ts b/web/src/user/user-settings/details/stages/prompt/PromptStage.ts index a25f0fd9c4..e52249a388 100644 --- a/web/src/user/user-settings/details/stages/prompt/PromptStage.ts +++ b/web/src/user/user-settings/details/stages/prompt/PromptStage.ts @@ -61,7 +61,9 @@ export class UserSettingsPromptStage extends PromptStage { return html`