* web: fix esbuild issue with style sheets
Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).
Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.
In standard mode, the following warning appears on the console when running a Flow:
```
Autofocus processing was blocked because a document already has a focused element.
```
In compatibility mode, the following **error** appears on the console when running a Flow:
```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
at initDomMutationObservers (crawler-inject.js:1106:18)
at crawler-inject.js:1114:24
at Array.forEach (<anonymous>)
at initDomMutationObservers (crawler-inject.js:1114:10)
at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```
Despite this error, nothing seems to be broken and flows work as anticipated.
* web: add more linting
* A reliable test for the extra code needed in analyzer, passing shellcheck
* web: re-enable custom-element-manifest and enable component checking in Typescript
This commit includes a monkeypatch to allow custom-element-manifest (CEM) to work correctly again
despite our rich collection of mixins, reactive controllers, symbol-oriented event handlers, and the
like. With that monkeypatch in place, we can now create the CEM manifest file and then exploit it so
that IDEs and the Typescript compilation pass can tell when a component is being used incorrectly;
when the wrong types are being passed to it, or when a required attribute is not initialized.
* Added building the manifest to the build process, rather than storing it. It is not appreciably slow.
* web: the most boring PR in the universe: Add HTMLTagNameElementMap to everyhing
This commit adds HTMLTagNameElementMap entries to every web component in the front end. Activating
and associating the HTMLTagNamElementMap with its class has enabled
[LitAnalyzer](https://github.com/runem/lit-analyzer/tree/master/packages/lit-analyzer) to reveal a
*lot* of basic problems within the UI, the most popular of which is "missing import." We usually get
away with it because the object being imported was already registered with the browser elsewhere,
but it still surprises me that we haven't gotten any complaints over things like:
```
./src/flow/stages/base.ts
Missing import for <ak-form-static>
96: <ak-form-static
no-missing-import
```
Given how early and fundamental that seems to be in our code, I'd have expected to hear _something_
about it.
I have not enabled most of the possible checks because, well, there are just a ton of warnings when
I do. I'd like to get in and fix those.
Aside from this, I have also _removed_ `customElement` declarations from anything declared as an
`abstract class`. It makes no sense to try and instantiate something that cannot, by definition, be
instantiated. If the class is capable of running on its own, it's not abstract, it just needs to be
overridden in child classes. Before removing the declaration I did check to make sure no other
piece of code was even *trying* to instantiate it, and so far I have detected no failures. Those
elements were:
- elements/forms/Form.ts
- element-/wizard/WizardFormPage.ts
The one that blows my mind, though, is this:
```
src/elements/forms/ProxyForm.ts
6-@customElement("ak-proxy-form")
7:export abstract class ProxyForm extends Form<unknown> {
```
Which, despite being `abstract`, is somehow instantiable?
```
src/admin/outposts/ServiceConnectionListPage.ts: <ak-proxy-form
src/admin/providers/ProviderListPage.ts: <ak-proxy-form
src/admin/sources/SourceWizard.ts: <ak-proxy-form
src/admin/sources/SourceListPage.ts: <ak-proxy-form
src/admin/providers/ProviderWizard.ts: <ak-proxy-form type=${type.component}></ak-proxy-form>
src/admin/stages/StageListPage.ts: <ak-proxy-form
```
I've made a note to investigate.
I've started a new folder where all of my one-off tools for *how* a certain PR was run. It has a
README describing what it's for, and the first tool, `add-htmlelementtagnamemaps-to-everything`, is
its first entry. That tool is also documented internally.
``` Gilbert & Sullivan
I've got a little list,
I've got a little list,
Of all the code that would never be missed,
The duplicate code of cute-and-paste,
The weak abstractions that lead to waste,
The embedded templates-- you get the gist,
There ain't none of 'em that will ever be missed,
And that's why I've got them on my list!
```
348 lines
15 KiB
TypeScript
348 lines
15 KiB
TypeScript
import { BasePolicyForm } from "@goauthentik/admin/policies/BasePolicyForm";
|
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
|
import { first } from "@goauthentik/common/utils";
|
|
import "@goauthentik/elements/forms/FormGroup";
|
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
|
|
|
import { msg } from "@lit/localize";
|
|
import { TemplateResult, html } from "lit";
|
|
import { customElement, state } from "lit/decorators.js";
|
|
import { ifDefined } from "lit/directives/if-defined.js";
|
|
|
|
import { PasswordPolicy, PoliciesApi } from "@goauthentik/api";
|
|
|
|
@customElement("ak-policy-password-form")
|
|
export class PasswordPolicyForm extends BasePolicyForm<PasswordPolicy> {
|
|
@state()
|
|
showStatic = true;
|
|
|
|
@state()
|
|
showHIBP = false;
|
|
|
|
@state()
|
|
showZxcvbn = false;
|
|
|
|
async loadInstance(pk: string): Promise<PasswordPolicy> {
|
|
const policy = await new PoliciesApi(DEFAULT_CONFIG).policiesPasswordRetrieve({
|
|
policyUuid: pk,
|
|
});
|
|
this.showStatic = policy.checkStaticRules || false;
|
|
this.showHIBP = policy.checkHaveIBeenPwned || false;
|
|
this.showZxcvbn = policy.checkZxcvbn || false;
|
|
return policy;
|
|
}
|
|
|
|
async send(data: PasswordPolicy): Promise<PasswordPolicy> {
|
|
if (this.instance) {
|
|
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordUpdate({
|
|
policyUuid: this.instance.pk || "",
|
|
passwordPolicyRequest: data,
|
|
});
|
|
} else {
|
|
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordCreate({
|
|
passwordPolicyRequest: data,
|
|
});
|
|
}
|
|
}
|
|
|
|
renderStaticRules(): TemplateResult {
|
|
return html` <ak-form-group>
|
|
<span slot="header"> ${msg("Static rules")} </span>
|
|
<div slot="body" class="pf-c-form">
|
|
<ak-form-element-horizontal
|
|
label=${msg("Minimum length")}
|
|
?required=${true}
|
|
name="lengthMin"
|
|
>
|
|
<input
|
|
type="number"
|
|
value="${first(this.instance?.lengthMin, 10)}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal
|
|
label=${msg("Minimum amount of Uppercase Characters")}
|
|
?required=${true}
|
|
name="amountUppercase"
|
|
>
|
|
<input
|
|
type="number"
|
|
value="${first(this.instance?.amountUppercase, 2)}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal
|
|
label=${msg("Minimum amount of Lowercase Characters")}
|
|
?required=${true}
|
|
name="amountLowercase"
|
|
>
|
|
<input
|
|
type="number"
|
|
value="${first(this.instance?.amountLowercase, 2)}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal
|
|
label=${msg("Minimum amount of Digits")}
|
|
?required=${true}
|
|
name="amountDigits"
|
|
>
|
|
<input
|
|
type="number"
|
|
value="${first(this.instance?.amountDigits, 2)}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal
|
|
label=${msg("Minimum amount of Symbols Characters")}
|
|
?required=${true}
|
|
name="amountSymbols"
|
|
>
|
|
<input
|
|
type="number"
|
|
value="${first(this.instance?.amountSymbols, 2)}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal
|
|
label=${msg("Error message")}
|
|
?required=${true}
|
|
name="errorMessage"
|
|
>
|
|
<input
|
|
type="text"
|
|
value="${ifDefined(this.instance?.errorMessage)}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal
|
|
label=${msg("Symbol charset")}
|
|
?required=${true}
|
|
name="symbolCharset"
|
|
>
|
|
<input
|
|
type="text"
|
|
value="${ifDefined(
|
|
this.instance?.symbolCharset || "!\\\"#$%&'()*+,-./:;<=>?@[]^_`{|}~ ",
|
|
)}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg("Characters which are considered as symbols.")}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
</div>
|
|
</ak-form-group>`;
|
|
}
|
|
|
|
renderHIBP(): TemplateResult {
|
|
return html`
|
|
<ak-form-group .expanded=${true}>
|
|
<span slot="header"> ${msg("HaveIBeenPwned settings")} </span>
|
|
<div slot="body" class="pf-c-form">
|
|
<ak-form-element-horizontal
|
|
label=${msg("Allowed count")}
|
|
?required=${true}
|
|
name="hibpAllowedCount"
|
|
>
|
|
<input
|
|
type="number"
|
|
value="${first(this.instance?.hibpAllowedCount, 0)}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg("Allow up to N occurrences in the HIBP database.")}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
</div>
|
|
</ak-form-group>
|
|
`;
|
|
}
|
|
|
|
renderZxcvbn(): TemplateResult {
|
|
return html`
|
|
<ak-form-group .expanded=${true}>
|
|
<span slot="header"> ${msg("zxcvbn settings")} </span>
|
|
<div slot="body" class="pf-c-form">
|
|
<ak-form-element-horizontal
|
|
label=${msg("Score threshold")}
|
|
?required=${true}
|
|
name="zxcvbnScoreThreshold"
|
|
>
|
|
<input
|
|
type="number"
|
|
value="${first(this.instance?.zxcvbnScoreThreshold, 0)}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"If the password's score is less than or equal this value, the policy will fail.",
|
|
)}
|
|
</p>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg("0: Too guessable: risky password. (guesses < 10^3)")}
|
|
</p>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"1: Very guessable: protection from throttled online attacks. (guesses < 10^6)",
|
|
)}
|
|
</p>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"2: Somewhat guessable: protection from unthrottled online attacks. (guesses < 10^8)",
|
|
)}
|
|
</p>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"3: Safely unguessable: moderate protection from offline slow-hash scenario. (guesses < 10^10)",
|
|
)}
|
|
</p>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"4: Very unguessable: strong protection from offline slow-hash scenario. (guesses >= 10^10)",
|
|
)}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
</div>
|
|
</ak-form-group>
|
|
`;
|
|
}
|
|
|
|
renderForm(): TemplateResult {
|
|
return html` <span>
|
|
${msg(
|
|
"Checks the value from the policy request against several rules, mostly used to ensure password strength.",
|
|
)}
|
|
</span>
|
|
<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
|
|
<input
|
|
type="text"
|
|
value="${ifDefined(this.instance?.name || "")}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal name="executionLogging">
|
|
<label class="pf-c-switch">
|
|
<input
|
|
class="pf-c-switch__input"
|
|
type="checkbox"
|
|
?checked=${first(this.instance?.executionLogging, false)}
|
|
/>
|
|
<span class="pf-c-switch__toggle">
|
|
<span class="pf-c-switch__toggle-icon">
|
|
<i class="fas fa-check" aria-hidden="true"></i>
|
|
</span>
|
|
</span>
|
|
<span class="pf-c-switch__label">${msg("Execution logging")}</span>
|
|
</label>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg(
|
|
"When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.",
|
|
)}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal
|
|
label=${msg("Password field")}
|
|
?required=${true}
|
|
name="passwordField"
|
|
>
|
|
<input
|
|
type="text"
|
|
value="${ifDefined(this.instance?.passwordField || "password")}"
|
|
class="pf-c-form-control"
|
|
required
|
|
/>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg("Field key to check, field keys defined in Prompt stages are available.")}
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
|
|
<ak-form-element-horizontal name="checkStaticRules">
|
|
<label class="pf-c-switch">
|
|
<input
|
|
class="pf-c-switch__input"
|
|
type="checkbox"
|
|
?checked=${first(this.instance?.checkStaticRules, true)}
|
|
@change=${(ev: Event) => {
|
|
const el = ev.target as HTMLInputElement;
|
|
this.showStatic = el.checked;
|
|
}}
|
|
/>
|
|
<span class="pf-c-switch__toggle">
|
|
<span class="pf-c-switch__toggle-icon">
|
|
<i class="fas fa-check" aria-hidden="true"></i>
|
|
</span>
|
|
</span>
|
|
<span class="pf-c-switch__label">${msg("Check static rules")}</span>
|
|
</label>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal name="checkHaveIBeenPwned">
|
|
<label class="pf-c-switch">
|
|
<input
|
|
class="pf-c-switch__input"
|
|
type="checkbox"
|
|
?checked=${first(this.instance?.checkHaveIBeenPwned, true)}
|
|
@change=${(ev: Event) => {
|
|
const el = ev.target as HTMLInputElement;
|
|
this.showHIBP = el.checked;
|
|
}}
|
|
/>
|
|
<span class="pf-c-switch__toggle">
|
|
<span class="pf-c-switch__toggle-icon">
|
|
<i class="fas fa-check" aria-hidden="true"></i>
|
|
</span>
|
|
</span>
|
|
<span class="pf-c-switch__label">${msg("Check haveibeenpwned.com")}</span>
|
|
</label>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg("For more info see:")}
|
|
<a href="https://haveibeenpwned.com/API/v2#SearchingPwnedPasswordsByRange"
|
|
>haveibeenpwned.com</a
|
|
>
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
<ak-form-element-horizontal name="checkZxcvbn">
|
|
<label class="pf-c-switch">
|
|
<input
|
|
class="pf-c-switch__input"
|
|
type="checkbox"
|
|
?checked=${first(this.instance?.checkZxcvbn, true)}
|
|
@change=${(ev: Event) => {
|
|
const el = ev.target as HTMLInputElement;
|
|
this.showZxcvbn = el.checked;
|
|
}}
|
|
/>
|
|
<span class="pf-c-switch__toggle">
|
|
<span class="pf-c-switch__toggle-icon">
|
|
<i class="fas fa-check" aria-hidden="true"></i>
|
|
</span>
|
|
</span>
|
|
<span class="pf-c-switch__label">${msg("Check zxcvbn")}</span>
|
|
</label>
|
|
<p class="pf-c-form__helper-text">
|
|
${msg("Password strength estimator created by Dropbox, see:")}
|
|
<a href="https://github.com/dropbox/zxcvbn#readme">dropbox/zxcvbn</a>
|
|
</p>
|
|
</ak-form-element-horizontal>
|
|
${this.showStatic ? this.renderStaticRules() : html``}
|
|
${this.showHIBP ? this.renderHIBP() : html``}
|
|
${this.showZxcvbn ? this.renderZxcvbn() : html``}`;
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"ak-policy-password-form": PasswordPolicyForm;
|
|
}
|
|
}
|