web/user: new end-user interface (#1404)

* web/user: migrate to top navbar

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

* web/user: prepare config from server

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

* re-sort

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

* remove old interface

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

* update issue template

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

* use notification badge

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

* web/user: re-add go-to-admin button

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

* *: fix remaining redirects directly to admin

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

* make settings better

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

* api: ensure sources and stages are sorted

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

* web/user: add sessions and consent

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

* providers/oauth2: add post wrapper to stage

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

* website/docs: add new interface to release notes

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2021-09-16 22:17:05 +02:00 committed by GitHub
parent 9441be1ee2
commit 13e2eea72f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 660 additions and 1708 deletions

View File

@ -27,7 +27,7 @@ If applicable, add screenshots to help explain your problem.
Output of docker-compose logs or kubectl logs respectively
**Version and Deployment (please complete the following information):**
- authentik version: [e.g. 0.10.0-stable]
- authentik version: [e.g. 2021.8.5]
- Deployment: [e.g. docker-compose, helm]
**Additional context**

View File

@ -20,7 +20,7 @@ If applicable, add screenshots to help explain your problem.
Output of docker-compose logs or kubectl logs respectively
**Version and Deployment (please complete the following information):**
- authentik version: [e.g. 0.10.0-stable]
- authentik version: [e.g. 2021.8.5]
- Deployment: [e.g. docker-compose, helm]
**Additional context**

View File

@ -11,6 +11,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet
from ua_parser import user_agent_parser
from authentik.api.authorization import OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import AuthenticatedSession
from authentik.events.geo import GEOIP_READER, GeoIPDict
@ -102,11 +103,8 @@ class AuthenticatedSessionViewSet(
search_fields = ["user__username", "last_ip", "last_user_agent"]
filterset_fields = ["user__username", "last_ip", "last_user_agent"]
ordering = ["user__username"]
filter_backends = [
DjangoFilterBackend,
OrderingFilter,
SearchFilter,
]
permission_classes = [OwnerPermissions]
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
def get_queryset(self):
user = self.request.user if self.request else get_anonymous_user()

View File

@ -95,7 +95,9 @@ class SourceViewSet(
@action(detail=False, pagination_class=None, filter_backends=[])
def user_settings(self, request: Request) -> Response:
"""Get all sources the user can configure"""
_all_sources: Iterable[Source] = Source.objects.filter(enabled=True).select_subclasses()
_all_sources: Iterable[Source] = (
Source.objects.filter(enabled=True).select_subclasses().order_by("name")
)
matching_sources: list[UserSettingSerializer] = []
for source in _all_sources:
user_settings = source.ui_user_settings

View File

@ -184,7 +184,7 @@ class SourceFlowManager:
# Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:if-admin"
NEXT_ARG_NAME, "authentik_core:if-user"
)
kwargs.update(
{
@ -243,9 +243,9 @@ class SourceFlowManager:
return self.handle_auth_user(connection)
return redirect(
reverse(
"authentik_core:if-admin",
"authentik_core:if-user",
)
+ f"#/user;page-{self.source.slug}"
+ f"#/settings;page-{self.source.slug}"
)
def handle_enroll(

View File

@ -21,7 +21,7 @@ You've logged out of {{ application }}.
{% endblocktrans %}
</p>
<a id="ak-back-home" href="{% url 'authentik_core:if-admin' %}" class="pf-c-button pf-m-primary">{% trans 'Go back to overview' %}</a>
<a id="ak-back-home" href="{% url 'authentik_core:root-redirect' %}" class="pf-c-button pf-m-primary">{% trans 'Go back to overview' %}</a>
<a id="logout" href="{% url 'authentik_flows:default-invalidation' %}" class="pf-c-button pf-m-secondary">{% trans 'Log out of authentik' %}</a>

View File

@ -58,4 +58,4 @@ class TestImpersonation(TestCase):
self.client.force_login(self.other_user)
response = self.client.get(reverse("authentik_core:impersonate-end"))
self.assertRedirects(response, reverse("authentik_core:if-admin"))
self.assertRedirects(response, reverse("authentik_core:if-user"))

View File

@ -28,7 +28,7 @@ class ImpersonateInitView(View):
Event.new(EventAction.IMPERSONATION_STARTED).from_http(request, user_to_be)
return redirect("authentik_core:if-admin")
return redirect("authentik_core:if-user")
class ImpersonateEndView(View):
@ -41,7 +41,7 @@ class ImpersonateEndView(View):
or SESSION_IMPERSONATE_ORIGINAL_USER not in request.session
):
LOGGER.debug("Can't end impersonation", user=request.user)
return redirect("authentik_core:if-admin")
return redirect("authentik_core:if-user")
original_user = request.session[SESSION_IMPERSONATE_ORIGINAL_USER]

View File

@ -86,7 +86,7 @@ class StageViewSet(
@action(detail=False, pagination_class=None, filter_backends=[])
def user_settings(self, request: Request) -> Response:
"""Get all stages the user can configure"""
_all_stages: Iterable[Stage] = Stage.objects.all().select_subclasses()
_all_stages: Iterable[Stage] = Stage.objects.all().select_subclasses().order_by("name")
matching_stages: list[dict] = []
for stage in _all_stages:
user_settings = stage.ui_user_settings

View File

@ -238,6 +238,10 @@ class OAuthFulfillmentStage(StageView):
parsed = urlparse(uri)
return HttpResponseRedirectScheme(uri, allowed_schemes=[parsed.scheme])
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Wrapper when this stage gets hit with a post request"""
return self.get(request, *args, **kwargs)
# pylint: disable=unused-argument
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""final Stage of an OAuth2 Flow"""

View File

@ -22,4 +22,4 @@ class UseTokenView(View):
login(request, token.user, backend=BACKEND_INBUILT)
token.delete()
messages.warning(request, _("Used recovery-link to authenticate."))
return redirect("authentik_core:if-admin")
return redirect("authentik_core:if-user")

View File

@ -190,7 +190,7 @@ class ResponseProcessor:
# Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self._http_request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:if-admin"
NEXT_ARG_NAME, "authentik_core:if-user"
)
if matching_users.exists():
# User exists already, switch to authentication flow

View File

@ -71,7 +71,7 @@ class InitiateView(View):
# Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:if-admin"
NEXT_ARG_NAME, "authentik_core:if-user"
)
kwargs.update(
{

View File

@ -28,6 +28,15 @@ export class Route {
return this;
}
redirectRaw(to: string): Route {
this.callback = () => {
console.debug(`authentik/router: redirecting ${to}`);
window.location.hash = `${to}`;
return html``;
};
return this;
}
then(render: (args: RouteArgs) => TemplateResult): Route {
this.callback = render;
return this;

View File

@ -32,7 +32,7 @@ export class SidebarUser extends LitElement {
render(): TemplateResult {
return html`
<a href="#/user" class="pf-c-nav__link user-avatar" id="user-settings">
<a href="/if/user/#settings" class="pf-c-nav__link user-avatar" id="user-settings">
${until(
me().then((u) => {
return html`<img

View File

@ -172,7 +172,7 @@ export class AdminInterface extends LitElement {
}),
)}
<ak-sidebar-item path="/if/user/" ?isAbsoluteLink=${true} ?highlight=${true}>
<span slot="label">${t`Go to user interface`}</span>
<span slot="label">${t`User interface`}</span>
</ak-sidebar-item>
<ak-sidebar-item path="/administration/overview">
<span slot="label">${t`Overview`}</span>

View File

@ -14,31 +14,30 @@ import "../elements/sidebar/SidebarItem";
import { t } from "@lingui/macro";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFBrand from "@patternfly/patternfly/components/Brand/brand.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
import PFNotificationBadge from "@patternfly/patternfly/components/NotificationBadge/notification-badge.css";
import AKGlobal from "../authentik.css";
import "../elements/router/RouterOutlet";
import "../elements/messages/MessageContainer";
import "../elements/notifications/NotificationDrawer";
import "../elements/sidebar/Sidebar";
import { until } from "lit-html/directives/until";
import {
EVENT_API_DRAWER_TOGGLE,
EVENT_NOTIFICATION_DRAWER_TOGGLE,
EVENT_SIDEBAR_TOGGLE,
VERSION,
} from "../constants";
import { AdminApi, Version } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../api/Config";
import { EVENT_API_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE } from "../constants";
import { CurrentTenant, EventsApi } from "@goauthentik/api";
import { DEFAULT_CONFIG, tenant } from "../api/Config";
import { WebsocketClient } from "../common/ws";
import { ROUTES } from "../routesUser";
import { first } from "../utils";
import { DefaultTenant } from "../elements/sidebar/SidebarBrand";
import { until } from "lit-html/directives/until";
import { uiConfig } from "../user/config";
@customElement("ak-interface-user")
export class UserInterface extends LitElement {
@property({ type: Boolean })
sidebarOpen = true;
@property({ type: Boolean })
notificationOpen = false;
@ -47,14 +46,22 @@ export class UserInterface extends LitElement {
ws: WebsocketClient;
private version: Promise<Version>;
@property({ attribute: false })
tenant: CurrentTenant = DefaultTenant;
@property({ type: Number })
notificationsCount = -1;
static get styles(): CSSResult[] {
return [
PFBase,
PFBrand,
PFPage,
PFAvatar,
PFButton,
PFDrawer,
PFDropdown,
PFNotificationBadge,
AKGlobal,
css`
.pf-c-page__main,
@ -65,6 +72,12 @@ export class UserInterface extends LitElement {
.display-none {
display: none;
}
.pf-c-brand {
min-height: 48px;
}
.has-notifications {
color: #2b9af3;
}
`,
];
}
@ -72,29 +85,152 @@ export class UserInterface extends LitElement {
constructor() {
super();
this.ws = new WebsocketClient();
this.sidebarOpen = window.innerWidth >= 1280;
window.addEventListener("resize", () => {
this.sidebarOpen = window.innerWidth >= 1280;
});
window.addEventListener(EVENT_SIDEBAR_TOGGLE, () => {
this.sidebarOpen = !this.sidebarOpen;
});
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
this.notificationOpen = !this.notificationOpen;
});
window.addEventListener(EVENT_API_DRAWER_TOGGLE, () => {
this.apiDrawerOpen = !this.apiDrawerOpen;
});
this.version = new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
tenant().then((tenant) => (this.tenant = tenant));
new EventsApi(DEFAULT_CONFIG)
.eventsNotificationsList({
seen: false,
ordering: "-created",
pageSize: 1,
})
.then((r) => {
this.notificationsCount = r.pagination.count;
});
}
render(): TemplateResult {
return html` <div class="pf-c-page">
<ak-sidebar
class="pf-c-page__sidebar ${this.sidebarOpen ? "pf-m-expanded" : "pf-m-collapsed"}"
>
${this.renderSidebarItems()}
</ak-sidebar>
<header class="pf-c-page__header">
<div class="pf-c-page__header-brand">
<a href="#/" class="pf-c-page__header-brand-link">
<img
class="pf-c-brand"
src="${first(this.tenant.brandingLogo, DefaultTenant.brandingLogo)}"
alt="${(this.tenant.brandingTitle, DefaultTenant.brandingTitle)}"
/>
</a>
</div>
<div class="pf-c-page__header-tools">
<div class="pf-c-page__header-tools-group">
${until(
uiConfig().then((config) => {
if (!config.enabledFeatures.apiDrawer) {
return html``;
}
return html`<div
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
>
<button
class="pf-c-button pf-m-plain"
type="button"
@click=${() => {
this.apiDrawerOpen = !this.apiDrawerOpen;
}}
>
<i class="fas fa-code" aria-hidden="true"></i>
</button>
</div>`;
}),
)}
${until(
uiConfig().then((config) => {
if (!config.enabledFeatures.notificationDrawer) {
return html``;
}
return html`
<button
class="pf-c-button pf-m-plain"
type="button"
aria-label="${t`Unread notifications`}"
@click=${() => {
this.notificationOpen = !this.notificationOpen;
}}
>
<span
class="pf-c-notification-badge ${this
.notificationsCount > 0
? html`pf-m-unread`
: html``}"
>
<i class="pf-icon-bell" aria-hidden="true"></i>
<span class="pf-c-notification-badge__count"
>${this.notificationsCount}</span
>
</span>
</button>
`;
}),
)}
${until(
uiConfig().then((config) => {
if (!config.enabledFeatures.settings) {
return html``;
}
return html` <div
class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"
>
<a
class="pf-c-button pf-m-plain"
type="button"
href="#/settings"
>
<i class="fas fa-cog" aria-hidden="true"></i>
</a>
</div>`;
}),
)}
<a href="/flows/-/default/invalidation/" class="pf-c-button pf-m-plain">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
</a>
${until(
me().then((u) => {
if (!u.user.isSuperuser) return html``;
return html`
<a class="pf-c-button pf-m-primary pf-m-small" href="/if/admin">
${t`Admin interface`}
</a>
`;
}),
)}
</div>
<div class="pf-c-page__header-tools-group">
<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md">
<span class="pf-c-dropdown__toggle-text"
>${until(
uiConfig().then((config) => {
return me().then((me) => {
switch (config.navbar.userDisplay) {
case "username":
return me.user.username;
case "name":
return me.user.name;
case "email":
return me.user.email;
default:
return me.user.username;
}
});
}),
)}
</span>
</div>
</div>
${until(
me().then((me) => {
return html`<img
class="pf-c-avatar"
src=${me.user.avatar}
alt="${t`Avatar image`}"
/>`;
}),
)}
</div>
</header>
<div class="pf-c-page__drawer">
<div
class="pf-c-drawer ${this.notificationOpen || this.apiDrawerOpen
@ -107,7 +243,7 @@ export class UserInterface extends LitElement {
<main class="pf-c-page__main">
<ak-router-outlet
role="main"
class="pf-c-page__main"
class="pf-l-bullseye__item pf-c-page__main"
tabindex="-1"
id="main-content"
defaultUrl="/library"
@ -134,43 +270,4 @@ export class UserInterface extends LitElement {
</div>
</div>`;
}
renderSidebarItems(): TemplateResult {
return html`
${until(
this.version.then((version) => {
if (version.versionCurrent !== VERSION) {
return html`<ak-sidebar-item ?highlight=${true}>
<span slot="label"
>${t`A newer version of the frontend is available.`}</span
>
</ak-sidebar-item>`;
}
return html``;
}),
)}
${until(
me().then((u) => {
if (u.original) {
return html`<ak-sidebar-item
?highlight=${true}
?isAbsoluteLink=${true}
path=${`/-/impersonation/end/?back=${window.location.pathname}%23${window.location.hash}`}
>
<span slot="label"
>${t`You're currently impersonating ${u.user.username}. Click to stop.`}</span
>
</ak-sidebar-item>`;
}
return html``;
}),
)}
<ak-sidebar-item path="/if/admin" ?isAbsoluteLink=${true} ?highlight=${true}>
<span slot="label">${t`Go to admin interface`}</span>
</ak-sidebar-item>
<ak-sidebar-item path="/library">
<span slot="label">${t`Library`}</span>
</ak-sidebar-item>
`;
}
}

View File

@ -34,7 +34,6 @@ msgid "8 digits, not compatible with apps like Google Authenticator"
msgstr "8 digits, not compatible with apps like Google Authenticator"
#: src/interfaces/AdminInterface.ts
#: src/interfaces/UserInterface.ts
msgid "A newer version of the frontend is available."
msgstr "A newer version of the frontend is available."
@ -172,6 +171,10 @@ msgstr "Additional user DN, prepended to the Base DN."
#~ msgid "Admin"
#~ msgstr "Admin"
#: src/interfaces/UserInterface.ts
msgid "Admin interface"
msgstr "Admin interface"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
@ -284,10 +287,8 @@ msgid "Application(s)"
msgstr "Application(s)"
#: src/interfaces/AdminInterface.ts
#: src/pages/LibraryPage.ts
#: src/pages/applications/ApplicationListPage.ts
#: src/pages/outposts/OutpostForm.ts
#: src/user/LibraryPage.ts
msgid "Applications"
msgstr "Applications"
@ -386,8 +387,7 @@ msgid "Authentication flow"
msgstr "Authentication flow"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "Authenticator"
msgstr "Authenticator"
@ -423,6 +423,10 @@ msgstr "Authorize URL"
msgid "Authorized application:"
msgstr "Authorized application:"
#: src/interfaces/UserInterface.ts
msgid "Avatar image"
msgstr "Avatar image"
#: src/pages/stages/password/PasswordStageForm.ts
msgid "Backends"
msgstr "Backends"
@ -608,8 +612,7 @@ msgstr "Certificate/Key used for authentication. Can be left empty for no authen
msgid "Certificates"
msgstr "Certificates"
#: src/pages/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/stages/UserSettingsPassword.ts
msgid "Change password"
msgstr "Change password"
@ -617,8 +620,7 @@ msgstr "Change password"
msgid "Change status"
msgstr "Change status"
#: src/pages/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/stages/UserSettingsPassword.ts
msgid "Change your password"
msgstr "Change your password"
@ -796,8 +798,7 @@ msgstr "Configuration flow"
msgid "Configuration stage"
msgstr "Configuration stage"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "Configure WebAuthn"
msgstr "Configure WebAuthn"
@ -829,10 +830,10 @@ msgstr "Configure how the flow executor should handle an invalid response to a c
msgid "Configure how the issuer field of the ID Token should be filled."
msgstr "Configure how the issuer field of the ID Token should be filled."
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Configure settings relevant to your user profile."
msgstr "Configure settings relevant to your user profile."
#:
#:
#~ msgid "Configure settings relevant to your user profile."
#~ msgstr "Configure settings relevant to your user profile."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Configure the maximum allowed time drift for an asseration."
@ -846,15 +847,16 @@ msgstr "Configure visual settings and defaults for different domains."
msgid "Configure what data should be used as unique User Identifier. For most cases, the default should be fine."
msgstr "Configure what data should be used as unique User Identifier. For most cases, the default should be fine."
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
msgid "Connect"
msgstr "Connect"
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Connected services"
msgstr "Connected services"
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
#: src/user/user-settings/sources/SourceSettingsPlex.ts
msgid "Connected."
msgstr "Connected."
@ -940,7 +942,6 @@ msgstr "Cookie domain"
msgid "Copy"
msgstr "Copy"
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Copy Key"
msgstr "Copy Key"
@ -998,8 +999,6 @@ msgstr "Copy recovery link"
#: src/pages/tenants/TenantListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
@ -1008,8 +1007,6 @@ msgstr "Copy recovery link"
msgid "Create"
msgstr "Create"
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Create App password"
@ -1087,8 +1084,6 @@ msgid "Create Tenant"
msgstr "Create Tenant"
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Create Token"
@ -1126,8 +1121,7 @@ msgstr "Create {0}"
msgid "Created by"
msgstr "Created by"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "Created {0}"
msgstr "Created {0}"
@ -1210,10 +1204,8 @@ msgstr "Define how notifications are sent to users, like Email or Webhook."
#: src/pages/stages/prompt/PromptListPage.ts
#: src/pages/tenants/TenantListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/UserListPage.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Delete"
msgstr "Delete"
@ -1239,7 +1231,6 @@ msgstr "Delete"
#~ msgid "Delete Session"
#~ msgstr "Delete Session"
#: src/pages/user-settings/UserSelfForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Delete account"
msgstr "Delete account"
@ -1277,7 +1268,6 @@ msgstr "Deny the user access"
#: src/pages/property-mappings/PropertyMappingScopeForm.ts
#: src/pages/system-tasks/SystemTaskListPage.ts
#: src/pages/tokens/TokenForm.ts
#: src/pages/user-settings/tokens/UserTokenForm.ts
#: src/user/user-settings/tokens/UserTokenForm.ts
msgid "Description"
msgstr "Description"
@ -1319,8 +1309,7 @@ msgstr "Device classes"
msgid "Device classes which can be used to authenticate."
msgstr "Device classes which can be used to authenticate."
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "Device name"
msgstr "Device name"
@ -1338,18 +1327,15 @@ msgstr "Digits"
#~ msgid "Disable"
#~ msgstr "Disable"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
msgid "Disable Duo authenticator"
msgstr "Disable Duo authenticator"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
msgid "Disable Static Tokens"
msgstr "Disable Static Tokens"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Disable Time-based OTP"
msgstr "Disable Time-based OTP"
@ -1357,10 +1343,8 @@ msgstr "Disable Time-based OTP"
msgid "Disabled"
msgstr "Disabled"
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
#: src/user/user-settings/sources/SourceSettingsPlex.ts
msgid "Disconnect"
msgstr "Disconnect"
@ -1395,8 +1379,7 @@ msgstr "Due to protocol limitations, this certificate is only used when the outp
msgid "Dummy stage used for testing. Shows a simple continue button and always passes."
msgstr "Dummy stage used for testing. Shows a simple continue button and always passes."
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
msgid "Duo"
msgstr "Duo"
@ -1455,7 +1438,6 @@ msgstr "Edit Stage"
msgid "Edit User"
msgstr "Edit User"
#: src/pages/LibraryPage.ts
#: src/user/LibraryPage.ts
msgid "Either no applications are defined, or you don't have access to any."
msgstr "Either no applications are defined, or you don't have access to any."
@ -1463,7 +1445,6 @@ msgstr "Either no applications are defined, or you don't have access to any."
#: src/flows/stages/identification/IdentificationStage.ts
#: src/pages/events/TransportForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserViewPage.ts
#: src/user/user-settings/UserSelfForm.ts
@ -1503,8 +1484,7 @@ msgstr "Embedded outpost is not configured correctly."
#~ msgid "Enable"
#~ msgstr "Enable"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
msgid "Enable Duo authenticator"
msgstr "Enable Duo authenticator"
@ -1512,13 +1492,11 @@ msgstr "Enable Duo authenticator"
msgid "Enable StartTLS"
msgstr "Enable StartTLS"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
msgid "Enable Static Tokens"
msgstr "Enable Static Tokens"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Enable TOTP"
msgstr "Enable TOTP"
@ -1570,13 +1548,11 @@ msgstr "Error when creating credential: {err}"
msgid "Error when validating assertion on server: {err}"
msgstr "Error when validating assertion on server: {err}"
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
#: src/user/user-settings/SourceSettings.ts
msgid "Error: unsupported source settings: {0}"
msgstr "Error: unsupported source settings: {0}"
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
#: src/user/user-settings/StageSettings.ts
msgid "Error: unsupported stage settings: {0}"
msgstr "Error: unsupported stage settings: {0}"
@ -1660,8 +1636,6 @@ msgstr "Expires on"
msgid "Expires?"
msgstr "Expires?"
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Expiring"
@ -1926,9 +1900,9 @@ msgstr "Generate"
msgid "Generate Certificate-Key Pair"
msgstr "Generate Certificate-Key Pair"
#: src/interfaces/UserInterface.ts
msgid "Go to admin interface"
msgstr "Go to admin interface"
#:
#~ msgid "Go to admin interface"
#~ msgstr "Go to admin interface"
#: src/elements/table/TablePagination.ts
msgid "Go to next page"
@ -1938,9 +1912,9 @@ msgstr "Go to next page"
msgid "Go to previous page"
msgstr "Go to previous page"
#: src/interfaces/AdminInterface.ts
msgid "Go to user interface"
msgstr "Go to user interface"
#:
#~ msgid "Go to user interface"
#~ msgstr "Go to user interface"
#: src/pages/events/RuleForm.ts
#: src/pages/policies/PolicyBindingForm.ts
@ -2077,8 +2051,6 @@ msgstr "Icon shown in the browser tab."
#: src/pages/system-tasks/SystemTaskListPage.ts
#: src/pages/tokens/TokenForm.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenForm.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenForm.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Identifier"
@ -2185,7 +2157,6 @@ msgstr "Integrations"
#: src/pages/tokens/TokenForm.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Intent"
msgstr "Intent"
@ -2327,9 +2298,9 @@ msgstr "Launch URL"
msgid "Let the user identify themselves with their username or Email address."
msgstr "Let the user identify themselves with their username or Email address."
#: src/interfaces/UserInterface.ts
msgid "Library"
msgstr "Library"
#:
#~ msgid "Library"
#~ msgstr "Library"
#: src/pages/sources/oauth/OAuthSourceForm.ts
#: src/pages/sources/plex/PlexSourceForm.ts
@ -2374,7 +2345,8 @@ msgstr "Load servers"
#: src/flows/stages/prompt/PromptStage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/user/user-settings/SourceSettings.ts
#: src/user/user-settings/StageSettings.ts
#: src/user/user-settings/UserSelfForm.ts
#: src/utils.ts
msgid "Loading"
@ -2599,11 +2571,14 @@ msgstr "Model updated"
#~ msgid "Monitor"
#~ msgstr "Monitor"
#: src/pages/LibraryPage.ts
#: src/user/LibraryPage.ts
msgid "My Applications"
msgstr "My Applications"
#: src/user/LibraryPage.ts
msgid "My applications"
msgstr "My applications"
#: src/elements/forms/DeleteBulkForm.ts
#: src/pages/applications/ApplicationForm.ts
#: src/pages/applications/ApplicationListPage.ts
@ -2677,7 +2652,6 @@ msgstr "My Applications"
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/pages/stages/user_logout/UserLogoutStageForm.ts
#: src/pages/stages/user_write/UserWriteStageForm.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/GroupSelectModal.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserListPage.ts
@ -2729,14 +2703,12 @@ msgstr "Newly created users are added to this group, if a group is selected."
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
#: src/pages/tenants/TenantListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/GroupSelectModal.ts
#: src/pages/users/UserListPage.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "No"
msgstr "No"
#: src/pages/LibraryPage.ts
#: src/user/LibraryPage.ts
msgid "No Applications available."
msgstr "No Applications available."
@ -2803,10 +2775,8 @@ msgstr "Not available"
msgid "Not configured action"
msgstr "Not configured action"
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
#: src/user/user-settings/sources/SourceSettingsPlex.ts
msgid "Not connected."
msgstr "Not connected."
@ -3071,6 +3041,10 @@ msgstr "Password set"
msgid "Password stage"
msgstr "Password stage"
#: src/user/user-settings/UserSettingsPage.ts
msgid "Password, 2FA, etc"
msgstr "Password, 2FA, etc"
#: src/pages/stages/prompt/PromptForm.ts
msgid "Password: Masked input, password is validated against sources. Policies still have to be applied to this Stage. If two of these are used in the same stage, they are ensured to be identical."
msgstr "Password: Masked input, password is validated against sources. Policies still have to be applied to this Stage. If two of these are used in the same stage, they are ensured to be identical."
@ -3471,7 +3445,6 @@ msgstr "Required"
msgid "Required."
msgstr "Required."
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/UserForm.ts
#: src/user/user-settings/UserSelfForm.ts
@ -3849,10 +3822,8 @@ msgstr "Something went wrong! Please try again later."
msgid "Source linked"
msgstr "Source linked"
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
#: src/user/user-settings/sources/SourceSettingsPlex.ts
msgid "Source {0}"
msgstr "Source {0}"
@ -3962,8 +3933,7 @@ msgstr "State"
msgid "Static Tokens"
msgstr "Static Tokens"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
msgid "Static tokens"
msgstr "Static tokens"
@ -3979,21 +3949,15 @@ msgstr "Statically deny the flow. To use this stage effectively, disable *Evalua
msgid "Status"
msgstr "Status"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Disabled"
msgstr "Status: Disabled"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Enabled"
msgstr "Status: Enabled"
@ -4134,7 +4098,6 @@ msgid "Successfully created tenant."
msgstr "Successfully created tenant."
#: src/pages/tokens/TokenForm.ts
#: src/pages/user-settings/tokens/UserTokenForm.ts
#: src/user/user-settings/tokens/UserTokenForm.ts
msgid "Successfully created token."
msgstr "Successfully created token."
@ -4193,13 +4156,11 @@ msgstr "Successfully updated binding."
msgid "Successfully updated certificate-key pair."
msgstr "Successfully updated certificate-key pair."
#: src/pages/user-settings/UserSelfForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Successfully updated details."
msgstr "Successfully updated details."
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "Successfully updated device."
msgstr "Successfully updated device."
@ -4294,7 +4255,6 @@ msgid "Successfully updated tenant."
msgstr "Successfully updated tenant."
#: src/pages/tokens/TokenForm.ts
#: src/pages/user-settings/tokens/UserTokenForm.ts
#: src/user/user-settings/tokens/UserTokenForm.ts
msgid "Successfully updated token."
msgstr "Successfully updated token."
@ -4538,8 +4498,7 @@ msgstr "Time in minutes the token sent is valid."
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Time-based One-Time Passwords"
msgstr "Time-based One-Time Passwords"
@ -4586,7 +4545,6 @@ msgid "Token validity"
msgstr "Token validity"
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Token(s)"
msgstr "Token(s)"
@ -4600,7 +4558,6 @@ msgstr "Tokens"
msgid "Tokens & App passwords"
msgstr "Tokens & App passwords"
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Tokens and App passwords"
msgstr "Tokens and App passwords"
@ -4722,6 +4679,10 @@ msgstr "Unknown"
#~ msgid "Unmanaged"
#~ msgstr "Unmanaged"
#: src/interfaces/UserInterface.ts
msgid "Unread notifications"
msgstr "Unread notifications"
#: src/pages/admin-overview/charts/LDAPSyncStatusChart.ts
msgid "Unsynced sources"
msgstr "Unsynced sources"
@ -4761,18 +4722,13 @@ msgstr "Up-to-date!"
#: src/pages/stages/prompt/PromptListPage.ts
#: src/pages/tenants/TenantListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/UserActiveForm.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserViewPage.ts
#: src/user/user-settings/UserSelfForm.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Update"
msgstr "Update"
@ -4856,7 +4812,6 @@ msgid "Update Tenant"
msgstr "Update Tenant"
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Update Token"
msgstr "Update Token"
@ -4871,7 +4826,6 @@ msgstr "Update User"
msgid "Update available"
msgstr "Update available"
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Update details"
msgstr "Update details"
@ -4949,7 +4903,6 @@ msgstr "Use this tenant for each domain that doesn't have a dedicated tenant."
#: src/pages/property-mappings/PropertyMappingTestForm.ts
#: src/pages/tokens/TokenForm.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/UserListPage.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "User"
@ -4968,10 +4921,10 @@ msgstr "User Property Mappings"
msgid "User Reputation"
msgstr "User Reputation"
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "User Settings"
msgstr "User Settings"
#:
#:
#~ msgid "User Settings"
#~ msgstr "User Settings"
#: src/pages/stages/password/PasswordStageForm.ts
msgid "User database + LDAP password"
@ -4985,7 +4938,6 @@ msgstr "User database + app passwords"
msgid "User database + standard password"
msgstr "User database + standard password"
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "User details"
msgstr "User details"
@ -4998,6 +4950,10 @@ msgstr "User events"
msgid "User fields"
msgstr "User fields"
#: src/interfaces/AdminInterface.ts
msgid "User interface"
msgstr "User interface"
#: src/pages/sources/oauth/OAuthSourceForm.ts
#: src/pages/sources/plex/PlexSourceForm.ts
msgid "User matching mode"
@ -5028,7 +4984,6 @@ msgstr "User {0}"
msgid "User's avatar"
msgstr "User's avatar"
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "User's display name."
@ -5053,7 +5008,6 @@ 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/UserSelfForm.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/UserForm.ts
@ -5177,8 +5131,7 @@ msgstr "Warning: authentik Domain is not configured, authentication will not wor
msgid "WebAuthn Authenticators"
msgstr "WebAuthn Authenticators"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "WebAuthn Devices"
msgstr "WebAuthn Devices"
@ -5271,7 +5224,6 @@ msgstr "X509 Subject"
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
#: src/pages/tenants/TenantListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/GroupSelectModal.ts
#: src/pages/users/UserListPage.ts
#: src/user/user-settings/tokens/UserTokenList.ts
@ -5283,7 +5235,6 @@ msgid "You can only select providers that match the type of the outpost."
msgstr "You can only select providers that match the type of the outpost."
#: src/interfaces/AdminInterface.ts
#: src/interfaces/UserInterface.ts
msgid "You're currently impersonating {0}. Click to stop."
msgstr "You're currently impersonating {0}. Click to stop."

View File

@ -34,7 +34,6 @@ msgid "8 digits, not compatible with apps like Google Authenticator"
msgstr ""
#: src/interfaces/AdminInterface.ts
#: src/interfaces/UserInterface.ts
msgid "A newer version of the frontend is available."
msgstr ""
@ -172,6 +171,10 @@ msgstr ""
#~ msgid "Admin"
#~ msgstr ""
#: src/interfaces/UserInterface.ts
msgid "Admin interface"
msgstr ""
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
@ -284,10 +287,8 @@ msgid "Application(s)"
msgstr ""
#: src/interfaces/AdminInterface.ts
#: src/pages/LibraryPage.ts
#: src/pages/applications/ApplicationListPage.ts
#: src/pages/outposts/OutpostForm.ts
#: src/user/LibraryPage.ts
msgid "Applications"
msgstr ""
@ -382,8 +383,7 @@ msgid "Authentication flow"
msgstr ""
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "Authenticator"
msgstr ""
@ -419,6 +419,10 @@ msgstr ""
msgid "Authorized application:"
msgstr ""
#: src/interfaces/UserInterface.ts
msgid "Avatar image"
msgstr ""
#: src/pages/stages/password/PasswordStageForm.ts
msgid "Backends"
msgstr ""
@ -604,8 +608,7 @@ msgstr ""
msgid "Certificates"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/stages/UserSettingsPassword.ts
msgid "Change password"
msgstr ""
@ -613,8 +616,7 @@ msgstr ""
msgid "Change status"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/stages/UserSettingsPassword.ts
msgid "Change your password"
msgstr ""
@ -790,8 +792,7 @@ msgstr ""
msgid "Configuration stage"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "Configure WebAuthn"
msgstr ""
@ -823,10 +824,10 @@ msgstr ""
msgid "Configure how the issuer field of the ID Token should be filled."
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Configure settings relevant to your user profile."
msgstr ""
#:
#:
#~ msgid "Configure settings relevant to your user profile."
#~ msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Configure the maximum allowed time drift for an asseration."
@ -840,15 +841,16 @@ msgstr ""
msgid "Configure what data should be used as unique User Identifier. For most cases, the default should be fine."
msgstr ""
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
msgid "Connect"
msgstr ""
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Connected services"
msgstr ""
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
#: src/user/user-settings/sources/SourceSettingsPlex.ts
msgid "Connected."
msgstr ""
@ -934,7 +936,6 @@ msgstr ""
msgid "Copy"
msgstr ""
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Copy Key"
msgstr ""
@ -992,8 +993,6 @@ msgstr ""
#: src/pages/tenants/TenantListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
@ -1002,8 +1001,6 @@ msgstr ""
msgid "Create"
msgstr ""
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Create App password"
@ -1081,8 +1078,6 @@ msgid "Create Tenant"
msgstr ""
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Create Token"
@ -1120,8 +1115,7 @@ msgstr ""
msgid "Created by"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "Created {0}"
msgstr ""
@ -1204,10 +1198,8 @@ msgstr ""
#: src/pages/stages/prompt/PromptListPage.ts
#: src/pages/tenants/TenantListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/UserListPage.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Delete"
msgstr ""
@ -1233,7 +1225,6 @@ msgstr ""
#~ msgid "Delete Session"
#~ msgstr ""
#: src/pages/user-settings/UserSelfForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Delete account"
msgstr ""
@ -1269,7 +1260,6 @@ msgstr ""
#: src/pages/property-mappings/PropertyMappingScopeForm.ts
#: src/pages/system-tasks/SystemTaskListPage.ts
#: src/pages/tokens/TokenForm.ts
#: src/pages/user-settings/tokens/UserTokenForm.ts
#: src/user/user-settings/tokens/UserTokenForm.ts
msgid "Description"
msgstr ""
@ -1311,8 +1301,7 @@ msgstr ""
msgid "Device classes which can be used to authenticate."
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "Device name"
msgstr ""
@ -1330,18 +1319,15 @@ msgstr ""
#~ msgid "Disable"
#~ msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
msgid "Disable Duo authenticator"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
msgid "Disable Static Tokens"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Disable Time-based OTP"
msgstr ""
@ -1349,10 +1335,8 @@ msgstr ""
msgid "Disabled"
msgstr ""
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
#: src/user/user-settings/sources/SourceSettingsPlex.ts
msgid "Disconnect"
msgstr ""
@ -1387,8 +1371,7 @@ msgstr ""
msgid "Dummy stage used for testing. Shows a simple continue button and always passes."
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
msgid "Duo"
msgstr ""
@ -1447,7 +1430,6 @@ msgstr ""
msgid "Edit User"
msgstr ""
#: src/pages/LibraryPage.ts
#: src/user/LibraryPage.ts
msgid "Either no applications are defined, or you don't have access to any."
msgstr ""
@ -1455,7 +1437,6 @@ msgstr ""
#: src/flows/stages/identification/IdentificationStage.ts
#: src/pages/events/TransportForm.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserViewPage.ts
#: src/user/user-settings/UserSelfForm.ts
@ -1495,8 +1476,7 @@ msgstr ""
#~ msgid "Enable"
#~ msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
msgid "Enable Duo authenticator"
msgstr ""
@ -1504,13 +1484,11 @@ msgstr ""
msgid "Enable StartTLS"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
msgid "Enable Static Tokens"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Enable TOTP"
msgstr ""
@ -1562,13 +1540,11 @@ msgstr ""
msgid "Error when validating assertion on server: {err}"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
#: src/user/user-settings/SourceSettings.ts
msgid "Error: unsupported source settings: {0}"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
#: src/user/user-settings/StageSettings.ts
msgid "Error: unsupported stage settings: {0}"
msgstr ""
@ -1652,8 +1628,6 @@ msgstr ""
msgid "Expires?"
msgstr ""
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Expiring"
@ -1918,9 +1892,9 @@ msgstr ""
msgid "Generate Certificate-Key Pair"
msgstr ""
#: src/interfaces/UserInterface.ts
msgid "Go to admin interface"
msgstr ""
#:
#~ msgid "Go to admin interface"
#~ msgstr ""
#: src/elements/table/TablePagination.ts
msgid "Go to next page"
@ -1930,9 +1904,9 @@ msgstr ""
msgid "Go to previous page"
msgstr ""
#: src/interfaces/AdminInterface.ts
msgid "Go to user interface"
msgstr ""
#:
#~ msgid "Go to user interface"
#~ msgstr ""
#: src/pages/events/RuleForm.ts
#: src/pages/policies/PolicyBindingForm.ts
@ -2069,8 +2043,6 @@ msgstr ""
#: src/pages/system-tasks/SystemTaskListPage.ts
#: src/pages/tokens/TokenForm.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenForm.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenForm.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Identifier"
@ -2177,7 +2149,6 @@ msgstr ""
#: src/pages/tokens/TokenForm.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Intent"
msgstr ""
@ -2319,9 +2290,9 @@ msgstr ""
msgid "Let the user identify themselves with their username or Email address."
msgstr ""
#: src/interfaces/UserInterface.ts
msgid "Library"
msgstr ""
#:
#~ msgid "Library"
#~ msgstr ""
#: src/pages/sources/oauth/OAuthSourceForm.ts
#: src/pages/sources/plex/PlexSourceForm.ts
@ -2366,7 +2337,8 @@ msgstr ""
#: src/flows/stages/prompt/PromptStage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/user/user-settings/SourceSettings.ts
#: src/user/user-settings/StageSettings.ts
#: src/user/user-settings/UserSelfForm.ts
#: src/utils.ts
msgid "Loading"
@ -2591,11 +2563,14 @@ msgstr ""
#~ msgid "Monitor"
#~ msgstr ""
#: src/pages/LibraryPage.ts
#: src/user/LibraryPage.ts
msgid "My Applications"
msgstr ""
#: src/user/LibraryPage.ts
msgid "My applications"
msgstr ""
#: src/elements/forms/DeleteBulkForm.ts
#: src/pages/applications/ApplicationForm.ts
#: src/pages/applications/ApplicationListPage.ts
@ -2669,7 +2644,6 @@ msgstr ""
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/pages/stages/user_logout/UserLogoutStageForm.ts
#: src/pages/stages/user_write/UserWriteStageForm.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/GroupSelectModal.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserListPage.ts
@ -2721,14 +2695,12 @@ msgstr ""
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
#: src/pages/tenants/TenantListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/GroupSelectModal.ts
#: src/pages/users/UserListPage.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "No"
msgstr ""
#: src/pages/LibraryPage.ts
#: src/user/LibraryPage.ts
msgid "No Applications available."
msgstr ""
@ -2795,10 +2767,8 @@ msgstr ""
msgid "Not configured action"
msgstr ""
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
#: src/user/user-settings/sources/SourceSettingsPlex.ts
msgid "Not connected."
msgstr ""
@ -3063,6 +3033,10 @@ msgstr ""
msgid "Password stage"
msgstr ""
#: src/user/user-settings/UserSettingsPage.ts
msgid "Password, 2FA, etc"
msgstr ""
#: src/pages/stages/prompt/PromptForm.ts
msgid "Password: Masked input, password is validated against sources. Policies still have to be applied to this Stage. If two of these are used in the same stage, they are ensured to be identical."
msgstr ""
@ -3463,7 +3437,6 @@ msgstr ""
msgid "Required."
msgstr ""
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/UserForm.ts
#: src/user/user-settings/UserSelfForm.ts
@ -3841,10 +3814,8 @@ msgstr ""
msgid "Source linked"
msgstr ""
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/pages/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/SourceSettingsPlex.ts
#: src/user/user-settings/sources/SourceSettingsOAuth.ts
#: src/user/user-settings/sources/SourceSettingsPlex.ts
msgid "Source {0}"
msgstr ""
@ -3954,8 +3925,7 @@ msgstr ""
msgid "Static Tokens"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
msgid "Static tokens"
msgstr ""
@ -3971,21 +3941,15 @@ msgstr ""
msgid "Status"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Disabled"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Enabled"
msgstr ""
@ -4126,7 +4090,6 @@ msgid "Successfully created tenant."
msgstr ""
#: src/pages/tokens/TokenForm.ts
#: src/pages/user-settings/tokens/UserTokenForm.ts
#: src/user/user-settings/tokens/UserTokenForm.ts
msgid "Successfully created token."
msgstr ""
@ -4185,13 +4148,11 @@ msgstr ""
msgid "Successfully updated certificate-key pair."
msgstr ""
#: src/pages/user-settings/UserSelfForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Successfully updated details."
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "Successfully updated device."
msgstr ""
@ -4286,7 +4247,6 @@ msgid "Successfully updated tenant."
msgstr ""
#: src/pages/tokens/TokenForm.ts
#: src/pages/user-settings/tokens/UserTokenForm.ts
#: src/user/user-settings/tokens/UserTokenForm.ts
msgid "Successfully updated token."
msgstr ""
@ -4523,8 +4483,7 @@ msgstr ""
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Time-based One-Time Passwords"
msgstr ""
@ -4571,7 +4530,6 @@ msgid "Token validity"
msgstr ""
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Token(s)"
msgstr ""
@ -4585,7 +4543,6 @@ msgstr ""
msgid "Tokens & App passwords"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Tokens and App passwords"
msgstr ""
@ -4707,6 +4664,10 @@ msgstr ""
#~ msgid "Unmanaged"
#~ msgstr ""
#: src/interfaces/UserInterface.ts
msgid "Unread notifications"
msgstr ""
#: src/pages/admin-overview/charts/LDAPSyncStatusChart.ts
msgid "Unsynced sources"
msgstr ""
@ -4746,18 +4707,13 @@ msgstr ""
#: src/pages/stages/prompt/PromptListPage.ts
#: src/pages/tenants/TenantListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/UserActiveForm.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserViewPage.ts
#: src/user/user-settings/UserSelfForm.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Update"
msgstr ""
@ -4841,7 +4797,6 @@ msgid "Update Tenant"
msgstr ""
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Update Token"
msgstr ""
@ -4856,7 +4811,6 @@ msgstr ""
msgid "Update available"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Update details"
msgstr ""
@ -4934,7 +4888,6 @@ msgstr ""
#: src/pages/property-mappings/PropertyMappingTestForm.ts
#: src/pages/tokens/TokenForm.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/UserListPage.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "User"
@ -4953,10 +4906,10 @@ msgstr ""
msgid "User Reputation"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "User Settings"
msgstr ""
#:
#:
#~ msgid "User Settings"
#~ msgstr ""
#: src/pages/stages/password/PasswordStageForm.ts
msgid "User database + LDAP password"
@ -4970,7 +4923,6 @@ msgstr ""
msgid "User database + standard password"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "User details"
msgstr ""
@ -4983,6 +4935,10 @@ msgstr ""
msgid "User fields"
msgstr ""
#: src/interfaces/AdminInterface.ts
msgid "User interface"
msgstr ""
#: src/pages/sources/oauth/OAuthSourceForm.ts
#: src/pages/sources/plex/PlexSourceForm.ts
msgid "User matching mode"
@ -5013,7 +4969,6 @@ msgstr ""
msgid "User's avatar"
msgstr ""
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "User's display name."
@ -5038,7 +4993,6 @@ msgstr ""
#: src/flows/stages/identification/IdentificationStage.ts
#: src/pages/policies/reputation/UserReputationListPage.ts
#: src/pages/stages/identification/IdentificationStageForm.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/UserForm.ts
@ -5162,8 +5116,7 @@ msgstr ""
msgid "WebAuthn Authenticators"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorWebAuthn.ts
msgid "WebAuthn Devices"
msgstr ""
@ -5254,7 +5207,6 @@ msgstr ""
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
#: src/pages/tenants/TenantListPage.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/pages/users/GroupSelectModal.ts
#: src/pages/users/UserListPage.ts
#: src/user/user-settings/tokens/UserTokenList.ts
@ -5266,7 +5218,6 @@ msgid "You can only select providers that match the type of the outpost."
msgstr ""
#: src/interfaces/AdminInterface.ts
#: src/interfaces/UserInterface.ts
msgid "You're currently impersonating {0}. Click to stop."
msgstr ""

View File

@ -1,170 +0,0 @@
import { t } from "@lingui/macro";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined";
import { until } from "lit-html/directives/until";
import { Application, CoreApi } from "@goauthentik/api";
import { AKResponse } from "../api/Client";
import { DEFAULT_CONFIG } from "../api/Config";
import { me } from "../api/Users";
import { loading, truncate } from "../utils";
import "../elements/PageHeader";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import AKGlobal from "../authentik.css";
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
@customElement("ak-library-app")
export class LibraryApplication extends LitElement {
@property({ attribute: false })
application?: Application;
static get styles(): CSSResult[] {
return [
PFBase,
PFCard,
PFButton,
PFAvatar,
AKGlobal,
css`
.pf-c-card {
height: 100%;
}
i.pf-icon {
height: 36px;
display: flex;
flex-direction: column;
justify-content: center;
}
.pf-c-avatar {
--pf-c-avatar--BorderRadius: 0;
}
.pf-c-card__header {
min-height: 60px;
justify-content: space-between;
}
.pf-c-card__header a {
display: flex;
flex-direction: column;
justify-content: center;
margin-right: 0.25em;
}
`,
];
}
render(): TemplateResult {
if (!this.application) {
return html`<ak-spinner></ak-spinner>`;
}
return html` <div class="pf-c-card pf-m-hoverable pf-m-compact">
<div class="pf-c-card__header">
${this.application.metaIcon
? html`<a href="${ifDefined(this.application.launchUrl ?? "")}"
><img
class="app-icon pf-c-avatar"
src="${ifDefined(this.application.metaIcon)}"
alt="Application Icon"
/></a>`
: html`<i class="fas fas fa-share-square"></i>`}
${until(
me().then((u) => {
if (!u.user.isSuperuser) return html``;
return html`
<a
class="pf-c-button pf-m-control pf-m-small"
href="#/core/applications/${this.application?.slug}"
>
<i class="fas fa-pencil-alt"></i>
</a>
`;
}),
)}
</div>
<div class="pf-c-card__title">
<p id="card-1-check-label">
<a href="${ifDefined(this.application.launchUrl ?? "")}"
>${this.application.name}</a
>
</p>
<div class="pf-c-content">
<small>${this.application.metaPublisher}</small>
</div>
</div>
<div class="pf-c-card__body">${truncate(this.application.metaDescription, 35)}</div>
</div>`;
}
}
@customElement("ak-library")
export class LibraryPage extends LitElement {
@property({ attribute: false })
apps?: AKResponse<Application>;
pageTitle(): string {
return t`My Applications`;
}
static get styles(): CSSResult[] {
return [PFBase, PFEmptyState, PFTitle, PFPage, PFContent, PFGallery, AKGlobal].concat(css`
:host,
main {
height: 100%;
}
`);
}
firstUpdated(): void {
new CoreApi(DEFAULT_CONFIG).coreApplicationsList({}).then((apps) => {
this.apps = apps;
});
}
renderEmptyState(): TemplateResult {
return html` <div class="pf-c-empty-state pf-m-full-height">
<div class="pf-c-empty-state__content">
<i class="fas fa-cubes pf-c-empty-state__icon" aria-hidden="true"></i>
<h1 class="pf-c-title pf-m-lg">${t`No Applications available.`}</h1>
<div class="pf-c-empty-state__body">
${t`Either no applications are defined, or you don't have access to any.`}
</div>
</div>
</div>`;
}
renderApps(): TemplateResult {
return html`<div class="pf-l-gallery pf-m-gutter">
${this.apps?.results.map(
(app) => html`<ak-library-app .application=${app}></ak-library-app>`,
)}
</div>`;
}
render(): TemplateResult {
return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
<ak-page-header icon="pf-icon pf-icon-applications" header=${t`Applications`}>
</ak-page-header>
<section class="pf-c-page__main-section">
${loading(
this.apps,
html`${(this.apps?.results.length || 0) > 0
? this.renderApps()
: this.renderEmptyState()}`,
)}
</section>
</main>`;
}
}

View File

@ -1,100 +0,0 @@
import { t } from "@lingui/macro";
import { customElement, html, TemplateResult } from "lit-element";
import { CoreApi, UserSelf } from "@goauthentik/api";
import { ifDefined } from "lit-html/directives/if-defined";
import { DEFAULT_CONFIG, tenant } from "../../api/Config";
import "../../elements/forms/FormElement";
import "../../elements/EmptyState";
import "../../elements/forms/Form";
import "../../elements/forms/HorizontalFormElement";
import { until } from "lit-html/directives/until";
import { ModelForm } from "../../elements/forms/ModelForm";
@customElement("ak-user-self-form")
export class UserSelfForm extends ModelForm<UserSelf, number> {
viewportCheck = false;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
loadInstance(pk: number): Promise<UserSelf> {
return new CoreApi(DEFAULT_CONFIG).coreUsersMeRetrieve().then((su) => {
return su.user;
});
}
getSuccessMessage(): string {
return t`Successfully updated details.`;
}
send = (data: UserSelf): Promise<UserSelf> => {
return new CoreApi(DEFAULT_CONFIG)
.coreUsersUpdateSelfUpdate({
userSelfRequest: data,
})
.then((su) => {
return su.user;
});
};
renderForm(): TemplateResult {
if (!this.instance) {
return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`;
}
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Username`} ?required=${true} name="username">
<input
type="text"
value="${ifDefined(this.instance?.username)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.`}
</p>
</ak-form-element-horizontal>
<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
/>
<p class="pf-c-form__helper-text">${t`User's display name.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Email`} name="email">
<input
type="email"
value="${ifDefined(this.instance?.email)}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>
<div class="pf-c-form__group pf-m-action">
<div class="pf-c-form__horizontal-group">
<div class="pf-c-form__actions">
<button
@click=${(ev: Event) => {
return this.submit(ev);
}}
class="pf-c-button pf-m-primary"
>
${t`Update`}
</button>
${until(
tenant().then((tenant) => {
if (tenant.flowUnenrollment) {
return html`<a
class="pf-c-button pf-m-danger"
href="/if/flow/${tenant.flowUnenrollment}"
>
${t`Delete account`}
</a>`;
}
return html``;
}),
)}
</div>
</div>
</div>
</form>`;
}
}

View File

@ -1,186 +0,0 @@
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import PFSizing from "@patternfly/patternfly/utilities/Sizing/sizing.css";
import PFFlex from "@patternfly/patternfly/utilities/Flex/flex.css";
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import AKGlobal from "../../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import { SourcesApi, StagesApi, UserSetting } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { until } from "lit-html/directives/until";
import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/Tabs";
import "../../elements/PageHeader";
import "./tokens/UserTokenList";
import "./UserSelfForm";
import "./settings/UserSettingsAuthenticatorDuo";
import "./settings/UserSettingsAuthenticatorStatic";
import "./settings/UserSettingsAuthenticatorTOTP";
import "./settings/UserSettingsAuthenticatorWebAuthn";
import "./settings/UserSettingsPassword";
import "./settings/SourceSettingsOAuth";
import "./settings/SourceSettingsPlex";
import { EVENT_REFRESH } from "../../constants";
@customElement("ak-user-settings")
export class UserSettingsPage extends LitElement {
static get styles(): CSSResult[] {
return [
PFBase,
PFPage,
PFFlex,
PFDisplay,
PFGallery,
PFContent,
PFCard,
PFDescriptionList,
PFSizing,
PFForm,
PFFormControl,
AKGlobal,
];
}
@property({ attribute: false })
userSettings?: Promise<UserSetting[]>;
@property({ attribute: false })
sourceSettings?: Promise<UserSetting[]>;
constructor() {
super();
this.addEventListener(EVENT_REFRESH, () => {
this.firstUpdated();
});
}
firstUpdated(): void {
this.userSettings = new StagesApi(DEFAULT_CONFIG).stagesAllUserSettingsList();
this.sourceSettings = new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList();
}
renderStageSettings(stage: UserSetting): TemplateResult {
switch (stage.component) {
case "ak-user-settings-authenticator-webauthn":
return html`<ak-user-settings-authenticator-webauthn
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-webauthn>`;
case "ak-user-settings-password":
return html`<ak-user-settings-password
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-password>`;
case "ak-user-settings-authenticator-totp":
return html`<ak-user-settings-authenticator-totp
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-totp>`;
case "ak-user-settings-authenticator-static":
return html`<ak-user-settings-authenticator-static
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-static>`;
case "ak-user-settings-authenticator-duo":
return html`<ak-user-settings-authenticator-duo
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-duo>`;
default:
return html`<p>${t`Error: unsupported stage settings: ${stage.component}`}</p>`;
}
}
renderSourceSettings(source: UserSetting): TemplateResult {
switch (source.component) {
case "ak-user-settings-source-oauth":
return html`<ak-user-settings-source-oauth
objectId=${source.objectUid}
title=${source.title}
.configureUrl=${source.configureUrl}
>
</ak-user-settings-source-oauth>`;
case "ak-user-settings-source-plex":
return html`<ak-user-settings-source-plex
objectId=${source.objectUid}
title=${source.title}
>
</ak-user-settings-source-plex>`;
default:
return html`<p>${t`Error: unsupported source settings: ${source.component}`}</p>`;
}
}
render(): TemplateResult {
return html`<div class="pf-c-page">
<main role="main" class="pf-c-page__main" tabindex="-1">
<ak-page-header
icon="pf-icon pf-icon-user"
header=${t`User Settings`}
description=${t`Configure settings relevant to your user profile.`}
>
</ak-page-header>
<ak-tabs ?vertical="${true}" style="height: 100%;">
<section
slot="page-details"
data-tab-title="${t`User details`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<div class="pf-c-card">
<div class="pf-c-card__title">${t`Update details`}</div>
<div class="pf-c-card__body">
<ak-user-self-form .instancePk=${1}></ak-user-self-form>
</div>
</div>
</section>
<section
slot="page-tokens"
data-tab-title="${t`Tokens and App passwords`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<ak-user-token-list></ak-user-token-list>
</section>
${until(
this.userSettings?.then((stages) => {
return stages.map((stage) => {
return html`<section
slot="page-${stage.objectUid}"
data-tab-title="${ifDefined(stage.title)}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
${this.renderStageSettings(stage)}
</section>`;
});
}),
)}
${until(
this.sourceSettings?.then((source) => {
return source.map((stage) => {
return html`<section
slot="page-${stage.objectUid}"
data-tab-title="${ifDefined(stage.title)}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
${this.renderSourceSettings(stage)}
</section>`;
});
}),
)}
</ak-tabs>
</main>
</div>`;
}
}

View File

@ -1,19 +0,0 @@
import { CSSResult, LitElement, property } from "lit-element";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import AKGlobal from "../../../authentik.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
export abstract class BaseUserSettings extends LitElement {
@property()
objectId!: string;
@property()
configureUrl?: string;
static get styles(): CSSResult[] {
return [PFBase, PFCard, PFButton, PFForm, PFFormControl, AKGlobal];
}
}

View File

@ -1,100 +0,0 @@
import { AuthenticatorsApi } from "@goauthentik/api";
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { STATIC_TOKEN_STYLE } from "../../../flows/stages/authenticator_static/AuthenticatorStaticStage";
import { BaseUserSettings } from "./BaseUserSettings";
import { EVENT_REFRESH } from "../../../constants";
@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`<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,
})
.then(() => {
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
});
});
}}
>
${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.configureUrl
? html`<a
href="${this.configureUrl}?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`Static tokens`}</div>
${until(
new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsStaticList({})
.then((devices) => {
return devices.results.length > 0
? this.renderEnabled()
: this.renderDisabled();
}),
)}
</div>`;
}
}

