Compare commits
	
		
			10 Commits
		
	
	
		
			20240219-m
			...
			version/20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 318e0cf9f8 | |||
| bd0815d894 | |||
| af35ecfe66 | |||
| 0c05cd64bb | |||
| cb80b76490 | |||
| 061d4bc758 | |||
| 8ff27f69e1 | |||
| 045cd98276 | |||
| b520843984 | |||
| 92216e4ea8 | 
@ -1,5 +1,5 @@
 | 
				
			|||||||
[bumpversion]
 | 
					[bumpversion]
 | 
				
			||||||
current_version = 2023.10.7
 | 
					current_version = 2024.2.0-rc2
 | 
				
			||||||
tag = True
 | 
					tag = True
 | 
				
			||||||
commit = True
 | 
					commit = True
 | 
				
			||||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
 | 
					parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										11
									
								
								.github/actions/docker-push-variables/action.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/actions/docker-push-variables/action.yml
									
									
									
									
										vendored
									
									
								
							@ -68,18 +68,21 @@ runs:
 | 
				
			|||||||
            for name in image_names:
 | 
					            for name in image_names:
 | 
				
			||||||
                image_tags += [
 | 
					                image_tags += [
 | 
				
			||||||
                    f"{name}:{version}",
 | 
					                    f"{name}:{version}",
 | 
				
			||||||
                    f"{name}:{version_family}",
 | 
					 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
            if not prerelease:
 | 
					            if not prerelease:
 | 
				
			||||||
                image_tags += [f"{name}:latest"]
 | 
					                image_tags += [
 | 
				
			||||||
 | 
					                    f"{name}:latest",
 | 
				
			||||||
 | 
					                    f"{name}:{version_family}",
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            suffix = ""
 | 
					            suffix = ""
 | 
				
			||||||
            if image_arch and image_arch != "amd64":
 | 
					            if image_arch and image_arch != "amd64":
 | 
				
			||||||
                suffix = f"-{image_arch}"
 | 
					                suffix = f"-{image_arch}"
 | 
				
			||||||
            for name in image_names:
 | 
					            for name in image_names:
 | 
				
			||||||
                image_tags += [
 | 
					                image_tags += [
 | 
				
			||||||
                    f"{name}:gh-{sha}{suffix}",
 | 
					                    f"{name}:gh-{sha}{suffix}",  # Used for ArgoCD and PR comments
 | 
				
			||||||
                    f"{name}:gh-{safe_branch_name}{suffix}",
 | 
					                    f"{name}:gh-{safe_branch_name}{suffix}",  # For convenience
 | 
				
			||||||
 | 
					                    f"{name}:gh-{safe_branch_name}-{int(time())}-{sha[:7]}{suffix}",  # Use by FluxCD
 | 
				
			||||||
                ]
 | 
					                ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        image_main_tag = image_tags[0]
 | 
					        image_main_tag = image_tags[0]
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci-main.yml
									
									
									
									
										vendored
									
									
								
							@ -70,7 +70,7 @@ jobs:
 | 
				
			|||||||
          cp authentik/lib/default.yml local.env.yml
 | 
					          cp authentik/lib/default.yml local.env.yml
 | 
				
			||||||
          cp -R .github ..
 | 
					          cp -R .github ..
 | 
				
			||||||
          cp -R scripts ..
 | 
					          cp -R scripts ..
 | 
				
			||||||
          git checkout version/$(python -c "from authentik import __version__; print(__version__)")
 | 
					          git checkout $(git tag --sort=version:refname | grep '^version/' | grep -vE -- '-rc[0-9]+$' | tail -n1)
 | 
				
			||||||
          rm -rf .github/ scripts/
 | 
					          rm -rf .github/ scripts/
 | 
				
			||||||
          mv ../.github ../scripts .
 | 
					          mv ../.github ../scripts .
 | 
				
			||||||
      - name: Setup authentik env (stable)
 | 
					      - name: Setup authentik env (stable)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							@ -172,8 +172,8 @@ jobs:
 | 
				
			|||||||
          image-name: ghcr.io/goauthentik/server
 | 
					          image-name: ghcr.io/goauthentik/server
 | 
				
			||||||
      - name: Get static files from docker image
 | 
					      - name: Get static files from docker image
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          docker pull ghcr.io/goauthentik/server:${{ steps.ev.outputs.imageMainTag }}
 | 
					          docker pull ${{ steps.ev.outputs.imageMainTag }}
 | 
				
			||||||
          container=$(docker container create ghcr.io/goauthentik/server:${{ steps.ev.outputs.imageMainTag }})
 | 
					          container=$(docker container create ${{ steps.ev.outputs.imageMainTag }})
 | 
				
			||||||
          docker cp ${container}:web/ .
 | 
					          docker cp ${container}:web/ .
 | 
				
			||||||
      - name: Create a Sentry.io release
 | 
					      - name: Create a Sentry.io release
 | 
				
			||||||
        uses: getsentry/action-release@v1
 | 
					        uses: getsentry/action-release@v1
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@
 | 
				
			|||||||
from os import environ
 | 
					from os import environ
 | 
				
			||||||
from typing import Optional
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__version__ = "2023.10.7"
 | 
					__version__ = "2024.2.0"
 | 
				
			||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
 | 
					ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -481,13 +481,6 @@ def _update_settings(app_path: str):
 | 
				
			|||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Load subapps's settings
 | 
					 | 
				
			||||||
for _app in set(SHARED_APPS + TENANT_APPS):
 | 
					 | 
				
			||||||
    if not _app.startswith("authentik"):
 | 
					 | 
				
			||||||
        continue
 | 
					 | 
				
			||||||
    _update_settings(f"{_app}.settings")
 | 
					 | 
				
			||||||
_update_settings("data.user_settings")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if DEBUG:
 | 
					if DEBUG:
 | 
				
			||||||
    CELERY["task_always_eager"] = True
 | 
					    CELERY["task_always_eager"] = True
 | 
				
			||||||
    os.environ[ENV_GIT_HASH_KEY] = "dev"
 | 
					    os.environ[ENV_GIT_HASH_KEY] = "dev"
 | 
				
			||||||
@ -512,5 +505,13 @@ except ImportError:
 | 
				
			|||||||
# being imported for @prefill_task
 | 
					# being imported for @prefill_task
 | 
				
			||||||
TENANT_APPS.append("authentik.events")
 | 
					TENANT_APPS.append("authentik.events")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Load subapps's settings
 | 
				
			||||||
 | 
					for _app in set(SHARED_APPS + TENANT_APPS):
 | 
				
			||||||
 | 
					    if not _app.startswith("authentik"):
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					    _update_settings(f"{_app}.settings")
 | 
				
			||||||
 | 
					_update_settings("data.user_settings")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SHARED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
 | 
					SHARED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
 | 
				
			||||||
INSTALLED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
 | 
					INSTALLED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
 | 
				
			||||||
 | 
				
			|||||||
@ -32,7 +32,7 @@ services:
 | 
				
			|||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - redis:/data
 | 
					      - redis:/data
 | 
				
			||||||
  server:
 | 
					  server:
 | 
				
			||||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.7}
 | 
					    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.2.0}
 | 
				
			||||||
    restart: unless-stopped
 | 
					    restart: unless-stopped
 | 
				
			||||||
    command: server
 | 
					    command: server
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
@ -53,7 +53,7 @@ services:
 | 
				
			|||||||
      - postgresql
 | 
					      - postgresql
 | 
				
			||||||
      - redis
 | 
					      - redis
 | 
				
			||||||
  worker:
 | 
					  worker:
 | 
				
			||||||
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.10.7}
 | 
					    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2024.2.0}
 | 
				
			||||||
    restart: unless-stopped
 | 
					    restart: unless-stopped
 | 
				
			||||||
    command: worker
 | 
					    command: worker
 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
 | 
				
			|||||||
