stages/authenticator_duo: initial duo stage
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
@ -15,6 +15,7 @@ import { Stage, StagesApi } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
|
||||
import "./authenticator_duo/AuthenticatorDuoStageForm.ts";
|
||||
import "./authenticator_static/AuthenticatorStaticStageForm.ts";
|
||||
import "./authenticator_totp/AuthenticatorTOTPStageForm.ts";
|
||||
import "./authenticator_validate/AuthenticatorValidateStageForm.ts";
|
||||
|
||||
@ -0,0 +1,105 @@
|
||||
import { FlowsApi, AuthenticatorDuoStage, StagesApi, FlowsInstancesListDesignationEnum, AuthenticatorDuoStageRequest } from "authentik-api";
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement } from "lit-element";
|
||||
import { html, TemplateResult } from "lit-html";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import "../../../elements/forms/HorizontalFormElement";
|
||||
import "../../../elements/forms/FormGroup";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { first } from "../../../utils";
|
||||
import { ModelForm } from "../../../elements/forms/ModelForm";
|
||||
|
||||
@customElement("ak-stage-authenticator-duo-form")
|
||||
export class AuthenticatorDuoStageForm extends ModelForm<AuthenticatorDuoStage, string> {
|
||||
|
||||
loadInstance(pk: string): Promise<AuthenticatorDuoStage> {
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoRetrieve({
|
||||
stageUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated stage.`;
|
||||
} else {
|
||||
return t`Successfully created stage.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: AuthenticatorDuoStage): Promise<AuthenticatorDuoStage> => {
|
||||
if (this.instance) {
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoPartialUpdate({
|
||||
stageUuid: this.instance.pk || "",
|
||||
patchedAuthenticatorDuoStageRequest: data
|
||||
});
|
||||
} else {
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorDuoCreate({
|
||||
authenticatorDuoStageRequest: data as unknown as AuthenticatorDuoStageRequest
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="form-help-text">
|
||||
${t`Stage used to configure a duo-based authenticator. This stage should be used for configuration flows.`}
|
||||
</div>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Name`}
|
||||
?required=${true}
|
||||
name="name">
|
||||
<input type="text" value="${ifDefined(this.instance?.name || "")}" class="pf-c-form-control" required>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header">
|
||||
${t`Stage-specific settings`}
|
||||
</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Client ID`}
|
||||
?required=${true}
|
||||
name="clientId">
|
||||
<input type="text" value="${first(this.instance?.clientId, "")}" class="pf-c-form-control" required>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Client Secret`}
|
||||
?required=${true}
|
||||
?writeOnly=${this.instance !== undefined}
|
||||
name="clientSecret">
|
||||
<input type="text" value="" class="pf-c-form-control" required>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`API Hostname`}
|
||||
?required=${true}
|
||||
name="apiHostname">
|
||||
<input type="text" value="${first(this.instance?.apiHostname, "")}" class="pf-c-form-control" required>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Configuration flow`}
|
||||
name="configureFlow">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.configureFlow === undefined}>---------</option>
|
||||
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
|
||||
ordering: "pk",
|
||||
designation: FlowsInstancesListDesignationEnum.StageConfiguration,
|
||||
}).then(flows => {
|
||||
return flows.results.map(flow => {
|
||||
let selected = this.instance?.configureFlow === flow.pk;
|
||||
if (!this.instance?.pk && !this.instance?.configureFlow && flow.slug === "default-otp-time-configure") {
|
||||
selected = true;
|
||||
}
|
||||
return html`<option value=${ifDefined(flow.pk)} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
|
||||
});
|
||||
}), html`<option>${t`Loading...`}</option>`)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
|
||||
}
|
||||
@ -34,8 +34,8 @@ export class AuthenticatorStaticStageForm extends ModelForm<AuthenticatorStaticS
|
||||
authenticatorStaticStageRequest: data
|
||||
});
|
||||
} else {
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesUserWriteCreate({
|
||||
userWriteStageRequest: data
|
||||
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorStaticCreate({
|
||||
authenticatorStaticStageRequest: data
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -21,6 +21,7 @@ import "../../elements/Tabs";
|
||||
import "../../elements/PageHeader";
|
||||
import "./tokens/UserTokenList";
|
||||
import "./UserDetailsPage";
|
||||
import "./settings/UserSettingsAuthenticatorDuo";
|
||||
import "./settings/UserSettingsAuthenticatorStatic";
|
||||
import "./settings/UserSettingsAuthenticatorTOTP";
|
||||
import "./settings/UserSettingsAuthenticatorWebAuthn";
|
||||
@ -48,6 +49,9 @@ export class UserSettingsPage extends LitElement {
|
||||
case "ak-user-settings-authenticator-static":
|
||||
return html`<ak-user-settings-authenticator-static objectId=${stage.objectUid}>
|
||||
</ak-user-settings-authenticator-static>`;
|
||||
case "ak-user-settings-authenticator-duo":
|
||||
return html`<ak-user-settings-authenticator-duo objectId=${stage.objectUid}>
|
||||
</ak-user-settings-authenticator-duo>`;
|
||||
default:
|
||||
return html`<p>${t`Error: unsupported stage settings: ${stage.component}`}</p>`;
|
||||
}
|
||||
|
||||
@ -0,0 +1,79 @@
|
||||
import { AuthenticatorsApi } from "authentik-api";
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { DEFAULT_CONFIG } from "../../../api/Config";
|
||||
import { FlowURLManager } from "../../../api/legacy";
|
||||
import { BaseUserSettings } from "./BaseUserSettings";
|
||||
|
||||
@customElement("ak-user-settings-authenticator-duo")
|
||||
export class UserSettingsAuthenticatorDuo extends BaseUserSettings {
|
||||
|
||||
@property({ type: Boolean })
|
||||
configureFlow = false;
|
||||
|
||||
renderEnabled(): TemplateResult {
|
||||
return html`<div class="pf-c-card__body">
|
||||
<p>
|
||||
${t`Status: Enabled`}
|
||||
<i class="pf-icon pf-icon-ok"></i>
|
||||
</p>
|
||||
<ul class="ak-otp-tokens">
|
||||
${until(new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticList({}).then((devices) => {
|
||||
if (devices.results.length < 1) {
|
||||
return;
|
||||
}
|
||||
return devices.results[0].tokenSet?.map((token) => {
|
||||
return html`<li>${token.token}</li>`;
|
||||
});
|
||||
}))}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<button
|
||||
class="pf-c-button pf-m-danger"
|
||||
@click=${() => {
|
||||
return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticList({}).then((devices) => {
|
||||
if (devices.results.length < 1) {
|
||||
return;
|
||||
}
|
||||
// TODO: Handle multiple devices, currently we assume only one TOTP Device
|
||||
return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticDestroy({
|
||||
id: devices.results[0].pk || 0
|
||||
});
|
||||
});
|
||||
}}>
|
||||
${t`Disable Static Tokens`}
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderDisabled(): TemplateResult {
|
||||
return html`
|
||||
<div class="pf-c-card__body">
|
||||
<p>
|
||||
${t`Status: Disabled`}
|
||||
<i class="pf-icon pf-icon-error-circle-o"></i>
|
||||
</p>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
${this.configureFlow ?
|
||||
html`<a href="${FlowURLManager.configure(this.objectId || "", "?next=/%23%2Fuser")}"
|
||||
class="pf-c-button pf-m-primary">${t`Enable Static Tokens`}
|
||||
</a>`: html``}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-card">
|
||||
<div class="pf-c-card__title">
|
||||
${t`Duo`}
|
||||
</div>
|
||||
${this.renderDisabled()}
|
||||
${until(new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticList({}).then((devices) => {
|
||||
return devices.results.length > 0 ? this.renderEnabled() : this.renderDisabled();
|
||||
}))}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
}
|
||||
@ -72,7 +72,7 @@ export class UserSettingsAuthenticatorStatic extends BaseUserSettings {
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-card">
|
||||
<div class="pf-c-card__title">
|
||||
${t`Time-based One-Time Passwords`}
|
||||
${t`Static tokens`}
|
||||
</div>
|
||||
${until(new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticList({}).then((devices) => {
|
||||
return devices.results.length > 0 ? this.renderEnabled() : this.renderDisabled();
|
||||
|
||||
@ -57,7 +57,7 @@ export class UserSettingsAuthenticatorTOTP extends BaseUserSettings {
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-card">
|
||||
<div class="pf-c-card__title">
|
||||
${t`Static tokens`}
|
||||
${t`Time-based One-Time Passwords`}
|
||||
</div>
|
||||
${until(new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpList({}).then((devices) => {
|
||||
return devices.results.length > 0 ? this.renderEnabled() : this.renderDisabled();
|
||||
|
||||
Reference in New Issue
Block a user