Skip to content

Conversation

@authentik-automation
Copy link
Contributor

Cherry-pick of #19892 to version-2025.12 branch.

Original PR: #19892
Original Author: @Mmx233
Cherry-picked commit: 54fad67

* web: fix Brand CSS not applied to nested Shadow DOM components

After PR #17444, Brand CSS was only applied when ThemeChangeEvent fired.
Components created after the initial event never received the custom styles.

This fix immediately applies Brand CSS when a style root is set, ensuring
all nested Shadow DOM components (like flow stages) receive brand styling
regardless of when they are created.

* Update web/src/elements/Base.ts

Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>

* Clarify.

---------

Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
@netlify
Copy link

netlify bot commented Feb 1, 2026

Deploy Preview for authentik-integrations ready!

Name Link
🔨 Latest commit 08598a2
🔍 Latest deploy log https://app.netlify.com/projects/authentik-integrations/deploys/697ef826f6c5c50008d5741e
😎 Deploy Preview https://deploy-preview-19900--authentik-integrations.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov
Copy link

codecov bot commented Feb 1, 2026

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
2884 1 2883 4
View the top 1 failed test(s) by shortest run time
tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit::test_authorization_consent_implied
Stack Traces | 19.2s run time
self = <unittest.case._Outcome object at 0x7febb035bce0>
test_case = <tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>
subTest = False

    @contextlib.contextmanager
    def testPartExecutor(self, test_case, subTest=False):
        old_success = self.success
        self.success = True
        try:
>           yield

.../hostedtoolcache/Python/3.13.11............/x64/lib/python3.13/unittest/case.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>
result = <TestCaseFunction test_authorization_consent_implied>

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            stopTestRun = getattr(result, 'stopTestRun', None)
            if startTestRun is not None:
                startTestRun()
        else:
            stopTestRun = None
    
        result.startTest(self)
        try:
            testMethod = getattr(self, self._testMethodName)
            if (getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)):
                # If the class or method was skipped.
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                _addSkip(result, self, skip_why)
                return result
    
            expecting_failure = (
                getattr(self, "__unittest_expecting_failure__", False) or
                getattr(testMethod, "__unittest_expecting_failure__", False)
            )
            outcome = _Outcome(result)
            start_time = time.perf_counter()
            try:
                self._outcome = outcome
    
                with outcome.testPartExecutor(self):
                    self._callSetUp()
                if outcome.success:
                    outcome.expecting_failure = expecting_failure
                    with outcome.testPartExecutor(self):
>                       self._callTestMethod(testMethod)

.../hostedtoolcache/Python/3.13.11............/x64/lib/python3.13/unittest/case.py:651: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>
method = <bound method TestProviderOAuth2OIDCImplicit.test_authorization_consent_implied of <tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>>

    def _callTestMethod(self, method):
>       if method() is not None:

.../hostedtoolcache/Python/3.13.11............/x64/lib/python3.13/unittest/case.py:606: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>
args = (), kwargs = {}

    @wraps(func)
    def wrapper(self: TransactionTestCase, *args, **kwargs):
        """Run test again if we're below max_retries, including tearDown and
        setUp. Otherwise raise the error"""
        nonlocal count
        try:
>           return func(self, *args, **kwargs)

tests/e2e/utils.py:405: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>,)
kwargs = {}, file = 'default/flow-default-invalidation-flow.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Invalidation flow\nentries:\n- attrs:\n    designation: invalidation\n    na...0\n    stage: !KeyOf default-invalidation-logout\n    target: !KeyOf flow\n  model: authentik_flows.flowstagebinding\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>,)
kwargs = {}
file = 'default/flow-default-provider-authorization-implicit-consent.yaml'
content = 'version: 1\nmetadata:\n  name: Default - Provider authorization flow (implicit consent)\nentries:\n- attrs:\n    desi...henticated\n  identifiers:\n    slug: default-provider-authorization-implicit-consent\n  model: authentik_flows.flow\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>,)
kwargs = {}, file = 'system/providers-oauth2.yaml'
content = 'version: 1\nmetadata:\n  labels:\n    blueprints.goauthentik.io/system: "true"\n  name: System - OAuth2 Provider - Sc... application the ability to access the authentik API\n        # on behalf of the authorizing user\n        return {}\n'

    @wraps(func)
    def wrapper(*args, **kwargs):
        for file in files:
            content = BlueprintInstance(path=file).retrieve()
            Importer.from_string(content).apply()
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:25: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