View File

@ -1,79 +0,0 @@
import { AuthenticatorsApi } from "@goauthentik/api";
import { t } from "@lingui/macro";
import { customElement, html, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { BaseUserSettings } from "./BaseUserSettings";
import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-user-settings-authenticator-totp")
export class UserSettingsAuthenticatorTOTP extends BaseUserSettings {
renderEnabled(): TemplateResult {
return html`<div class="pf-c-card__body">
<p>
${t`Status: Enabled`}
<i class="pf-icon pf-icon-ok"></i>
</p>
</div>
<div class="pf-c-card__footer">
<button
class="pf-c-button pf-m-danger"
@click=${() => {
return new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsTotpList({})
.then((devices) => {
if (devices.results.length < 1) {
return;
}
// TODO: Handle multiple devices, currently we assume only one TOTP Device
return new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsTotpDestroy({
id: devices.results[0].pk || 0,
})
.then(() => {
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
});
});
}}
>
${t`Disable Time-based OTP`}
</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.configureUrl
? html`<a
href="${this.configureUrl}?next=/%23%2Fuser"
class="pf-c-button pf-m-primary"
>${t`Enable TOTP`}
</a>`
: html``}
</div>`;
}
render(): TemplateResult {
return html`<div class="pf-c-card">
<div class="pf-c-card__title">${t`Time-based One-Time Passwords`}</div>
${until(
new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsTotpList({}).then((devices) => {
return devices.results.length > 0
? this.renderEnabled()
: this.renderDisabled();
}),
)}
</div>`;
}
}

View File

@ -1,62 +0,0 @@
import { CoreApi, IntentEnum, Token } from "@goauthentik/api";
import { t } from "@lingui/macro";
import { customElement, property } 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 { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-user-token-form")
export class UserTokenForm extends ModelForm<Token, string> {
@property()
intent: IntentEnum = IntentEnum.Api;
loadInstance(pk: string): Promise<Token> {
return new CoreApi(DEFAULT_CONFIG).coreTokensRetrieve({
identifier: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated token.`;
} else {
return t`Successfully created token.`;
}
}
send = (data: Token): Promise<Token> => {
if (this.instance) {
return new CoreApi(DEFAULT_CONFIG).coreTokensUpdate({
identifier: this.instance.identifier,
tokenRequest: data,
});
} else {
data.intent = this.intent;
return new CoreApi(DEFAULT_CONFIG).coreTokensCreate({
tokenRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${t`Identifier`} ?required=${true} name="identifier">
<input
type="text"
value="${ifDefined(this.instance?.identifier)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Description`} name="description">
<input
type="text"
value="${ifDefined(this.instance?.description)}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -1,156 +0,0 @@
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, property, TemplateResult } from "lit-element";
import { AKResponse } from "../../../api/Client";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import "../../../elements/forms/DeleteBulkForm";
import "../../../elements/forms/ModalForm";
import "../../../elements/buttons/ModalButton";
import "../../../elements/buttons/Dropdown";
import "../../../elements/buttons/TokenCopyButton";
import { Table, TableColumn } from "../../../elements/table/Table";
import { PAGE_SIZE } from "../../../constants";
import { CoreApi, IntentEnum, Token } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import "./UserTokenForm";
import { IntentToLabel } from "../../tokens/TokenListPage";
@customElement("ak-user-token-list")
export class UserTokenList extends Table<Token> {
searchEnabled(): boolean {
return true;
}
expandable = true;
checkbox = true;
@property()
order = "expires";
apiEndpoint(page: number): Promise<AKResponse<Token>> {
return new CoreApi(DEFAULT_CONFIG).coreTokensList({
ordering: this.order,
page: page,
pageSize: PAGE_SIZE,
search: this.search || "",
});
}
columns(): TableColumn[] {
return [new TableColumn(t`Identifier`, "identifier"), new TableColumn("")];
}
static get styles(): CSSResult[] {
return super.styles.concat(PFDescriptionList);
}
renderToolbar(): TemplateResult {
return html`
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create Token`} </span>
<ak-user-token-form intent=${IntentEnum.Api} slot="form"> </ak-user-token-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Create Token`}
</button>
</ak-forms-modal>
<ak-forms-modal>
<span slot="submit"> ${t`Create`} </span>
<span slot="header"> ${t`Create App password`} </span>
<ak-user-token-form intent=${IntentEnum.AppPassword} slot="form">
</ak-user-token-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${t`Create App password`}
</button>
</ak-forms-modal>
${super.renderToolbar()}
`;
}
renderExpanded(item: Token): TemplateResult {
return html` <td role="cell" colspan="3">
<div class="pf-c-table__expandable-row-content">
<dl class="pf-c-description-list pf-m-horizontal">
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`User`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${item.user?.username}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Expiring`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${item.expiring ? t`Yes` : t`No`}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Expiring`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${item.expiring ? item.expires?.toLocaleString() : "-"}
</div>
</dd>
</div>
<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text">${t`Intent`}</span>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${IntentToLabel(item.intent || IntentEnum.Api)}
</div>
</dd>
</div>
</dl>
</div>
</td>
<td></td>`;
}
renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${t`Token(s)`}
.objects=${this.selectedElements}
.delete=${(item: Token) => {
return new CoreApi(DEFAULT_CONFIG).coreTokensDestroy({
identifier: item.identifier,
});
}}
>
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`}
</button>
</ak-forms-delete-bulk>`;
}
row(item: Token): TemplateResult[] {
return [
html`${item.identifier}`,
html`
<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update Token`} </span>
<ak-user-token-form slot="form" .instancePk=${item.identifier}>
</ak-user-token-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<i class="fas fa-edit"></i>
</button>
</ak-forms-modal>
<ak-token-copy-button identifier="${item.identifier}">
${t`Copy Key`}
</ak-token-copy-button>
`,
];
}
}

View File

@ -12,7 +12,6 @@ import "./pages/events/TransportListPage";
import "./pages/flows/FlowListPage";
import "./pages/flows/FlowViewPage";
import "./pages/groups/GroupListPage";
import "./user/LibraryPage";
import "./pages/outposts/OutpostListPage";
import "./pages/outposts/ServiceConnectionListPage";
import "./pages/policies/PolicyListPage";
@ -29,7 +28,6 @@ import "./pages/stages/StageListPage";
import "./pages/system-tasks/SystemTaskListPage";
import "./pages/tenants/TenantListPage";
import "./pages/tokens/TokenListPage";
import "./pages/user-settings/UserSettingsPage";
import "./pages/users/UserListPage";
import "./pages/users/UserViewPage";
@ -37,7 +35,7 @@ export const ROUTES: Route[] = [
// Prevent infinite Shell loops
new Route(new RegExp("^/$")).redirect("/administration/overview"),
new Route(new RegExp("^#.*")).redirect("/administration/overview"),
new Route(new RegExp("^/library$"), html`<ak-library></ak-library>`),
new Route(new RegExp("^/library$")).redirectRaw("/if/user/"),
new Route(
new RegExp("^/administration/overview$"),
html`<ak-admin-overview></ak-admin-overview>`,
@ -112,5 +110,4 @@ export const ROUTES: Route[] = [
new RegExp("^/crypto/certificates$"),
html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`,
),
new Route(new RegExp("^/user$"), html`<ak-user-settings></ak-user-settings>`),
];

View File

@ -9,5 +9,5 @@ export const ROUTES: Route[] = [
new Route(new RegExp("^/$")).redirect("/library"),
new Route(new RegExp("^#.*")).redirect("/library"),
new Route(new RegExp("^/library$"), html`<ak-library></ak-library>`),
new Route(new RegExp("^/user$"), html`<ak-user-settings></ak-user-settings>`),
new Route(new RegExp("^/settings$"), html`<ak-user-settings></ak-user-settings>`),
];

View File

@ -15,17 +15,16 @@ import { AKResponse } from "../api/Client";
import { DEFAULT_CONFIG } from "../api/Config";
import { me } from "../api/Users";
import { loading, truncate } from "../utils";
import "../elements/PageHeader";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import AKGlobal from "../authentik.css";
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
import PFGallery from "@patternfly/patternfly/layouts/Gallery/gallery.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import { uiConfig } from "./config";
@customElement("ak-library-app")
export class LibraryApplication extends LitElement {
@ -81,16 +80,21 @@ export class LibraryApplication extends LitElement {
/></a>`
: html`<i class="fas fas fa-share-square"></i>`}
${until(
me().then((u) => {
if (!u.user.isSuperuser) return html``;
return html`
<a
class="pf-c-button pf-m-control pf-m-small"
href="#/core/applications/${this.application?.slug}"
>
<i class="fas fa-pencil-alt"></i>
</a>
`;
uiConfig().then((config) => {
if (!config.enabledFeatures.applicationEdit) {
return html``;
}
return me().then((u) => {
if (!u.user.isSuperuser) return html``;
return html`
<a
class="pf-c-button pf-m-control pf-m-small"
href="#/core/applications/${this.application?.slug}"
>
<i class="fas fa-pencil-alt"></i>
</a>
`;
});
}),
)}
</div>
@ -119,10 +123,11 @@ export class LibraryPage extends LitElement {
}
static get styles(): CSSResult[] {
return [PFBase, PFEmptyState, PFTitle, PFPage, PFContent, PFGallery, AKGlobal].concat(css`
return [PFBase, PFEmptyState, PFPage, PFContent, PFGallery, AKGlobal].concat(css`
:host,
main {
height: 100%;
padding: 3% 5%;
}
`);
}
@ -147,16 +152,17 @@ export class LibraryPage extends LitElement {
renderApps(): TemplateResult {
return html`<div class="pf-l-gallery pf-m-gutter">
${this.apps?.results.map(
(app) => html`<ak-library-app .application=${app}></ak-library-app>`,
)}
${this.apps?.results
.filter((app) => app.launchUrl)
.map((app) => html`<ak-library-app .application=${app}></ak-library-app>`)}
</div>`;
}
render(): TemplateResult {
return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
<ak-page-header icon="pf-icon pf-icon-applications" header=${t`Applications`}>
</ak-page-header>
<div class="pf-c-content">
<h1>${t`My applications`}</h1>
</div>
<section class="pf-c-page__main-section">
${loading(
this.apps,

31
web/src/user/config.ts Normal file
View File

@ -0,0 +1,31 @@
export interface UIConfig {
enabledFeatures: {
// API Request drawer in navbar
apiDrawer: boolean;
// Notification drawer in navbar
notificationDrawer: boolean;
// Settings in user dropdown
settings: boolean;
// Application edit in library (only shown when user is superuser)
applicationEdit: boolean;
};
navbar: {
userDisplay: "username" | "name" | "email";
};
}
export const DefaultUIConfig: UIConfig = {
enabledFeatures: {
apiDrawer: true,
notificationDrawer: true,
settings: true,
applicationEdit: true,
},
navbar: {
userDisplay: "name",
},
};
export function uiConfig(): Promise<UIConfig> {
return Promise.resolve(DefaultUIConfig);
}

View File

@ -2,7 +2,7 @@ import { CSSResult, LitElement, property } from "lit-element";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import AKGlobal from "../../../authentik.css";
import AKGlobal from "../../authentik.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";

View File

@ -77,7 +77,7 @@ export class UserSelfForm extends ModelForm<UserSelf, number> {
}}
class="pf-c-button pf-m-primary"
>
${t`Update`}
${t`Save`}
</button>
${until(
tenant().then((tenant) => {

View File

@ -1,5 +1,5 @@
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { CSSResult, customElement, html, LitElement, TemplateResult } from "lit-element";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
@ -13,22 +13,15 @@ import AKGlobal from "../../authentik.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import { SourcesApi, StagesApi, UserSetting } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../../api/Config";
import { until } from "lit-html/directives/until";
import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/Tabs";
import "../../elements/PageHeader";
import "../../elements/user/SessionList";
import "../../elements/user/UserConsentList";
import "./tokens/UserTokenList";
import "./UserSelfForm";
import "./settings/UserSettingsAuthenticatorDuo";
import "./settings/UserSettingsAuthenticatorStatic";
import "./settings/UserSettingsAuthenticatorTOTP";
import "./settings/UserSettingsAuthenticatorWebAuthn";
import "./settings/UserSettingsPassword";
import "./settings/SourceSettingsOAuth";
import "./settings/SourceSettingsPlex";
import { EVENT_REFRESH } from "../../constants";
import "./sources/SourceSettings";
import "./stages/StageSettings";
import { until } from "lit-html/directives/until";
import { me } from "../../api/Users";
@customElement("ak-user-settings")
export class UserSettingsPage extends LitElement {
@ -49,91 +42,10 @@ export class UserSettingsPage extends LitElement {
];
}
@property({ attribute: false })
userSettings?: Promise<UserSetting[]>;
@property({ attribute: false })
sourceSettings?: Promise<UserSetting[]>;
constructor() {
super();
this.addEventListener(EVENT_REFRESH, () => {
this.firstUpdated();
});
}
firstUpdated(): void {
this.userSettings = new StagesApi(DEFAULT_CONFIG).stagesAllUserSettingsList();
this.sourceSettings = new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList();
}
renderStageSettings(stage: UserSetting): TemplateResult {
switch (stage.component) {
case "ak-user-settings-authenticator-webauthn":
return html`<ak-user-settings-authenticator-webauthn
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-webauthn>`;
case "ak-user-settings-password":
return html`<ak-user-settings-password
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-password>`;
case "ak-user-settings-authenticator-totp":
return html`<ak-user-settings-authenticator-totp
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-totp>`;
case "ak-user-settings-authenticator-static":
return html`<ak-user-settings-authenticator-static
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-static>`;
case "ak-user-settings-authenticator-duo":
return html`<ak-user-settings-authenticator-duo
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-duo>`;
default:
return html`<p>${t`Error: unsupported stage settings: ${stage.component}`}</p>`;
}
}
renderSourceSettings(source: UserSetting): TemplateResult {
switch (source.component) {
case "ak-user-settings-source-oauth":
return html`<ak-user-settings-source-oauth
objectId=${source.objectUid}
title=${source.title}
.configureUrl=${source.configureUrl}
>
</ak-user-settings-source-oauth>`;
case "ak-user-settings-source-plex":
return html`<ak-user-settings-source-plex
objectId=${source.objectUid}
title=${source.title}
>
</ak-user-settings-source-plex>`;
default:
return html`<p>${t`Error: unsupported source settings: ${source.component}`}</p>`;
}
}
render(): TemplateResult {
return html`<div class="pf-c-page">
<main role="main" class="pf-c-page__main" tabindex="-1">
<ak-page-header
icon="pf-icon pf-icon-user"
header=${t`User Settings`}
description=${t`Configure settings relevant to your user profile.`}
>
</ak-page-header>
<ak-tabs ?vertical="${true}" style="height: 100%;">
<ak-tabs ?vertical="${true}">
<section
slot="page-details"
data-tab-title="${t`User details`}"
@ -146,6 +58,46 @@ export class UserSettingsPage extends LitElement {
</div>
</div>
</section>
<section
slot="page-sessions"
data-tab-title="${t`Sessions`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
${until(
me().then((u) => {
return html`<ak-user-session-list
targetUser=${u.user.username}
></ak-user-session-list>`;
}),
)}
</section>
<section
slot="page-consents"
data-tab-title="${t`Consent`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
${until(
me().then((u) => {
return html`<ak-user-consent-list
userId=${u.user.pk}
></ak-user-consent-list>`;
}),
)}
</section>
<section
slot="page-stages"
data-tab-title="${t`Password, 2FA, etc`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<ak-user-settings-stage></ak-user-settings-stage>
</section>
<section
slot="page-sources"
data-tab-title="${t`Connected services`}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<ak-user-settings-source></ak-user-settings-source>
</section>
<section
slot="page-tokens"
data-tab-title="${t`Tokens and App passwords`}"
@ -153,32 +105,6 @@ export class UserSettingsPage extends LitElement {
>
<ak-user-token-list></ak-user-token-list>
</section>
${until(
this.userSettings?.then((stages) => {
return stages.map((stage) => {
return html`<section
slot="page-${stage.objectUid}"
data-tab-title="${ifDefined(stage.title)}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
${this.renderStageSettings(stage)}
</section>`;
});
}),
)}
${until(
this.sourceSettings?.then((source) => {
return source.map((stage) => {
return html`<section
slot="page-${stage.objectUid}"
data-tab-title="${ifDefined(stage.title)}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
${this.renderSourceSettings(stage)}
</section>`;
});
}),
)}
</ak-tabs>
</main>
</div>`;

View File

@ -1,50 +0,0 @@
import { customElement, html, property, TemplateResult } from "lit-element";
import { BaseUserSettings } from "./BaseUserSettings";
import { SourcesApi } from "@goauthentik/api";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { t } from "@lingui/macro";
import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-user-settings-source-oauth")
export class SourceSettingsOAuth extends BaseUserSettings {
@property()
title!: string;
render(): TemplateResult {
return html`<div class="pf-c-card">
<div class="pf-c-card__title">${t`Source ${this.title}`}</div>
<div class="pf-c-card__body">${this.renderInner()}</div>
</div>`;
}
renderInner(): TemplateResult {
return html`${until(
new SourcesApi(DEFAULT_CONFIG)
.sourcesUserConnectionsOauthList({
sourceSlug: this.objectId,
})
.then((connection) => {
if (connection.results.length > 0) {
return html`<p>${t`Connected.`}</p>
<button
class="pf-c-button pf-m-danger"
@click=${() => {
return new SourcesApi(
DEFAULT_CONFIG,
).sourcesUserConnectionsOauthDestroy({
id: connection.results[0].pk || 0,
});
}}
>
${t`Disconnect`}
</button>`;
}
return html`<p>${t`Not connected.`}</p>
<a class="pf-c-button pf-m-primary" href=${ifDefined(this.configureUrl)}>
${t`Connect`}
</a>`;
}),
)}`;
}
}

View File

@ -1,46 +0,0 @@
import { customElement, html, property, TemplateResult } from "lit-element";
import { BaseUserSettings } from "./BaseUserSettings";
import { SourcesApi } from "@goauthentik/api";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { t } from "@lingui/macro";
@customElement("ak-user-settings-source-plex")
export class SourceSettingsPlex extends BaseUserSettings {
@property()
title!: string;
render(): TemplateResult {
return html`<div class="pf-c-card">
<div class="pf-c-card__title">${t`Source ${this.title}`}</div>
<div class="pf-c-card__body">${this.renderInner()}</div>
</div>`;
}
renderInner(): TemplateResult {
return html`${until(
new SourcesApi(DEFAULT_CONFIG)
.sourcesUserConnectionsPlexList({
sourceSlug: this.objectId,
})
.then((connection) => {
if (connection.results.length > 0) {
return html`<p>${t`Connected.`}</p>
<button
class="pf-c-button pf-m-danger"
@click=${() => {
return new SourcesApi(
DEFAULT_CONFIG,
).sourcesUserConnectionsPlexDestroy({
id: connection.results[0].pk || 0,
});
}}
>
${t`Disconnect`}
</button>`;
}
return html`<p>${t`Not connected.`}</p>`;
}),
)}`;
}
}

View File

@ -1,79 +0,0 @@
import { AuthenticatorsApi } from "@goauthentik/api";
import { t } from "@lingui/macro";
import { customElement, html, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { BaseUserSettings } from "./BaseUserSettings";
import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-user-settings-authenticator-duo")
export class UserSettingsAuthenticatorDuo extends BaseUserSettings {
renderEnabled(): TemplateResult {
return html`<div class="pf-c-card__body">
<p>
${t`Status: Enabled`}
<i class="pf-icon pf-icon-ok"></i>
</p>
</div>
<div class="pf-c-card__footer">
<button
class="pf-c-button pf-m-danger"
@click=${() => {
return new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsDuoList({})
.then((devices) => {
if (devices.results.length < 1) {
return;
}
// TODO: Handle multiple devices, currently we assume only one TOTP Device
return new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsDuoDestroy({
id: devices.results[0].pk || 0,
})
.then(() => {
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
});
});
}}
>
${t`Disable Duo authenticator`}
</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.configureUrl
? html`<a
href="${this.configureUrl}?next=/%23%2Fuser"
class="pf-c-button pf-m-primary"
>${t`Enable Duo authenticator`}
</a>`
: html``}
</div>`;
}
render(): TemplateResult {
return html`<div class="pf-c-card">
<div class="pf-c-card__title">${t`Duo`}</div>
${until(
new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsDuoList({}).then((devices) => {
return devices.results.length > 0
? this.renderEnabled()
: this.renderDisabled();
}),
)}
</div>`;
}
}

View File

@ -1,125 +0,0 @@
import { CSSResult, customElement, html, TemplateResult } from "lit-element";
import { t } from "@lingui/macro";
import { AuthenticatorsApi, WebAuthnDevice } from "@goauthentik/api";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { BaseUserSettings } from "./BaseUserSettings";
import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css";
import "../../../elements/buttons/ModalButton";
import "../../../elements/buttons/SpinnerButton";
import "../../../elements/forms/DeleteForm";
import "../../../elements/forms/Form";
import "../../../elements/forms/ModalForm";
import "../../../elements/forms/HorizontalFormElement";
import { ifDefined } from "lit-html/directives/if-defined";
import { EVENT_REFRESH } from "../../../constants";
@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`<ak-forms-delete
.obj=${device}
objectLabel=${t`Authenticator`}
.delete=${() => {
return new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsWebauthnDestroy({
id: device.pk || 0,
})
.then(() => {
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
});
}}
>
<button slot="trigger" class="pf-c-button pf-m-danger">${t`Delete`}</button>
</ak-forms-delete>`;
}
renderUpdate(device: WebAuthnDevice): TemplateResult {
return html`<ak-forms-modal>
<span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update`} </span>
<ak-form
slot="form"
successMessage=${t`Successfully updated device.`}
.send=${(data: unknown) => {
return new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsWebauthnUpdate({
id: device.pk || 0,
webAuthnDeviceRequest: data as WebAuthnDevice,
})
.then(() => {
this.requestUpdate();
});
}}
>
<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${t`Device name`}
?required=${true}
name="name"
>
<input
type="text"
value="${ifDefined(device.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
</form>
</ak-form>
<button slot="trigger" class="pf-c-button pf-m-primary">${t`Update`}</button>
</ak-forms-modal>`;
}
render(): TemplateResult {
return html`<div class="pf-c-card">
<div class="pf-c-card__title">${t`WebAuthn Devices`}</div>
<div class="pf-c-card__body">
<ul class="pf-c-data-list" role="list">
${until(
new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsWebauthnList({})
.then((devices) => {
return devices.results.map((device) => {
return html`<li class="pf-c-data-list__item">
<div class="pf-c-data-list__item-row">
<div class="pf-c-data-list__item-content">
<div class="pf-c-data-list__cell">
${device.name || "-"}
</div>
<div class="pf-c-data-list__cell">
${t`Created ${device.createdOn?.toLocaleString()}`}
</div>
<div class="pf-c-data-list__cell">
${this.renderUpdate(device)}
${this.renderDelete(device)}
</div>
</div>
</div>
</li>`;
});
}),
)}
</ul>
</div>
<div class="pf-c-card__footer">
${this.configureUrl
? html`<a
href="${this.configureUrl}?next=/%23%2Fuser"
class="pf-c-button pf-m-primary"
>${t`Configure WebAuthn`}
</a>`
: html``}
</div>
</div>`;
}
}

View File

@ -1,20 +0,0 @@
import { customElement, html, TemplateResult } from "lit-element";
import { t } from "@lingui/macro";
import { BaseUserSettings } from "./BaseUserSettings";
import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-user-settings-password")
export class UserSettingsPassword extends BaseUserSettings {
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.
return html`<div class="pf-c-card">
<div class="pf-c-card__title">${t`Change your password`}</div>
<div class="pf-c-card__body">
<a href="${ifDefined(this.configureUrl)}" class="pf-c-button pf-m-primary">
${t`Change password`}
</a>
</div>
</div>`;
}
}

View File

@ -0,0 +1,67 @@
import { SourcesApi, UserSetting } from "@goauthentik/api";
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { EVENT_REFRESH } from "../../../constants";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import "../../../elements/EmptyState";
import "./SourceSettingsOAuth";
import "./SourceSettingsPlex";
@customElement("ak-user-settings-source")
export class UserSourceSettingsPage extends LitElement {
@property({ attribute: false })
sourceSettings?: Promise<UserSetting[]>;
static get styles(): CSSResult[] {
return [PFGrid];
}
constructor() {
super();
this.addEventListener(EVENT_REFRESH, () => {
this.firstUpdated();
});
}
firstUpdated(): void {
this.sourceSettings = new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList();
}
renderSourceSettings(source: UserSetting): TemplateResult {
switch (source.component) {
case "ak-user-settings-source-oauth":
return html`<ak-user-settings-source-oauth
objectId=${source.objectUid}
title=${source.title}
.configureUrl=${source.configureUrl}
>
</ak-user-settings-source-oauth>`;
case "ak-user-settings-source-plex":
return html`<ak-user-settings-source-plex
objectId=${source.objectUid}
title=${source.title}
>
</ak-user-settings-source-plex>`;
default:
return html`<p>${t`Error: unsupported source settings: ${source.component}`}</p>`;
}
}
render(): TemplateResult {
return html`<div class="pf-l-grid pf-m-gutter">
${until(
this.sourceSettings?.then((source) => {
return source.map((stage) => {
return html`<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl">
${this.renderSourceSettings(stage)}
</div>`;
});
}),
html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`,
)}
</div>`;
}
}

View File

@ -1,5 +1,5 @@
import { customElement, html, property, TemplateResult } from "lit-element";
import { BaseUserSettings } from "./BaseUserSettings";
import { BaseUserSettings } from "../BaseUserSettings";
import { SourcesApi } from "@goauthentik/api";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";

View File

@ -1,5 +1,5 @@
import { customElement, html, property, TemplateResult } from "lit-element";
import { BaseUserSettings } from "./BaseUserSettings";
import { BaseUserSettings } from "../BaseUserSettings";
import { SourcesApi } from "@goauthentik/api";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";

View File

@ -0,0 +1,87 @@
import { StagesApi, UserSetting } from "@goauthentik/api";
import { t } from "@lingui/macro";
import { CSSResult, customElement, html, LitElement, property, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { EVENT_REFRESH } from "../../../constants";
import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css";
import "../../../elements/EmptyState";
import "./UserSettingsAuthenticatorDuo";
import "./UserSettingsAuthenticatorStatic";
import "./UserSettingsAuthenticatorTOTP";
import "./UserSettingsAuthenticatorWebAuthn";
import "./UserSettingsPassword";
@customElement("ak-user-settings-stage")
export class UserStageSettingsPage extends LitElement {
@property({ attribute: false })
userSettings?: Promise<UserSetting[]>;
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`<ak-user-settings-authenticator-webauthn
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-webauthn>`;
case "ak-user-settings-password":
return html`<ak-user-settings-password
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-password>`;
case "ak-user-settings-authenticator-totp":
return html`<ak-user-settings-authenticator-totp
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-totp>`;
case "ak-user-settings-authenticator-static":
return html`<ak-user-settings-authenticator-static
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-static>`;
case "ak-user-settings-authenticator-duo":
return html`<ak-user-settings-authenticator-duo
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-duo>`;
default:
return html`<p>${t`Error: unsupported stage settings: ${stage.component}`}</p>`;
}
}
render(): TemplateResult {
return html`<div class="pf-l-stack pf-m-gutter">
${until(
this.userSettings?.then((stages) => {
return stages.map((stage) => {
return html`<div class="pf-l-stack__item">
${this.renderStageSettings(stage)}
</div>`;
});
}),
html`<ak-empty-state ?loading="${true}" header=${t`Loading`}></ak-empty-state>`,
)}
</div>`;
}
}

View File

@ -3,7 +3,7 @@ import { t } from "@lingui/macro";
import { customElement, html, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { BaseUserSettings } from "./BaseUserSettings";
import { BaseUserSettings } from "../BaseUserSettings";
import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-user-settings-authenticator-duo")

View File

@ -4,7 +4,7 @@ import { CSSResult, customElement, html, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { STATIC_TOKEN_STYLE } from "../../../flows/stages/authenticator_static/AuthenticatorStaticStage";
import { BaseUserSettings } from "./BaseUserSettings";
import { BaseUserSettings } from "../BaseUserSettings";
import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-user-settings-authenticator-static")

View File

@ -3,7 +3,7 @@ import { t } from "@lingui/macro";
import { customElement, html, TemplateResult } from "lit-element";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { BaseUserSettings } from "./BaseUserSettings";
import { BaseUserSettings } from "../BaseUserSettings";
import { EVENT_REFRESH } from "../../../constants";
@customElement("ak-user-settings-authenticator-totp")

View File

@ -3,7 +3,7 @@ import { t } from "@lingui/macro";
import { AuthenticatorsApi, WebAuthnDevice } from "@goauthentik/api";
import { until } from "lit-html/directives/until";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { BaseUserSettings } from "./BaseUserSettings";
import { BaseUserSettings } from "../BaseUserSettings";
import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css";
import "../../../elements/buttons/ModalButton";
import "../../../elements/buttons/SpinnerButton";

View File

@ -1,6 +1,6 @@
import { customElement, html, TemplateResult } from "lit-element";
import { t } from "@lingui/macro";
import { BaseUserSettings } from "./BaseUserSettings";
import { BaseUserSettings } from "../BaseUserSettings";
import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ak-user-settings-password")

View File

@ -77,7 +77,7 @@ export class UserTokenList extends Table<Token> {
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${item.user?.username}
${item.userObj?.username}
</div>
</dd>
</div>

View File

@ -5,6 +5,14 @@ slug: "2021.9"
## Headline Changes
- Split user interface
This release splits the administration interface from the end-user interface. This makes things clearer for end-users, as all their options are layed out more clearly.
Additionally, the new end-user interface will be more customisable than the admin interface, allowing Administrators to configure what their users can see.
The admin interface remains the same, and familiar buttons will redirect you between interfaces.
- New proxy
The proxy outpost has been rewritten from scratch. This replaces the old proxy, which was based on oauth2_proxy. The new proxy allows us a much greater degree of flexibility, is much lighter and reports errors better.

View File

@ -69,6 +69,16 @@ rules:
- delete
- list
- patch
- apiGroups:
- monitoring.coreos.com
resources:
- servicemonitors
verbs:
- get
- create
- delete
- list
- patch
- apiGroups:
- apiextensions.k8s.io
resources: