web: re-organise frontend and cleanup common code (#3572)

* fix repo in api client

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: re-organise files to match their interface

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* core: include version in script tags

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* cleanup maybe broken

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* revert rename

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: get rid of Client.ts

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* move more to common

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* more moving

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* format

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* unfuck files that vscode fucked, thanks

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* move more

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* finish moving (maybe)

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* ok more moving

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* fix more stuff that vs code destroyed

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* get rid "web" prefix for virtual package

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* fix locales

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* use custom base element

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* fix css file

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* don't run autoDetectLanguage when importing locale

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* fix circular dependencies

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: fix build

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L
2022-09-15 00:05:21 +02:00
committed by GitHub
parent 369440652c
commit 4a91a7d2e2
291 changed files with 2062 additions and 1921 deletions

View File

@ -0,0 +1,204 @@
import "@goauthentik/admin/groups/GroupForm";
import "@goauthentik/admin/policies/PolicyBindingForm";
import "@goauthentik/admin/policies/PolicyWizard";
import "@goauthentik/admin/users/UserForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config";
import { PFColor } from "@goauthentik/elements/Label";
import { PFSize } from "@goauthentik/elements/Spinner";
import "@goauthentik/elements/Tabs";
import "@goauthentik/elements/forms/DeleteBulkForm";
import "@goauthentik/elements/forms/ModalForm";
import "@goauthentik/elements/forms/ProxyForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { PoliciesApi, PolicyBinding } from "@goauthentik/api";
@customElement("ak-bound-policies-list")
export class BoundPoliciesList extends Table<PolicyBinding> {
@property()
target?: string;
@property({ type: Boolean })
policyOnly = false;
checkbox = true;
async apiEndpoint(page: number): Promise<PaginatedResponse<PolicyBinding>> {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
target: this.target || "",
ordering: "order",
page: page,
pageSize: (await uiConfig()).pagination.perPage,
});
}
columns(): TableColumn[] {
return [
new TableColumn(t`Policy / User / Group`),
new TableColumn(t`Enabled`, "enabled"),
new TableColumn(t`Order`, "order"),
new TableColumn(t`Timeout`, "timeout"),
new TableColumn(t`Actions`),
];
}
getPolicyUserGroupRowLabel(item: PolicyBinding): string {
if (item.policy) {
return t`Policy ${item.policyObj?.name}`;
} else if (item.group) {
return t`Group ${item.groupObj?.name}`;
} else if (item.user) {
return t`User ${item.userObj?.name}`;
} else {
return t`-`;
}
}
getPolicyUserGroupRow(item: PolicyBinding): TemplateResult {
const label = this.getPolicyUserGroupRowLabel(item);
if (item.user) {
return html` <a href=${`#/identity/users/${item.user}`}> ${label} </a> `;
}
if (item.group) {
return html` <a href=${`#/identity/groups/${item.group}`}> ${label} </a> `;
}
return html`${label}`;
}
getObjectEditButton(item: PolicyBinding): TemplateResult {
if (item.policy) {
return html`<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.policyObj?.name}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.policyObj?.pk,
}}
type=${ifDefined(item.policyObj?.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit Policy`}</button>
</ak-forms-modal>`;
} else if (item.group) {
return html`<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Group`} </span>
<ak-group-form slot="form" .instancePk=${item.groupObj?.pk}> </ak-group-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit Group`}</button>
</ak-forms-modal>`;
} else if (item.user) {
return html`<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update User`} </span>
<ak-user-form slot="form" .instancePk=${item.userObj?.pk}> </ak-user-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit User`}</button>
</ak-forms-modal>`;
} else {
return html``;
}
}
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${t`Policy binding(s)`}
.objects=${this.selectedElements}
.metadata=${(item: PolicyBinding) => {
return [
{ key: t`Order`, value: item.order.toString() },
{ key: t`Policy / User / Group`, value: this.getPolicyUserGroupRowLabel(item) },
];
}}
.usedBy=${(item: PolicyBinding) => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUsedByList({
policyBindingUuid: item.pk,
});
}}
.delete=${(item: PolicyBinding) => {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsDestroy({
policyBindingUuid: item.pk,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete-bulk>`;
}
row(item: PolicyBinding): TemplateResult[] {
return [
html`${this.getPolicyUserGroupRow(item)}`,
html` <ak-label color=${item.enabled ? PFColor.Green : PFColor.Orange}>
${item.enabled ? t`Yes` : t`No`}
</ak-label>`,
html`${item.order}`,
html`${item.timeout}`,
html` ${this.getObjectEditButton(item)}
<ak-forms-modal size=${PFSize.Medium}>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Binding`} </span>
<ak-policy-binding-form
slot="form"
.instancePk=${item.pk}
targetPk=${ifDefined(this.target)}
?policyOnly=${this.policyOnly}
>
</ak-policy-binding-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Edit Binding`}
</button>
</ak-forms-modal>`,
];
}
renderEmpty(): TemplateResult {
return super.renderEmpty(html`<ak-empty-state
header=${t`No Policies bound.`}
icon="pf-icon-module"
>
<div slot="body">${t`No policies are currently bound to this object.`}</div>
<div slot="primary">
<ak-forms-modal size=${PFSize.Medium}>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Binding`} </span>
<ak-policy-binding-form
slot="form"
targetPk=${ifDefined(this.target)}
?policyOnly=${this.policyOnly}
>
</ak-policy-binding-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create Binding`}
</button>
</ak-forms-modal>
</div>
</ak-empty-state>`);
}
renderToolbar(): TemplateResult {
return html`<ak-forms-modal size=${PFSize.Medium}>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Binding`} </span>
<ak-policy-binding-form
slot="form"
targetPk=${ifDefined(this.target)}
?policyOnly=${this.policyOnly}
>
</ak-policy-binding-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${t`Create Binding`}
</button>
</ak-forms-modal>
<ak-policy-wizard createText=${t`Create Policy`}></ak-policy-wizard> `;
}
}

View File

@ -0,0 +1,325 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first, groupBy } from "@goauthentik/common/utils";
import "@goauthentik/elements/SearchSelect";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { UserOption } from "@goauthentik/elements/user/utils";
import { t } from "@lingui/macro";
import { CSSResult, css } from "lit";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";
import {
CoreApi,
CoreGroupsListRequest,
CoreUsersListRequest,
Group,
PoliciesApi,
Policy,
PolicyBinding,
User,
} from "@goauthentik/api";
enum target {
policy,
group,
user,
}
@customElement("ak-policy-binding-form")
export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
loadInstance(pk: string): Promise<PolicyBinding> {
return new PoliciesApi(DEFAULT_CONFIG)
.policiesBindingsRetrieve({
policyBindingUuid: pk,
})
.then((binding) => {
if (binding?.policyObj) {
this.policyGroupUser = target.policy;
}
if (binding?.groupObj) {
this.policyGroupUser = target.group;
}
if (binding?.userObj) {
this.policyGroupUser = target.user;
}
return binding;
});
}
@property()
targetPk?: string;
@property({ type: Number })
policyGroupUser: target = target.policy;
@property({ type: Boolean })
policyOnly = false;
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated binding.`;
} else {
return t`Successfully created binding.`;
}
}
static get styles(): CSSResult[] {
return super.styles.concat(
PFToggleGroup,
PFContent,
css`
.pf-c-toggle-group {
justify-content: center;
}
`,
);
}
send = (data: PolicyBinding): Promise<PolicyBinding> => {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUpdate({
policyBindingUuid: this.instance.pk || "",
policyBindingRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsCreate({
policyBindingRequest: data,
});
}
};
groupPolicies(policies: Policy[]): TemplateResult {
return html`
${groupBy<Policy>(policies, (p) => p.verboseName || "").map(([group, policies]) => {
return html`<optgroup label=${group}>
${policies.map((p) => {
const selected = this.instance?.policy === p.pk;
return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>
${p.name}
</option>`;
})}
</optgroup>`;
})}
`;
}
getOrder(): Promise<number> {
if (this.instance) {
return Promise.resolve(this.instance.order);
}
return new PoliciesApi(DEFAULT_CONFIG)
.policiesBindingsList({
target: this.targetPk || "",
})
.then((bindings) => {
const orders = bindings.results.map((binding) => binding.order);
if (orders.length < 1) {
return 0;
}
return Math.max(...orders) + 1;
});
}
renderModeSelector(): TemplateResult {
return html` <div class="pf-c-toggle-group__item">
<button
class="pf-c-toggle-group__button ${this.policyGroupUser === target.policy
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.policyGroupUser = target.policy;
}}
>
<span class="pf-c-toggle-group__text">${t`Policy`}</span>
</button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
<div class="pf-c-toggle-group__item">
<button
class="pf-c-toggle-group__button ${this.policyGroupUser === target.group
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.policyGroupUser = target.group;
}}
>
<span class="pf-c-toggle-group__text">${t`Group`}</span>
</button>
</div>
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
<div class="pf-c-toggle-group__item">
<button
class="pf-c-toggle-group__button ${this.policyGroupUser === target.user
? "pf-m-selected"
: ""}"
type="button"
@click=${() => {
this.policyGroupUser = target.user;
}}
>
<span class="pf-c-toggle-group__text">${t`User`}</span>
</button>
</div>`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<div class="pf-c-card pf-m-selectable pf-m-selected">
<div class="pf-c-card__body">
<div class="pf-c-toggle-group">${this.renderModeSelector()}</div>
</div>
<div class="pf-c-card__footer">
<ak-form-element-horizontal
label=${t`Policy`}
name="policy"
?hidden=${this.policyGroupUser !== target.policy}
>
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.policy === undefined}>
---------
</option>
${until(
new PoliciesApi(DEFAULT_CONFIG)
.policiesAllList({
ordering: "name",
})
.then((policies) => {
return this.groupPolicies(policies.results);
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Group`}
name="group"
?hidden=${this.policyGroupUser !== target.group}
>
<!-- @ts-ignore -->
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {
const args: CoreGroupsListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(
args,
);
return groups.results;
}}
.renderElement=${(group: Group): string => {
return group.name;
}}
.value=${(group: Group | undefined): string | undefined => {
return group ? group.pk : undefined;
}}
.selected=${(group: Group): boolean => {
return group.pk === this.instance?.group;
}}
?blankable=${true}
>
</ak-search-select>
${this.policyOnly
? html`<p class="pf-c-form__helper-text">
${t`Group mappings can only be checked if a user is already logged in when trying to access this source.`}
</p>`
: html``}
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`User`}
name="user"
?hidden=${this.policyGroupUser !== target.user}
>
<!-- @ts-ignore -->
<ak-search-select
.fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = {
ordering: "username",
};
if (query !== undefined) {
args.search = query;
}
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList(args);
return users.results;
}}
.renderElement=${(user: User): string => {
return UserOption(user);
}}
.value=${(user: User | undefined): number | undefined => {
return user ? user.pk : undefined;
}}
.selected=${(user: User): boolean => {
return user.pk === this.instance?.user;
}}
?blankable=${true}
>
</ak-search-select>
${this.policyOnly
? html`<p class="pf-c-form__helper-text">
${t`User mappings can only be checked if a user is already logged in when trying to access this source.`}
</p>`
: html``}
</ak-form-element-horizontal>
</div>
</div>
<input
required
name="target"
type="hidden"
value=${ifDefined(this.instance?.target || this.targetPk)}
/>
<ak-form-element-horizontal name="enabled">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.enabled, true)}
/>
<label class="pf-c-check__label"> ${t`Enabled`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="negate">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.negate, false)}
/>
<label class="pf-c-check__label"> ${t`Negate result`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`Negates the outcome of the binding. Messages are unaffected.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Order`} ?required=${true} name="order">
<!-- @ts-ignore -->
<input
type="number"
value="${until(this.getOrder())}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Timeout`} ?required=${true} name="timeout">
<input
type="number"
value="${first(this.instance?.timeout, 30)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -0,0 +1,154 @@
import "@goauthentik/admin/policies/PolicyTestForm";
import "@goauthentik/admin/policies/PolicyWizard";
import "@goauthentik/admin/policies/dummy/DummyPolicyForm";
import "@goauthentik/admin/policies/event_matcher/EventMatcherPolicyForm";
import "@goauthentik/admin/policies/expiry/ExpiryPolicyForm";
import "@goauthentik/admin/policies/expression/ExpressionPolicyForm";
import "@goauthentik/admin/policies/hibp/HaveIBeenPwnedPolicyForm";
import "@goauthentik/admin/policies/password/PasswordPolicyForm";
import "@goauthentik/admin/policies/reputation/ReputationPolicyForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config";
import { groupBy } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/ConfirmationForm";
import "@goauthentik/elements/forms/DeleteBulkForm";
import "@goauthentik/elements/forms/ModalForm";
import "@goauthentik/elements/forms/ProxyForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { TableColumn } from "@goauthentik/elements/table/Table";
import { TablePage } from "@goauthentik/elements/table/TablePage";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { PoliciesApi, Policy } from "@goauthentik/api";
@customElement("ak-policy-list")
export class PolicyListPage extends TablePage<Policy> {
searchEnabled(): boolean {
return true;
}
pageTitle(): string {
return t`Policies`;
}
pageDescription(): string {
return t`Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages.`;
}
pageIcon(): string {
return "pf-icon pf-icon-infrastructure";
}
checkbox = true;
@property()
order = "name";
async apiEndpoint(page: number): Promise<PaginatedResponse<Policy>> {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllList({
ordering: this.order,
page: page,
pageSize: (await uiConfig()).pagination.perPage,
search: this.search || "",
});
}
columns(): TableColumn[] {
return [
new TableColumn(t`Name`, "name"),
new TableColumn(t`Type`),
new TableColumn(t`Actions`),
];
}
groupBy(items: Policy[]): [string, Policy[]][] {
return groupBy(items, (policy) => policy.verboseNamePlural);
}
row(item: Policy): TemplateResult[] {
return [
html`<div>
<div>${item.name}</div>
${(item.boundTo || 0) > 0
? html`<i class="pf-icon pf-icon-ok"></i>
<small>${t`Assigned to ${item.boundTo} object(s).`}</small>`
: html`<i class="pf-icon pf-icon-warning-triangle"></i>
<small>${t`Warning: Policy is not assigned.`}</small>`}
</div>`,
html`${item.verboseName}`,
html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
<ak-proxy-form
slot="form"
.args=${{
instancePk: item.pk,
}}
type=${ifDefined(item.component)}
>
</ak-proxy-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<i class="fas fa-pencil-alt" aria-hidden="true"></i>
</button>
</ak-forms-modal>
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
<span slot="submit"> ${t`Test`} </span>
<span slot="header"> ${t`Test Policy`} </span>
<ak-policy-test-form slot="form" .policy=${item}> </ak-policy-test-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<i class="fas fa-vial" aria-hidden="true"></i>
</button>
</ak-forms-modal>`,
];
}
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${t`Policy / Policies`}
.objects=${this.selectedElements}
.usedBy=${(item: Policy) => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllUsedByList({
policyUuid: item.pk,
});
}}
.delete=${(item: Policy) => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllDestroy({
policyUuid: item.pk,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete-bulk>`;
}
renderObjectCreate(): TemplateResult {
return html`<ak-policy-wizard> </ak-policy-wizard>`;
}
renderToolbar(): TemplateResult {
return html` ${super.renderToolbar()}
<ak-forms-confirm
successMessage=${t`Successfully cleared policy cache`}
errorMessage=${t`Failed to delete policy cache`}
action=${t`Clear cache`}
.onConfirm=${() => {
return new PoliciesApi(DEFAULT_CONFIG).policiesAllCacheClearCreate();
}}
>
<span slot="header"> ${t`Clear Policy cache`} </span>
<p slot="body">
${t`Are you sure you want to clear the policy cache?
This will cause all policies to be re-evaluated on their next usage.`}
</p>
<button slot="trigger" class="pf-c-button pf-m-secondary" type="button">
${t`Clear cache`}
</button>
<div slot="modal"></div>
</ak-forms-confirm>`;
}
}

View File

@ -0,0 +1,154 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror";
import { PFColor } from "@goauthentik/elements/Label";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { UserOption } from "@goauthentik/elements/user/utils";
import YAML from "yaml";
import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import {
CoreApi,
PoliciesApi,
Policy,
PolicyTestRequest,
PolicyTestResult,
} from "@goauthentik/api";
@customElement("ak-policy-test-form")
export class PolicyTestForm extends Form<PolicyTestRequest> {
@property({ attribute: false })
policy?: Policy;
@property({ attribute: false })
result?: PolicyTestResult;
@property({ attribute: false })
request?: PolicyTestRequest;
getSuccessMessage(): string {
return t`Successfully sent test-request.`;
}
send = (data: PolicyTestRequest): Promise<PolicyTestResult> => {
this.request = data;
return new PoliciesApi(DEFAULT_CONFIG)
.policiesAllTestCreate({
policyUuid: this.policy?.pk || "",
policyTestRequest: data,
})
.then((result) => (this.result = result));
};
static get styles(): CSSResult[] {
return super.styles.concat(PFDescriptionList);
}
renderResult(): TemplateResult {
return html`
<ak-form-element-horizontal label=${t`Passing`}>
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<span class="pf-c-form__label-text">
<ak-label color=${this.result?.passing ? PFColor.Green : PFColor.Red}>
${this.result?.passing ? t`Yes` : t`No`}
</ak-label>
</span>
</div>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Messages`}>
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<ul>
${(this.result?.messages || []).length > 0
? this.result?.messages?.map((m) => {
return html`<li>
<span class="pf-c-form__label-text">${m}</span>
</li>`;
})
: html`<li>
<span class="pf-c-form__label-text">-</span>
</li>`}
</ul>
</div>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Log messages`}>
<div class="pf-c-form__group-label">
<div class="c-form__horizontal-group">
<dl class="pf-c-description-list pf-m-horizontal">
${(this.result?.logMessages || []).length > 0
? this.result?.logMessages?.map((m) => {
return html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${m.log_level}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${m.event}
</div>
</dd>
</div>`;
})
: html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`No log messages.`}</span
>
</dt>
</div>`}
</dl>
</div>
</div>
</ak-form-element-horizontal>
`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`User`} ?required=${true} name="user">
<select class="pf-c-form-control">
${until(
new CoreApi(DEFAULT_CONFIG)
.coreUsersList({
ordering: "username",
})
.then((users) => {
return users.results.map((user) => {
return html`<option
?selected=${this.request?.user.toString() ===
user.pk.toString()}
value=${user.pk}
>
${UserOption(user)}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Context`} name="context">
<ak-codemirror mode="yaml" value=${YAML.stringify(first(this.request?.context, {}))}
>>
</ak-codemirror>
<p class="pf-c-form__helper-text">
${t`Set custom attributes using YAML or JSON.`}
</p>
</ak-form-element-horizontal>
${this.result ? this.renderResult() : html``}
</form>`;
}
}

View File

@ -0,0 +1,107 @@
import "@goauthentik/admin/policies/dummy/DummyPolicyForm";
import "@goauthentik/admin/policies/event_matcher/EventMatcherPolicyForm";
import "@goauthentik/admin/policies/expiry/ExpiryPolicyForm";
import "@goauthentik/admin/policies/expression/ExpressionPolicyForm";
import "@goauthentik/admin/policies/hibp/HaveIBeenPwnedPolicyForm";
import "@goauthentik/admin/policies/password/PasswordPolicyForm";
import "@goauthentik/admin/policies/reputation/ReputationPolicyForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/forms/ProxyForm";
import "@goauthentik/elements/wizard/FormWizardPage";
import "@goauthentik/elements/wizard/Wizard";
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
import { t } from "@lingui/macro";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { CSSResult, TemplateResult, html } from "lit";
import { property } from "lit/decorators.js";
import AKGlobal from "@goauthentik/common/styles/authentik.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { PoliciesApi, TypeCreate } from "@goauthentik/api";
@customElement("ak-policy-wizard-initial")
export class InitialPolicyWizardPage extends WizardPage {
@property({ attribute: false })
policyTypes: TypeCreate[] = [];
static get styles(): CSSResult[] {
return [PFBase, PFForm, PFButton, AKGlobal, PFRadio];
}
sidebarLabel = () => t`Select type`;
render(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
${this.policyTypes.map((type) => {
return html`<div class="pf-c-radio">
<input
class="pf-c-radio__input"
type="radio"
name="type"
id=${`${type.component}-${type.modelName}`}
@change=${() => {
this.host.steps = [
"initial",
`type-${type.component}-${type.modelName}`,
];
this.host.isValid = true;
}}
/>
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
>${type.name}</label
>
<span class="pf-c-radio__description">${type.description}</span>
</div>`;
})}
</form>`;
}
}
@customElement("ak-policy-wizard")
export class PolicyWizard extends AKElement {
static get styles(): CSSResult[] {
return [PFBase, PFButton, AKGlobal, PFRadio];
}
@property()
createText = t`Create`;
@property({ attribute: false })
policyTypes: TypeCreate[] = [];
firstUpdated(): void {
new PoliciesApi(DEFAULT_CONFIG).policiesAllTypesList().then((types) => {
this.policyTypes = types;
});
}
render(): TemplateResult {
return html`
<ak-wizard
.steps=${["initial"]}
header=${t`New policy`}
description=${t`Create a new policy.`}
>
<ak-policy-wizard-initial slot="initial" .policyTypes=${this.policyTypes}>
</ak-policy-wizard-initial>
${this.policyTypes.map((type) => {
return html`
<ak-wizard-page-form
slot=${`type-${type.component}-${type.modelName}`}
.sidebarLabel=${() => t`Create ${type.name}`}
>
<ak-proxy-form type=${type.component}></ak-proxy-form>
</ak-wizard-page-form>
`;
})}
<button slot="trigger" class="pf-c-button pf-m-primary">${this.createText}</button>
</ak-wizard>
`;
}
}

View File

@ -0,0 +1,114 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { DummyPolicy, PoliciesApi } from "@goauthentik/api";
@customElement("ak-policy-dummy-form")
export class DummyPolicyForm extends ModelForm<DummyPolicy, string> {
loadInstance(pk: string): Promise<DummyPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesDummyRetrieve({
policyUuid: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated policy.`;
} else {
return t`Successfully created policy.`;
}
}
send = (data: DummyPolicy): Promise<DummyPolicy> => {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesDummyUpdate({
policyUuid: this.instance.pk || "",
dummyPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesDummyCreate({
dummyPolicyRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<div class="form-help-text">
${t`A policy used for testing. Always returns the same result as specified below after waiting a random duration.`}
</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-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="result">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.result, false)}
/>
<label class="pf-c-check__label"> ${t`Pass policy?`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Wait (min)`}
?required=${true}
name="waitMin"
>
<input
type="number"
value="${first(this.instance?.waitMin, 1)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`The policy takes a random time to execute. This controls the minimum time it will take.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Wait (max)`}
?required=${true}
name="waitMax"
>
<input
type="number"
value="${first(this.instance?.waitMax, 5)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -0,0 +1,137 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { AdminApi, EventMatcherPolicy, EventsApi, PoliciesApi } from "@goauthentik/api";
@customElement("ak-policy-event-matcher-form")
export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string> {
loadInstance(pk: string): Promise<EventMatcherPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesEventMatcherRetrieve({
policyUuid: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated policy.`;
} else {
return t`Successfully created policy.`;
}
}
send = (data: EventMatcherPolicy): Promise<EventMatcherPolicy> => {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesEventMatcherUpdate({
policyUuid: this.instance.pk || "",
eventMatcherPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesEventMatcherCreate({
eventMatcherPolicyRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<div class="form-help-text">
${t`Matches an event against a set of criteria. If any of the configured values match, the policy passes.`}
</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-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`Action`} name="action">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.action === undefined}>
---------
</option>
${until(
new EventsApi(DEFAULT_CONFIG)
.eventsEventsActionsList()
.then((actions) => {
return actions.map((action) => {
return html`<option
value=${action.component}
?selected=${this.instance?.action ===
action.component}
>
${action.name}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">
${t`Match created events with this action type. When left empty, all action types will be matched.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Client IP`} name="clientIp">
<input
type="text"
value="${ifDefined(this.instance?.clientIp || "")}"
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">
${t`Matches Event's Client IP (strict matching, for network matching use an Expression Policy.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`App`} name="app">
<select class="pf-c-form-control">
<option value="" ?selected=${this.instance?.app === undefined}>
---------
</option>
${until(
new AdminApi(DEFAULT_CONFIG).adminAppsList().then((apps) => {
return apps.map((app) => {
return html`<option
value=${app.name}
?selected=${this.instance?.app === app.name}
>
${app.label}
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">
${t`Match events created by selected application. When left empty, all applications are matched.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -0,0 +1,101 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { PasswordExpiryPolicy, PoliciesApi } from "@goauthentik/api";
@customElement("ak-policy-password-expiry-form")
export class PasswordExpiryPolicyForm extends ModelForm<PasswordExpiryPolicy, string> {
loadInstance(pk: string): Promise<PasswordExpiryPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordExpiryRetrieve({
policyUuid: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated policy.`;
} else {
return t`Successfully created policy.`;
}
}
send = (data: PasswordExpiryPolicy): Promise<PasswordExpiryPolicy> => {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordExpiryUpdate({
policyUuid: this.instance.pk || "",
passwordExpiryPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordExpiryCreate({
passwordExpiryPolicyRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<div class="form-help-text">
${t`Checks if the request's user's password has been changed in the last x days, and denys based on settings.`}
</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-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Maximum age (in days)`}
?required=${true}
name="days"
>
<input
type="number"
value="${ifDefined(this.instance?.days || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="denyOnly">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.denyOnly, false)}
/>
<label class="pf-c-check__label">
${t`Only fail the policy, don't invalidate user's password.`}
</label>
</div>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -0,0 +1,98 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { ExpressionPolicy, PoliciesApi } from "@goauthentik/api";
@customElement("ak-policy-expression-form")
export class ExpressionPolicyForm extends ModelForm<ExpressionPolicy, string> {
loadInstance(pk: string): Promise<ExpressionPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionRetrieve({
policyUuid: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated policy.`;
} else {
return t`Successfully created policy.`;
}
}
send = (data: ExpressionPolicy): Promise<ExpressionPolicy> => {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionUpdate({
policyUuid: this.instance.pk || "",
expressionPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionCreate({
expressionPolicyRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<div class="form-help-text">
${t`Executes the python snippet to determine whether to allow or deny a request.`}
</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-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Expression`}
?required=${true}
name="expression"
>
<ak-codemirror
mode="python"
value="${ifDefined(this.instance?.expression)}"
>
</ak-codemirror>
<p class="pf-c-form__helper-text">
${t`Expression using Python.`}
<a
target="_blank"
href="https://goauthentik.io/docs/policies/expression?utm_source=authentik"
>
${t`See documentation for a list of all variables.`}
</a>
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -0,0 +1,108 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { HaveIBeenPwendPolicy, PoliciesApi } from "@goauthentik/api";
@customElement("ak-policy-hibp-form")
export class HaveIBeenPwnedPolicyForm extends ModelForm<HaveIBeenPwendPolicy, string> {
loadInstance(pk: string): Promise<HaveIBeenPwendPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesHaveibeenpwnedRetrieve({
policyUuid: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated policy.`;
} else {
return t`Successfully created policy.`;
}
}
send = (data: HaveIBeenPwendPolicy): Promise<HaveIBeenPwendPolicy> => {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesHaveibeenpwnedUpdate({
policyUuid: this.instance.pk || "",
haveIBeenPwendPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesHaveibeenpwnedCreate({
haveIBeenPwendPolicyRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<div class="form-help-text">
${t`Checks a value from the policy request against the Have I been Pwned API, and denys the request based upon that.
Note that only a part of the hash of the password is sent, the full comparison is done clientside.`}
</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-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Password field`}
?required=${true}
name="passwordField"
>
<input
type="text"
value="${ifDefined(this.instance?.passwordField || "password")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Field key to check, field keys defined in Prompt stages are available.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Allowed count`}
?required=${true}
name="allowedCount"
>
<input
type="number"
value="${first(this.instance?.allowedCount, 0)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Allow up to N occurrences in the HIBP database.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -0,0 +1,188 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { PasswordPolicy, PoliciesApi } from "@goauthentik/api";
@customElement("ak-policy-password-form")
export class PasswordPolicyForm extends ModelForm<PasswordPolicy, string> {
loadInstance(pk: string): Promise<PasswordPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordRetrieve({
policyUuid: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated policy.`;
} else {
return t`Successfully created policy.`;
}
}
send = (data: PasswordPolicy): Promise<PasswordPolicy> => {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordUpdate({
policyUuid: this.instance.pk || "",
passwordPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordCreate({
passwordPolicyRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<div class="form-help-text">
${t`Checks the value from the policy request against several rules, mostly used to ensure password strength.`}
</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-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Password field`}
?required=${true}
name="passwordField"
>
<input
type="text"
value="${ifDefined(this.instance?.passwordField || "password")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Field key to check, field keys defined in Prompt stages are available.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum length`}
?required=${true}
name="lengthMin"
>
<input
type="number"
value="${first(this.instance?.lengthMin, 10)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum amount of Uppercase Characters`}
?required=${true}
name="amountUppercase"
>
<input
type="number"
value="${first(this.instance?.amountUppercase, 2)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum amount of Lowercase Characters`}
?required=${true}
name="amountLowercase"
>
<input
type="number"
value="${first(this.instance?.amountLowercase, 2)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum amount of Digits`}
?required=${true}
name="amountDigits"
>
<input
type="number"
value="${first(this.instance?.amountDigits, 2)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Minimum amount of Symbols Characters`}
?required=${true}
name="amountSymbols"
>
<input
type="number"
value="${first(this.instance?.amountSymbols, 2)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Error message`}
?required=${true}
name="errorMessage"
>
<input
type="text"
value="${ifDefined(this.instance?.errorMessage)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${t`Advanced settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Symbol charset`}
?required=${true}
name="symbolCharset"
>
<input
type="text"
value="${ifDefined(
this.instance?.symbolCharset ||
"!\\\"#$%&'()*+,-./:;<=>?@[]^_`{|}~ ",
)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Characters which are considered as symbols.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -0,0 +1,90 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/elements/buttons/ModalButton";
import "@goauthentik/elements/buttons/SpinnerButton";
import "@goauthentik/elements/forms/DeleteBulkForm";
import "@goauthentik/elements/forms/ModalForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { TableColumn } from "@goauthentik/elements/table/Table";
import { TablePage } from "@goauthentik/elements/table/TablePage";
import getUnicodeFlagIcon from "country-flag-icons/unicode";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { PoliciesApi, Reputation } from "@goauthentik/api";
@customElement("ak-policy-reputation-list")
export class ReputationListPage extends TablePage<Reputation> {
searchEnabled(): boolean {
return true;
}
pageTitle(): string {
return t`Reputation scores`;
}
pageDescription(): string {
return t`Reputation for IP and user identifiers. Scores are decreased for each failed login and increased for each successful login.`;
}
pageIcon(): string {
return "fa fa-ban";
}
@property()
order = "identifier";
checkbox = true;
async apiEndpoint(page: number): Promise<PaginatedResponse<Reputation>> {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationScoresList({
ordering: this.order,
page: page,
pageSize: (await uiConfig()).pagination.perPage,
search: this.search || "",
});
}
columns(): TableColumn[] {
return [
new TableColumn(t`Identifier`, "identifier"),
new TableColumn(t`IP`, "ip"),
new TableColumn(t`Score`, "score"),
new TableColumn(t`Updated`, "updated"),
];
}
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${t`Reputation`}
.objects=${this.selectedElements}
.usedBy=${(item: Reputation) => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationScoresUsedByList({
reputationUuid: item.pk || "",
});
}}
.delete=${(item: Reputation) => {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationScoresDestroy({
reputationUuid: item.pk || "",
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete-bulk>`;
}
row(item: Reputation): TemplateResult[] {
return [
html`${item.identifier}`,
html`${item.ipGeoData?.country
? html` ${getUnicodeFlagIcon(item.ipGeoData.country)} `
: html``}
${item.ip}`,
html`${item.score}`,
html`${item.updated.toLocaleString()}`,
];
}
}

View File

@ -0,0 +1,118 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { PoliciesApi, ReputationPolicy } from "@goauthentik/api";
@customElement("ak-policy-reputation-form")
export class ReputationPolicyForm extends ModelForm<ReputationPolicy, string> {
loadInstance(pk: string): Promise<ReputationPolicy> {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationRetrieve({
policyUuid: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated policy.`;
} else {
return t`Successfully created policy.`;
}
}
send = (data: ReputationPolicy): Promise<ReputationPolicy> => {
if (this.instance) {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationUpdate({
policyUuid: this.instance.pk || "",
reputationPolicyRequest: data,
});
} else {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationCreate({
reputationPolicyRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<div class="form-help-text">
${t`Allows/denys requests based on the users and/or the IPs reputation.`}
</div>
<div class="form-help-text">
${t`Invalid login attempts will decrease the score for the client's IP, and the
username they are attempting to login as, by one.`}
</div>
<div class="form-help-text">
${t`The policy passes when the reputation score is below the threshold, and
doesn't pass when either or both of the selected options are equal or above the
threshold.`}
</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-element-horizontal name="executionLogging">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.executionLogging, false)}
/>
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
</div>
<p class="pf-c-form__helper-text">
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
</p>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Policy-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal name="checkIp">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.checkIp, false)}
/>
<label class="pf-c-check__label"> ${t`Check IP`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="checkUsername">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.checkUsername, false)}
/>
<label class="pf-c-check__label"> ${t`Check Username`} </label>
</div>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Threshold`}
?required=${true}
name="threshold"
>
<input
type="number"
value="${ifDefined(this.instance?.threshold || -5)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}