args = (<tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>,)
kwargs = {}, config = <AuthentikCryptoConfig: authentik_crypto>

    @wraps(func)
    def wrapper(*args, **kwargs):
        config = apps.get_app_config(app_name)
        if isinstance(config, ManagedAppConfig):
            config._on_startup_callback(None)
>       return func(*args, **kwargs)

.../blueprints/tests/__init__.py:43: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>

    @retry()
    @apply_blueprint(
        "default/flow-default-authentication-flow.yaml",
        "default/flow-default-invalidation-flow.yaml",
    )
    @apply_blueprint("default/flow-default-provider-authorization-implicit-consent.yaml")
    @apply_blueprint("system/providers-oauth2.yaml")
    @reconcile_app("authentik_crypto")
    def test_authorization_consent_implied(self):
        """test OpenID Provider flow (default authorization flow with implied consent)"""
        sleep(1)
        # Bootstrap all needed objects
        authorization_flow = Flow.objects.get(
            slug="default-provider-authorization-implicit-consent"
        )
        provider = OAuth2Provider.objects.create(
            name=self.application_slug,
            client_type=ClientTypes.CONFIDENTIAL,
            client_id=self.client_id,
            client_secret=self.client_secret,
            signing_key=create_test_cert(),
            redirect_uris=[
                RedirectURI(RedirectURIMatchingMode.STRICT, "http://localhost:9009/implicit/")
            ],
            authorization_flow=authorization_flow,
        )
        provider.property_mappings.set(
            ScopeMapping.objects.filter(
                scope_name__in=[
                    SCOPE_OPENID,
                    SCOPE_OPENID_EMAIL,
                    SCOPE_OPENID_PROFILE,
                    SCOPE_OFFLINE_ACCESS,
                ]
            )
        )
        provider.save()
        Application.objects.create(
            name=self.application_slug,
            slug=self.application_slug,
            provider=provider,
        )
        self.setup_client()
    
        self.driver.get("http://localhost:9009/implicit/")
        self.wait.until(ec.title_contains("authentik"))
        self.login()
    
>       body = self.parse_json_content()