@ -29,4 +29,4 @@ func UserAgent() string {
 | 
				
			|||||||
	return fmt.Sprintf("authentik@%s", FullVersion())
 | 
						return fmt.Sprintf("authentik@%s", FullVersion())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const VERSION = "2023.10.7"
 | 
					const VERSION = "2024.2.0"
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,7 @@ elif [[ "$1" == "bash" ]]; then
 | 
				
			|||||||
    /bin/bash
 | 
					    /bin/bash
 | 
				
			||||||
elif [[ "$1" == "test-all" ]]; then
 | 
					elif [[ "$1" == "test-all" ]]; then
 | 
				
			||||||
    prepare_debug
 | 
					    prepare_debug
 | 
				
			||||||
 | 
					    chmod 777 /root
 | 
				
			||||||
    check_if_root "python -m manage test authentik"
 | 
					    check_if_root "python -m manage test authentik"
 | 
				
			||||||
elif [[ "$1" == "healthcheck" ]]; then
 | 
					elif [[ "$1" == "healthcheck" ]]; then
 | 
				
			||||||
    run_authentik healthcheck $(cat $MODE_FILE)
 | 
					    run_authentik healthcheck $(cat $MODE_FILE)
 | 
				
			||||||
 | 
				
			|||||||
@ -113,7 +113,7 @@ filterwarnings = [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[tool.poetry]
 | 
					[tool.poetry]
 | 
				
			||||||
name = "authentik"
 | 
					name = "authentik"
 | 
				
			||||||
version = "2023.10.7"
 | 
					version = "2024.2.0"
 | 
				
			||||||
description = ""
 | 
					description = ""
 | 
				
			||||||
authors = ["authentik Team <hello@goauthentik.io>"]
 | 
					authors = ["authentik Team <hello@goauthentik.io>"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
openapi: 3.0.3
 | 
					openapi: 3.0.3
 | 
				
			||||||
info:
 | 
					info:
 | 
				
			||||||
  title: authentik
 | 
					  title: authentik
 | 
				
			||||||
  version: 2023.10.7
 | 
					  version: 2024.2.0
 | 
				
			||||||
  description: Making authentication simple.
 | 
					  description: Making authentication simple.
 | 
				
			||||||
  contact:
 | 
					  contact:
 | 
				
			||||||
    email: hello@goauthentik.io
 | 
					    email: hello@goauthentik.io
 | 
				
			||||||
 | 
				
			|||||||
@ -6,3 +6,4 @@ dist
 | 
				
			|||||||
coverage
 | 
					coverage
 | 
				
			||||||
src/locale-codes.ts
 | 
					src/locale-codes.ts
 | 
				
			||||||
storybook-static/
 | 
					storybook-static/
 | 
				
			||||||
 | 
					src/locales/**
 | 
				
			||||||
 | 
				
			|||||||
@ -22,25 +22,36 @@ import { AdminApi, Settings, SettingsRequest } from "@goauthentik/api";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@customElement("ak-admin-settings-form")
 | 
					@customElement("ak-admin-settings-form")
 | 
				
			||||||
export class AdminSettingsForm extends Form<SettingsRequest> {
 | 
					export class AdminSettingsForm extends Form<SettingsRequest> {
 | 
				
			||||||
    @property({ attribute: false })
 | 
					    //
 | 
				
			||||||
    set settings(value: Settings) {
 | 
					    // Custom property accessors in Lit 2 require a manual call to requestUpdate(). See:
 | 
				
			||||||
 | 
					    // https://lit.dev/docs/v2/components/properties/#accessors-custom
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    set settings(value: Settings | undefined) {
 | 
				
			||||||
        this._settings = value;
 | 
					        this._settings = value;
 | 
				
			||||||
 | 
					        this.requestUpdate();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property({ type: Object })
 | 
				
			||||||
 | 
					    get settings() {
 | 
				
			||||||
 | 
					        return this._settings;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private _settings?: Settings;
 | 
					    private _settings?: Settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static get styles(): CSSResult[] {
 | 
				
			||||||
 | 
					        return super.styles.concat(PFList);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getSuccessMessage(): string {
 | 
					    getSuccessMessage(): string {
 | 
				
			||||||
        return msg("Successfully updated settings.");
 | 
					        return msg("Successfully updated settings.");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async send(data: SettingsRequest): Promise<Settings> {
 | 
					    async send(data: SettingsRequest): Promise<Settings> {
 | 
				
			||||||
        return new AdminApi(DEFAULT_CONFIG).adminSettingsUpdate({
 | 
					        const result = await new AdminApi(DEFAULT_CONFIG).adminSettingsUpdate({
 | 
				
			||||||
            settingsRequest: data,
 | 
					            settingsRequest: data,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					        this.dispatchEvent(new CustomEvent("ak-admin-setting-changed"));
 | 
				
			||||||
 | 
					        return result;
 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					 | 
				
			||||||
        return super.styles.concat(PFList);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    renderForm(): TemplateResult {
 | 
					    renderForm(): TemplateResult {
 | 
				
			||||||
 | 
				
			|||||||
@ -14,8 +14,8 @@ import "@goauthentik/elements/buttons/SpinnerButton";
 | 
				
			|||||||
import "@goauthentik/elements/forms/ModalForm";
 | 
					import "@goauthentik/elements/forms/ModalForm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
import { CSSResult, TemplateResult, html } from "lit";
 | 
					import { html, nothing } from "lit";
 | 
				
			||||||
import { customElement, property } from "lit/decorators.js";
 | 
					import { customElement, query, state } from "lit/decorators.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
 | 
					import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
 | 
				
			||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
					import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
				
			||||||
@ -32,7 +32,7 @@ import { AdminApi, Settings } from "@goauthentik/api";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@customElement("ak-admin-settings")
 | 
					@customElement("ak-admin-settings")
 | 
				
			||||||
export class AdminSettingsPage extends AKElement {
 | 
					export class AdminSettingsPage extends AKElement {
 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					    static get styles() {
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
            PFBase,
 | 
					            PFBase,
 | 
				
			||||||
            PFButton,
 | 
					            PFButton,
 | 
				
			||||||
@ -46,41 +46,46 @@ export class AdminSettingsPage extends AKElement {
 | 
				
			|||||||
            PFBanner,
 | 
					            PFBanner,
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    @property({ attribute: false })
 | 
					
 | 
				
			||||||
 | 
					    @query("ak-admin-settings-form#form")
 | 
				
			||||||
 | 
					    form?: AdminSettingsForm;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @state()
 | 
				
			||||||
    settings?: Settings;
 | 
					    settings?: Settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    loadSettings(): void {
 | 
					    constructor() {
 | 
				
			||||||
        new AdminApi(DEFAULT_CONFIG).adminSettingsRetrieve().then((settings) => {
 | 
					        super();
 | 
				
			||||||
 | 
					        AdminSettingsPage.fetchSettings().then((settings) => {
 | 
				
			||||||
            this.settings = settings;
 | 
					            this.settings = settings;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        this.save = this.save.bind(this);
 | 
				
			||||||
 | 
					        this.reset = this.reset.bind(this);
 | 
				
			||||||
 | 
					        this.addEventListener("ak-admin-setting-changed", this.handleUpdate.bind(this));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    firstUpdated(): void {
 | 
					    static async fetchSettings() {
 | 
				
			||||||
        this.loadSettings();
 | 
					        return await new AdminApi(DEFAULT_CONFIG).adminSettingsRetrieve();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async save(): Promise<void> {
 | 
					    async handleUpdate() {
 | 
				
			||||||
        const form = this.shadowRoot?.querySelector<AdminSettingsForm>("ak-admin-settings-form");
 | 
					        this.settings = await AdminSettingsPage.fetchSettings();
 | 
				
			||||||
        if (!form) {
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async save() {
 | 
				
			||||||
 | 
					        if (!this.form) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        await form.submit(new Event("submit"));
 | 
					        await this.form.submit(new Event("submit"));
 | 
				
			||||||
        this.resetForm();
 | 
					        this.settings = await AdminSettingsPage.fetchSettings();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    resetForm(): void {
 | 
					    async reset() {
 | 
				
			||||||
        const form = this.shadowRoot?.querySelector<AdminSettingsForm>("ak-admin-settings-form");
 | 
					        this.form?.resetForm();
 | 
				
			||||||
        if (!form) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        this.loadSettings();
 | 
					 | 
				
			||||||
        form.settings = this.settings!;
 | 
					 | 
				
			||||||
        form.resetForm();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render(): TemplateResult {
 | 
					    render() {
 | 
				
			||||||
        if (!this.settings) {
 | 
					        if (!this.settings) {
 | 
				
			||||||
            return html``;
 | 
					            return nothing;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return html`
 | 
					        return html`
 | 
				
			||||||
            <ak-page-header icon="fa fa-cog" header="" description="">
 | 
					            <ak-page-header icon="fa fa-cog" header="" description="">
 | 
				
			||||||
@ -93,18 +98,10 @@ export class AdminSettingsPage extends AKElement {
 | 
				
			|||||||
                        </ak-admin-settings-form>
 | 
					                        </ak-admin-settings-form>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div class="pf-c-card__footer">
 | 
					                    <div class="pf-c-card__footer">
 | 
				
			||||||
                        <ak-spinner-button
 | 
					                        <ak-spinner-button .callAction=${this.save} class="pf-m-primary"
 | 
				
			||||||
                            .callAction=${async () => {
 | 
					 | 
				
			||||||
                                await this.save();
 | 
					 | 
				
			||||||
                            }}
 | 
					 | 
				
			||||||
                            class="pf-m-primary"
 | 
					 | 
				
			||||||
                            >${msg("Save")}</ak-spinner-button
 | 
					                            >${msg("Save")}</ak-spinner-button
 | 
				
			||||||
                        >
 | 
					                        >
 | 
				
			||||||
                        <ak-spinner-button
 | 
					                        <ak-spinner-button .callAction=${this.reset} class="pf-m-secondary"
 | 
				
			||||||
                            .callAction=${() => {
 | 
					 | 
				
			||||||
                                this.resetForm();
 | 
					 | 
				
			||||||
                            }}
 | 
					 | 
				
			||||||
                            class="pf-m-secondary"
 | 
					 | 
				
			||||||
                            >${msg("Cancel")}</ak-spinner-button
 | 
					                            >${msg("Cancel")}</ak-spinner-button
 | 
				
			||||||
                        >
 | 
					                        >
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -125,6 +125,7 @@ export class RelatedGroupList extends Table<Group> {
 | 
				
			|||||||
            actionSubtext=${msg(
 | 
					            actionSubtext=${msg(
 | 
				
			||||||
                str`Are you sure you want to remove user ${this.targetUser?.username} from the following groups?`,
 | 
					                str`Are you sure you want to remove user ${this.targetUser?.username} from the following groups?`,
 | 
				
			||||||
            )}
 | 
					            )}
 | 
				
			||||||
 | 
					            buttonLabel=${msg("Remove")}
 | 
				
			||||||
            .objects=${this.selectedElements}
 | 
					            .objects=${this.selectedElements}
 | 
				
			||||||
            .delete=${(item: Group) => {
 | 
					            .delete=${(item: Group) => {
 | 
				
			||||||
                if (!this.targetUser) return;
 | 
					                if (!this.targetUser) return;
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
 | 
				
			|||||||
export const ERROR_CLASS = "pf-m-danger";
 | 
					export const ERROR_CLASS = "pf-m-danger";
 | 
				
			||||||
export const PROGRESS_CLASS = "pf-m-in-progress";
 | 
					export const PROGRESS_CLASS = "pf-m-in-progress";
 | 
				
			||||||
export const CURRENT_CLASS = "pf-m-current";
 | 
					export const CURRENT_CLASS = "pf-m-current";
 | 
				
			||||||
export const VERSION = "2023.10.7";
 | 
					export const VERSION = "2024.2.0";
 | 
				
			||||||
export const TITLE_DEFAULT = "authentik";
 | 
					export const TITLE_DEFAULT = "authentik";
 | 
				
			||||||
export const ROUTE_SEPARATOR = ";";
 | 
					export const ROUTE_SEPARATOR = ";";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
					import { AKElement } from "@goauthentik/elements/Base";
 | 
				
			||||||
import { PFSize } from "@goauthentik/elements/Spinner";
 | 
					import { PFSize } from "@goauthentik/elements/Spinner";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CSSResult, TemplateResult, html } from "lit";
 | 
					import { CSSResult, TemplateResult, css, html } from "lit";
 | 
				
			||||||
import { customElement, property } from "lit/decorators.js";
 | 
					import { customElement, property } from "lit/decorators.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
 | 
					import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
 | 
				
			||||||
@ -23,7 +23,17 @@ export class EmptyState extends AKElement {
 | 
				
			|||||||
    header = "";
 | 
					    header = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					    static get styles(): CSSResult[] {
 | 
				
			||||||
        return [PFBase, PFEmptyState, PFTitle];
 | 
					        return [
 | 
				
			||||||
 | 
					            PFBase,
 | 
				
			||||||
 | 
					            PFEmptyState,
 | 
				
			||||||
 | 
					            PFTitle,
 | 
				
			||||||
 | 
					            css`
 | 
				
			||||||
 | 
					                i.pf-c-empty-state__icon {
 | 
				
			||||||
 | 
					                    height: var(--pf-global--icon--FontSize--2xl);
 | 
				
			||||||
 | 
					                    line-height: var(--pf-global--icon--FontSize--2xl);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            `,
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render(): TemplateResult {
 | 
					    render(): TemplateResult {
 | 
				
			||||||
 | 
				
			|||||||
@ -131,6 +131,9 @@ export class DeleteBulkForm<T> extends ModalButton {
 | 
				
			|||||||
    @property()
 | 
					    @property()
 | 
				
			||||||
    actionSubtext?: string;
 | 
					    actionSubtext?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property()
 | 
				
			||||||
 | 
					    buttonLabel = msg("Delete");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property({ attribute: false })
 | 
					    @property({ attribute: false })
 | 
				
			||||||
    metadata: (item: T) => BulkDeleteMetadata = (item: T) => {
 | 
					    metadata: (item: T) => BulkDeleteMetadata = (item: T) => {
 | 
				
			||||||
        const rec = item as Record<string, unknown>;
 | 
					        const rec = item as Record<string, unknown>;
 | 
				
			||||||
@ -222,7 +225,7 @@ export class DeleteBulkForm<T> extends ModalButton {
 | 
				
			|||||||
                    }}
 | 
					                    }}
 | 
				
			||||||
                    class="pf-m-danger"
 | 
					                    class="pf-m-danger"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    ${msg("Delete")} </ak-spinner-button
 | 
					                    ${this.buttonLabel} </ak-spinner-button
 | 
				
			||||||
                > 
 | 
					                > 
 | 
				
			||||||
                <ak-spinner-button
 | 
					                <ak-spinner-button
 | 
				
			||||||
                    .callAction=${async () => {
 | 
					                    .callAction=${async () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ import "@goauthentik/flow/sources/apple/AppleLoginInit";
 | 
				
			|||||||
import "@goauthentik/flow/sources/plex/PlexLoginInit";
 | 
					import "@goauthentik/flow/sources/plex/PlexLoginInit";
 | 
				
			||||||
import "@goauthentik/flow/stages/FlowErrorStage";
 | 
					import "@goauthentik/flow/stages/FlowErrorStage";
 | 
				
			||||||
import "@goauthentik/flow/stages/RedirectStage";
 | 
					import "@goauthentik/flow/stages/RedirectStage";
 | 
				
			||||||
import { StageHost } from "@goauthentik/flow/stages/base";
 | 
					import { StageHost, SubmitOptions } from "@goauthentik/flow/stages/base";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
 | 
					import { CSSResult, TemplateResult, css, html, nothing } from "lit";
 | 
				
			||||||
@ -189,12 +189,17 @@ export class FlowExecutor extends Interface implements StageHost {
 | 
				
			|||||||
        return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
 | 
					        return globalAK()?.brand.uiTheme || UiThemeEnum.Automatic;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async submit(payload?: FlowChallengeResponseRequest): Promise<boolean> {
 | 
					    async submit(
 | 
				
			||||||
 | 
					        payload?: FlowChallengeResponseRequest,
 | 
				
			||||||
 | 
					        options?: SubmitOptions,
 | 
				
			||||||
 | 
					    ): Promise<boolean> {
 | 
				
			||||||
        if (!payload) return Promise.reject();
 | 
					        if (!payload) return Promise.reject();
 | 
				
			||||||
        if (!this.challenge) return Promise.reject();
 | 
					        if (!this.challenge) return Promise.reject();
 | 
				
			||||||
        // @ts-ignore
 | 
					        // @ts-expect-error
 | 
				
			||||||
        payload.component = this.challenge.component;
 | 
					        payload.component = this.challenge.component;
 | 
				
			||||||
 | 
					        if (!options?.invisible) {
 | 
				
			||||||
            this.loading = true;
 | 
					            this.loading = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
 | 
					            const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
 | 
				
			||||||
                flowSlug: this.flowSlug,
 | 
					                flowSlug: this.flowSlug,
 | 
				
			||||||
 | 
				
			|||||||
@ -40,6 +40,7 @@ export class AuthenticatorStaticStage extends BaseStage<
 | 
				
			|||||||
                    columns: 2;
 | 
					                    columns: 2;
 | 
				
			||||||
                    -webkit-columns: 2;
 | 
					                    -webkit-columns: 2;
 | 
				
			||||||
                    -moz-columns: 2;
 | 
					                    -moz-columns: 2;
 | 
				
			||||||
 | 
					                    column-width: 1em;
 | 
				
			||||||
                    margin-left: var(--pf-global--spacer--xs);
 | 
					                    margin-left: var(--pf-global--spacer--xs);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                ul li {
 | 
					                ul li {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,13 +2,12 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
				
			|||||||
import "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStageCode";
 | 
					import "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStageCode";
 | 
				
			||||||
import "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStageDuo";
 | 
					import "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStageDuo";
 | 
				
			||||||
import "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn";
 | 
					import "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStageWebAuthn";
 | 
				
			||||||
import { BaseStage, StageHost } from "@goauthentik/flow/stages/base";
 | 
					import { BaseStage, StageHost, SubmitOptions } from "@goauthentik/flow/stages/base";
 | 
				
			||||||
import { PasswordManagerPrefill } from "@goauthentik/flow/stages/identification/IdentificationStage";
 | 
					import { PasswordManagerPrefill } from "@goauthentik/flow/stages/identification/IdentificationStage";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
import { CSSResult, TemplateResult, css, html } from "lit";
 | 
					import { CSSResult, TemplateResult, css, html } from "lit";
 | 
				
			||||||
import { customElement, state } from "lit/decorators.js";
 | 
					import { customElement, state } from "lit/decorators.js";
 | 
				
			||||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
					import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
				
			||||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
 | 
					import PFForm from "@patternfly/patternfly/components/Form/form.css";
 | 
				
			||||||
@ -59,7 +58,7 @@ export class AuthenticatorValidateStage
 | 
				
			|||||||
        // We don't use this.submit here, as we don't want to advance the flow.
 | 
					        // We don't use this.submit here, as we don't want to advance the flow.
 | 
				
			||||||
        // We just want to notify the backend which challenge has been selected.
 | 
					        // We just want to notify the backend which challenge has been selected.
 | 
				
			||||||
        new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
 | 
					        new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
 | 
				
			||||||
            flowSlug: this.host.flowSlug || "",
 | 
					            flowSlug: this.host?.flowSlug || "",
 | 
				
			||||||
            query: window.location.search.substring(1),
 | 
					            query: window.location.search.substring(1),
 | 
				
			||||||
            flowChallengeResponseRequest: {
 | 
					            flowChallengeResponseRequest: {
 | 
				
			||||||
                // @ts-ignore
 | 
					                // @ts-ignore
 | 
				
			||||||
@ -73,8 +72,11 @@ export class AuthenticatorValidateStage
 | 
				
			|||||||
        return this._selectedDeviceChallenge;
 | 
					        return this._selectedDeviceChallenge;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<boolean> {
 | 
					    submit(
 | 
				
			||||||
        return this.host?.submit(payload) || Promise.resolve();
 | 
					        payload: AuthenticatorValidationChallengeResponseRequest,
 | 
				
			||||||
 | 
					        options?: SubmitOptions,
 | 
				
			||||||
 | 
					    ): Promise<boolean> {
 | 
				
			||||||
 | 
					        return this.host?.submit(payload, options) || Promise.resolve();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					    static get styles(): CSSResult[] {
 | 
				
			||||||
@ -253,23 +255,7 @@ export class AuthenticatorValidateStage
 | 
				
			|||||||
                ? this.renderDeviceChallenge()
 | 
					                ? this.renderDeviceChallenge()
 | 
				
			||||||
                : html`<div class="pf-c-login__main-body">
 | 
					                : html`<div class="pf-c-login__main-body">
 | 
				
			||||||
                          <form class="pf-c-form">
 | 
					                          <form class="pf-c-form">
 | 
				
			||||||
                              <ak-form-static
 | 
					                              ${this.renderUserInfo()}
 | 
				
			||||||
                                  class="pf-c-form__group"
 | 
					 | 
				
			||||||
                                  userAvatar="${this.challenge.pendingUserAvatar}"
 | 
					 | 
				
			||||||
                                  user=${this.challenge.pendingUser}
 | 
					 | 
				
			||||||
                              >
 | 
					 | 
				
			||||||
                                  <div slot="link">
 | 
					 | 
				
			||||||
                                      <a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
 | 
					 | 
				
			||||||
                                          >${msg("Not you?")}</a
 | 
					 | 
				
			||||||
                                      >
 | 
					 | 
				
			||||||
                                  </div>
 | 
					 | 
				
			||||||
                              </ak-form-static>
 | 
					 | 
				
			||||||
                              <input
 | 
					 | 
				
			||||||
                                  name="username"
 | 
					 | 
				
			||||||
                                  autocomplete="username"
 | 
					 | 
				
			||||||
                                  type="hidden"
 | 
					 | 
				
			||||||
                                  value="${this.challenge.pendingUser}"
 | 
					 | 
				
			||||||
                              />
 | 
					 | 
				
			||||||
                              ${this.selectedDeviceChallenge
 | 
					                              ${this.selectedDeviceChallenge
 | 
				
			||||||
                                  ? ""
 | 
					                                  ? ""
 | 
				
			||||||
                                  : html`<p>${msg("Select an authentication method.")}</p>`}
 | 
					                                  : html`<p>${msg("Select an authentication method.")}</p>`}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,49 +1,25 @@
 | 
				
			|||||||
 | 
					import { BaseDeviceStage } from "@goauthentik/app/flow/stages/authenticator_validate/base";
 | 
				
			||||||
import "@goauthentik/elements/EmptyState";
 | 
					import "@goauthentik/elements/EmptyState";
 | 
				
			||||||
import "@goauthentik/elements/forms/FormElement";
 | 
					import "@goauthentik/elements/forms/FormElement";
 | 
				
			||||||
import "@goauthentik/flow/FormStatic";
 | 
					 | 
				
			||||||
import { AuthenticatorValidateStage } from "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage";
 | 
					 | 
				
			||||||
import { BaseStage } from "@goauthentik/flow/stages/base";
 | 
					 | 
				
			||||||
import { PasswordManagerPrefill } from "@goauthentik/flow/stages/identification/IdentificationStage";
 | 
					import { PasswordManagerPrefill } from "@goauthentik/flow/stages/identification/IdentificationStage";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
import { CSSResult, TemplateResult, css, html } from "lit";
 | 
					import { CSSResult, TemplateResult, css, html } from "lit";
 | 
				
			||||||
import { customElement, property } from "lit/decorators.js";
 | 
					import { customElement } from "lit/decorators.js";
 | 
				
			||||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
					 | 
				
			||||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
 | 
					 | 
				
			||||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
 | 
					 | 
				
			||||||
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
 | 
					 | 
				
			||||||
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
 | 
					 | 
				
			||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    AuthenticatorValidationChallenge,
 | 
					    AuthenticatorValidationChallenge,
 | 
				
			||||||
    AuthenticatorValidationChallengeResponseRequest,
 | 
					    AuthenticatorValidationChallengeResponseRequest,
 | 
				
			||||||
    DeviceChallenge,
 | 
					 | 
				
			||||||
    DeviceClassesEnum,
 | 
					    DeviceClassesEnum,
 | 
				
			||||||
} from "@goauthentik/api";
 | 
					} from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-stage-authenticator-validate-code")
 | 
					@customElement("ak-stage-authenticator-validate-code")
 | 
				
			||||||
export class AuthenticatorValidateStageWebCode extends BaseStage<
 | 
					export class AuthenticatorValidateStageWebCode extends BaseDeviceStage<
 | 
				
			||||||
    AuthenticatorValidationChallenge,
 | 
					    AuthenticatorValidationChallenge,
 | 
				
			||||||
    AuthenticatorValidationChallengeResponseRequest
 | 
					    AuthenticatorValidationChallengeResponseRequest
 | 
				
			||||||
> {
 | 
					> {
 | 
				
			||||||
    @property({ attribute: false })
 | 
					 | 
				
			||||||
    deviceChallenge?: DeviceChallenge;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property({ type: Boolean })
 | 
					 | 
				
			||||||
    showBackButton = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					    static get styles(): CSSResult[] {
 | 
				
			||||||
        return [
 | 
					        return super.styles.concat(css`
 | 
				
			||||||
            PFBase,
 | 
					 | 
				
			||||||
            PFLogin,
 | 
					 | 
				
			||||||
            PFForm,
 | 
					 | 
				
			||||||
            PFFormControl,
 | 
					 | 
				
			||||||
            PFTitle,
 | 
					 | 
				
			||||||
            PFButton,
 | 
					 | 
				
			||||||
            css`
 | 
					 | 
				
			||||||
            .icon-description {
 | 
					            .icon-description {
 | 
				
			||||||
                display: flex;
 | 
					                display: flex;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -52,8 +28,7 @@ export class AuthenticatorValidateStageWebCode extends BaseStage<
 | 
				
			|||||||
                padding: 0.25em;
 | 
					                padding: 0.25em;
 | 
				
			||||||
                padding-right: 0.5em;
 | 
					                padding-right: 0.5em;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            `,
 | 
					        `);
 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render(): TemplateResult {
 | 
					    render(): TemplateResult {
 | 
				
			||||||
@ -68,17 +43,7 @@ export class AuthenticatorValidateStageWebCode extends BaseStage<
 | 
				
			|||||||
                    this.submitForm(e);
 | 
					                    this.submitForm(e);
 | 
				
			||||||
                }}
 | 
					                }}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                    <ak-form-static
 | 
					                ${this.renderUserInfo()}
 | 
				
			||||||
                        class="pf-c-form__group"
 | 
					 | 
				
			||||||
                        userAvatar="${this.challenge.pendingUserAvatar}"
 | 
					 | 
				
			||||||
                        user=${this.challenge.pendingUser}
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                        <div slot="link">
 | 
					 | 
				
			||||||
                            <a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
 | 
					 | 
				
			||||||
                                >${msg("Not you?")}</a
 | 
					 | 
				
			||||||
                            >
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                    </ak-form-static>
 | 
					 | 
				
			||||||
                <div class="icon-description">
 | 
					                <div class="icon-description">
 | 
				
			||||||
                    <i
 | 
					                    <i
 | 
				
			||||||
                        class="fa ${this.deviceChallenge?.deviceClass == DeviceClassesEnum.Sms
 | 
					                        class="fa ${this.deviceChallenge?.deviceClass == DeviceClassesEnum.Sms
 | 
				
			||||||
@ -106,12 +71,10 @@ export class AuthenticatorValidateStageWebCode extends BaseStage<
 | 
				
			|||||||
                    <input
 | 
					                    <input
 | 
				
			||||||
                        type="text"
 | 
					                        type="text"
 | 
				
			||||||
                        name="code"
 | 
					                        name="code"
 | 
				
			||||||
                            inputmode="${this.deviceChallenge?.deviceClass ===
 | 
					                        inputmode="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
 | 
				
			||||||
                            DeviceClassesEnum.Static
 | 
					 | 
				
			||||||
                            ? "text"
 | 
					                            ? "text"
 | 
				
			||||||
                            : "numeric"}"
 | 
					                            : "numeric"}"
 | 
				
			||||||
                            pattern="${this.deviceChallenge?.deviceClass ===
 | 
					                        pattern="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
 | 
				
			||||||
                            DeviceClassesEnum.Static
 | 
					 | 
				
			||||||
                            ? "[0-9a-zA-Z]*"
 | 
					                            ? "[0-9a-zA-Z]*"
 | 
				
			||||||
                            : "[0-9]*"}"
 | 
					                            : "[0-9]*"}"
 | 
				
			||||||
                        placeholder="${msg("Please enter your code")}"
 | 
					                        placeholder="${msg("Please enter your code")}"
 | 
				
			||||||
@ -127,27 +90,9 @@ export class AuthenticatorValidateStageWebCode extends BaseStage<
 | 
				
			|||||||
                    <button type="submit" class="pf-c-button pf-m-primary pf-m-block">
 | 
					                    <button type="submit" class="pf-c-button pf-m-primary pf-m-block">
 | 
				
			||||||
                        ${msg("Continue")}
 | 
					                        ${msg("Continue")}
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
 | 
					                    ${this.renderReturnToDevicePicker()}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </form>
 | 
					            </form>
 | 
				
			||||||
            </div>
 | 
					        </div>`;
 | 
				
			||||||
            <footer class="pf-c-login__main-footer">
 | 
					 | 
				
			||||||
                <ul class="pf-c-login__main-footer-links">
 | 
					 | 
				
			||||||
                    ${this.showBackButton
 | 
					 | 
				
			||||||
                        ? html`<li class="pf-c-login__main-footer-links-item">
 | 
					 | 
				
			||||||
                              <button
 | 
					 | 
				
			||||||
                                  class="pf-c-button pf-m-secondary pf-m-block"
 | 
					 | 
				
			||||||
                                  @click=${() => {
 | 
					 | 
				
			||||||
                                      if (!this.host) return;
 | 
					 | 
				
			||||||
                                      (
 | 
					 | 
				
			||||||
                                          this.host as AuthenticatorValidateStage
 | 
					 | 
				
			||||||
                                      ).selectedDeviceChallenge = undefined;
 | 
					 | 
				
			||||||
                                  }}
 | 
					 | 
				
			||||||
                              >
 | 
					 | 
				
			||||||
                                  ${msg("Return to device picker")}
 | 
					 | 
				
			||||||
                              </button>
 | 
					 | 
				
			||||||
                          </li>`
 | 
					 | 
				
			||||||
                        : html``}
 | 
					 | 
				
			||||||
                </ul>
 | 
					 | 
				
			||||||
            </footer>`;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,20 +1,10 @@
 | 
				
			|||||||
 | 
					import { BaseDeviceStage } from "@goauthentik/app/flow/stages/authenticator_validate/base";
 | 
				
			||||||
import "@goauthentik/elements/EmptyState";
 | 
					import "@goauthentik/elements/EmptyState";
 | 
				
			||||||
import "@goauthentik/elements/forms/FormElement";
 | 
					import "@goauthentik/elements/forms/FormElement";
 | 
				
			||||||
import "@goauthentik/flow/FormStatic";
 | 
					 | 
				
			||||||
import { AuthenticatorValidateStage } from "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage";
 | 
					 | 
				
			||||||
import { BaseStage } from "@goauthentik/flow/stages/base";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
import { CSSResult, TemplateResult, html } from "lit";
 | 
					import { TemplateResult, html } from "lit";
 | 
				
			||||||
import { customElement, property } from "lit/decorators.js";
 | 
					import { customElement, property, state } from "lit/decorators.js";
 | 
				
			||||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
					 | 
				
			||||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
 | 
					 | 
				
			||||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
 | 
					 | 
				
			||||||
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
 | 
					 | 
				
			||||||
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
 | 
					 | 
				
			||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    AuthenticatorValidationChallenge,
 | 
					    AuthenticatorValidationChallenge,
 | 
				
			||||||
@ -23,7 +13,7 @@ import {
 | 
				
			|||||||
} from "@goauthentik/api";
 | 
					} from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-stage-authenticator-validate-duo")
 | 
					@customElement("ak-stage-authenticator-validate-duo")
 | 
				
			||||||
export class AuthenticatorValidateStageWebDuo extends BaseStage<
 | 
					export class AuthenticatorValidateStageWebDuo extends BaseDeviceStage<
 | 
				
			||||||
    AuthenticatorValidationChallenge,
 | 
					    AuthenticatorValidationChallenge,
 | 
				
			||||||
    AuthenticatorValidationChallengeResponseRequest
 | 
					    AuthenticatorValidationChallengeResponseRequest
 | 
				
			||||||
> {
 | 
					> {
 | 
				
			||||||
@ -33,13 +23,23 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage<
 | 
				
			|||||||
    @property({ type: Boolean })
 | 
					    @property({ type: Boolean })
 | 
				
			||||||
    showBackButton = false;
 | 
					    showBackButton = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					    @state()
 | 
				
			||||||
        return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton];
 | 
					    authenticating = false;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    firstUpdated(): void {
 | 
					    firstUpdated(): void {
 | 
				
			||||||
        this.host?.submit({
 | 
					        this.authenticating = true;
 | 
				
			||||||
 | 
					        this.host
 | 
				
			||||||
 | 
					            ?.submit(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
                    duo: this.deviceChallenge?.deviceUid,
 | 
					                    duo: this.deviceChallenge?.deviceUid,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                { invisible: true },
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .then(() => {
 | 
				
			||||||
 | 
					                this.authenticating = false;
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .catch(() => {
 | 
				
			||||||
 | 
					                this.authenticating = false;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -49,6 +49,7 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage<
 | 
				
			|||||||
            </ak-empty-state>`;
 | 
					            </ak-empty-state>`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const errors = this.challenge.responseErrors?.duo || [];
 | 
					        const errors = this.challenge.responseErrors?.duo || [];
 | 
				
			||||||
 | 
					        const errorMessage = errors.map((err) => err.string);
 | 
				
			||||||
        return html`<div class="pf-c-login__main-body">
 | 
					        return html`<div class="pf-c-login__main-body">
 | 
				
			||||||
            <form
 | 
					            <form
 | 
				
			||||||
                class="pf-c-form"
 | 
					                class="pf-c-form"
 | 
				
			||||||
@ -56,49 +57,17 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage<
 | 
				
			|||||||
                    this.submitForm(e);
 | 
					                    this.submitForm(e);
 | 
				
			||||||
                }}
 | 
					                }}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
                    <ak-form-static
 | 
					                ${this.renderUserInfo()}
 | 
				
			||||||
                        class="pf-c-form__group"
 | 
					                <ak-empty-state
 | 
				
			||||||
                        userAvatar="${this.challenge.pendingUserAvatar}"
 | 
					                    ?loading="${this.authenticating}"
 | 
				
			||||||
                        user=${this.challenge.pendingUser}
 | 
					                    header=${this.authenticating
 | 
				
			||||||
 | 
					                        ? msg("Sending Duo push notification...")
 | 
				
			||||||
 | 
					                        : errorMessage.join(", ") || msg("Failed to authenticate")}
 | 
				
			||||||
 | 
					                    icon="fas fa-times"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                        <div slot="link">
 | 
					                </ak-empty-state>
 | 
				
			||||||
                            <a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
 | 
					                <div class="pf-c-form__group pf-m-action">${this.renderReturnToDevicePicker()}</div>
 | 
				
			||||||
                                >${msg("Not you?")}</a
 | 
					 | 
				
			||||||
                            >
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                    </ak-form-static>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    ${errors.length > 0
 | 
					 | 
				
			||||||
                        ? errors.map((err) => {
 | 
					 | 
				
			||||||
                              if (err.code === "denied") {
 | 
					 | 
				
			||||||
                                  return html` <ak-stage-access-denied-icon
 | 
					 | 
				
			||||||
                                      errorMessage=${err.string}
 | 
					 | 
				
			||||||
                                  >
 | 
					 | 
				
			||||||
                                  </ak-stage-access-denied-icon>`;
 | 
					 | 
				
			||||||
                              }
 | 
					 | 
				
			||||||
                              return html`<p>${err.string}</p>`;
 | 
					 | 
				
			||||||
                          })
 | 
					 | 
				
			||||||
                        : html`${msg("Sending Duo push notification")}`}
 | 
					 | 
				
			||||||
            </form>
 | 
					            </form>
 | 
				
			||||||
            </div>
 | 
					        </div>`;
 | 
				
			||||||
            <footer class="pf-c-login__main-footer">
 | 
					 | 
				
			||||||
                <ul class="pf-c-login__main-footer-links">
 | 
					 | 
				
			||||||
                    ${this.showBackButton
 | 
					 | 
				
			||||||
                        ? html`<li class="pf-c-login__main-footer-links-item">
 | 
					 | 
				
			||||||
                              <button
 | 
					 | 
				
			||||||
                                  class="pf-c-button pf-m-secondary pf-m-block"
 | 
					 | 
				
			||||||
                                  @click=${() => {
 | 
					 | 
				
			||||||
                                      if (!this.host) return;
 | 
					 | 
				
			||||||
                                      (
 | 
					 | 
				
			||||||
                                          this.host as AuthenticatorValidateStage
 | 
					 | 
				
			||||||
                                      ).selectedDeviceChallenge = undefined;
 | 
					 | 
				
			||||||
                                  }}
 | 
					 | 
				
			||||||
                              >
 | 
					 | 
				
			||||||
                                  ${msg("Return to device picker")}
 | 
					 | 
				
			||||||
                              </button>
 | 
					 | 
				
			||||||
                          </li>`
 | 
					 | 
				
			||||||
                        : html``}
 | 
					 | 
				
			||||||
                </ul>
 | 
					 | 
				
			||||||
            </footer>`;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,23 +1,14 @@
 | 
				
			|||||||
 | 
					import { BaseDeviceStage } from "@goauthentik/app/flow/stages/authenticator_validate/base";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    checkWebAuthnSupport,
 | 
					    checkWebAuthnSupport,
 | 
				
			||||||
    transformAssertionForServer,
 | 
					    transformAssertionForServer,
 | 
				
			||||||
    transformCredentialRequestOptions,
 | 
					    transformCredentialRequestOptions,
 | 
				
			||||||
} from "@goauthentik/common/helpers/webauthn";
 | 
					} from "@goauthentik/common/helpers/webauthn";
 | 
				
			||||||
import { AuthenticatorValidateStage } from "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage";
 | 
					import "@goauthentik/elements/EmptyState";
 | 
				
			||||||
import { BaseStage } from "@goauthentik/flow/stages/base";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { msg, str } from "@lit/localize";
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
import { CSSResult, TemplateResult, html } from "lit";
 | 
					import { TemplateResult, html, nothing } from "lit";
 | 
				
			||||||
import { customElement, property } from "lit/decorators.js";
 | 
					import { customElement, property, state } from "lit/decorators.js";
 | 
				
			||||||
 | 
					 | 
				
			||||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
					 | 
				
			||||||
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
 | 
					 | 
				
			||||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
 | 
					 | 
				
			||||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
 | 
					 | 
				
			||||||
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
 | 
					 | 
				
			||||||
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
 | 
					 | 
				
			||||||
import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
 | 
					 | 
				
			||||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
    AuthenticatorValidationChallenge,
 | 
					    AuthenticatorValidationChallenge,
 | 
				
			||||||
@ -26,7 +17,7 @@ import {
 | 
				
			|||||||
} from "@goauthentik/api";
 | 
					} from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@customElement("ak-stage-authenticator-validate-webauthn")
 | 
					@customElement("ak-stage-authenticator-validate-webauthn")
 | 
				
			||||||
export class AuthenticatorValidateStageWebAuthn extends BaseStage<
 | 
					export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage<
 | 
				
			||||||
    AuthenticatorValidationChallenge,
 | 
					    AuthenticatorValidationChallenge,
 | 
				
			||||||
    AuthenticatorValidationChallengeResponseRequest
 | 
					    AuthenticatorValidationChallengeResponseRequest
 | 
				
			||||||
> {
 | 
					> {
 | 
				
			||||||
@ -34,25 +25,15 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage<
 | 
				
			|||||||
    deviceChallenge?: DeviceChallenge;
 | 
					    deviceChallenge?: DeviceChallenge;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property()
 | 
					    @property()
 | 
				
			||||||
    authenticateMessage?: string;
 | 
					    errorMessage?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property({ type: Boolean })
 | 
					    @property({ type: Boolean })
 | 
				
			||||||
    showBackButton = false;
 | 
					    showBackButton = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    transformedCredentialRequestOptions?: PublicKeyCredentialRequestOptions;
 | 
					    @state()
 | 
				
			||||||
 | 
					    authenticating = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static get styles(): CSSResult[] {
 | 
					    transformedCredentialRequestOptions?: PublicKeyCredentialRequestOptions;
 | 
				
			||||||
        return [
 | 
					 | 
				
			||||||
            PFBase,
 | 
					 | 
				
			||||||
            PFLogin,
 | 
					 | 
				
			||||||
            PFEmptyState,
 | 
					 | 
				
			||||||
            PFBullseye,
 | 
					 | 
				
			||||||
            PFForm,
 | 
					 | 
				
			||||||
            PFFormControl,
 | 
					 | 
				
			||||||
            PFTitle,
 | 
					 | 
				
			||||||
            PFButton,
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async authenticate(): Promise<void> {
 | 
					    async authenticate(): Promise<void> {
 | 
				
			||||||
        // request the authenticator to create an assertion signature using the
 | 
					        // request the authenticator to create an assertion signature using the
 | 
				
			||||||
@ -64,10 +45,10 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage<
 | 
				
			|||||||
                publicKey: this.transformedCredentialRequestOptions,
 | 
					                publicKey: this.transformedCredentialRequestOptions,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            if (!assertion) {
 | 
					            if (!assertion) {
 | 
				
			||||||
                throw new Error(msg("Assertions is empty"));
 | 
					                throw new Error("Assertions is empty");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } catch (err) {
 | 
					        } catch (err) {
 | 
				
			||||||
            throw new Error(msg(str`Error when creating credential: ${err}`));
 | 
					            throw new Error(`Error when creating credential: ${err}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // we now have an authentication assertion! encode the byte arrays contained
 | 
					        // we now have an authentication assertion! encode the byte arrays contained
 | 
				
			||||||
@ -78,11 +59,16 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage<
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // post the assertion to the server for verification.
 | 
					        // post the assertion to the server for verification.
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            await this.host?.submit({
 | 
					            await this.host?.submit(
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
                    webauthn: transformedAssertionForServer,
 | 
					                    webauthn: transformedAssertionForServer,
 | 
				
			||||||
            });
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    invisible: true,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
        } catch (err) {
 | 
					        } catch (err) {
 | 
				
			||||||
            throw new Error(msg(str`Error when validating assertion on server: ${err}`));
 | 
					            throw new Error(`Error when validating assertion on server: ${err}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -97,58 +83,46 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage<
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async authenticateWrapper(): Promise<void> {
 | 
					    async authenticateWrapper(): Promise<void> {
 | 
				
			||||||
        if (this.host.loading) {
 | 
					        if (this.authenticating) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        this.host.loading = true;
 | 
					        this.authenticating = true;
 | 
				
			||||||
        this.authenticate()
 | 
					        this.authenticate()
 | 
				
			||||||
            .catch((e) => {
 | 
					            .catch((e: Error) => {
 | 
				
			||||||
                console.error(e);
 | 
					                console.warn(`authentik/flows/authenticator_validate/webauthn: ${e.toString()}`);
 | 
				
			||||||
                this.authenticateMessage = e.toString();
 | 
					                this.errorMessage = msg("Authentication failed.");
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .finally(() => {
 | 
					            .finally(() => {
 | 
				
			||||||
                this.host.loading = false;
 | 
					                this.authenticating = false;
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    render(): TemplateResult {
 | 
					    render(): TemplateResult {
 | 
				
			||||||
        return html`<div class="pf-c-login__main-body">
 | 
					        return html`<div class="pf-c-login__main-body">
 | 
				
			||||||
                ${this.authenticateMessage
 | 
					            <form class="pf-c-form">
 | 
				
			||||||
                    ? html`<div class="pf-c-form__group pf-m-action">
 | 
					                ${this.renderUserInfo()}
 | 
				
			||||||
                          <p class="pf-m-block">${this.authenticateMessage}</p>
 | 
					                <ak-empty-state
 | 
				
			||||||
                          <button
 | 
					                    ?loading="${this.authenticating}"
 | 
				
			||||||
 | 
					                    header=${this.authenticating
 | 
				
			||||||
 | 
					                        ? msg("Authenticating...")
 | 
				
			||||||
 | 
					                        : this.errorMessage || msg("Failed to authenticate")}
 | 
				
			||||||
 | 
					                    icon="fa-times"
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                </ak-empty-state>
 | 
				
			||||||
 | 
					                <div class="pf-c-form__group pf-m-action">
 | 
				
			||||||
 | 
					                    ${this.errorMessage
 | 
				
			||||||
 | 
					                        ? html` <button
 | 
				
			||||||
                              class="pf-c-button pf-m-primary pf-m-block"
 | 
					                              class="pf-c-button pf-m-primary pf-m-block"
 | 
				
			||||||
                              @click=${() => {
 | 
					                              @click=${() => {
 | 
				
			||||||
                                  this.authenticateWrapper();
 | 
					                                  this.authenticateWrapper();
 | 
				
			||||||
                              }}
 | 
					                              }}
 | 
				
			||||||
                          >
 | 
					                          >
 | 
				
			||||||
                              ${msg("Retry authentication")}
 | 
					                              ${msg("Retry authentication")}
 | 
				
			||||||
                          </button>
 | 
					                          </button>`
 | 
				
			||||||
                      </div>`
 | 
					                        : nothing}
 | 
				
			||||||
                    : html`<div class="pf-c-form__group pf-m-action">
 | 
					                    ${this.renderReturnToDevicePicker()}
 | 
				
			||||||
                          <p class="pf-m-block"> </p>
 | 
					 | 
				
			||||||
                          <p class="pf-m-block"> </p>
 | 
					 | 
				
			||||||
                          <p class="pf-m-block"> </p>
 | 
					 | 
				
			||||||
                      </div> `}
 | 
					 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            <footer class="pf-c-login__main-footer">
 | 
					            </form>
 | 
				
			||||||
                <ul class="pf-c-login__main-footer-links">
 | 
					        </div>`;
 | 
				
			||||||
                    ${this.showBackButton
 | 
					 | 
				
			||||||
                        ? html`<li class="pf-c-login__main-footer-links-item">
 | 
					 | 
				
			||||||
                              <button
 | 
					 | 
				
			||||||
                                  class="pf-c-button pf-m-secondary pf-m-block"
 | 
					 | 
				
			||||||
                                  @click=${() => {
 | 
					 | 
				
			||||||
                                      if (!this.host) return;
 | 
					 | 
				
			||||||
                                      (
 | 
					 | 
				
			||||||
                                          this.host as AuthenticatorValidateStage
 | 
					 | 
				
			||||||
                                      ).selectedDeviceChallenge = undefined;
 | 
					 | 
				
			||||||
                                  }}
 | 
					 | 
				
			||||||
                              >
 | 
					 | 
				
			||||||
                                  ${msg("Return to device picker")}
 | 
					 | 
				
			||||||
                              </button>
 | 
					 | 
				
			||||||
                          </li>`
 | 
					 | 
				
			||||||
                        : html``}
 | 
					 | 
				
			||||||
                </ul>
 | 
					 | 
				
			||||||
            </footer>`;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										69
									
								
								web/src/flow/stages/authenticator_validate/base.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								web/src/flow/stages/authenticator_validate/base.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					    BaseStage,
 | 
				
			||||||
 | 
					    FlowInfoChallenge,
 | 
				
			||||||
 | 
					    PendingUserChallenge,
 | 
				
			||||||
 | 
					} from "@goauthentik/app/flow/stages/base";
 | 
				
			||||||
 | 
					import { AuthenticatorValidateStage } from "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
 | 
					import { CSSResult, TemplateResult, css, html } from "lit";
 | 
				
			||||||
 | 
					import { property } from "lit/decorators.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import PFButton from "@patternfly/patternfly/components/Button/button.css";
 | 
				
			||||||
 | 
					import PFForm from "@patternfly/patternfly/components/Form/form.css";
 | 
				
			||||||
 | 
					import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
 | 
				
			||||||
 | 
					import PFLogin from "@patternfly/patternfly/components/Login/login.css";
 | 
				
			||||||
 | 
					import PFTitle from "@patternfly/patternfly/components/Title/title.css";
 | 
				
			||||||
 | 
					import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { DeviceChallenge } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class BaseDeviceStage<
 | 
				
			||||||
 | 
					    Tin extends FlowInfoChallenge & PendingUserChallenge,
 | 
				
			||||||
 | 
					    Tout,
 | 
				
			||||||
 | 
					> extends BaseStage<Tin, Tout> {
 | 
				
			||||||
 | 
					    @property({ attribute: false })
 | 
				
			||||||
 | 
					    deviceChallenge?: DeviceChallenge;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property({ type: Boolean })
 | 
				
			||||||
 | 
					    showBackButton = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static get styles(): CSSResult[] {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            PFBase,
 | 
				
			||||||
 | 
					            PFLogin,
 | 
				
			||||||
 | 
					            PFForm,
 | 
				
			||||||
 | 
					            PFFormControl,
 | 
				
			||||||
 | 
					            PFTitle,
 | 
				
			||||||
 | 
					            PFButton,
 | 
				
			||||||
 | 
					            css`
 | 
				
			||||||
 | 
					                .pf-c-form__group.pf-m-action {
 | 
				
			||||||
 | 
					                    display: flex;
 | 
				
			||||||
 | 
					                    gap: 16px;
 | 
				
			||||||
 | 
					                    margin-top: 0;
 | 
				
			||||||
 | 
					                    margin-bottom: calc(var(--pf-c-form__group--m-action--MarginTop) / 2);
 | 
				
			||||||
 | 
					                    flex-direction: column;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            `,
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submit(payload: Tin): Promise<boolean> {
 | 
				
			||||||
 | 
					        return this.host?.submit(payload) || Promise.resolve();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    renderReturnToDevicePicker(): TemplateResult {
 | 
				
			||||||
 | 
					        if (!this.showBackButton) {
 | 
				
			||||||
 | 
					            return html``;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return html`<button
 | 
				
			||||||
 | 
					            class="pf-c-button pf-m-secondary pf-m-block"
 | 
				
			||||||
 | 
					            @click=${() => {
 | 
				
			||||||
 | 
					                if (!this.host) return;
 | 
				
			||||||
 | 
					                (this.host as AuthenticatorValidateStage).selectedDeviceChallenge = undefined;
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            ${msg("Return to device picker")}
 | 
				
			||||||
 | 
					        </button>`;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,16 +1,22 @@
 | 
				
			|||||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
					import { AKElement } from "@goauthentik/elements/Base";
 | 
				
			||||||
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
 | 
					import { KeyUnknown } from "@goauthentik/elements/forms/Form";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { msg } from "@lit/localize";
 | 
				
			||||||
import { TemplateResult, html } from "lit";
 | 
					import { TemplateResult, html } from "lit";
 | 
				
			||||||
import { property } from "lit/decorators.js";
 | 
					import { property } from "lit/decorators.js";
 | 
				
			||||||
 | 
					import { ifDefined } from "lit/directives/if-defined.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CurrentBrand, ErrorDetail } from "@goauthentik/api";
 | 
					import { ContextualFlowInfo, CurrentBrand, ErrorDetail } from "@goauthentik/api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SubmitOptions {
 | 
				
			||||||
 | 
					    invisible: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface StageHost {
 | 
					export interface StageHost {
 | 
				
			||||||
    challenge?: unknown;
 | 
					    challenge?: unknown;
 | 
				
			||||||
    flowSlug?: string;
 | 
					    flowSlug?: string;
 | 
				
			||||||
    loading: boolean;
 | 
					    loading: boolean;
 | 
				
			||||||
    submit(payload: unknown): Promise<boolean>;
 | 
					    submit(payload: unknown, options?: SubmitOptions): Promise<boolean>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    readonly brand?: CurrentBrand;
 | 
					    readonly brand?: CurrentBrand;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -26,7 +32,21 @@ export function readFileAsync(file: Blob) {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class BaseStage<Tin, Tout> extends AKElement {
 | 
					// Challenge which contains flow info
 | 
				
			||||||
 | 
					export interface FlowInfoChallenge {
 | 
				
			||||||
 | 
					    flowInfo?: ContextualFlowInfo;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Challenge which has a pending user
 | 
				
			||||||
 | 
					export interface PendingUserChallenge {
 | 
				
			||||||
 | 
					    pendingUser?: string;
 | 
				
			||||||
 | 
					    pendingUserAvatar?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class BaseStage<
 | 
				
			||||||
 | 
					    Tin extends FlowInfoChallenge & PendingUserChallenge,
 | 
				
			||||||
 | 
					    Tout,
 | 
				
			||||||
 | 
					> extends AKElement {
 | 
				
			||||||
    host!: StageHost;
 | 
					    host!: StageHost;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property({ attribute: false })
 | 
					    @property({ attribute: false })
 | 
				
			||||||
@ -68,6 +88,31 @@ export class BaseStage<Tin, Tout> extends AKElement {
 | 
				
			|||||||
        </div>`;
 | 
					        </div>`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    renderUserInfo(): TemplateResult {
 | 
				
			||||||
 | 
					        if (!this.challenge.pendingUser || !this.challenge.pendingUserAvatar) {
 | 
				
			||||||
 | 
					            return html``;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return html`
 | 
				
			||||||
 | 
					            <ak-form-static
 | 
				
			||||||
 | 
					                class="pf-c-form__group"
 | 
				
			||||||
 | 
					                userAvatar="${this.challenge.pendingUserAvatar}"
 | 
				
			||||||
 | 
					                user=${this.challenge.pendingUser}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <div slot="link">
 | 
				
			||||||
 | 
					                    <a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
 | 
				
			||||||
 | 
					                        >${msg("Not you?")}</a
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </ak-form-static>
 | 
				
			||||||
 | 
					            <input
 | 
				
			||||||
 | 
					                name="username"
 | 
				
			||||||
 | 
					                autocomplete="username"
 | 
				
			||||||
 | 
					                type="hidden"
 | 
				
			||||||
 | 
					                value="${this.challenge.pendingUser}"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					        `;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cleanup(): void {
 | 
					    cleanup(): void {
 | 
				
			||||||
        // Method that can be overridden by stages
 | 
					        // Method that can be overridden by stages
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user