Compare commits
10 Commits
web/update
...
version/20
Author | SHA1 | Date | |
---|---|---|---|
318e0cf9f8 | |||
bd0815d894 | |||
af35ecfe66 | |||
0c05cd64bb | |||
cb80b76490 | |||
061d4bc758 | |||
8ff27f69e1 | |||
045cd98276 | |||
b520843984 | |||
92216e4ea8 |
@ -1,16 +1,16 @@
|
||||
[bumpversion]
|
||||
current_version = 2023.10.7
|
||||
current_version = 2024.2.0-rc2
|
||||
tag = 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*))?
|
||||
serialize =
|
||||
serialize =
|
||||
{major}.{minor}.{patch}-{rc_t}{rc_n}
|
||||
{major}.{minor}.{patch}
|
||||
message = release: {new_version}
|
||||
tag_name = version/{new_version}
|
||||
|
||||
[bumpversion:part:rc_t]
|
||||
values =
|
||||
values =
|
||||
rc
|
||||
final
|
||||
optional_value = final
|
||||
|
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:
|
||||
image_tags += [
|
||||
f"{name}:{version}",
|
||||
f"{name}:{version_family}",
|
||||
]
|
||||
if not prerelease:
|
||||
image_tags += [f"{name}:latest"]
|
||||
image_tags += [
|
||||
f"{name}:latest",
|
||||
f"{name}:{version_family}",
|
||||
]
|
||||
else:
|
||||
suffix = ""
|
||||
if image_arch and image_arch != "amd64":
|
||||
suffix = f"-{image_arch}"
|
||||
for name in image_names:
|
||||
image_tags += [
|
||||
f"{name}:gh-{sha}{suffix}",
|
||||
f"{name}:gh-{safe_branch_name}{suffix}",
|
||||
f"{name}:gh-{sha}{suffix}", # Used for ArgoCD and PR comments
|
||||
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]
|
||||
|
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 -R .github ..
|
||||
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/
|
||||
mv ../.github ../scripts .
|
||||
- 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
|
||||
- name: Get static files from docker image
|
||||
run: |
|
||||
docker pull ghcr.io/goauthentik/server:${{ steps.ev.outputs.imageMainTag }}
|
||||
container=$(docker container create ghcr.io/goauthentik/server:${{ steps.ev.outputs.imageMainTag }})
|
||||
docker pull ${{ steps.ev.outputs.imageMainTag }}
|
||||
container=$(docker container create ${{ steps.ev.outputs.imageMainTag }})
|
||||
docker cp ${container}:web/ .
|
||||
- name: Create a Sentry.io release
|
||||
uses: getsentry/action-release@v1
|
||||
|
@ -3,7 +3,7 @@
|
||||
from os import environ
|
||||
from typing import Optional
|
||||
|
||||
__version__ = "2023.10.7"
|
||||
__version__ = "2024.2.0"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -481,13 +481,6 @@ def _update_settings(app_path: str):
|
||||
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:
|
||||
CELERY["task_always_eager"] = True
|
||||
os.environ[ENV_GIT_HASH_KEY] = "dev"
|
||||
@ -512,5 +505,13 @@ except ImportError:
|
||||
# being imported for @prefill_task
|
||||
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))
|
||||
INSTALLED_APPS = list(OrderedDict.fromkeys(SHARED_APPS + TENANT_APPS))
|
||||
|
@ -32,7 +32,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
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
|
||||
command: server
|
||||
environment:
|
||||
@ -53,7 +53,7 @@ services:
|
||||
- postgresql
|
||||
- redis
|
||||
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
|
||||
command: worker
|
||||
environment:
|
||||
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
||||
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
|
||||
elif [[ "$1" == "test-all" ]]; then
|
||||
prepare_debug
|
||||
chmod 777 /root
|
||||
check_if_root "python -m manage test authentik"
|
||||
elif [[ "$1" == "healthcheck" ]]; then
|
||||
run_authentik healthcheck $(cat $MODE_FILE)
|
||||
|
@ -113,7 +113,7 @@ filterwarnings = [
|
||||
|
||||
[tool.poetry]
|
||||
name = "authentik"
|
||||
version = "2023.10.7"
|
||||
version = "2024.2.0"
|
||||
description = ""
|
||||
authors = ["authentik Team <hello@goauthentik.io>"]
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2023.10.7
|
||||
version: 2024.2.0
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@goauthentik.io
|
||||
|
@ -6,3 +6,4 @@ dist
|
||||
coverage
|
||||
src/locale-codes.ts
|
||||
storybook-static/
|
||||
src/locales/**
|
||||
|
@ -22,25 +22,36 @@ import { AdminApi, Settings, SettingsRequest } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-admin-settings-form")
|
||||
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.requestUpdate();
|
||||
}
|
||||
|
||||
@property({ type: Object })
|
||||
get settings() {
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
private _settings?: Settings;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(PFList);
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
return msg("Successfully updated settings.");
|
||||
}
|
||||
|
||||
async send(data: SettingsRequest): Promise<Settings> {
|
||||
return new AdminApi(DEFAULT_CONFIG).adminSettingsUpdate({
|
||||
const result = await new AdminApi(DEFAULT_CONFIG).adminSettingsUpdate({
|
||||
settingsRequest: data,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(PFList);
|
||||
this.dispatchEvent(new CustomEvent("ak-admin-setting-changed"));
|
||||
return result;
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
|
@ -14,8 +14,8 @@ import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { html, nothing } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
|
||||
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
@ -32,7 +32,7 @@ import { AdminApi, Settings } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-admin-settings")
|
||||
export class AdminSettingsPage extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
static get styles() {
|
||||
return [
|
||||
PFBase,
|
||||
PFButton,
|
||||
@ -46,41 +46,46 @@ export class AdminSettingsPage extends AKElement {
|
||||
PFBanner,
|
||||
];
|
||||
}
|
||||
@property({ attribute: false })
|
||||
|
||||
@query("ak-admin-settings-form#form")
|
||||
form?: AdminSettingsForm;
|
||||
|
||||
@state()
|
||||
settings?: Settings;
|
||||
|
||||
loadSettings(): void {
|
||||
new AdminApi(DEFAULT_CONFIG).adminSettingsRetrieve().then((settings) => {
|
||||
constructor() {
|
||||
super();
|
||||
AdminSettingsPage.fetchSettings().then((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 {
|
||||
this.loadSettings();
|
||||
static async fetchSettings() {
|
||||
return await new AdminApi(DEFAULT_CONFIG).adminSettingsRetrieve();
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
const form = this.shadowRoot?.querySelector<AdminSettingsForm>("ak-admin-settings-form");
|
||||
if (!form) {
|
||||
async handleUpdate() {
|
||||
this.settings = await AdminSettingsPage.fetchSettings();
|
||||
}
|
||||
|
||||
async save() {
|
||||
if (!this.form) {
|
||||
return;
|
||||
}
|
||||
await form.submit(new Event("submit"));
|
||||
this.resetForm();
|
||||
await this.form.submit(new Event("submit"));
|
||||
this.settings = await AdminSettingsPage.fetchSettings();
|
||||
}
|
||||
|
||||
resetForm(): void {
|
||||
const form = this.shadowRoot?.querySelector<AdminSettingsForm>("ak-admin-settings-form");
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
this.loadSettings();
|
||||
form.settings = this.settings!;
|
||||
form.resetForm();
|
||||
async reset() {
|
||||
this.form?.resetForm();
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
render() {
|
||||
if (!this.settings) {
|
||||
return html``;
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ak-page-header icon="fa fa-cog" header="" description="">
|
||||
@ -93,18 +98,10 @@ export class AdminSettingsPage extends AKElement {
|
||||
</ak-admin-settings-form>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<ak-spinner-button
|
||||
.callAction=${async () => {
|
||||
await this.save();
|
||||
}}
|
||||
class="pf-m-primary"
|
||||
<ak-spinner-button .callAction=${this.save} class="pf-m-primary"
|
||||
>${msg("Save")}</ak-spinner-button
|
||||
>
|
||||
<ak-spinner-button
|
||||
.callAction=${() => {
|
||||
this.resetForm();
|
||||
}}
|
||||
class="pf-m-secondary"
|
||||
<ak-spinner-button .callAction=${this.reset} class="pf-m-secondary"
|
||||
>${msg("Cancel")}</ak-spinner-button
|
||||
>
|
||||
</div>
|
||||
|
@ -125,6 +125,7 @@ export class RelatedGroupList extends Table<Group> {
|
||||
actionSubtext=${msg(
|
||||
str`Are you sure you want to remove user ${this.targetUser?.username} from the following groups?`,
|
||||
)}
|
||||
buttonLabel=${msg("Remove")}
|
||||
.objects=${this.selectedElements}
|
||||
.delete=${(item: Group) => {
|
||||
if (!this.targetUser) return;
|
||||
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
||||
export const ERROR_CLASS = "pf-m-danger";
|
||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
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 ROUTE_SEPARATOR = ";";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
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 PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
|
||||
@ -23,7 +23,17 @@ export class EmptyState extends AKElement {
|
||||
header = "";
|
||||
|
||||
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 {
|
||||
|
@ -131,6 +131,9 @@ export class DeleteBulkForm<T> extends ModalButton {
|
||||
@property()
|
||||
actionSubtext?: string;
|
||||
|
||||
@property()
|
||||
buttonLabel = msg("Delete");
|
||||
|
||||
@property({ attribute: false })
|
||||
metadata: (item: T) => BulkDeleteMetadata = (item: T) => {
|
||||
const rec = item as Record<string, unknown>;
|
||||
@ -222,7 +225,7 @@ export class DeleteBulkForm<T> extends ModalButton {
|
||||
}}
|
||||
class="pf-m-danger"
|
||||
>
|
||||
${msg("Delete")} </ak-spinner-button
|
||||
${this.buttonLabel} </ak-spinner-button
|
||||
>
|
||||
<ak-spinner-button
|
||||
.callAction=${async () => {
|
||||
|
@ -15,7 +15,7 @@ import "@goauthentik/flow/sources/apple/AppleLoginInit";
|
||||
import "@goauthentik/flow/sources/plex/PlexLoginInit";
|
||||
import "@goauthentik/flow/stages/FlowErrorStage";
|
||||
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 { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
@ -189,12 +189,17 @@ export class FlowExecutor extends Interface implements StageHost {
|
||||
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 (!this.challenge) return Promise.reject();
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
payload.component = this.challenge.component;
|
||||
this.loading = true;
|
||||
if (!options?.invisible) {
|
||||
this.loading = true;
|
||||
}
|
||||
try {
|
||||
const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
|
||||
flowSlug: this.flowSlug,
|
||||
|
@ -40,6 +40,7 @@ export class AuthenticatorStaticStage extends BaseStage<
|
||||
columns: 2;
|
||||
-webkit-columns: 2;
|
||||
-moz-columns: 2;
|
||||
column-width: 1em;
|
||||
margin-left: var(--pf-global--spacer--xs);
|
||||
}
|
||||
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/AuthenticatorValidateStageDuo";
|
||||
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 { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
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 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 just want to notify the backend which challenge has been selected.
|
||||
new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
|
||||
flowSlug: this.host.flowSlug || "",
|
||||
flowSlug: this.host?.flowSlug || "",
|
||||
query: window.location.search.substring(1),
|
||||
flowChallengeResponseRequest: {
|
||||
// @ts-ignore
|
||||
@ -73,8 +72,11 @@ export class AuthenticatorValidateStage
|
||||
return this._selectedDeviceChallenge;
|
||||
}
|
||||
|
||||
submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<boolean> {
|
||||
return this.host?.submit(payload) || Promise.resolve();
|
||||
submit(
|
||||
payload: AuthenticatorValidationChallengeResponseRequest,
|
||||
options?: SubmitOptions,
|
||||
): Promise<boolean> {
|
||||
return this.host?.submit(payload, options) || Promise.resolve();
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
@ -253,23 +255,7 @@ export class AuthenticatorValidateStage
|
||||
? this.renderDeviceChallenge()
|
||||
: html`<div class="pf-c-login__main-body">
|
||||
<form class="pf-c-form">
|
||||
<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}"
|
||||
/>
|
||||
${this.renderUserInfo()}
|
||||
${this.selectedDeviceChallenge
|
||||
? ""
|
||||
: html`<p>${msg("Select an authentication method.")}</p>`}
|
||||
|
@ -1,59 +1,34 @@
|
||||
import { BaseDeviceStage } from "@goauthentik/app/flow/stages/authenticator_validate/base";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
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 { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property } 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 { customElement } from "lit/decorators.js";
|
||||
|
||||
import {
|
||||
AuthenticatorValidationChallenge,
|
||||
AuthenticatorValidationChallengeResponseRequest,
|
||||
DeviceChallenge,
|
||||
DeviceClassesEnum,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-stage-authenticator-validate-code")
|
||||
export class AuthenticatorValidateStageWebCode extends BaseStage<
|
||||
export class AuthenticatorValidateStageWebCode extends BaseDeviceStage<
|
||||
AuthenticatorValidationChallenge,
|
||||
AuthenticatorValidationChallengeResponseRequest
|
||||
> {
|
||||
@property({ attribute: false })
|
||||
deviceChallenge?: DeviceChallenge;
|
||||
|
||||
@property({ type: Boolean })
|
||||
showBackButton = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFLogin,
|
||||
PFForm,
|
||||
PFFormControl,
|
||||
PFTitle,
|
||||
PFButton,
|
||||
css`
|
||||
.icon-description {
|
||||
display: flex;
|
||||
}
|
||||
.icon-description i {
|
||||
font-size: 2em;
|
||||
padding: 0.25em;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
`,
|
||||
];
|
||||
return super.styles.concat(css`
|
||||
.icon-description {
|
||||
display: flex;
|
||||
}
|
||||
.icon-description i {
|
||||
font-size: 2em;
|
||||
padding: 0.25em;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
@ -62,92 +37,62 @@ export class AuthenticatorValidateStageWebCode extends BaseStage<
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
return html`<div class="pf-c-login__main-body">
|
||||
<form
|
||||
class="pf-c-form"
|
||||
@submit=${(e: Event) => {
|
||||
this.submitForm(e);
|
||||
}}
|
||||
<form
|
||||
class="pf-c-form"
|
||||
@submit=${(e: Event) => {
|
||||
this.submitForm(e);
|
||||
}}
|
||||
>
|
||||
${this.renderUserInfo()}
|
||||
<div class="icon-description">
|
||||
<i
|
||||
class="fa ${this.deviceChallenge?.deviceClass == DeviceClassesEnum.Sms
|
||||
? "fa-key"
|
||||
: "fa-mobile-alt"}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
${this.deviceChallenge?.deviceClass == DeviceClassesEnum.Sms
|
||||
? html`<p>${msg("A code has been sent to you via SMS.")}</p>`
|
||||
: html`<p>
|
||||
${msg(
|
||||
"Open your two-factor authenticator app to view your authentication code.",
|
||||
)}
|
||||
</p>`}
|
||||
</div>
|
||||
<ak-form-element
|
||||
label="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
|
||||
? msg("Static token")
|
||||
: msg("Authentication code")}"
|
||||
?required="${true}"
|
||||
class="pf-c-form__group"
|
||||
.errors=${(this.challenge?.responseErrors || {})["code"]}
|
||||
>
|
||||
<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>
|
||||
<div class="icon-description">
|
||||
<i
|
||||
class="fa ${this.deviceChallenge?.deviceClass == DeviceClassesEnum.Sms
|
||||
? "fa-key"
|
||||
: "fa-mobile-alt"}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
${this.deviceChallenge?.deviceClass == DeviceClassesEnum.Sms
|
||||
? html`<p>${msg("A code has been sent to you via SMS.")}</p>`
|
||||
: html`<p>
|
||||
${msg(
|
||||
"Open your two-factor authenticator app to view your authentication code.",
|
||||
)}
|
||||
</p>`}
|
||||
</div>
|
||||
<ak-form-element
|
||||
label="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
|
||||
? msg("Static token")
|
||||
: msg("Authentication code")}"
|
||||
?required="${true}"
|
||||
class="pf-c-form__group"
|
||||
.errors=${(this.challenge?.responseErrors || {})["code"]}
|
||||
>
|
||||
<!-- @ts-ignore -->
|
||||
<input
|
||||
type="text"
|
||||
name="code"
|
||||
inputmode="${this.deviceChallenge?.deviceClass ===
|
||||
DeviceClassesEnum.Static
|
||||
? "text"
|
||||
: "numeric"}"
|
||||
pattern="${this.deviceChallenge?.deviceClass ===
|
||||
DeviceClassesEnum.Static
|
||||
? "[0-9a-zA-Z]*"
|
||||
: "[0-9]*"}"
|
||||
placeholder="${msg("Please enter your code")}"
|
||||
autofocus=""
|
||||
autocomplete="one-time-code"
|
||||
class="pf-c-form-control"
|
||||
value="${PasswordManagerPrefill.totp || ""}"
|
||||
required
|
||||
/>
|
||||
</ak-form-element>
|
||||
<!-- @ts-ignore -->
|
||||
<input
|
||||
type="text"
|
||||
name="code"
|
||||
inputmode="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
|
||||
? "text"
|
||||
: "numeric"}"
|
||||
pattern="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
|
||||
? "[0-9a-zA-Z]*"
|
||||
: "[0-9]*"}"
|
||||
placeholder="${msg("Please enter your code")}"
|
||||
autofocus=""
|
||||
autocomplete="one-time-code"
|
||||
class="pf-c-form-control"
|
||||
value="${PasswordManagerPrefill.totp || ""}"
|
||||
required
|
||||
/>
|
||||
</ak-form-element>
|
||||
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
|
||||
${msg("Continue")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</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>`;
|
||||
<div class="pf-c-form__group pf-m-action">
|
||||
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
|
||||
${msg("Continue")}
|
||||
</button>
|
||||
${this.renderReturnToDevicePicker()}
|
||||
</div>
|
||||
</form>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,10 @@
|
||||
import { BaseDeviceStage } from "@goauthentik/app/flow/stages/authenticator_validate/base";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
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 { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } 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 { TemplateResult, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import {
|
||||
AuthenticatorValidationChallenge,
|
||||
@ -23,7 +13,7 @@ import {
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-stage-authenticator-validate-duo")
|
||||
export class AuthenticatorValidateStageWebDuo extends BaseStage<
|
||||
export class AuthenticatorValidateStageWebDuo extends BaseDeviceStage<
|
||||
AuthenticatorValidationChallenge,
|
||||
AuthenticatorValidationChallengeResponseRequest
|
||||
> {
|
||||
@ -33,14 +23,24 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage<
|
||||
@property({ type: Boolean })
|
||||
showBackButton = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFLogin, PFForm, PFFormControl, PFTitle, PFButton];
|
||||
}
|
||||
@state()
|
||||
authenticating = false;
|
||||
|
||||
firstUpdated(): void {
|
||||
this.host?.submit({
|
||||
duo: this.deviceChallenge?.deviceUid,
|
||||
});
|
||||
this.authenticating = true;
|
||||
this.host
|
||||
?.submit(
|
||||
{
|
||||
duo: this.deviceChallenge?.deviceUid,
|
||||
},
|
||||
{ invisible: true },
|
||||
)
|
||||
.then(() => {
|
||||
this.authenticating = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.authenticating = false;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
@ -49,56 +49,25 @@ export class AuthenticatorValidateStageWebDuo extends BaseStage<
|
||||
</ak-empty-state>`;
|
||||
}
|
||||
const errors = this.challenge.responseErrors?.duo || [];
|
||||
const errorMessage = errors.map((err) => err.string);
|
||||
return html`<div class="pf-c-login__main-body">
|
||||
<form
|
||||
class="pf-c-form"
|
||||
@submit=${(e: Event) => {
|
||||
this.submitForm(e);
|
||||
}}
|
||||
<form
|
||||
class="pf-c-form"
|
||||
@submit=${(e: Event) => {
|
||||
this.submitForm(e);
|
||||
}}
|
||||
>
|
||||
${this.renderUserInfo()}
|
||||
<ak-empty-state
|
||||
?loading="${this.authenticating}"
|
||||
header=${this.authenticating
|
||||
? msg("Sending Duo push notification...")
|
||||
: errorMessage.join(", ") || msg("Failed to authenticate")}
|
||||
icon="fas fa-times"
|
||||
>
|
||||
<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>
|
||||
|
||||
${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>
|
||||
</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>`;
|
||||
</ak-empty-state>
|
||||
<div class="pf-c-form__group pf-m-action">${this.renderReturnToDevicePicker()}</div>
|
||||
</form>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,14 @@
|
||||
import { BaseDeviceStage } from "@goauthentik/app/flow/stages/authenticator_validate/base";
|
||||
import {
|
||||
checkWebAuthnSupport,
|
||||
transformAssertionForServer,
|
||||
transformCredentialRequestOptions,
|
||||
} from "@goauthentik/common/helpers/webauthn";
|
||||
import { AuthenticatorValidateStage } from "@goauthentik/flow/stages/authenticator_validate/AuthenticatorValidateStage";
|
||||
import { BaseStage } from "@goauthentik/flow/stages/base";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } 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 { msg } from "@lit/localize";
|
||||
import { TemplateResult, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
||||
import {
|
||||
AuthenticatorValidationChallenge,
|
||||
@ -26,7 +17,7 @@ import {
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-stage-authenticator-validate-webauthn")
|
||||
export class AuthenticatorValidateStageWebAuthn extends BaseStage<
|
||||
export class AuthenticatorValidateStageWebAuthn extends BaseDeviceStage<
|
||||
AuthenticatorValidationChallenge,
|
||||
AuthenticatorValidationChallengeResponseRequest
|
||||
> {
|
||||
@ -34,25 +25,15 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage<
|
||||
deviceChallenge?: DeviceChallenge;
|
||||
|
||||
@property()
|
||||
authenticateMessage?: string;
|
||||
errorMessage?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
showBackButton = false;
|
||||
|
||||
transformedCredentialRequestOptions?: PublicKeyCredentialRequestOptions;
|
||||
@state()
|
||||
authenticating = false;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFLogin,
|
||||
PFEmptyState,
|
||||
PFBullseye,
|
||||
PFForm,
|
||||
PFFormControl,
|
||||
PFTitle,
|
||||
PFButton,
|
||||
];
|
||||
}
|
||||
transformedCredentialRequestOptions?: PublicKeyCredentialRequestOptions;
|
||||
|
||||
async authenticate(): Promise<void> {
|
||||
// request the authenticator to create an assertion signature using the
|
||||
@ -64,10 +45,10 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage<
|
||||
publicKey: this.transformedCredentialRequestOptions,
|
||||
});
|
||||
if (!assertion) {
|
||||
throw new Error(msg("Assertions is empty"));
|
||||
throw new Error("Assertions is empty");
|
||||
}
|
||||
} 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
|
||||
@ -78,11 +59,16 @@ export class AuthenticatorValidateStageWebAuthn extends BaseStage<
|
||||
|
||||
// post the assertion to the server for verification.
|
||||
try {
|
||||
await this.host?.submit({
|
||||
webauthn: transformedAssertionForServer,
|
||||
});
|
||||
await this.host?.submit(
|
||||
{
|
||||
webauthn: transformedAssertionForServer,
|
||||
},
|
||||
{
|
||||
invisible: true,
|
||||
},
|
||||
);
|
||||
} 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> {
|
||||
if (this.host.loading) {
|
||||
if (this.authenticating) {
|
||||
return;
|
||||
}
|
||||
this.host.loading = true;
|
||||
this.authenticating = true;
|
||||
this.authenticate()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
this.authenticateMessage = e.toString();
|
||||
.catch((e: Error) => {
|
||||
console.warn(`authentik/flows/authenticator_validate/webauthn: ${e.toString()}`);
|
||||
this.errorMessage = msg("Authentication failed.");
|
||||
})
|
||||
.finally(() => {
|
||||
this.host.loading = false;
|
||||
this.authenticating = false;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-login__main-body">
|
||||
${this.authenticateMessage
|
||||
? html`<div class="pf-c-form__group pf-m-action">
|
||||
<p class="pf-m-block">${this.authenticateMessage}</p>
|
||||
<button
|
||||
<form class="pf-c-form">
|
||||
${this.renderUserInfo()}
|
||||
<ak-empty-state
|
||||
?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"
|
||||
@click=${() => {
|
||||
this.authenticateWrapper();
|
||||
}}
|
||||
>
|
||||
${msg("Retry authentication")}
|
||||
</button>
|
||||
</div>`
|
||||
: html`<div class="pf-c-form__group pf-m-action">
|
||||
<p class="pf-m-block"> </p>
|
||||
<p class="pf-m-block"> </p>
|
||||
<p class="pf-m-block"> </p>
|
||||
</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>`;
|
||||
</button>`
|
||||
: nothing}
|
||||
${this.renderReturnToDevicePicker()}
|
||||
</div>
|
||||
</form>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
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 { KeyUnknown } from "@goauthentik/elements/forms/Form";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
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 {
|
||||
challenge?: unknown;
|
||||
flowSlug?: string;
|
||||
loading: boolean;
|
||||
submit(payload: unknown): Promise<boolean>;
|
||||
submit(payload: unknown, options?: SubmitOptions): Promise<boolean>;
|
||||
|
||||
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;
|
||||
|
||||
@property({ attribute: false })
|
||||
@ -68,6 +88,31 @@ export class BaseStage<Tin, Tout> extends AKElement {
|
||||
</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 {
|
||||
// Method that can be overridden by stages
|
||||
return;
|
||||
|
Reference in New Issue
Block a user