stages/identification: allow setting of a password stage to check password and identity in a single step
closes #970 Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
@ -1,10 +0,0 @@
|
||||
import { ChallengeChoices } from "authentik-api";
|
||||
|
||||
export interface Error {
|
||||
code: string;
|
||||
string: string;
|
||||
}
|
||||
|
||||
export interface ErrorDict {
|
||||
[key: string]: Error[];
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
import { customElement, LitElement, CSSResult, property, css } from "lit-element";
|
||||
import { TemplateResult, html } from "lit-html";
|
||||
import { Error } from "../../api/Flows";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import { ErrorDetail } from "authentik-api";
|
||||
|
||||
@customElement("ak-form-element")
|
||||
export class FormElement extends LitElement {
|
||||
@ -25,7 +25,7 @@ export class FormElement extends LitElement {
|
||||
required = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
errors?: Error[];
|
||||
errors?: ErrorDetail[];
|
||||
|
||||
updated(): void {
|
||||
this.querySelectorAll<HTMLInputElement>("input[autofocus]").forEach(input => {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { LitElement, property } from "lit-element";
|
||||
import { ErrorDetail } from "authentik-api";
|
||||
import { html, LitElement, property, TemplateResult } from "lit-element";
|
||||
|
||||
export interface StageHost {
|
||||
challenge?: unknown;
|
||||
@ -22,4 +23,23 @@ export class BaseStage<Tin, Tout> extends LitElement {
|
||||
this.host?.submit(object as unknown as Tout);
|
||||
}
|
||||
|
||||
renderNonFieldErrors(errors: ErrorDetail[]): TemplateResult {
|
||||
if (!errors) {
|
||||
return html``;
|
||||
}
|
||||
return html`<div class="pf-c-form__alert">
|
||||
${errors.map(err => {
|
||||
return html`<div class="pf-c-alert pf-m-inline pf-m-danger">
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
</div>
|
||||
<h4 class="pf-c-alert__title">
|
||||
${err.string}
|
||||
</h4>
|
||||
</div>`;
|
||||
})}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import PFFormControl from "@patternfly/patternfly/components/FormControl/form-co
|
||||
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
||||
import AKGlobal from "../../../authentik.css";
|
||||
import "../../../elements/forms/FormElement";
|
||||
import "../../../elements/EmptyState";
|
||||
@ -25,7 +26,7 @@ export const PasswordManagerPrefill: {
|
||||
export class IdentificationStage extends BaseStage<IdentificationChallenge, IdentificationChallengeResponseRequest> {
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal].concat(
|
||||
return [PFBase, PFAlert, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal].concat(
|
||||
css`
|
||||
/* login page's icons */
|
||||
.pf-c-login__main-footer-links-item button {
|
||||
@ -160,7 +161,7 @@ export class IdentificationStage extends BaseStage<IdentificationChallenge, Iden
|
||||
label=${label}
|
||||
?required="${true}"
|
||||
class="pf-c-form__group"
|
||||
.errors=${(this.challenge?.responseErrors || {})["uid_field"]}>
|
||||
.errors=${(this.challenge.responseErrors || {})["uid_field"]}>
|
||||
<!-- @ts-ignore -->
|
||||
<input type=${type}
|
||||
name="uidField"
|
||||
@ -170,6 +171,25 @@ export class IdentificationStage extends BaseStage<IdentificationChallenge, Iden
|
||||
class="pf-c-form-control"
|
||||
required>
|
||||
</ak-form-element>
|
||||
${this.challenge.passwordFields ? html`
|
||||
<ak-form-element
|
||||
label="${t`Password`}"
|
||||
?required="${true}"
|
||||
class="pf-c-form__group"
|
||||
.errors=${(this.challenge.responseErrors || {})["password"]}>
|
||||
<input type="password"
|
||||
name="password"
|
||||
placeholder="${t`Password`}"
|
||||
autofocus=""
|
||||
autocomplete="current-password"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
value=${PasswordManagerPrefill.password || ""}>
|
||||
</ak-form-element>
|
||||
`: html``}
|
||||
${"non_field_errors" in (this.challenge?.responseErrors || {}) ?
|
||||
this.renderNonFieldErrors(this.challenge?.responseErrors?.non_field_errors || []) :
|
||||
html``}
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
|
||||
${this.challenge.primaryAction}
|
||||
|
||||
@ -13,7 +13,6 @@ import { BaseStage } from "../base";
|
||||
import "../../../elements/forms/FormElement";
|
||||
import "../../../elements/EmptyState";
|
||||
import "../../../elements/Divider";
|
||||
import { Error } from "../../../api/Flows";
|
||||
import { PromptChallenge, PromptChallengeResponseRequest, StagePrompt } from "authentik-api";
|
||||
|
||||
|
||||
@ -103,24 +102,6 @@ export class PromptStage extends BaseStage<PromptChallenge, PromptChallengeRespo
|
||||
return "";
|
||||
}
|
||||
|
||||
renderNonFieldErrors(errors: Error[]): TemplateResult {
|
||||
if (!errors) {
|
||||
return html``;
|
||||
}
|
||||
return html`<div class="pf-c-form__alert">
|
||||
${errors.map(err => {
|
||||
return html`<div class="pf-c-alert pf-m-inline pf-m-danger">
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
</div>
|
||||
<h4 class="pf-c-alert__title">
|
||||
${err.string}
|
||||
</h4>
|
||||
</div>`;
|
||||
})}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
if (!this.challenge) {
|
||||
return html`<ak-empty-state
|
||||
|
||||
@ -2032,6 +2032,7 @@ msgstr "Loading"
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/stages/password/PasswordStageForm.ts
|
||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||
@ -2549,6 +2550,8 @@ msgstr "Pass policy?"
|
||||
msgid "Passing"
|
||||
msgstr "Passing"
|
||||
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
#: src/flows/stages/identification/IdentificationStage.ts
|
||||
#: src/flows/stages/password/PasswordStage.ts
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
@ -2558,6 +2561,10 @@ msgstr "Password"
|
||||
msgid "Password field"
|
||||
msgstr "Password field"
|
||||
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "Password stage"
|
||||
msgstr "Password stage"
|
||||
|
||||
#: src/pages/stages/prompt/PromptForm.ts
|
||||
msgid "Password: Masked input, password is validated against sources. Policies still have to be applied to this Stage. If two of these are used in the same stage, they are ensured to be identical."
|
||||
msgstr "Password: Masked input, password is validated against sources. Policies still have to be applied to this Stage. If two of these are used in the same stage, they are ensured to be identical."
|
||||
@ -4354,6 +4361,10 @@ msgstr "When enabled, the invitation will be deleted after usage."
|
||||
msgid "When enabled, user fields are matched regardless of their casing."
|
||||
msgstr "When enabled, user fields are matched regardless of their casing."
|
||||
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||
msgstr "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
msgid "When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default."
|
||||
msgstr "When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default."
|
||||
|
||||
@ -2031,6 +2031,7 @@ msgstr ""
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
@ -2541,6 +2542,8 @@ msgstr ""
|
||||
msgid "Passing"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
@ -2550,6 +2553,10 @@ msgstr ""
|
||||
msgid "Password field"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Password stage"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Password: Masked input, password is validated against sources. Policies still have to be applied to this Stage. If two of these are used in the same stage, they are ensured to be identical."
|
||||
msgstr ""
|
||||
@ -4342,6 +4349,10 @@ msgstr ""
|
||||
msgid "When enabled, user fields are matched regardless of their casing."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "When selected, incoming assertion's Signatures will be validated against this certificate. To allow unsigned Requests, leave on default."
|
||||
msgstr ""
|
||||
|
||||
@ -76,6 +76,22 @@ export class IdentificationStageForm extends ModelForm<IdentificationStage, stri
|
||||
<p class="pf-c-form__helper-text">${t`Fields a user can identify themselves with. If no fields are selected, the user will only be able to use sources.`}</p>
|
||||
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Password stage`}
|
||||
name="passwordStage">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.passwordStage === undefined}>---------</option>
|
||||
${until(new StagesApi(DEFAULT_CONFIG).stagesPasswordList({
|
||||
ordering: "pk",
|
||||
}).then(stages => {
|
||||
return stages.results.map(stage => {
|
||||
const selected = this.instance?.passwordStage === stage.pk;
|
||||
return html`<option value=${ifDefined(stage.pk)} ?selected=${selected}>${stage.name}</option>`;
|
||||
});
|
||||
}), html`<option>${t`Loading...`}</option>`)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">${t`When selected, a password field is shown on the same page instead of a separate page. This prevents username enumeration attacks.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="caseInsensitiveMatching">
|
||||
<div class="pf-c-check">
|
||||
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?.caseInsensitiveMatching, true)}>
|
||||
|
||||
Reference in New Issue
Block a user