From 254bfd2a609e30b181aa4d76a4d62ab9d609b420 Mon Sep 17 00:00:00 2001 From: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:55:26 -0800 Subject: [PATCH] web/maintenance: no unknown attributes part 2 (#19014) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * web: Add InvalidationFlow to Radius Provider dialogues ## What - Bugfix: adds the InvalidationFlow to the Radius Provider dialogues - Repairs: `{"invalidation_flow":["This field is required."]}` message, which was *not* propagated to the Notification. - Nitpick: Pretties `?foo=${true}` expressions: `s/\?([^=]+)=\$\{true\}/\1/` ## Note Yes, I know I'm going to have to do more magic when we harmonize the forms, and no, I didn't add the Property Mappings to the wizard, and yes, I know I'm going to have pain with the *new* version of the wizard. But this is a serious bug; you can't make Radius servers with *either* of the current dialogues at the moment. * This (temporary) change is needed to prevent the unit tests from failing. \# What \# Why \# How \# Designs \# Test Steps \# Other Notes * Revert "This (temporary) change is needed to prevent the unit tests from failing." This reverts commit dddde09be571a639ecd041569dd3a282aab3f9be. * website: fix bad escaping of URLs in release notes ## What Fixes bad escaping of URLs in the release notes that resulted in mangled output. v2024.6.4 had entries that looked like this: ``` ##### `GET` /providers/google_workspace/{#123;id}#125;/ ``` v2025.4.md had entries that looked like this: ``` ##### `GET` /policies/unique_password/{#125;#123;policy_uuid}/ ``` A couple of straightforward search-and-replaces has fixed the issue. ## Notes Two of the release notes had bad escaping of URLs. I'm not sure how the error was made or got past, but it was obvious when visiting the page. @Beryju suggested that the bug is due to our using `{...}` to symbolize parameters in a URL while Docusaurus wants to interpret `{...}` as an internal template instruction, resulting in odd behavior. In either case, docusarus interpreted the hashtagged entries as links to unrelated issues in Github (the same two issues, which were "bump version of pylint" and "bump version of sentry"), which could be very confusing. The inconsistencies between the two releases, and the working releases, suggests that the error was introduced manually. * web/maintenance/no-unknown-attributes-1 # What This commit is a collection of fixes and adaptations discovered while running lit-analyzer in a stricter role than usual. These fixes are to 9 of the existing issues; there are 16 more that will be addressed in the next two pull requests. The following issues were uncovered. - `ak-slug-input` does not take `autocomplete`. - `ak-wizard-page-type-create` does not take, or use, the `name` attribute. It also has no `value` of its own, so it is not processed as a form object. - `ak-endpoints-device-access-groups-form` does not take a `pk` attribute. It takes an `.instancePk` property. - `ak-provider-oauth2-redirect-uri` is only used in one place, and that place uses the term `input-id` for the key. The component was expected `inputId`. Since it is a string and therefore an attribute, kebab-case is the appropriate fix here. - `input-mode` is not a valid attribute. The attribute is `inputmode`, and the property is `inputMode`. It may not be undefined. If it is defined, the default is `text`. I have fixed this in the attribute and in the two Forms that used it. - `form-associated-element` had both `name` and `type` as readonly. Since they are native attributes, they can be attributes or they can be readonly. They can’t be both. I have made them read-write. - `user-source-settings-page` is only used in one place, and that place uses the term `input-id` for the key. The component was expected `inputId`. Since it is a string and therefore an attribute, kebab-case is the appropriate fix here. These guideposts will be placed on the PR. * Update web/src/admin/providers/oauth2/OAuth2ProviderRedirectURI.ts Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Signed-off-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com> * Update web/src/components/ak-text-input.ts Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Signed-off-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com> * web/maintenance/no-unknown-attributes-2 # What This commit is a collection of fixes and adaptations discovered while running lit-analyzer in a stricter role than usual. - `src/admin/endpoints/connectors/agent/AgentConnectorSetup.ts` After talking to @beryju, we determined that these labels aren’t shown and aren’t used. - `src/admin/admin-overview/AdminOverviewPage.ts` - `src/admin/admin-overview/DashboardUserPage.ts` - `src/elements/cards/AggregatePromiseCard.ts` - `src/elements/cards/stories/AggregatePromiseCard.stories.ts` The `Promise` version of our card is not used by any client code. The Dashboard pages that were importing it want the vanilla `AggregateCard` instead. - `./src/flow/stages/identification/IdentificationStage.ts` Anchors do not have a `name` attribute, I cannot find any code using the name attributes as lookups, nor any CSS that might use the name attributes as guides. `ak-flow-password-input` is always required; the flag is unsupported and unnecessary. - `./src/flow/stages/password/PasswordStage.ts` Anchors do not have a `name` attribute, I cannot find any code using the name attributes as lookups, nor any CSS that might use the name attributes as guides. `ak-flow-password-input` is always required; the flag is unsupported and unnecessary. - `src/user/user-settings/UserSettingsPage.ts` This change to the `UserSettingsPage`: ``` diff - userId=${ifPresent(currentUser?.pk)} + user-id=${ifPresent(currentUser?.pk)} ``` … corresponds correctly with: ``` typescript @property({ type: Number, attribute: "user-id" }) public userId?: number; ``` I find it odd (and remarkable) that nobody has complained about this yet. I even went so far as to [confirm my understanding](https://codepen.io/kensternberg-authentik/pen/raLNBwO) and, yes: - when an attribute is truthy, property syntax does not set the field - when an attribute is deliberately given a kebab-case name, using the camelCase variant does not set the field However, when the attribute is truthy, attribute names are case-insensitive: ‘user-id’ and ‘User-Id’ in client code would work just fine. ## Note A large enough number of warnings remain. Some of those are due to `lit-analyzer` not being updated to recognize newly Baseline global DOM properties like `inert` or `popover`. The rest are from RapiDoc and QrCode, which do not supply sufficient documentation or metadata for Lit-anaylzer to read correctly. * web/bug/hidden-secrets-not-propagating # What This commit updates ak-secret-text-input, adding the `name` attribute to all valid input fields and updating the value writer to match those of known-working components, to ensure that either variety of the display is fully and correctly updated with the content of the hidden secret. # Why The hidden input field is the one that HorizontalFormElement was expecting to read its value from, but that field never received a `name` because it wasn’t present when the field was first updated. HorizontalFormElement writes the `name` field to the first `` it finds. That was the “dummy” input field, which has no working value. Form ignored the input element because the value it read came with an undefined name. Object-oriented state management sometimes bites. * Turns out, I was wrong. Someone *does* use the `name` attribute: the tests. MDN says that `name` is incorrect, and we should use `id` instead. I have compromised; I have switched to using the Open UI Automation ID instead, since that's what we're doing: automated tests. \# What \# Why \# How \# Designs \# Test Steps \# Other Notes --------- Signed-off-by: Ken Sternberg <133134217+kensternberg-authentik@users.noreply.github.com> Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Co-authored-by: Jens L. --- tests/e2e/test_flows_enroll.py | 4 +- tests/e2e/test_flows_recovery.py | 4 +- .../admin/admin-overview/AdminOverviewPage.ts | 2 +- .../admin/admin-overview/DashboardUserPage.ts | 2 +- .../ak-dual-select-available-pane.ts | 4 +- .../ak-dual-select-selected-pane.ts | 2 +- .../stories/AggregatePromiseCard.stories.ts | 120 ------------------ .../identification/IdentificationStage.ts | 7 +- web/src/flow/stages/password/PasswordStage.ts | 5 +- 9 files changed, 13 insertions(+), 137 deletions(-) delete mode 100644 web/src/elements/cards/stories/AggregatePromiseCard.stories.ts diff --git a/tests/e2e/test_flows_enroll.py b/tests/e2e/test_flows_enroll.py index 226a34fad4..370e30d8b5 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, "a[name='enroll']"))) - identification_stage.find_element(By.CSS_SELECTOR, "a[name='enroll']").click() + wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "a[ouiaId='enroll']"))) + identification_stage.find_element(By.CSS_SELECTOR, "a[ouiaId='enroll']").click() # First prompt stage flow_executor = self.get_shadow_root("ak-flow-executor") diff --git a/tests/e2e/test_flows_recovery.py b/tests/e2e/test_flows_recovery.py index e0e068e50c..320eb55639 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, "a[name='recovery']"))) - identification_stage.find_element(By.CSS_SELECTOR, "a[name='recovery']").click() + wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "a[ouiaId='recovery']"))) + identification_stage.find_element(By.CSS_SELECTOR, "a[ouiaId='recovery']").click() # First prompt stage flow_executor = self.get_shadow_root("ak-flow-executor") diff --git a/web/src/admin/admin-overview/AdminOverviewPage.ts b/web/src/admin/admin-overview/AdminOverviewPage.ts index 5624e9172b..ed640f0f8a 100644 --- a/web/src/admin/admin-overview/AdminOverviewPage.ts +++ b/web/src/admin/admin-overview/AdminOverviewPage.ts @@ -8,7 +8,7 @@ import "#admin/admin-overview/cards/WorkerStatusCard"; import "#admin/admin-overview/charts/AdminLoginAuthorizeChart"; import "#admin/admin-overview/charts/OutpostStatusChart"; import "#admin/admin-overview/charts/SyncStatusChart"; -import "#elements/cards/AggregatePromiseCard"; +import "#elements/cards/AggregateCard"; import "#elements/cards/QuickActionsCard"; import { formatUserDisplayName } from "#common/users"; diff --git a/web/src/admin/admin-overview/DashboardUserPage.ts b/web/src/admin/admin-overview/DashboardUserPage.ts index cecaa265e0..7769d15e03 100644 --- a/web/src/admin/admin-overview/DashboardUserPage.ts +++ b/web/src/admin/admin-overview/DashboardUserPage.ts @@ -1,5 +1,5 @@ import "#admin/admin-overview/charts/AdminModelPerDay"; -import "#elements/cards/AggregatePromiseCard"; +import "#elements/cards/AggregateCard"; import { AKElement } from "#elements/Base"; diff --git a/web/src/elements/ak-dual-select/components/ak-dual-select-available-pane.ts b/web/src/elements/ak-dual-select/components/ak-dual-select-available-pane.ts index 28d3e2979c..b0bddf8db5 100644 --- a/web/src/elements/ak-dual-select/components/ak-dual-select-available-pane.ts +++ b/web/src/elements/ak-dual-select/components/ak-dual-select-available-pane.ts @@ -49,7 +49,7 @@ export class AkDualSelectAvailablePane extends CustomEmitterElement = new Set(); + public selected: Set = new Set(); //#endregion diff --git a/web/src/elements/ak-dual-select/components/ak-dual-select-selected-pane.ts b/web/src/elements/ak-dual-select/components/ak-dual-select-selected-pane.ts index 1dbb7bd32b..ca06626d27 100644 --- a/web/src/elements/ak-dual-select/components/ak-dual-select-selected-pane.ts +++ b/web/src/elements/ak-dual-select/components/ak-dual-select-selected-pane.ts @@ -42,7 +42,7 @@ export class AkDualSelectSelectedPane extends CustomEmitterElement = { - title: "Elements/", - component: "ak-aggregate-card-promise", - tags: ["autodocs"], - parameters: { - docs: { - description: { - component: /* md */ ` -# Aggregate Promise Cards - -Aggregate Promise Cards are Aggregate Cards that take a promise from client code and either display -the contents of that promise or a pre-configured failure notice. The contents must be compliant with -and produce a meaningful result via the \`.toString()\` API. HTML in the string will currently be -escaped. - -## Usage - -\`\`\`Typescript -import "#elements/cards/AggregatePromiseCard"; -\`\`\` - -\`\`\`html - -\`\`\` -`, - }, - }, - }, - argTypes: { - icon: { control: "text" }, - label: { control: "text" }, - headerLink: { control: "text" }, - subtext: { control: "text" }, - failureMessage: { control: "text" }, - }, -}; - -export default metadata; - -const text = - "Curl up and sleep on the freshly laundered towels mew, but make meme, make cute face growl at dogs in my sleep. Scratch me there, elevator butt humans, humans, humans oh how much they love us felines we are the center of attention they feed, they clean hopped up on catnip mice. Kitty time flop over, for see owner, run in terror"; - -const MILLIS_PER_SECOND = 1000; -const EXAMPLE_TIMEOUT = 8000; // 8 seconds - -export const DefaultStory: StoryObj = { - args: { - icon: undefined, - header: "Default", - headerLink: undefined, - subtext: `Demo has a ${EXAMPLE_TIMEOUT / MILLIS_PER_SECOND} second delay until resolution`, - }, - render: ({ icon, label, headerLink, subtext }: IAggregatePromiseCard) => { - const runThis = (timeout: number, value: string) => - new Promise((resolve) => setTimeout(resolve, timeout, value)); - - return html`> - - - `; - }, -}; - -export const PromiseRejected: StoryObj = { - args: { - icon: undefined, - header: "Default", - headerLink: undefined, - subtext: `Demo has a ${EXAMPLE_TIMEOUT / MILLIS_PER_SECOND} second delay until resolution`, - failureMessage: undefined, - }, - render: ({ icon, label, headerLink, subtext, failureMessage }: IAggregatePromiseCard) => { - const runThis = (timeout: number, value: string) => - new Promise((_resolve, reject) => setTimeout(reject, timeout, value)); - - return html` - - - - `; - }, -}; diff --git a/web/src/flow/stages/identification/IdentificationStage.ts b/web/src/flow/stages/identification/IdentificationStage.ts index bc939406a3..9355d30748 100644 --- a/web/src/flow/stages/identification/IdentificationStage.ts +++ b/web/src/flow/stages/identification/IdentificationStage.ts @@ -365,13 +365,13 @@ export class IdentificationStage extends BaseStage< const enrollmentItem = enrollUrl ? html`` : null; const recoveryItem = recoveryUrl ? html`` @@ -450,7 +450,6 @@ export class IdentificationStage extends BaseStage< ${msg("Use a security key")} ` diff --git a/web/src/flow/stages/password/PasswordStage.ts b/web/src/flow/stages/password/PasswordStage.ts index 18f55a42ea..d34f406e50 100644 --- a/web/src/flow/stages/password/PasswordStage.ts +++ b/web/src/flow/stages/password/PasswordStage.ts @@ -46,7 +46,6 @@ export class PasswordStage extends BaseStage ${msg("Additional actions")} ` : null}