Merge branch 'next' into new-forms
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> # Conflicts: # web/src/api/legacy.ts # web/src/main.ts # web/src/pages/users/UserSettingsPage.ts
This commit is contained in:
commit
95ecad8382
|
@ -3,7 +3,7 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<script src="{% static 'dist/main.js' %}?v={{ ak_version }}" type="module"></script>
|
<script src="{% static 'dist/AdminInterface.js' %}?v={{ ak_version }}" type="module"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<script src="{% static 'dist/flow.js' %}?v={{ ak_version }}" type="module"></script>
|
<script src="{% static 'dist/FlowInterface.js' %}?v={{ ak_version }}" type="module"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
|
|
@ -416,7 +416,7 @@ class TestFlowExecutor(TestCase):
|
||||||
{
|
{
|
||||||
"background": flow.background.url,
|
"background": flow.background.url,
|
||||||
"type": ChallengeTypes.native.value,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "",
|
"component": "ak-stage-dummy",
|
||||||
"title": binding.stage.name,
|
"title": binding.stage.name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,7 +25,7 @@ class ChannelsStorage(FallbackStorage):
|
||||||
uid,
|
uid,
|
||||||
{
|
{
|
||||||
"type": "event.update",
|
"type": "event.update",
|
||||||
"level_tag": message.level_tag,
|
"level": message.level_tag,
|
||||||
"tags": message.tags,
|
"tags": message.tags,
|
||||||
"message": message.message,
|
"message": message.message,
|
||||||
},
|
},
|
||||||
|
|
|
@ -25,7 +25,7 @@ class DummyStageView(ChallengeStageView):
|
||||||
return DummyChallenge(
|
return DummyChallenge(
|
||||||
data={
|
data={
|
||||||
"type": ChallengeTypes.native.value,
|
"type": ChallengeTypes.native.value,
|
||||||
"component": "",
|
"component": "ak-stage-dummy",
|
||||||
"title": self.executor.current_stage.name,
|
"title": self.executor.current_stage.name,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -65,7 +65,7 @@ export default [
|
||||||
},
|
},
|
||||||
// Main Application
|
// Main Application
|
||||||
{
|
{
|
||||||
input: "./src/main.ts",
|
input: "./src/interfaces/AdminInterface.ts",
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
format: "es",
|
format: "es",
|
||||||
|
@ -92,7 +92,7 @@ export default [
|
||||||
},
|
},
|
||||||
// Flow executor
|
// Flow executor
|
||||||
{
|
{
|
||||||
input: "./src/flow.ts",
|
input: "./src/interfaces/FlowInterface.ts",
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
format: "es",
|
format: "es",
|
||||||
|
|
|
@ -4,7 +4,8 @@ import { VERSION } from "../constants";
|
||||||
import { SentryIgnoredError } from "../common/errors";
|
import { SentryIgnoredError } from "../common/errors";
|
||||||
import { Config, Configuration, RootApi } from "authentik-api";
|
import { Config, Configuration, RootApi } from "authentik-api";
|
||||||
import { getCookie } from "../utils";
|
import { getCookie } from "../utils";
|
||||||
import { MIDDLEWARE } from "../elements/notifications/APIDrawer";
|
import { API_DRAWER_MIDDLEWARE } from "../elements/notifications/APIDrawer";
|
||||||
|
import { MessageMiddleware } from "../elements/messages/Middleware";
|
||||||
|
|
||||||
export const DEFAULT_CONFIG = new Configuration({
|
export const DEFAULT_CONFIG = new Configuration({
|
||||||
basePath: "/api/v2beta",
|
basePath: "/api/v2beta",
|
||||||
|
@ -13,7 +14,8 @@ export const DEFAULT_CONFIG = new Configuration({
|
||||||
"X-Authentik-Prevent-Basic": "true"
|
"X-Authentik-Prevent-Basic": "true"
|
||||||
},
|
},
|
||||||
middleware: [
|
middleware: [
|
||||||
MIDDLEWARE
|
API_DRAWER_MIDDLEWARE,
|
||||||
|
new MessageMiddleware(),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -113,4 +113,8 @@ export class FlowURLManager {
|
||||||
return `/flows/-/configure/${stageUuid}/${rest}`;
|
return `/flows/-/configure/${stageUuid}/${rest}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static cancel(): string {
|
||||||
|
return "/flows/-/cancel/";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { customElement, property } from "lit-element";
|
||||||
import { ERROR_CLASS, SUCCESS_CLASS } from "../../constants";
|
import { ERROR_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||||
import { SpinnerButton } from "./SpinnerButton";
|
import { SpinnerButton } from "./SpinnerButton";
|
||||||
import { showMessage } from "../messages/MessageContainer";
|
import { showMessage } from "../messages/MessageContainer";
|
||||||
|
import { MessageLevel } from "../messages/Message";
|
||||||
|
|
||||||
@customElement("ak-action-button")
|
@customElement("ak-action-button")
|
||||||
export class ActionButton extends SpinnerButton {
|
export class ActionButton extends SpinnerButton {
|
||||||
|
@ -26,13 +27,13 @@ export class ActionButton extends SpinnerButton {
|
||||||
.catch((e: Error | Response) => {
|
.catch((e: Error | Response) => {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
showMessage({
|
showMessage({
|
||||||
level_tag: "error",
|
level: MessageLevel.error,
|
||||||
message: e.toString()
|
message: e.toString()
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
e.text().then(t => {
|
e.text().then(t => {
|
||||||
showMessage({
|
showMessage({
|
||||||
level_tag: "error",
|
level: MessageLevel.error,
|
||||||
message: t
|
message: t
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { convertToSlug } from "../../utils";
|
||||||
import { SpinnerButton } from "./SpinnerButton";
|
import { SpinnerButton } from "./SpinnerButton";
|
||||||
import { PRIMARY_CLASS, EVENT_REFRESH } from "../../constants";
|
import { PRIMARY_CLASS, EVENT_REFRESH } from "../../constants";
|
||||||
import { showMessage } from "../messages/MessageContainer";
|
import { showMessage } from "../messages/MessageContainer";
|
||||||
|
import { MessageLevel } from "../messages/Message";
|
||||||
|
|
||||||
@customElement("ak-modal-button")
|
@customElement("ak-modal-button")
|
||||||
export class ModalButton extends LitElement {
|
export class ModalButton extends LitElement {
|
||||||
|
@ -122,7 +123,7 @@ export class ModalButton extends LitElement {
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
showMessage({
|
showMessage({
|
||||||
level_tag: "error",
|
level: MessageLevel.error,
|
||||||
message: "Unexpected error"
|
message: "Unexpected error"
|
||||||
});
|
});
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -150,7 +151,7 @@ export class ModalButton extends LitElement {
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
showMessage({
|
showMessage({
|
||||||
level_tag: "error",
|
level: MessageLevel.error,
|
||||||
message: "Unexpected error"
|
message: "Unexpected error"
|
||||||
});
|
});
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { gettext } from "django";
|
||||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||||
import { EVENT_REFRESH } from "../../constants";
|
import { EVENT_REFRESH } from "../../constants";
|
||||||
import { ModalButton } from "../buttons/ModalButton";
|
import { ModalButton } from "../buttons/ModalButton";
|
||||||
|
import { MessageLevel } from "../messages/Message";
|
||||||
import { showMessage } from "../messages/MessageContainer";
|
import { showMessage } from "../messages/MessageContainer";
|
||||||
|
|
||||||
@customElement("ak-forms-confirm")
|
@customElement("ak-forms-confirm")
|
||||||
|
@ -36,14 +37,14 @@ export class ConfirmationForm extends ModalButton {
|
||||||
onSuccess(): void {
|
onSuccess(): void {
|
||||||
showMessage({
|
showMessage({
|
||||||
message: gettext(this.successMessage),
|
message: gettext(this.successMessage),
|
||||||
level_tag: "success",
|
level: MessageLevel.success,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onError(e: Error): void {
|
onError(e: Error): void {
|
||||||
showMessage({
|
showMessage({
|
||||||
message: gettext(`${this.errorMessage}: ${e.toString()}`),
|
message: gettext(`${this.errorMessage}: ${e.toString()}`),
|
||||||
level_tag: "error",
|
level: MessageLevel.error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { gettext } from "django";
|
||||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||||
import { EVENT_REFRESH } from "../../constants";
|
import { EVENT_REFRESH } from "../../constants";
|
||||||
import { ModalButton } from "../buttons/ModalButton";
|
import { ModalButton } from "../buttons/ModalButton";
|
||||||
|
import { MessageLevel } from "../messages/Message";
|
||||||
import { showMessage } from "../messages/MessageContainer";
|
import { showMessage } from "../messages/MessageContainer";
|
||||||
|
|
||||||
@customElement("ak-forms-delete")
|
@customElement("ak-forms-delete")
|
||||||
|
@ -34,14 +35,14 @@ export class DeleteForm extends ModalButton {
|
||||||
onSuccess(): void {
|
onSuccess(): void {
|
||||||
showMessage({
|
showMessage({
|
||||||
message: gettext(`Successfully deleted ${this.objectLabel} ${ this.obj?.name }`),
|
message: gettext(`Successfully deleted ${this.objectLabel} ${ this.obj?.name }`),
|
||||||
level_tag: "success",
|
level: MessageLevel.success,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onError(e: Error): void {
|
onError(e: Error): void {
|
||||||
showMessage({
|
showMessage({
|
||||||
message: gettext(`Failed to delete ${this.objectLabel}: ${e.toString()}`),
|
message: gettext(`Failed to delete ${this.objectLabel}: ${e.toString()}`),
|
||||||
level_tag: "error",
|
level: MessageLevel.error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,17 @@ import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
|
||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||||
|
|
||||||
|
export enum MessageLevel {
|
||||||
|
error = "error",
|
||||||
|
warning = "warning",
|
||||||
|
success = "success",
|
||||||
|
info = "info"
|
||||||
|
}
|
||||||
export interface APIMessage {
|
export interface APIMessage {
|
||||||
level_tag: string;
|
level: MessageLevel;
|
||||||
tags?: string;
|
tags?: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LEVEL_ICON_MAP: { [key: string]: string } = {
|
const LEVEL_ICON_MAP: { [key: string]: string } = {
|
||||||
|
@ -44,13 +51,16 @@ export class Message extends LitElement {
|
||||||
|
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
return html`<li class="pf-c-alert-group__item">
|
return html`<li class="pf-c-alert-group__item">
|
||||||
<div class="pf-c-alert pf-m-${this.message?.level_tag} ${this.message?.level_tag === "error" ? "pf-m-danger" : ""}">
|
<div class="pf-c-alert pf-m-${this.message?.level} ${this.message?.level === MessageLevel.error ? "pf-m-danger" : ""}">
|
||||||
<div class="pf-c-alert__icon">
|
<div class="pf-c-alert__icon">
|
||||||
<i class="${this.message ? LEVEL_ICON_MAP[this.message.level_tag] : ""}"></i>
|
<i class="${this.message ? LEVEL_ICON_MAP[this.message.level] : ""}"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="pf-c-alert__title">
|
<p class="pf-c-alert__title">
|
||||||
${this.message?.message}
|
${this.message?.message}
|
||||||
</p>
|
</p>
|
||||||
|
${this.message?.description && html`<div class="pf-c-alert__description">
|
||||||
|
<p>${this.message.description}</p>
|
||||||
|
</div>`}
|
||||||
<div class="pf-c-alert__action">
|
<div class="pf-c-alert__action">
|
||||||
<button class="pf-c-button pf-m-plain" type="button" @click=${() => {
|
<button class="pf-c-button pf-m-plain" type="button" @click=${() => {
|
||||||
if (!this.message) return;
|
if (!this.message) return;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { LitElement, html, customElement, TemplateResult, property, CSSResult, css } from "lit-element";
|
import { LitElement, html, customElement, TemplateResult, property, CSSResult, css } from "lit-element";
|
||||||
import "./Message";
|
import "./Message";
|
||||||
import { APIMessage } from "./Message";
|
import { APIMessage, MessageLevel } from "./Message";
|
||||||
import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css";
|
import PFAlertGroup from "@patternfly/patternfly/components/AlertGroup/alert-group.css";
|
||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ export class MessageContainer extends LitElement {
|
||||||
console.debug(`authentik/messages: closed ws connection: ${e}`);
|
console.debug(`authentik/messages: closed ws connection: ${e}`);
|
||||||
if (this.retryDelay > 3000) {
|
if (this.retryDelay > 3000) {
|
||||||
showMessage({
|
showMessage({
|
||||||
level_tag: "error",
|
level: MessageLevel.error,
|
||||||
message: gettext("Connection error, reconnecting...")
|
message: gettext("Connection error, reconnecting...")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Middleware, ResponseContext } from "authentik-api";
|
||||||
|
import { gettext } from "django";
|
||||||
|
import { MessageLevel } from "./Message";
|
||||||
|
import { showMessage } from "./MessageContainer";
|
||||||
|
|
||||||
|
export class MessageMiddleware implements Middleware {
|
||||||
|
|
||||||
|
post(context: ResponseContext): Promise<Response | void> {
|
||||||
|
if (!context.response.ok) {
|
||||||
|
showMessage({
|
||||||
|
level: MessageLevel.error,
|
||||||
|
message: gettext("API request failed"),
|
||||||
|
description: `${context.init.method} ${context.url}: ${context.response.status}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve(context.response);
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ export class APIMiddleware implements Middleware {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MAX_REQUESTS = 50;
|
export const MAX_REQUESTS = 50;
|
||||||
export const MIDDLEWARE = new APIMiddleware();
|
export const API_DRAWER_MIDDLEWARE = new APIMiddleware();
|
||||||
|
|
||||||
@customElement("ak-api-drawer")
|
@customElement("ak-api-drawer")
|
||||||
export class APIDrawer extends LitElement {
|
export class APIDrawer extends LitElement {
|
||||||
|
@ -76,7 +76,7 @@ export class APIDrawer extends LitElement {
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-c-notification-drawer__body">
|
<div class="pf-c-notification-drawer__body">
|
||||||
<ul class="pf-c-notification-drawer__list">
|
<ul class="pf-c-notification-drawer__list">
|
||||||
${MIDDLEWARE.requests.map(n => this.renderItem(n))}
|
${API_DRAWER_MIDDLEWARE.requests.map(n => this.renderItem(n))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
import "construct-style-sheets-polyfill";
|
|
||||||
|
|
||||||
import "./elements/messages/MessageContainer";
|
|
||||||
import "./flows/FlowExecutor";
|
|
|
@ -9,6 +9,7 @@ import PFList from "@patternfly/patternfly/components/List/list.css";
|
||||||
import AKGlobal from "../authentik.css";
|
import AKGlobal from "../authentik.css";
|
||||||
|
|
||||||
import { unsafeHTML } from "lit-html/directives/unsafe-html";
|
import { unsafeHTML } from "lit-html/directives/unsafe-html";
|
||||||
|
import "./access_denied/FlowAccessDenied";
|
||||||
import "./stages/authenticator_static/AuthenticatorStaticStage";
|
import "./stages/authenticator_static/AuthenticatorStaticStage";
|
||||||
import "./stages/authenticator_totp/AuthenticatorTOTPStage";
|
import "./stages/authenticator_totp/AuthenticatorTOTPStage";
|
||||||
import "./stages/authenticator_validate/AuthenticatorValidateStage";
|
import "./stages/authenticator_validate/AuthenticatorValidateStage";
|
||||||
|
@ -16,11 +17,11 @@ import "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
|
||||||
import "./stages/autosubmit/AutosubmitStage";
|
import "./stages/autosubmit/AutosubmitStage";
|
||||||
import "./stages/captcha/CaptchaStage";
|
import "./stages/captcha/CaptchaStage";
|
||||||
import "./stages/consent/ConsentStage";
|
import "./stages/consent/ConsentStage";
|
||||||
|
import "./stages/dummy/DummyStage";
|
||||||
import "./stages/email/EmailStage";
|
import "./stages/email/EmailStage";
|
||||||
import "./stages/identification/IdentificationStage";
|
import "./stages/identification/IdentificationStage";
|
||||||
import "./stages/password/PasswordStage";
|
import "./stages/password/PasswordStage";
|
||||||
import "./stages/prompt/PromptStage";
|
import "./stages/prompt/PromptStage";
|
||||||
import "./access_denied/FlowAccessDenied";
|
|
||||||
import { ShellChallenge, RedirectChallenge } from "../api/Flows";
|
import { ShellChallenge, RedirectChallenge } from "../api/Flows";
|
||||||
import { IdentificationChallenge } from "./stages/identification/IdentificationStage";
|
import { IdentificationChallenge } from "./stages/identification/IdentificationStage";
|
||||||
import { PasswordChallenge } from "./stages/password/PasswordStage";
|
import { PasswordChallenge } from "./stages/password/PasswordStage";
|
||||||
|
@ -193,6 +194,8 @@ export class FlowExecutor extends LitElement implements StageHost {
|
||||||
return html`<ak-stage-captcha .host=${this} .challenge=${this.challenge as CaptchaChallenge}></ak-stage-captcha>`;
|
return html`<ak-stage-captcha .host=${this} .challenge=${this.challenge as CaptchaChallenge}></ak-stage-captcha>`;
|
||||||
case "ak-stage-consent":
|
case "ak-stage-consent":
|
||||||
return html`<ak-stage-consent .host=${this} .challenge=${this.challenge as ConsentChallenge}></ak-stage-consent>`;
|
return html`<ak-stage-consent .host=${this} .challenge=${this.challenge as ConsentChallenge}></ak-stage-consent>`;
|
||||||
|
case "ak-stage-dummy":
|
||||||
|
return html`<ak-stage-dummy .host=${this} .challenge=${this.challenge as Challenge}></ak-stage-dummy>`;
|
||||||
case "ak-stage-email":
|
case "ak-stage-email":
|
||||||
return html`<ak-stage-email .host=${this} .challenge=${this.challenge as EmailChallenge}></ak-stage-email>`;
|
return html`<ak-stage-email .host=${this} .challenge=${this.challenge as EmailChallenge}></ak-stage-email>`;
|
||||||
case "ak-stage-autosubmit":
|
case "ak-stage-autosubmit":
|
||||||
|
|
|
@ -14,7 +14,6 @@ import "../../elements/EmptyState";
|
||||||
|
|
||||||
export interface AccessDeniedChallenge extends Challenge {
|
export interface AccessDeniedChallenge extends Challenge {
|
||||||
error_message?: string;
|
error_message?: string;
|
||||||
policy_result?: Record<string, unknown>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ak-stage-access-denied")
|
@customElement("ak-stage-access-denied")
|
||||||
|
@ -49,27 +48,6 @@ export class FlowAccessDenied extends BaseStage {
|
||||||
${this.challenge?.error_message &&
|
${this.challenge?.error_message &&
|
||||||
html`<hr>
|
html`<hr>
|
||||||
<p>${this.challenge.error_message}</p>`}
|
<p>${this.challenge.error_message}</p>`}
|
||||||
${this.challenge.policy_result &&
|
|
||||||
html`<hr>
|
|
||||||
<em>
|
|
||||||
${gettext("Explanation:")}
|
|
||||||
</em>
|
|
||||||
<ul class="pf-c-list">
|
|
||||||
{% for source_result in policy_result.source_results %}
|
|
||||||
<li>
|
|
||||||
{% blocktrans with name=source_result.source_policy.name result=source_result.passing %}
|
|
||||||
Policy '{{ name }}' returned result '{{ result }}'
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% if source_result.messages %}
|
|
||||||
<ul class="pf-c-list">
|
|
||||||
{% for message in source_result.messages %}
|
|
||||||
<li>{{ message }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>`}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { BaseStage } from "../base";
|
||||||
import "../../../elements/forms/FormElement";
|
import "../../../elements/forms/FormElement";
|
||||||
import "../../../elements/EmptyState";
|
import "../../../elements/EmptyState";
|
||||||
import "../../FormStatic";
|
import "../../FormStatic";
|
||||||
|
import { FlowURLManager } from "../../../api/legacy";
|
||||||
|
|
||||||
export const STATIC_TOKEN_STYLE = css`
|
export const STATIC_TOKEN_STYLE = css`
|
||||||
/* Static OTP Tokens */
|
/* Static OTP Tokens */
|
||||||
|
@ -61,7 +62,7 @@ export class AuthenticatorStaticStage extends BaseStage {
|
||||||
userAvatar="${this.challenge.pending_user_avatar}"
|
userAvatar="${this.challenge.pending_user_avatar}"
|
||||||
user=${this.challenge.pending_user}>
|
user=${this.challenge.pending_user}>
|
||||||
<div slot="link">
|
<div slot="link">
|
||||||
<a href="/flows/-/cancel/">${gettext("Not you?")}</a>
|
<a href="${FlowURLManager.cancel()}">${gettext("Not you?")}</a>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-static>
|
</ak-form-static>
|
||||||
<ak-form-element
|
<ak-form-element
|
||||||
|
|
|
@ -14,6 +14,8 @@ import "../../../elements/forms/FormElement";
|
||||||
import { showMessage } from "../../../elements/messages/MessageContainer";
|
import { showMessage } from "../../../elements/messages/MessageContainer";
|
||||||
import "../../../elements/EmptyState";
|
import "../../../elements/EmptyState";
|
||||||
import "../../FormStatic";
|
import "../../FormStatic";
|
||||||
|
import { MessageLevel } from "../../../elements/messages/Message";
|
||||||
|
import { FlowURLManager } from "../../../api/legacy";
|
||||||
|
|
||||||
export interface AuthenticatorTOTPChallenge extends WithUserInfoChallenge {
|
export interface AuthenticatorTOTPChallenge extends WithUserInfoChallenge {
|
||||||
config_url: string;
|
config_url: string;
|
||||||
|
@ -48,7 +50,7 @@ export class AuthenticatorTOTPStage extends BaseStage {
|
||||||
userAvatar="${this.challenge.pending_user_avatar}"
|
userAvatar="${this.challenge.pending_user_avatar}"
|
||||||
user=${this.challenge.pending_user}>
|
user=${this.challenge.pending_user}>
|
||||||
<div slot="link">
|
<div slot="link">
|
||||||
<a href="/flows/-/cancel/">${gettext("Not you?")}</a>
|
<a href="${FlowURLManager.cancel()}">${gettext("Not you?")}</a>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-static>
|
</ak-form-static>
|
||||||
<input type="hidden" name="otp_uri" value=${this.challenge.config_url} />
|
<input type="hidden" name="otp_uri" value=${this.challenge.config_url} />
|
||||||
|
@ -60,7 +62,7 @@ export class AuthenticatorTOTPStage extends BaseStage {
|
||||||
if (!this.challenge?.config_url) return;
|
if (!this.challenge?.config_url) return;
|
||||||
navigator.clipboard.writeText(this.challenge?.config_url).then(() => {
|
navigator.clipboard.writeText(this.challenge?.config_url).then(() => {
|
||||||
showMessage({
|
showMessage({
|
||||||
level_tag: "success",
|
level: MessageLevel.success,
|
||||||
message: gettext("Successfully copied TOTP Config.")
|
message: gettext("Successfully copied TOTP Config.")
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ import AKGlobal from "../../../authentik.css";
|
||||||
import { BaseStage, StageHost } from "../base";
|
import { BaseStage, StageHost } from "../base";
|
||||||
import "./AuthenticatorValidateStageWebAuthn";
|
import "./AuthenticatorValidateStageWebAuthn";
|
||||||
import "./AuthenticatorValidateStageCode";
|
import "./AuthenticatorValidateStageCode";
|
||||||
|
import { PasswordManagerPrefill } from "../identification/IdentificationStage";
|
||||||
|
|
||||||
export enum DeviceClasses {
|
export enum DeviceClasses {
|
||||||
STATIC = "static",
|
STATIC = "static",
|
||||||
|
@ -83,6 +84,17 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
|
||||||
<small>${gettext("Use a security key to prove your identity.")}</small>
|
<small>${gettext("Use a security key to prove your identity.")}</small>
|
||||||
</div>`;
|
</div>`;
|
||||||
case DeviceClasses.TOTP:
|
case DeviceClasses.TOTP:
|
||||||
|
// TOTP is a bit special, assuming that TOTP is allowed from the backend,
|
||||||
|
// and we have a pre-filled value from the password manager,
|
||||||
|
// directly set the the TOTP device Challenge as active.
|
||||||
|
if (PasswordManagerPrefill.totp) {
|
||||||
|
console.debug("authentik/stages/authenticator_validate: found prefill totp code, selecting totp challenge");
|
||||||
|
this.selectedDeviceChallenge = deviceChallenge;
|
||||||
|
// Delay the update as a re-render isn't triggered from here
|
||||||
|
setTimeout(() => {
|
||||||
|
this.requestUpdate();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
return html`<i class="fas fa-clock"></i>
|
return html`<i class="fas fa-clock"></i>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<p>${gettext("Traditional authenticator")}</p>
|
<p>${gettext("Traditional authenticator")}</p>
|
||||||
|
@ -141,9 +153,9 @@ export class AuthenticatorValidateStage extends BaseStage implements StageHost {
|
||||||
render(): TemplateResult {
|
render(): TemplateResult {
|
||||||
if (!this.challenge) {
|
if (!this.challenge) {
|
||||||
return html`<ak-empty-state
|
return html`<ak-empty-state
|
||||||
?loading="${true}"
|
?loading="${true}"
|
||||||
header=${gettext("Loading")}>
|
header=${gettext("Loading")}>
|
||||||
</ak-empty-state>`;
|
</ak-empty-state>`;
|
||||||
}
|
}
|
||||||
// User only has a single device class, so we don't show a picker
|
// User only has a single device class, so we don't show a picker
|
||||||
if (this.challenge?.device_challenges.length === 1) {
|
if (this.challenge?.device_challenges.length === 1) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import "../../../elements/forms/FormElement";
|
||||||
import "../../../elements/EmptyState";
|
import "../../../elements/EmptyState";
|
||||||
import { PasswordManagerPrefill } from "../identification/IdentificationStage";
|
import { PasswordManagerPrefill } from "../identification/IdentificationStage";
|
||||||
import "../../FormStatic";
|
import "../../FormStatic";
|
||||||
|
import { FlowURLManager } from "../../../api/legacy";
|
||||||
|
|
||||||
@customElement("ak-stage-authenticator-validate-code")
|
@customElement("ak-stage-authenticator-validate-code")
|
||||||
export class AuthenticatorValidateStageWebCode extends BaseStage {
|
export class AuthenticatorValidateStageWebCode extends BaseStage {
|
||||||
|
@ -44,7 +45,7 @@ export class AuthenticatorValidateStageWebCode extends BaseStage {
|
||||||
userAvatar="${this.challenge.pending_user_avatar}"
|
userAvatar="${this.challenge.pending_user_avatar}"
|
||||||
user=${this.challenge.pending_user}>
|
user=${this.challenge.pending_user}>
|
||||||
<div slot="link">
|
<div slot="link">
|
||||||
<a href="/flows/-/cancel/">${gettext("Not you?")}</a>
|
<a href="${FlowURLManager.cancel()}">${gettext("Not you?")}</a>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-static>
|
</ak-form-static>
|
||||||
<ak-form-element
|
<ak-form-element
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { BaseStage } from "../base";
|
||||||
import "../../../elements/forms/FormElement";
|
import "../../../elements/forms/FormElement";
|
||||||
import "../../../elements/EmptyState";
|
import "../../../elements/EmptyState";
|
||||||
import "../../FormStatic";
|
import "../../FormStatic";
|
||||||
|
import { FlowURLManager } from "../../../api/legacy";
|
||||||
|
|
||||||
export interface CaptchaChallenge extends WithUserInfoChallenge {
|
export interface CaptchaChallenge extends WithUserInfoChallenge {
|
||||||
site_key: string;
|
site_key: string;
|
||||||
|
@ -78,7 +79,7 @@ export class CaptchaStage extends BaseStage {
|
||||||
userAvatar="${this.challenge.pending_user_avatar}"
|
userAvatar="${this.challenge.pending_user_avatar}"
|
||||||
user=${this.challenge.pending_user}>
|
user=${this.challenge.pending_user}>
|
||||||
<div slot="link">
|
<div slot="link">
|
||||||
<a href="/flows/-/cancel/">${gettext("Not you?")}</a>
|
<a href="${FlowURLManager.cancel()}">${gettext("Not you?")}</a>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-static>
|
</ak-form-static>
|
||||||
<div class="ak-loading">
|
<div class="ak-loading">
|
||||||
|
|
|
@ -11,6 +11,7 @@ import AKGlobal from "../../../authentik.css";
|
||||||
import { BaseStage } from "../base";
|
import { BaseStage } from "../base";
|
||||||
import "../../../elements/EmptyState";
|
import "../../../elements/EmptyState";
|
||||||
import "../../FormStatic";
|
import "../../FormStatic";
|
||||||
|
import { FlowURLManager } from "../../../api/legacy";
|
||||||
|
|
||||||
export interface Permission {
|
export interface Permission {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -53,7 +54,7 @@ export class ConsentStage extends BaseStage {
|
||||||
userAvatar="${this.challenge.pending_user_avatar}"
|
userAvatar="${this.challenge.pending_user_avatar}"
|
||||||
user=${this.challenge.pending_user}>
|
user=${this.challenge.pending_user}>
|
||||||
<div slot="link">
|
<div slot="link">
|
||||||
<a href="/flows/-/cancel/">${gettext("Not you?")}</a>
|
<a href="${FlowURLManager.cancel()}">${gettext("Not you?")}</a>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-static>
|
</ak-form-static>
|
||||||
<div class="pf-c-form__group">
|
<div class="pf-c-form__group">
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { gettext } from "django";
|
||||||
|
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
|
||||||
|
import { Challenge } from "../../../api/Flows";
|
||||||
|
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
|
||||||
|
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||||
|
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||||
|
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 AKGlobal from "../../../authentik.css";
|
||||||
|
import { BaseStage } from "../base";
|
||||||
|
import "../../../elements/EmptyState";
|
||||||
|
import "../../FormStatic";
|
||||||
|
|
||||||
|
@customElement("ak-stage-dummy")
|
||||||
|
export class DummyStage extends BaseStage {
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
challenge?: Challenge;
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal];
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): TemplateResult {
|
||||||
|
if (!this.challenge) {
|
||||||
|
return html`<ak-empty-state
|
||||||
|
?loading="${true}"
|
||||||
|
header=${gettext("Loading")}>
|
||||||
|
</ak-empty-state>`;
|
||||||
|
}
|
||||||
|
return html`<header class="pf-c-login__main-header">
|
||||||
|
<h1 class="pf-c-title pf-m-3xl">
|
||||||
|
${this.challenge.title}
|
||||||
|
</h1>
|
||||||
|
</header>
|
||||||
|
<div class="pf-c-login__main-body">
|
||||||
|
<form class="pf-c-form" @submit=${(e: Event) => { this.submitForm(e); }}>
|
||||||
|
<div class="pf-c-form__group pf-m-action">
|
||||||
|
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
|
||||||
|
${gettext("Continue")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<footer class="pf-c-login__main-footer">
|
||||||
|
<ul class="pf-c-login__main-footer-links">
|
||||||
|
</ul>
|
||||||
|
</footer>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import "../../../elements/forms/FormElement";
|
||||||
import "../../../elements/EmptyState";
|
import "../../../elements/EmptyState";
|
||||||
import { PasswordManagerPrefill } from "../identification/IdentificationStage";
|
import { PasswordManagerPrefill } from "../identification/IdentificationStage";
|
||||||
import "../../FormStatic";
|
import "../../FormStatic";
|
||||||
|
import { FlowURLManager } from "../../../api/legacy";
|
||||||
|
|
||||||
export interface PasswordChallenge extends WithUserInfoChallenge {
|
export interface PasswordChallenge extends WithUserInfoChallenge {
|
||||||
recovery_url?: string;
|
recovery_url?: string;
|
||||||
|
@ -47,7 +48,7 @@ export class PasswordStage extends BaseStage {
|
||||||
userAvatar="${this.challenge.pending_user_avatar}"
|
userAvatar="${this.challenge.pending_user_avatar}"
|
||||||
user=${this.challenge.pending_user}>
|
user=${this.challenge.pending_user}>
|
||||||
<div slot="link">
|
<div slot="link">
|
||||||
<a href="/flows/-/cancel/">${gettext("Not you?")}</a>
|
<a href="${FlowURLManager.cancel()}">${gettext("Not you?")}</a>
|
||||||
</div>
|
</div>
|
||||||
</ak-form-static>
|
</ak-form-static>
|
||||||
<input name="username" autocomplete="username" type="hidden" value="${this.challenge.pending_user}">
|
<input name="username" autocomplete="username" type="hidden" value="${this.challenge.pending_user}">
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
import "construct-style-sheets-polyfill";
|
||||||
|
|
||||||
|
// Elements that are used by SiteShell pages
|
||||||
|
// And can't dynamically be imported
|
||||||
|
import "../elements/CodeMirror";
|
||||||
|
import "../elements/messages/MessageContainer";
|
||||||
import { customElement } from "lit-element";
|
import { customElement } from "lit-element";
|
||||||
import { me } from "../api/Users";
|
import { me } from "../api/Users";
|
||||||
import { SidebarItem } from "../elements/sidebar/Sidebar";
|
import { SidebarItem } from "../elements/sidebar/Sidebar";
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
import "construct-style-sheets-polyfill";
|
||||||
|
|
||||||
|
import "../elements/messages/MessageContainer";
|
||||||
|
import "../flows/FlowExecutor";
|
|
@ -8,7 +8,8 @@
|
||||||
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css">
|
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css">
|
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css">
|
||||||
<script src="/api/jsi18n/"></script>
|
<script src="/api/jsi18n/"></script>
|
||||||
<script src="/static/dist/main.js" type="module"></script>
|
<script src="/static/dist/AdminInterface.js" type="module"></script>
|
||||||
|
<title>authentik</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<ak-message-container></ak-message-container>
|
<ak-message-container></ak-message-container>
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css">
|
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css">
|
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css">
|
||||||
<script src="/api/jsi18n/"></script>
|
<script src="/api/jsi18n/"></script>
|
||||||
<script src="/static/dist/flow.js" type="module"></script>
|
<script src="/static/dist/FlowInterface.js" type="module"></script>
|
||||||
|
<title>authentik</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<ak-message-container></ak-message-container>
|
<ak-message-container></ak-message-container>
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import "construct-style-sheets-polyfill";
|
|
||||||
|
|
||||||
// Elements that are used by SiteShell pages
|
|
||||||
// And can't dynamically be imported
|
|
||||||
import "./elements/buttons/ActionButton";
|
|
||||||
import "./elements/buttons/Dropdown";
|
|
||||||
import "./elements/buttons/ModalButton";
|
|
||||||
import "./elements/buttons/SpinnerButton";
|
|
||||||
import "./elements/CodeMirror";
|
|
||||||
|
|
||||||
import "./pages/generic/SiteShell";
|
|
||||||
import "./interfaces/AdminInterface";
|
|
||||||
import "./elements/messages/MessageContainer";
|
|
|
@ -57,8 +57,8 @@ export class FlowListPage extends TablePage<Flow> {
|
||||||
</a>`,
|
</a>`,
|
||||||
html`${item.name}`,
|
html`${item.name}`,
|
||||||
html`${item.designation}`,
|
html`${item.designation}`,
|
||||||
html`${item.stages?.size}`,
|
html`${Array.from(item.stages || []).length}`,
|
||||||
html`${item.policies?.size}`,
|
html`${Array.from(item.policies || []).length}`,
|
||||||
html`
|
html`
|
||||||
<ak-modal-button href="${AdminURLManager.flows(`${item.pk}/update/`)}">
|
<ak-modal-button href="${AdminURLManager.flows(`${item.pk}/update/`)}">
|
||||||
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
<ak-spinner-button slot="trigger" class="pf-m-secondary">
|
||||||
|
|
|
@ -19,6 +19,7 @@ import AKGlobal from "../../authentik.css";
|
||||||
import CodeMirrorStyle from "codemirror/lib/codemirror.css";
|
import CodeMirrorStyle from "codemirror/lib/codemirror.css";
|
||||||
import CodeMirrorTheme from "codemirror/theme/monokai.css";
|
import CodeMirrorTheme from "codemirror/theme/monokai.css";
|
||||||
import { EVENT_REFRESH } from "../../constants";
|
import { EVENT_REFRESH } from "../../constants";
|
||||||
|
import { MessageLevel } from "../../elements/messages/Message";
|
||||||
|
|
||||||
@customElement("ak-site-shell")
|
@customElement("ak-site-shell")
|
||||||
export class SiteShell extends LitElement {
|
export class SiteShell extends LitElement {
|
||||||
|
@ -79,7 +80,7 @@ export class SiteShell extends LitElement {
|
||||||
}
|
}
|
||||||
console.debug(`authentik/site-shell: Request failed ${this._url}`);
|
console.debug(`authentik/site-shell: Request failed ${this._url}`);
|
||||||
showMessage({
|
showMessage({
|
||||||
level_tag: "error",
|
level: MessageLevel.error,
|
||||||
message: gettext(`Request failed: ${response.statusText}`),
|
message: gettext(`Request failed: ${response.statusText}`),
|
||||||
});
|
});
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
@ -148,7 +149,7 @@ export class SiteShell extends LitElement {
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
showMessage({
|
showMessage({
|
||||||
level_tag: "error",
|
level: MessageLevel.error,
|
||||||
message: "Unexpected error"
|
message: "Unexpected error"
|
||||||
});
|
});
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { gettext } from "django";
|
import { gettext } from "django";
|
||||||
import { customElement, html, TemplateResult } from "lit-element";
|
import { customElement, html, TemplateResult } from "lit-element";
|
||||||
import { DeleteForm } from "../../elements/forms/DeleteForm";
|
import { DeleteForm } from "../../elements/forms/DeleteForm";
|
||||||
|
import { MessageLevel } from "../../elements/messages/Message";
|
||||||
import { showMessage } from "../../elements/messages/MessageContainer";
|
import { showMessage } from "../../elements/messages/MessageContainer";
|
||||||
|
|
||||||
@customElement("ak-user-active-form")
|
@customElement("ak-user-active-form")
|
||||||
|
@ -9,14 +10,14 @@ export class UserActiveForm extends DeleteForm {
|
||||||
onSuccess(): void {
|
onSuccess(): void {
|
||||||
showMessage({
|
showMessage({
|
||||||
message: gettext(`Successfully updated ${this.objectLabel} ${this.obj?.name}`),
|
message: gettext(`Successfully updated ${this.objectLabel} ${this.obj?.name}`),
|
||||||
level_tag: "success",
|
level: MessageLevel.success,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onError(e: Error): void {
|
onError(e: Error): void {
|
||||||
showMessage({
|
showMessage({
|
||||||
message: gettext(`Failed to update ${this.objectLabel}: ${e.toString()}`),
|
message: gettext(`Failed to update ${this.objectLabel}: ${e.toString()}`),
|
||||||
level_tag: "error",
|
level: MessageLevel.error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in New Issue