tests/e2e/test_provider_oidc_implicit.py:153: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>
context = None, timeout = 10

    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 <body> 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.
        """
        use_body = context is None
        wait_timeout = timeout or self.wait_timeout
    
        def get_context() -> WebElement:
            """Get or refresh the context element."""
            if use_body:
                return self.driver.find_element(By.TAG_NAME, "body")
            return context
    
        def get_text_safely() -> str:
            """Get element text, re-finding element if stale."""
            for _ in range(5):
                try:
                    return get_context().text.strip()
                except StaleElementReferenceException:
                    sleep(0.5)
            return get_context().text.strip()
    
        def get_inner_html_safely() -> str:
            """Get innerHTML, re-finding element if stale."""
            for _ in range(5):
                try:
                    return get_context().get_attribute("innerHTML") or ""
                except StaleElementReferenceException:
                    sleep(0.5)
            return get_context().get_attribute("innerHTML") or ""
    
        try:
            get_context()
        except NoSuchElementException:
            self.fail(
                f"No element found (defaulted to <body>). Current URL: {self.driver.current_url}"
            )
    
        wait = WebDriverWait(self.driver, wait_timeout)
    
        try:
            wait.until(lambda d: len(get_text_safely()) != 0)
        except TimeoutException:
            snippet = get_text_safely()[:500].replace("\n", " ")
            self.fail(
                f"Timed out waiting for element text to appear at {self.driver.current_url}. "
                f"Current content: {snippet or '<empty>'}"
            )
    
        body_text = get_text_safely()
        inner_html = get_inner_html_safely()
    
        if "redirecting" in inner_html.lower():
            try:
                wait.until(lambda d: "redirecting" not in get_inner_html_safely().lower())
            except TimeoutException:
                snippet = get_text_safely()[:500].replace("\n", " ")
                inner_html = get_inner_html_safely()
    
                self.fail(
                    f"Timed out waiting for redirect to finish at {self.driver.current_url}. "
                    f"Current content: {snippet or '<empty>'}"
                    f"{inner_html or '<empty>'}"
                )
    
            inner_html = get_inner_html_safely()
            body_text = get_text_safely()
    
        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 '<empty>'}"
                f"{inner_html or '<empty>'}"
            )

tests/e2e/utils.py:232: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <tests.e2e.test_provider_oidc_implicit.TestProviderOAuth2OIDCImplicit testMethod=test_authorization_consent_implied>
msg = 'Expected JSON content but got non-JSON text at http://localhost:9009/implicit/#access_token=eyJhbGciOiJSUzI1NiIsImtpZ...en_type=Bearer&expires_in=3600&state=0fe7bc75ce484c888878bd21b83e185d: <empty>\n    <pre id="loginResult"></pre>\n\n\n'

    def fail(self, msg=None):
        """Fail immediately, with the given message."""
>       raise self.failureException(msg)
E       AssertionError: Expected JSON content but got non-JSON text at http://localhost:9009/implicit/#access_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjcyZWUwY2MwOTlmOTYxZGI5NWM2MDk5YWU1ZGUzZjliIiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwOi8vMTAuMS4wLjI5OjM5OTE5L2FwcGxpY2F0aW9uL28vdGVzdC8iLCJzdWIiOiI3OTZlY2Y4YTkyMjc1ZjY4NWU2OTM5ZDRmMjBkMmY3NTMyZmY1MjdmNTJmNmIzYzFkNTNkNzM1MzhiYTkzNmI0IiwiYXVkIjoiOFBGdklPSDRDazA0OUZBOXNXUGpZaGxSQ1VmQWNxbVFNM3JSWTJoSiIsImV4cCI6MTc2OTkzMjk4MSwiaWF0IjoxNzY5OTI5MzgxLCJhdXRoX3RpbWUiOjE3Njk5MjkzODAsImFjciI6ImdvYXV0aGVudGlrLmlvL3Byb3ZpZGVycy9vYXV0aDIvZGVmYXVsdCIsImFtciI6WyJwd2QiXSwibm9uY2UiOiI1OTM1ZjA0NGU0N2Q0NTEyOTFjZDQwMTc5NWY0ZDFjOSIsInNpZCI6IjUwN2ZjODE2MGNiZDllOTQxZWJlNzA0MDkyZWQ2YzRiZDE3N2VjZWZkN2U5OWI3NGRmMTExNTliMDk3ZTZkN2IiLCJlbWFpbCI6InAxd2dGVnZLT01KU25vbFV3T3JPQGdvYXV0aGVudGlrLmlvIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoicDF3Z0ZWdktPTUpTbm9sVXdPck8iLCJnaXZlbl9uYW1lIjoicDF3Z0ZWdktPTUpTbm9sVXdPck8iLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJwMXdnRlZ2S09NSlNub2xVd09yTyIsIm5pY2tuYW1lIjoicDF3Z0ZWdktPTUpTbm9sVXdPck8iLCJncm91cHMiOlsicDF3Z0ZWdktPTUpTbm9sVXdPck8iXSwiYXpwIjoiOFBGdklPSDRDazA0OUZBOXNXUGpZaGxSQ1VmQWNxbVFNM3JSWTJoSiIsInVpZCI6IkhJT3VLZ01EamxGQ0NJWGFKVkt3YjhXYzRidXdYZUg3MHFqTDV1WUkiLCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIn0.wT3RqdNJoqcxYU7PuN0f-MzK8CFx2JFmOReqk8TZAAXLbFBImiLxmECIYqtsn2a00YMVTRT_5ZFxIWWd29CvPzcUF5Sf8TeKk5AXKIDAW3Qr-AiRfGsinErRm9_d6qV4hbQz40sXBRkghoK4dLYkNzhsrkZic7xZxjAKN4hZ6p39m8GceeY-FJ1EoQr9fT2gK9mM_oDze1JwboOaLRzIishT3KDKSoDEyVb-aFCW7A6Lurn82zluemFMOlQghLdPNZJkjm0ZoKX1Xszd_xbuO5L898THHZlprRX7ps_k0IH-YKs6RKi6AnL1e5gQ9mC3YWiPa8cPwmO1zpFdULmwMZvlX3x8-e9TQwemxRm0HZ8dovWCmFRiaCRQgeNCOqVxPx2_uiPyyQqu6kgKJP8PhXDJzcNl1SVO1D4_CG04Tv7YmcamAAE49Epo_iQsmirotNyNlkZHx7X3mIfNSiD9Dju5m6sNpoOD6VaJuBAWqA_t440kPbedLt9XzB64CN3nZX6X9AEAAzZUDwHE95jVh2OT23W3vxDjcGksC7fxh2NZuVjrd0kyeI31T0bSZ1p6i6VVZWZaHFKGwxvyAiKSUgGibkGMQE3ZrKLqVt3p7GPuFrsz1MvSKxgtOhtApe8aANMNT-GaQfrh-gC_nq8whUFj17cBGG3gktQu_iQwi70&id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjcyZWUwY2MwOTlmOTYxZGI5NWM2MDk5YWU1ZGUzZjliIiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwOi8vMTAuMS4wLjI5OjM5OTE5L2FwcGxpY2F0aW9uL28vdGVzdC8iLCJzdWIiOiI3OTZlY2Y4YTkyMjc1ZjY4NWU2OTM5ZDRmMjBkMmY3NTMyZmY1MjdmNTJmNmIzYzFkNTNkNzM1MzhiYTkzNmI0IiwiYXVkIjoiOFBGdklPSDRDazA0OUZBOXNXUGpZaGxSQ1VmQWNxbVFNM3JSWTJoSiIsImV4cCI6MTc2OTkzMjk4MSwiaWF0IjoxNzY5OTI5MzgxLCJhdXRoX3RpbWUiOjE3Njk5MjkzODAsImFjciI6ImdvYXV0aGVudGlrLmlvL3Byb3ZpZGVycy9vYXV0aDIvZGVmYXVsdCIsImFtciI6WyJwd2QiXSwibm9uY2UiOiI1OTM1ZjA0NGU0N2Q0NTEyOTFjZDQwMTc5NWY0ZDFjOSIsImF0X2hhc2giOiJuVzBpbTFoRlR5THBGci05czhaUGhnIiwic2lkIjoiNTA3ZmM4MTYwY2JkOWU5NDFlYmU3MDQwOTJlZDZjNGJkMTc3ZWNlZmQ3ZTk5Yjc0ZGYxMTE1OWIwOTdlNmQ3YiIsImVtYWlsIjoicDF3Z0ZWdktPTUpTbm9sVXdPck9AZ29hdXRoZW50aWsuaW8iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJwMXdnRlZ2S09NSlNub2xVd09yTyIsImdpdmVuX25hbWUiOiJwMXdnRlZ2S09NSlNub2xVd09yTyIsInByZWZlcnJlZF91c2VybmFtZSI6InAxd2dGVnZLT01KU25vbFV3T3JPIiwibmlja25hbWUiOiJwMXdnRlZ2S09NSlNub2xVd09yTyIsImdyb3VwcyI6WyJwMXdnRlZ2S09NSlNub2xVd09yTyJdfQ.MTxZmR0p2uUfaDV2gV9biosVAJQ2cQASXFm6lY7ho4o_zA2eb8XfHCSX6208Ih4PAlJTToNsBW3WroOnws0rBz67r2liAzMNxOi6p45461a8x433gCFv_BLGkxF2WWEP1Gj_6szK5b_4tu1wAL30r06xfoJRaAtHUejvqx_j49BA6b3lLRI5aQefcfnnOg246vpy_VL_oDznXyXTDcLvfko0d6CxjNoF2j27D7BBIPWsMi6VLifPu9xbniGyU26xiUg9yW5mqE9zt1jnnvJFpa8EwLSH5jO-PB3Wl75HVGgtU653f-mHiKvK9f2FxGCbZcJY6Hxngg7gfr6b_8Zk9oqxw85kS4KZiEJcycK2kZnaJuLpbBHlQYpylWrfgwKe2TODTFgHfqDJ2-ZVmS_J349-zGSj8XKDav3f40Y7_4PvzhlC1Y2tDhqn69iLymSqcOaiVAXRGEfaShRM542KQLgWFofXsXTRuGBub6N-QIQRViM6CoNqI1_2Ve5_f8snMsJZAjVr9ENI_XCB7aNnACmez31H8xzphLanOXv0tw6nz8w8j3LR2DOpuerY-Z-BrEdETjYMDtvvxgNRhaLm_YoMpPQZhypy-y8hYxG-Dy8Gv0fEdonYCe5pBmAxpPK4FgTQwy2jBM7TDExy7zqcbs_9gRr7fWeULVJt4xd7oGQ&token_type=Bearer&expires_in=3600&state=0fe7bc75ce484c888878bd21b83e185d: <empty>
E           <pre id="loginResult"></pre>

.../hostedtoolcache/Python/3.13.11............/x64/lib/python3.13/unittest/case.py:732: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant