diff --git a/web/src/interfaces/AdminInterface.ts b/web/src/interfaces/AdminInterface.ts
index fd6e87426..8a25367ae 100644
--- a/web/src/interfaces/AdminInterface.ts
+++ b/web/src/interfaces/AdminInterface.ts
@@ -73,6 +73,12 @@ export class AdminInterface extends Interface {
${t`Policies`}
+
+ ${t`Reputation policy - IPs`}
+
+
+ ${t`Reputation policy - Users`}
+
${t`Property Mappings`}
diff --git a/web/src/locales/en.po b/web/src/locales/en.po
index c0d115e4a..3b6c448e6 100644
--- a/web/src/locales/en.po
+++ b/web/src/locales/en.po
@@ -985,6 +985,8 @@ msgstr "Define how notifications are sent to users, like Email or Webhook."
#: src/pages/outposts/OutpostListPage.ts
#: src/pages/outposts/ServiceConnectionListPage.ts
#: src/pages/policies/PolicyListPage.ts
+#: src/pages/policies/reputation/IPReputationListPage.ts
+#: src/pages/policies/reputation/UserReputationListPage.ts
#: src/pages/property-mappings/PropertyMappingListPage.ts
#: src/pages/providers/ProviderListPage.ts
#: src/pages/sources/SourcesListPage.ts
@@ -1698,6 +1700,15 @@ msgstr "How many attempts a user has before the flow is canceled. To lock the us
msgid "ID"
msgstr "ID"
+#: src/pages/policies/reputation/IPReputationListPage.ts
+msgid "IP"
+msgstr "IP"
+
+#: src/pages/policies/reputation/IPReputationListPage.ts
+#: src/pages/policies/reputation/IPReputationListPage.ts
+msgid "IP Reputation"
+msgstr "IP Reputation"
+
#: src/pages/applications/ApplicationForm.ts
msgid "Icon"
msgstr "Icon"
@@ -1796,6 +1807,10 @@ msgstr "Invalidation"
msgid "Invalidation flow"
msgstr "Invalidation flow"
+#: src/pages/stages/invitation/InvitationListPage.ts
+msgid "Invitation"
+msgstr "Invitation"
+
#: src/interfaces/AdminInterface.ts
#: src/pages/stages/invitation/InvitationListPage.ts
msgid "Invitations"
@@ -2619,7 +2634,6 @@ msgstr "Private key, acquired from https://www.google.com/recaptcha/intro/v3.htm
msgid "Profile URL"
msgstr "Profile URL"
-#: src/pages/stages/invitation/InvitationListPage.ts
#: src/pages/stages/prompt/PromptListPage.ts
msgid "Prompt"
msgstr "Prompt"
@@ -2815,6 +2829,22 @@ msgstr "Reload"
msgid "Remove the user from the current session."
msgstr "Remove the user from the current session."
+#: src/pages/policies/reputation/IPReputationListPage.ts
+msgid "Reputation for IPs. Scores are decreased for each failed login and increased for each successful login."
+msgstr "Reputation for IPs. Scores are decreased for each failed login and increased for each successful login."
+
+#: src/pages/policies/reputation/UserReputationListPage.ts
+msgid "Reputation for usernames. Scores are decreased for each failed login and increased for each successful login."
+msgstr "Reputation for usernames. Scores are decreased for each failed login and increased for each successful login."
+
+#: src/interfaces/AdminInterface.ts
+msgid "Reputation policy - IPs"
+msgstr "Reputation policy - IPs"
+
+#: src/interfaces/AdminInterface.ts
+msgid "Reputation policy - Users"
+msgstr "Reputation policy - Users"
+
#: src/pages/events/EventInfo.ts
#: src/pages/events/EventInfo.ts
msgid "Request"
@@ -2942,6 +2972,11 @@ msgstr "Scope which the client can specify to access these properties."
msgid "Scopes"
msgstr "Scopes"
+#: src/pages/policies/reputation/IPReputationListPage.ts
+#: src/pages/policies/reputation/UserReputationListPage.ts
+msgid "Score"
+msgstr "Score"
+
#: src/elements/table/TableSearch.ts
msgid "Search..."
msgstr "Search..."
@@ -4075,6 +4110,11 @@ msgstr "User Info"
msgid "User Property Mappings"
msgstr "User Property Mappings"
+#: src/pages/policies/reputation/UserReputationListPage.ts
+#: src/pages/policies/reputation/UserReputationListPage.ts
+msgid "User Reputation"
+msgstr "User Reputation"
+
#: src/pages/user-settings/UserSettingsPage.ts
msgid "User Settings"
msgstr "User Settings"
@@ -4131,6 +4171,7 @@ msgid "Userinfo URL"
msgstr "Userinfo URL"
#: src/flows/stages/identification/IdentificationStage.ts
+#: src/pages/policies/reputation/UserReputationListPage.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/user-settings/UserDetailsPage.ts
#: src/pages/users/UserForm.ts
diff --git a/web/src/locales/pseudo-LOCALE.po b/web/src/locales/pseudo-LOCALE.po
index f57d337d2..b7c4710d6 100644
--- a/web/src/locales/pseudo-LOCALE.po
+++ b/web/src/locales/pseudo-LOCALE.po
@@ -990,6 +990,8 @@ msgstr ""
#:
#:
#:
+#:
+#:
msgid "Delete"
msgstr ""
@@ -1690,6 +1692,15 @@ msgstr ""
msgid "ID"
msgstr ""
+#:
+msgid "IP"
+msgstr ""
+
+#:
+#:
+msgid "IP Reputation"
+msgstr ""
+
#:
msgid "Icon"
msgstr ""
@@ -1788,6 +1799,10 @@ msgstr ""
msgid "Invalidation flow"
msgstr ""
+#:
+msgid "Invitation"
+msgstr ""
+
#:
#:
msgid "Invitations"
@@ -2611,7 +2626,6 @@ msgstr ""
msgid "Profile URL"
msgstr ""
-#:
#:
msgid "Prompt"
msgstr ""
@@ -2807,6 +2821,22 @@ msgstr ""
msgid "Remove the user from the current session."
msgstr ""
+#:
+msgid "Reputation for IPs. Scores are decreased for each failed login and increased for each successful login."
+msgstr ""
+
+#:
+msgid "Reputation for usernames. Scores are decreased for each failed login and increased for each successful login."
+msgstr ""
+
+#:
+msgid "Reputation policy - IPs"
+msgstr ""
+
+#:
+msgid "Reputation policy - Users"
+msgstr ""
+
#:
#:
msgid "Request"
@@ -2934,6 +2964,11 @@ msgstr ""
msgid "Scopes"
msgstr ""
+#:
+#:
+msgid "Score"
+msgstr ""
+
#:
msgid "Search..."
msgstr ""
@@ -4063,6 +4098,11 @@ msgstr ""
msgid "User Property Mappings"
msgstr ""
+#:
+#:
+msgid "User Reputation"
+msgstr ""
+
#:
msgid "User Settings"
msgstr ""
@@ -4123,6 +4163,7 @@ msgstr ""
#:
#:
#:
+#:
msgid "Username"
msgstr ""
diff --git a/web/src/pages/policies/reputation/IPReputationListPage.ts b/web/src/pages/policies/reputation/IPReputationListPage.ts
new file mode 100644
index 000000000..b621f225c
--- /dev/null
+++ b/web/src/pages/policies/reputation/IPReputationListPage.ts
@@ -0,0 +1,70 @@
+import { t } from "@lingui/macro";
+import { customElement, html, property, TemplateResult } from "lit-element";
+import { AKResponse } from "../../../api/Client";
+import { TablePage } from "../../../elements/table/TablePage";
+
+import "../../../elements/buttons/ModalButton";
+import "../../../elements/buttons/SpinnerButton";
+import "../../../elements/forms/DeleteForm";
+import "../../../elements/forms/ModalForm";
+import { TableColumn } from "../../../elements/table/Table";
+import { PAGE_SIZE } from "../../../constants";
+import { IPReputation, PoliciesApi } from "authentik-api";
+import { DEFAULT_CONFIG } from "../../../api/Config";
+
+@customElement("ak-policy-reputation-ip-list")
+export class IPReputationListPage extends TablePage {
+ searchEnabled(): boolean {
+ return true;
+ }
+ pageTitle(): string {
+ return t`IP Reputation`;
+ }
+ pageDescription(): string {
+ return t`Reputation for IPs. Scores are decreased for each failed login and increased for each successful login.`;
+ }
+ pageIcon(): string {
+ return "fa fa-ban";
+ }
+
+ @property()
+ order = "ip";
+
+ apiEndpoint(page: number): Promise> {
+ return new PoliciesApi(DEFAULT_CONFIG).policiesReputationIpsList({
+ ordering: this.order,
+ page: page,
+ pageSize: PAGE_SIZE,
+ search: this.search || "",
+ });
+ }
+
+ columns(): TableColumn[] {
+ return [
+ new TableColumn(t`IP`, "ip"),
+ new TableColumn(t`Score`, "score"),
+ new TableColumn(""),
+ ];
+ }
+
+ row(item: IPReputation): TemplateResult[] {
+ return [
+ html`${item.ip}`,
+ html`${item.score}`,
+ html`
+ {
+ return new PoliciesApi(DEFAULT_CONFIG).policiesReputationIpsDestroy({
+ id: item.pk,
+ });
+ }}>
+
+ `,
+ ];
+ }
+
+}
diff --git a/web/src/pages/policies/reputation/UserReputationListPage.ts b/web/src/pages/policies/reputation/UserReputationListPage.ts
new file mode 100644
index 000000000..5daba84e7
--- /dev/null
+++ b/web/src/pages/policies/reputation/UserReputationListPage.ts
@@ -0,0 +1,70 @@
+import { t } from "@lingui/macro";
+import { customElement, html, property, TemplateResult } from "lit-element";
+import { AKResponse } from "../../../api/Client";
+import { TablePage } from "../../../elements/table/TablePage";
+
+import "../../../elements/buttons/ModalButton";
+import "../../../elements/buttons/SpinnerButton";
+import "../../../elements/forms/DeleteForm";
+import "../../../elements/forms/ModalForm";
+import { TableColumn } from "../../../elements/table/Table";
+import { PAGE_SIZE } from "../../../constants";
+import { UserReputation, PoliciesApi } from "authentik-api";
+import { DEFAULT_CONFIG } from "../../../api/Config";
+
+@customElement("ak-policy-reputation-user-list")
+export class UserReputationListPage extends TablePage {
+ searchEnabled(): boolean {
+ return true;
+ }
+ pageTitle(): string {
+ return t`User Reputation`;
+ }
+ pageDescription(): string {
+ return t`Reputation for usernames. Scores are decreased for each failed login and increased for each successful login.`;
+ }
+ pageIcon(): string {
+ return "fa fa-ban";
+ }
+
+ @property()
+ order = "username";
+
+ apiEndpoint(page: number): Promise> {
+ return new PoliciesApi(DEFAULT_CONFIG).policiesReputationUsersList({
+ ordering: this.order,
+ page: page,
+ pageSize: PAGE_SIZE,
+ search: this.search || "",
+ });
+ }
+
+ columns(): TableColumn[] {
+ return [
+ new TableColumn(t`Username`, "username"),
+ new TableColumn(t`Score`, "score"),
+ new TableColumn(""),
+ ];
+ }
+
+ row(item: UserReputation): TemplateResult[] {
+ return [
+ html`${item.username}`,
+ html`${item.score}`,
+ html`
+ {
+ return new PoliciesApi(DEFAULT_CONFIG).policiesReputationUsersDestroy({
+ id: item.pk,
+ });
+ }}>
+
+ `,
+ ];
+ }
+
+}
diff --git a/web/src/pages/stages/invitation/InvitationListPage.ts b/web/src/pages/stages/invitation/InvitationListPage.ts
index 788f03474..f30a1f9c3 100644
--- a/web/src/pages/stages/invitation/InvitationListPage.ts
+++ b/web/src/pages/stages/invitation/InvitationListPage.ts
@@ -57,7 +57,7 @@ export class InvitationListPage extends TablePage {
html`
{
return new StagesApi(DEFAULT_CONFIG).stagesInvitationInvitationsDestroy({
inviteUuid: item.pk || ""
diff --git a/web/src/pages/tenants/TenantForm.ts b/web/src/pages/tenants/TenantForm.ts
index a6a094b1d..89976fae2 100644
--- a/web/src/pages/tenants/TenantForm.ts
+++ b/web/src/pages/tenants/TenantForm.ts
@@ -4,6 +4,7 @@ import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../api/Config";
import "../../elements/forms/HorizontalFormElement";
+import "../../elements/forms/FormGroup";
import { first } from "../../utils";
import { ModelForm } from "../../elements/forms/ModelForm";
import { until } from "lit-html/directives/until";
diff --git a/web/src/routes.ts b/web/src/routes.ts
index e159c6d47..e4cffc393 100644
--- a/web/src/routes.ts
+++ b/web/src/routes.ts
@@ -16,6 +16,8 @@ import "./pages/LibraryPage";
import "./pages/outposts/OutpostListPage";
import "./pages/outposts/ServiceConnectionListPage";
import "./pages/policies/PolicyListPage";
+import "./pages/policies/reputation/IPReputationListPage";
+import "./pages/policies/reputation/UserReputationListPage";
import "./pages/property-mappings/PropertyMappingListPage";
import "./pages/providers/ProviderListPage";
import "./pages/providers/ProviderViewPage";
@@ -54,6 +56,8 @@ export const ROUTES: Route[] = [
new Route(new RegExp("^/core/tokens$"), html``),
new Route(new RegExp("^/core/tenants$"), html``),
new Route(new RegExp("^/policy/policies$"), html``),
+ new Route(new RegExp("^/policy/reputation/ip$"), html``),
+ new Route(new RegExp("^/policy/reputation/user$"), html``),
new Route(new RegExp("^/identity/groups$"), html``),
new Route(new RegExp("^/identity/users$"), html``),
new Route(new RegExp(`^/identity/users/(?${ID_REGEX})$`)).then((args) => {
diff --git a/website/docs/releases/next.md b/website/docs/releases/next.md
index c8d49c274..40d23dc25 100644
--- a/website/docs/releases/next.md
+++ b/website/docs/releases/next.md
@@ -25,6 +25,7 @@ title: Next
## Minor changes
- You can now specify which sources should be shown on an Identification stage.
+- Add UI for the reputation of IPs and usernames for reputation policies.
## Upgrading