@@ -54,10 +75,30 @@ export class UserSettingsPage extends LitElement { data-tab-title="${t`User details`}" class="pf-c-page__main-section pf-m-no-padding-mobile" > -
-
${t`Update details`}
-
- +
+
+
+
${t`Update details`}
+
+ +
+
+
+
+ ${until( + this.userSettings?.then((settings) => { + if ( + settings.filter( + (stage) => + stage.component === "ak-user-settings-password", + ).length > 0 + ) { + return html``; + } + }), + )}
@@ -88,11 +129,13 @@ export class UserSettingsPage extends LitElement { )}
- +
{ +@customElement("ak-user-details-form") +export class UserDetailsForm extends ModelForm { viewportCheck = false; // eslint-disable-next-line @typescript-eslint/no-unused-vars loadInstance(pk: number): Promise { - return new CoreApi(DEFAULT_CONFIG).coreUsersMeRetrieve().then((su) => { - return su.user; + return me().then((user) => { + return user.user; }); } diff --git a/web/src/user/user-settings/stages/UserSettingsPassword.ts b/web/src/user/user-settings/details/UserPassword.ts similarity index 50% rename from web/src/user/user-settings/stages/UserSettingsPassword.ts rename to web/src/user/user-settings/details/UserPassword.ts index d784994e2..31b8873a5 100644 --- a/web/src/user/user-settings/stages/UserSettingsPassword.ts +++ b/web/src/user/user-settings/details/UserPassword.ts @@ -1,13 +1,30 @@ import { t } from "@lingui/macro"; import { TemplateResult, html } from "lit"; +import { CSSResult, LitElement } from "lit"; import { customElement } from "lit/decorators"; +import { property } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; -import { BaseUserSettings } from "../BaseUserSettings"; +import AKGlobal from "../../../authentik.css"; +import PFButton from "@patternfly/patternfly/components/Button/button.css"; +import PFCard from "@patternfly/patternfly/components/Card/card.css"; +import PFForm from "@patternfly/patternfly/components/Form/form.css"; +import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css"; +import PFBase from "@patternfly/patternfly/patternfly-base.css"; @customElement("ak-user-settings-password") -export class UserSettingsPassword extends BaseUserSettings { +export class UserSettingsPassword extends LitElement { + @property() + objectId!: string; + + @property() + configureUrl?: string; + + static get styles(): CSSResult[] { + return [PFBase, PFCard, PFButton, PFForm, PFFormControl, AKGlobal]; + } + render(): TemplateResult { // For this stage we don't need to check for a configureFlow, // as the stage won't return any UI Elements if no configureFlow is set. diff --git a/web/src/user/user-settings/mfa/MFADevicesPage.ts b/web/src/user/user-settings/mfa/MFADevicesPage.ts new file mode 100644 index 000000000..87d650cd4 --- /dev/null +++ b/web/src/user/user-settings/mfa/MFADevicesPage.ts @@ -0,0 +1,155 @@ +import { t } from "@lingui/macro"; + +import { TemplateResult, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import { until } from "lit/directives/until"; + +import { AuthenticatorsApi, Device, UserSetting } from "@goauthentik/api"; + +import { AKResponse } from "../../../api/Client"; +import { DEFAULT_CONFIG } from "../../../api/Config"; +import "../../../elements/buttons/Dropdown"; +import "../../../elements/buttons/ModalButton"; +import "../../../elements/buttons/TokenCopyButton"; +import "../../../elements/forms/DeleteBulkForm"; +import "../../../elements/forms/ModalForm"; +import { Table, TableColumn } from "../../../elements/table/Table"; + +export function stageToAuthenticatorName(stage: UserSetting): string { + switch (stage.component) { + case "ak-user-settings-authenticator-duo": + return t`Duo authenticator`; + case "ak-user-settings-authenticator-sms": + return t`SMS authenticator`; + case "ak-user-settings-authenticator-static": + return t`Static authenticator`; + case "ak-user-settings-authenticator-totp": + return t`TOTP authenticator`; + case "ak-user-settings-authenticator-webauthn": + return t`Security key authenticator`; + } + return `Invalid stage component ${stage.component}`; +} + +@customElement("ak-user-settings-mfa") +export class MFADevicesPage extends Table { + @property({ attribute: false }) + userSettings?: Promise; + + checkbox = true; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async apiEndpoint(page: number): Promise> { + const devices = await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsAllList(); + return { + pagination: { + current: 0, + count: devices.length, + totalPages: 1, + startIndex: 1, + endIndex: devices.length, + }, + results: devices, + }; + } + + columns(): TableColumn[] { + return [new TableColumn(t`Name`), new TableColumn(t`Type`), new TableColumn("")]; + } + + renderToolbar(): TemplateResult { + return html` + + + + ${super.renderToolbar()}`; + } + + async deleteWrapper(device: Device) { + switch (device.type) { + case "authentik_stages_authenticator_duo.DuoDevice": + return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsDuoDestroy({ + id: device.pk, + }); + case "authentik_stages_authenticator_sms.SMSDevice": + return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsSmsDestroy({ + id: device.pk, + }); + case "otp_totp.TOTPDevice": + return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpDestroy({ + id: device.pk, + }); + case "otp_static.StaticDevice": + return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsStaticDestroy({ + id: device.pk, + }); + case "authentik_stages_authenticator_webauthn.WebAuthnDevice": + return new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsWebauthnDestroy({ + id: device.pk, + }); + default: + break; + } + } + + renderToolbarSelected(): TemplateResult { + const disabled = this.selectedElements.length < 1; + return html` { + return this.deleteWrapper(item); + }} + > + + `; + } + + row(item: Device): TemplateResult[] { + return [ + html`${item.name}`, + html`${item.verboseName}`, + html` + + ${t`Update`} + ${t`Update Device`} + + + + `, + ]; + } +} diff --git a/web/src/user/user-settings/stages/StageSettings.ts b/web/src/user/user-settings/stages/StageSettings.ts deleted file mode 100644 index 59855e1de..000000000 --- a/web/src/user/user-settings/stages/StageSettings.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { t } from "@lingui/macro"; - -import { CSSResult, LitElement, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators"; -import { until } from "lit/directives/until"; - -import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; - -import { StagesApi, UserSetting } from "@goauthentik/api"; - -import { DEFAULT_CONFIG } from "../../../api/Config"; -import { EVENT_REFRESH } from "../../../constants"; -import "../../../elements/EmptyState"; -import "./UserSettingsAuthenticatorDuo"; -import "./UserSettingsAuthenticatorSMS"; -import "./UserSettingsAuthenticatorStatic"; -import "./UserSettingsAuthenticatorTOTP"; -import "./UserSettingsAuthenticatorWebAuthn"; -import "./UserSettingsPassword"; - -@customElement("ak-user-settings-stage") -export class UserStageSettingsPage extends LitElement { - @property({ attribute: false }) - userSettings?: Promise; - - static get styles(): CSSResult[] { - return [PFStack]; - } - - constructor() { - super(); - this.addEventListener(EVENT_REFRESH, () => { - this.firstUpdated(); - }); - } - - firstUpdated(): void { - this.userSettings = new StagesApi(DEFAULT_CONFIG).stagesAllUserSettingsList(); - } - - renderStageSettings(stage: UserSetting): TemplateResult { - switch (stage.component) { - case "ak-user-settings-authenticator-webauthn": - return html` - `; - case "ak-user-settings-password": - return html` - `; - case "ak-user-settings-authenticator-totp": - return html` - `; - case "ak-user-settings-authenticator-static": - return html` - `; - case "ak-user-settings-authenticator-duo": - return html` - `; - case "ak-user-settings-authenticator-sms": - return html` - `; - default: - return html`

${t`Error: unsupported stage settings: ${stage.component}`}

`; - } - } - - render(): TemplateResult { - return html`
- ${until( - this.userSettings?.then((stages) => { - return stages.map((stage) => { - return html`
- ${this.renderStageSettings(stage)} -
`; - }); - }), - html``, - )} -
`; - } -} diff --git a/web/src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts b/web/src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts deleted file mode 100644 index 72d4ba8b8..000000000 --- a/web/src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { t } from "@lingui/macro"; - -import { TemplateResult, html } from "lit"; -import { customElement } from "lit/decorators"; -import { until } from "lit/directives/until"; - -import { AuthenticatorsApi } from "@goauthentik/api"; - -import { DEFAULT_CONFIG } from "../../../api/Config"; -import { EVENT_REFRESH } from "../../../constants"; -import { BaseUserSettings } from "../BaseUserSettings"; - -@customElement("ak-user-settings-authenticator-duo") -export class UserSettingsAuthenticatorDuo extends BaseUserSettings { - renderEnabled(): TemplateResult { - return html`
-

- ${t`Status: Enabled`} - -

-
- `; - } - - renderDisabled(): TemplateResult { - return html`
-

- ${t`Status: Disabled`} - -

-
- `; - } - - render(): TemplateResult { - return html`
-
${t`Duo`}
- ${until( - new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsDuoList({}).then((devices) => { - return devices.results.length > 0 - ? this.renderEnabled() - : this.renderDisabled(); - }), - )} -
`; - } -} diff --git a/web/src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts b/web/src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts deleted file mode 100644 index d2e5aa87c..000000000 --- a/web/src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { t } from "@lingui/macro"; - -import { TemplateResult, html } from "lit"; -import { customElement } from "lit/decorators"; -import { until } from "lit/directives/until"; - -import { AuthenticatorsApi } from "@goauthentik/api"; - -import { DEFAULT_CONFIG } from "../../../api/Config"; -import { EVENT_REFRESH } from "../../../constants"; -import { BaseUserSettings } from "../BaseUserSettings"; - -@customElement("ak-user-settings-authenticator-sms") -export class UserSettingsAuthenticatorSMS extends BaseUserSettings { - renderEnabled(): TemplateResult { - return html`
-

- ${t`Status: Enabled`} - -

-
- `; - } - - renderDisabled(): TemplateResult { - return html`
-

- ${t`Status: Disabled`} - -

-
- `; - } - - render(): TemplateResult { - return html`
-
${t`SMS`}
- ${until( - new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsSmsList({}).then((devices) => { - return devices.results.length > 0 - ? this.renderEnabled() - : this.renderDisabled(); - }), - )} -
`; - } -} diff --git a/web/src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts b/web/src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts deleted file mode 100644 index dfadb2ab6..000000000 --- a/web/src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { t } from "@lingui/macro"; - -import { CSSResult, TemplateResult, html } from "lit"; -import { customElement } from "lit/decorators"; -import { until } from "lit/directives/until"; - -import { AuthenticatorsApi } from "@goauthentik/api"; - -import { DEFAULT_CONFIG } from "../../../api/Config"; -import { EVENT_REFRESH } from "../../../constants"; -import { STATIC_TOKEN_STYLE } from "../../../flows/stages/authenticator_static/AuthenticatorStaticStage"; -import { BaseUserSettings } from "../BaseUserSettings"; - -@customElement("ak-user-settings-authenticator-static") -export class UserSettingsAuthenticatorStatic extends BaseUserSettings { - static get styles(): CSSResult[] { - return super.styles.concat(STATIC_TOKEN_STYLE); - } - - renderEnabled(): TemplateResult { - return html`
-

- ${t`Status: Enabled`} - -

-
    - ${until( - new AuthenticatorsApi(DEFAULT_CONFIG) - .authenticatorsStaticList({}) - .then((devices) => { - if (devices.results.length < 1) { - return; - } - return devices.results[0].tokenSet?.map((token) => { - return html`
  • ${token.token}
  • `; - }); - }), - )} -
-
- `; - } - - renderDisabled(): TemplateResult { - return html`
-

- ${t`Status: Disabled`} - -

-
- `; - } - - render(): TemplateResult { - return html`
-
${t`Static tokens`}
- ${until( - new AuthenticatorsApi(DEFAULT_CONFIG) - .authenticatorsStaticList({}) - .then((devices) => { - return devices.results.length > 0 - ? this.renderEnabled() - : this.renderDisabled(); - }), - )} -
`; - } -} diff --git a/web/src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts b/web/src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts deleted file mode 100644 index 3a82ed3e3..000000000 --- a/web/src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { t } from "@lingui/macro"; - -import { TemplateResult, html } from "lit"; -import { customElement } from "lit/decorators"; -import { until } from "lit/directives/until"; - -import { AuthenticatorsApi } from "@goauthentik/api"; - -import { DEFAULT_CONFIG } from "../../../api/Config"; -import { EVENT_REFRESH } from "../../../constants"; -import { BaseUserSettings } from "../BaseUserSettings"; - -@customElement("ak-user-settings-authenticator-totp") -export class UserSettingsAuthenticatorTOTP extends BaseUserSettings { - renderEnabled(): TemplateResult { - return html`
-

- ${t`Status: Enabled`} - -

-
- `; - } - - renderDisabled(): TemplateResult { - return html`
-

- ${t`Status: Disabled`} - -

-
- `; - } - - render(): TemplateResult { - return html`
-
${t`Time-based One-Time Passwords`}
- ${until( - new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpList({}).then((devices) => { - return devices.results.length > 0 - ? this.renderEnabled() - : this.renderDisabled(); - }), - )} -
`; - } -} diff --git a/web/src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts b/web/src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts deleted file mode 100644 index 507913e5e..000000000 --- a/web/src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { t } from "@lingui/macro"; - -import { CSSResult, TemplateResult, html } from "lit"; -import { customElement } from "lit/decorators"; -import { ifDefined } from "lit/directives/if-defined"; -import { until } from "lit/directives/until"; - -import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css"; - -import { AuthenticatorsApi, WebAuthnDevice } from "@goauthentik/api"; - -import { DEFAULT_CONFIG } from "../../../api/Config"; -import { EVENT_REFRESH } from "../../../constants"; -import "../../../elements/buttons/ModalButton"; -import "../../../elements/buttons/SpinnerButton"; -import "../../../elements/forms/DeleteForm"; -import "../../../elements/forms/Form"; -import "../../../elements/forms/HorizontalFormElement"; -import "../../../elements/forms/ModalForm"; -import { BaseUserSettings } from "../BaseUserSettings"; - -@customElement("ak-user-settings-authenticator-webauthn") -export class UserSettingsAuthenticatorWebAuthn extends BaseUserSettings { - static get styles(): CSSResult[] { - return super.styles.concat(PFDataList); - } - - renderDelete(device: WebAuthnDevice): TemplateResult { - return html` { - return new AuthenticatorsApi(DEFAULT_CONFIG) - .authenticatorsWebauthnDestroy({ - id: device.pk || 0, - }) - .then(() => { - this.dispatchEvent( - new CustomEvent(EVENT_REFRESH, { - bubbles: true, - composed: true, - }), - ); - }); - }} - > - - `; - } - - renderUpdate(device: WebAuthnDevice): TemplateResult { - return html` - ${t`Update`} - ${t`Update`} - { - return new AuthenticatorsApi(DEFAULT_CONFIG) - .authenticatorsWebauthnUpdate({ - id: device.pk || 0, - webAuthnDeviceRequest: data as WebAuthnDevice, - }) - .then(() => { - this.requestUpdate(); - }); - }} - > -
- - - -
-
- -
`; - } - - render(): TemplateResult { - return html`
-
${t`WebAuthn Devices`}
-
-
    - ${until( - new AuthenticatorsApi(DEFAULT_CONFIG) - .authenticatorsWebauthnList({}) - .then((devices) => { - return devices.results.map((device) => { - return html`
  • -
    -
    -
    - ${device.name || t`-`} -
    -
    - ${t`Created ${device.createdOn?.toLocaleString()}`} -
    -
    - ${this.renderUpdate(device)} - ${this.renderDelete(device)} -
    -
    -
    -
  • `; - }); - }), - )} -
-
- -
`; - } -}