stages/authenticator_duo: initial duo stage

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer
2021-05-23 21:04:37 +02:00
parent a5cd9fa141
commit 9f5a3c396d
21 changed files with 1134 additions and 25 deletions

View File

@ -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";

View File

@ -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>`;
}
}

View File

@ -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
});
}
};

View File

@ -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>`;
}

View File

@ -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>`;
}
}

View File

@ -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();

View File

@ -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();