interface split (#943)

This commit is contained in:
Jens L 2021-09-16 17:30:16 +02:00 committed by GitHub
parent d7ab2a362a
commit 9441be1ee2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1804 additions and 243 deletions

View File

@ -25,14 +25,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run pylint
run: pipenv run pylint authentik tests lifecycle
@ -43,14 +43,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run black
run: pipenv run black --check authentik tests lifecycle
@ -61,14 +61,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run isort
run: pipenv run isort --check authentik tests lifecycle
@ -79,14 +79,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run bandit
run: pipenv run bandit -r authentik tests lifecycle
@ -113,14 +113,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run migrations
run: pipenv run python -m lifecycle.migrate
@ -138,14 +138,14 @@ jobs:
# Copy current, latest config to local
cp authentik/lib/default.yml local.env.yml
git checkout $(git describe --abbrev=0 --match 'version/*')
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run migrations to stable
run: pipenv run python -m lifecycle.migrate
@ -168,14 +168,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- uses: testspace-com/setup-testspace@v1
with:
@ -197,14 +197,14 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- uses: testspace-com/setup-testspace@v1
with:
@ -236,14 +236,14 @@ jobs:
- uses: testspace-com/setup-testspace@v1
with:
domain: ${{github.repository_owner}}
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
# - id: cache-pipenv
# uses: actions/cache@v2.1.6
# with:
# path: ~/.local/share/virtualenvs
# key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
# env:
# INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: |
scripts/ci_prepare.sh
docker-compose -f tests/e2e/ci.docker-compose.yml up -d

View File

@ -17,7 +17,6 @@
{% endblock %}
<link rel="stylesheet" type="text/css" href="{% static 'dist/authentik.css' %}">
<script src="{% static 'dist/poly.js' %}" type="module"></script>
<script>window["polymerSkipLoadingFontRoboto"] = true;</script>
{% block head %}
{% endblock %}
</head>

View File

@ -4,6 +4,7 @@
{% load i18n %}
{% block head_before %}
{{ block.super }}
{% if flow.compatibility_mode %}
<script>ShadyDOM = { force: !navigator.webdriver };</script>
{% endif %}

View File

@ -0,0 +1,28 @@
{% extends "base/skeleton.html" %}
{% load static %}
{% load i18n %}
{% block head %}
<script src="{% static 'dist/UserInterface.js' %}" type="module"></script>
{% endblock %}
{% block body %}
<ak-message-container></ak-message-container>
<ak-interface-user>
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
<div class="pf-c-empty-state" style="height: 100vh;">
<div class="pf-c-empty-state__content">
<span class="pf-c-spinner pf-m-xl pf-c-empty-state__icon" role="progressbar" aria-valuetext="{% trans 'Loading...' %}">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<h1 class="pf-c-title pf-m-lg">
{% trans "Loading..." %}
</h1>
</div>
</div>
</section>
</ak-interface-user>
{% endblock %}

View File

@ -12,7 +12,7 @@ from authentik.core.views.session import EndSessionView
urlpatterns = [
path(
"",
login_required(RedirectView.as_view(pattern_name="authentik_core:if-admin")),
login_required(RedirectView.as_view(pattern_name="authentik_core:if-user")),
name="root-redirect",
),
# Impersonation
@ -32,6 +32,11 @@ urlpatterns = [
ensure_csrf_cookie(TemplateView.as_view(template_name="if/admin.html")),
name="if-admin",
),
path(
"if/user/",
ensure_csrf_cookie(TemplateView.as_view(template_name="if/user.html")),
name="if-user",
),
path(
"if/flow/<slug:flow_slug>/",
ensure_csrf_cookie(FlowInterfaceView.as_view()),

View File

@ -5,7 +5,6 @@ from django.db.models import F, Q
from django.db.models import Value as V
from django.http.request import HttpRequest
from authentik import __version__
from authentik.lib.config import CONFIG
from authentik.tenants.models import Tenant
@ -31,6 +30,5 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
tenant = getattr(request, "tenant", DEFAULT_TENANT)
return {
"tenant": tenant,
"ak_version": __version__,
"footer_links": CONFIG.y("footer_links"),
}

View File

@ -52,7 +52,7 @@ class TestFlowsAuthenticator(SeleniumTestCase):
code_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(totp.token())
code_stage.find_element(By.CSS_SELECTOR, "input[name=code]").send_keys(Keys.ENTER)
self.wait_for_url(self.if_admin_url("/library"))
self.wait_for_url(self.if_user_url("/library"))
self.assert_user(USER())
@retry()
@ -67,7 +67,7 @@ class TestFlowsAuthenticator(SeleniumTestCase):
self.driver.get(self.url("authentik_core:if-flow", flow_slug=flow.slug))
self.login()
self.wait_for_url(self.if_admin_url("/library"))
self.wait_for_url(self.if_user_url("/library"))
self.assert_user(USER())
self.driver.get(
@ -112,7 +112,7 @@ class TestFlowsAuthenticator(SeleniumTestCase):
self.driver.get(self.url("authentik_core:if-flow", flow_slug=flow.slug))
self.login()
self.wait_for_url(self.if_admin_url("/library"))
self.wait_for_url(self.if_user_url("/library"))
self.assert_user(USER())
self.driver.get(

View File

@ -96,11 +96,11 @@ class TestFlowsEnroll(SeleniumTestCase):
self.initial_stages()
interface_admin = self.get_shadow_root("ak-interface-admin")
wait = WebDriverWait(interface_admin, self.wait_timeout)
interface_user = self.get_shadow_root("ak-interface-user")
wait = WebDriverWait(interface_user, self.wait_timeout)
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
self.driver.get(self.if_admin_url("/user"))
self.driver.get(self.if_user_url("/user"))
user = User.objects.get(username="foo")
self.assertEqual(user.username, "foo")
@ -195,10 +195,10 @@ class TestFlowsEnroll(SeleniumTestCase):
sleep(2)
# We're now logged in
wait = WebDriverWait(self.get_shadow_root("ak-interface-admin"), self.wait_timeout)
wait = WebDriverWait(self.get_shadow_root("ak-interface-user"), self.wait_timeout)
wait.until(ec.presence_of_element_located((By.CSS_SELECTOR, "ak-sidebar")))
self.driver.get(self.if_admin_url("/user"))
self.driver.get(self.if_user_url("/user"))
self.assert_user(User.objects.get(username="foo"))

View File

@ -22,5 +22,5 @@ class TestFlowsLogin(SeleniumTestCase):
)
)
self.login()
self.wait_for_url(self.if_admin_url("/library"))
self.wait_for_url(self.if_user_url("/library"))
self.assert_user(USER())

View File

@ -42,7 +42,7 @@ class TestFlowsStageSetup(SeleniumTestCase):
)
)
self.login()
self.wait_for_url(self.if_admin_url("/library"))
self.wait_for_url(self.if_user_url("/library"))
self.driver.get(
self.url(
@ -62,7 +62,7 @@ class TestFlowsStageSetup(SeleniumTestCase):
Keys.ENTER
)
self.wait_for_url(self.if_admin_url("/library"))
self.wait_for_url(self.if_user_url("/library"))
# Because USER() is cached, we need to get the user manually here
user = User.objects.get(username=USER().username)
self.assertTrue(user.check_password(new_password))

View File

@ -174,8 +174,8 @@ class TestSourceOAuth2(SeleniumTestCase):
prompt_stage.find_element(By.CSS_SELECTOR, "input[name=username]").send_keys(Keys.ENTER)
# Wait until we've logged in
self.wait_for_url(self.if_admin_url("/library"))
self.driver.get(self.if_admin_url("/user"))
self.wait_for_url(self.if_user_url("/library"))
self.driver.get(self.if_user_url("/user"))
self.assert_user(User(username="foo", name="admin", email="admin@example.com"))
@ -253,8 +253,8 @@ class TestSourceOAuth2(SeleniumTestCase):
self.driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
# Wait until we've logged in
self.wait_for_url(self.if_admin_url("/library"))
self.driver.get(self.if_admin_url("/user"))
self.wait_for_url(self.if_user_url("/library"))
self.driver.get(self.if_user_url("/user"))
self.assert_user(User(username="foo", name="admin", email="admin@example.com"))
@ -348,7 +348,7 @@ class TestSourceOAuth1(SeleniumTestCase):
# Wait until we've loaded the user info page
sleep(2)
# Wait until we've logged in
self.wait_for_url(self.if_admin_url("/library"))
self.driver.get(self.if_admin_url("/user"))
self.wait_for_url(self.if_user_url("/library"))
self.driver.get(self.if_user_url("/user"))
self.assert_user(User(username="example-user", name="test name", email="foo@example.com"))

View File

@ -153,8 +153,8 @@ class TestSourceSAML(SeleniumTestCase):
self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER)
# Wait until we're logged in
self.wait_for_url(self.if_admin_url("/library"))
self.driver.get(self.if_admin_url("/user"))
self.wait_for_url(self.if_user_url("/library"))
self.driver.get(self.if_user_url("/user"))
self.assert_user(
User.objects.exclude(username="akadmin")
@ -233,8 +233,8 @@ class TestSourceSAML(SeleniumTestCase):
self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER)
# Wait until we're logged in
self.wait_for_url(self.if_admin_url("/library"))
self.driver.get(self.if_admin_url("/user"))
self.wait_for_url(self.if_user_url("/library"))
self.driver.get(self.if_user_url("/user"))
self.assert_user(
User.objects.exclude(username="akadmin")
@ -300,8 +300,8 @@ class TestSourceSAML(SeleniumTestCase):
self.driver.find_element(By.ID, "password").send_keys(Keys.ENTER)
# Wait until we're logged in
self.wait_for_url(self.if_admin_url("/library"))
self.driver.get(self.if_admin_url("/user"))
self.wait_for_url(self.if_user_url("/library"))
self.driver.get(self.if_user_url("/user"))
self.assert_user(
User.objects.exclude(username="akadmin")

View File

@ -126,9 +126,9 @@ class SeleniumTestCase(StaticLiveServerTestCase):
"""reverse `view` with `**kwargs` into full URL using live_server_url"""
return self.live_server_url + reverse(view, kwargs=kwargs)
def if_admin_url(self, view) -> str:
def if_user_url(self, view) -> str:
"""same as self.url() but show URL in shell"""
return f"{self.live_server_url}/if/admin/#{view}"
return f"{self.live_server_url}/if/user/#{view}"
def get_shadow_root(self, selector: str, container: Optional[WebElement] = None) -> WebElement:
"""Get shadow root element's inner shadowRoot"""

View File

@ -1,2 +1,4 @@
// @ts-ignore
window["polymerSkipLoadingFontRoboto"] = true;
import "construct-style-sheets-polyfill";
import "@webcomponents/webcomponentsjs";

View File

@ -87,40 +87,7 @@ export default [
clearScreen: false,
},
},
// Main Application
{
input: "./src/interfaces/AdminInterface.ts",
context: "window",
output: [
{
format: "es",
dir: "dist",
sourcemap: true,
manualChunks: manualChunks,
chunkFileNames: "admin-[name].js",
},
],
plugins: [
cssimport(),
resolve({ extensions, browser: true }),
commonjs(),
babel({
extensions,
babelHelpers: "runtime",
include: ["src/**/*"],
}),
replace({
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
"preventAssignment": true,
}),
sourcemaps(),
isProdBuild && terser(),
].filter((p) => p),
watch: {
clearScreen: false,
},
},
// Flow executor
// Flow interface
{
input: "./src/interfaces/FlowInterface.ts",
context: "window",
@ -153,4 +120,70 @@ export default [
clearScreen: false,
},
},
// Admin interface
{
input: "./src/interfaces/AdminInterface.ts",
context: "window",
output: [
{
format: "es",
dir: "dist",
sourcemap: true,
manualChunks: manualChunks,
chunkFileNames: "admin-[name].js",
},
],
plugins: [
cssimport(),
resolve({ extensions, browser: true }),
commonjs(),
babel({
extensions,
babelHelpers: "runtime",
include: ["src/**/*"],
}),
replace({
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
"preventAssignment": true,
}),
sourcemaps(),
isProdBuild && terser(),
].filter((p) => p),
watch: {
clearScreen: false,
},
},
// User interface
{
input: "./src/interfaces/UserInterface.ts",
context: "window",
output: [
{
format: "es",
dir: "dist",
sourcemap: true,
manualChunks: manualChunks,
chunkFileNames: "user-[name].js",
},
],
plugins: [
cssimport(),
resolve({ extensions, browser: true }),
commonjs(),
babel({
extensions,
babelHelpers: "runtime",
include: ["src/**/*"],
}),
replace({
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
"preventAssignment": true,
}),
sourcemaps(),
isProdBuild && terser(),
].filter((p) => p),
watch: {
clearScreen: false,
},
},
];

View File

@ -83,9 +83,6 @@ html > form > input {
color: var(--pf-global--danger-color--100);
}
body {
background-color: var(--ak-dark-background) !important;
}
.ak-static-page h1 {
color: var(--ak-dark-foreground);
}
@ -99,6 +96,9 @@ body {
}
@media (prefers-color-scheme: dark) {
body {
background-color: var(--ak-dark-background) !important;
}
:root {
--pf-global--Color--100: var(--ak-dark-foreground);
--pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker);

View File

@ -8,7 +8,6 @@ import {
TemplateResult,
} from "lit-element";
import { Route } from "./Route";
import { ROUTES } from "../../routes";
import { RouteMatch } from "./RouteMatch";
import AKGlobal from "../../authentik.css";
@ -45,6 +44,9 @@ export class RouterOutlet extends LitElement {
@property()
defaultUrl?: string;
@property({ attribute: false })
routes: Route[] = [];
static get styles(): CSSResult[] {
return [
AKGlobal,
@ -59,8 +61,6 @@ export class RouterOutlet extends LitElement {
}
}
*:first-child {
height: 100%;
display: flex;
flex-direction: column;
}
`,
@ -90,7 +90,7 @@ export class RouterOutlet extends LitElement {
return;
}
let matchedRoute: RouteMatch | null = null;
ROUTES.some((route) => {
this.routes.some((route) => {
const match = route.url.exec(activeUrl);
if (match != null) {
matchedRoute = new RouteMatch(route);

View File

@ -33,6 +33,7 @@ import {
import { AdminApi, Version } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../api/Config";
import { WebsocketClient } from "../common/ws";
import { ROUTES } from "../routesAdmin";
@customElement("ak-interface-admin")
export class AdminInterface extends LitElement {
@ -110,7 +111,8 @@ export class AdminInterface extends LitElement {
class="pf-c-page__main"
tabindex="-1"
id="main-content"
defaultUrl="/library"
defaultUrl="/administration/overview"
.routes=${ROUTES}
>
</ak-router-outlet>
</main>
@ -135,9 +137,11 @@ export class AdminInterface extends LitElement {
}
renderSidebarItems(): TemplateResult {
const superUserCondition = () => {
return me().then((u) => u.user.isSuperuser || false);
};
me().then((u) => {
if (!u.user.isSuperuser) {
window.location.assign("/if/user");
}
});
return html`
${until(
this.version.then((version) => {
@ -167,19 +171,16 @@ export class AdminInterface extends LitElement {
return html``;
}),
)}
<ak-sidebar-item path="/library">
<span slot="label">${t`Library`}</span>
<ak-sidebar-item path="/if/user/" ?isAbsoluteLink=${true} ?highlight=${true}>
<span slot="label">${t`Go to user interface`}</span>
</ak-sidebar-item>
<ak-sidebar-item .condition=${superUserCondition}>
<span slot="label">${t`Monitor`}</span>
<ak-sidebar-item path="/administration/overview">
<span slot="label">${t`Overview`}</span>
</ak-sidebar-item>
<ak-sidebar-item path="/administration/system-tasks">
<span slot="label">${t`System Tasks`}</span>
</ak-sidebar-item>
</ak-sidebar-item>
<ak-sidebar-item .condition=${superUserCondition}>
<ak-sidebar-item>
<span slot="label">${t`Resources`}</span>
<ak-sidebar-item
path="/core/applications"
@ -203,7 +204,7 @@ export class AdminInterface extends LitElement {
<span slot="label">${t`Tenants`}</span>
</ak-sidebar-item>
</ak-sidebar-item>
<ak-sidebar-item .condition=${superUserCondition}>
<ak-sidebar-item>
<span slot="label">${t`Outposts`}</span>
<ak-sidebar-item path="/outpost/outposts">
<span slot="label">${t`Outposts`}</span>
@ -212,7 +213,7 @@ export class AdminInterface extends LitElement {
<span slot="label">${t`Integrations`}</span>
</ak-sidebar-item>
</ak-sidebar-item>
<ak-sidebar-item .condition=${superUserCondition}>
<ak-sidebar-item>
<span slot="label">${t`Events`}</span>
<ak-sidebar-item
path="/events/log"
@ -227,7 +228,7 @@ export class AdminInterface extends LitElement {
<span slot="label">${t`Notification Transports`}</span>
</ak-sidebar-item>
</ak-sidebar-item>
<ak-sidebar-item .condition=${superUserCondition}>
<ak-sidebar-item>
<span slot="label">${t`Customisation`}</span>
<ak-sidebar-item path="/policy/policies">
<span slot="label">${t`Policies`}</span>
@ -242,7 +243,7 @@ export class AdminInterface extends LitElement {
<span slot="label">${t`Property Mappings`}</span>
</ak-sidebar-item>
</ak-sidebar-item>
<ak-sidebar-item .condition=${superUserCondition}>
<ak-sidebar-item>
<span slot="label">${t`Flows`}</span>
<ak-sidebar-item
path="/flow/flows"
@ -260,7 +261,7 @@ export class AdminInterface extends LitElement {
<span slot="label">${t`Invitations`}</span>
</ak-sidebar-item>
</ak-sidebar-item>
<ak-sidebar-item .condition=${superUserCondition}>
<ak-sidebar-item>
<span slot="label">${t`Identity & Cryptography`}</span>
<ak-sidebar-item
path="/identity/users"

View File

@ -0,0 +1,176 @@
import "../elements/messages/MessageContainer";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { me } from "../api/Users";
import "./locale";
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 PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.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 { WebsocketClient } from "../common/ws";
import { ROUTES } from "../routesUser";
@customElement("ak-interface-user")
export class UserInterface extends LitElement {
@property({ type: Boolean })
sidebarOpen = true;
@property({ type: Boolean })
notificationOpen = false;
@property({ type: Boolean })
apiDrawerOpen = false;
ws: WebsocketClient;
private version: Promise<Version>;
static get styles(): CSSResult[] {
return [
PFBase,
PFPage,
PFButton,
PFDrawer,
AKGlobal,
css`
.pf-c-page__main,
.pf-c-drawer__content,
.pf-c-page__drawer {
z-index: auto !important;
}
.display-none {
display: none;
}
`,
];
}
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();
}
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>
<div class="pf-c-page__drawer">
<div
class="pf-c-drawer ${this.notificationOpen || this.apiDrawerOpen
? "pf-m-expanded"
: "pf-m-collapsed"}"
>
<div class="pf-c-drawer__main">
<div class="pf-c-drawer__content">
<div class="pf-c-drawer__body">
<main class="pf-c-page__main">
<ak-router-outlet
role="main"
class="pf-c-page__main"
tabindex="-1"
id="main-content"
defaultUrl="/library"
.routes=${ROUTES}
>
</ak-router-outlet>
</main>
</div>
</div>
<ak-notification-drawer
class="pf-c-drawer__panel pf-m-width-33 ${this.notificationOpen
? ""
: "display-none"}"
?hidden=${!this.notificationOpen}
></ak-notification-drawer>
<ak-api-drawer
class="pf-c-drawer__panel pf-m-width-33 ${this.apiDrawerOpen
? ""
: "display-none"}"
?hidden=${!this.apiDrawerOpen}
></ak-api-drawer>
</div>
</div>
</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

@ -1,39 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css" />
<link rel="stylesheet" type="text/css" href="/static/dist/page.css" />
<link rel="stylesheet" type="text/css" href="/static/dist/empty-state.css" />
<link rel="stylesheet" type="text/css" href="/static/dist/spinner.css" />
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css" />
<script src="/static/dist/poly.js" type="module"></script>
<script>
window["polymerSkipLoadingFontRoboto"] = true;
</script>
<script src="/static/dist/AdminInterface.js" type="module"></script>
<title>authentik</title>
</head>
<body>
<ak-message-container></ak-message-container>
<ak-interface-admin>
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
<div class="pf-c-empty-state" style="height: 100vh">
<div class="pf-c-empty-state__content">
<span
class="pf-c-spinner pf-m-xl pf-c-empty-state__icon"
role="progressbar"
aria-valuetext="Loading..."
>
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<h1 class="pf-c-title pf-m-lg">Loading...</h1>
</div>
</div>
</section>
</ak-interface-admin>
</body>
</html>

View File

@ -1,40 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<link rel="stylesheet" type="text/css" href="/static/dist/patternfly-base.css" />
<link rel="stylesheet" type="text/css" href="/static/dist/page.css" />
<link rel="stylesheet" type="text/css" href="/static/dist/empty-state.css" />
<link rel="stylesheet" type="text/css" href="/static/dist/spinner.css" />
<link rel="stylesheet" type="text/css" href="/static/dist/authentik.css" />
<script>
ShadyDOM = { force: !navigator.webdriver };
window["polymerSkipLoadingFontRoboto"] = true;
</script>
<script src="/static/dist/poly.js" type="module"></script>
<script src="/static/dist/FlowInterface.js" type="module"></script>
<title>authentik</title>
</head>
<body>
<ak-message-container></ak-message-container>
<ak-flow-executor>
<section class="ak-static-page pf-c-page__main-section pf-m-no-padding-mobile pf-m-xl">
<div class="pf-c-empty-state" style="height: 100vh">
<div class="pf-c-empty-state__content">
<span
class="pf-c-spinner pf-m-xl pf-c-empty-state__icon"
role="progressbar"
aria-valuetext="Loading..."
>
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
<h1 class="pf-c-title pf-m-lg">Loading...</h1>
</div>
</div>
</section>
</ak-flow-executor>
</body>
</html>

View File

@ -34,6 +34,7 @@ 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."
@ -167,6 +168,10 @@ msgstr "Additional group DN, prepended to the Base DN."
msgid "Additional user DN, prepended to the Base DN."
msgstr "Additional user DN, prepended to the Base DN."
#:
#~ msgid "Admin"
#~ msgstr "Admin"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
@ -282,6 +287,7 @@ msgstr "Application(s)"
#: src/pages/LibraryPage.ts
#: src/pages/applications/ApplicationListPage.ts
#: src/pages/outposts/OutpostForm.ts
#: src/user/LibraryPage.ts
msgid "Applications"
msgstr "Applications"
@ -381,6 +387,7 @@ msgstr "Authentication flow"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
msgid "Authenticator"
msgstr "Authenticator"
@ -602,6 +609,7 @@ msgid "Certificates"
msgstr "Certificates"
#: src/pages/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/settings/UserSettingsPassword.ts
msgid "Change password"
msgstr "Change password"
@ -610,6 +618,7 @@ msgid "Change status"
msgstr "Change status"
#: src/pages/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/settings/UserSettingsPassword.ts
msgid "Change your password"
msgstr "Change your password"
@ -788,6 +797,7 @@ msgid "Configuration stage"
msgstr "Configuration stage"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
msgid "Configure WebAuthn"
msgstr "Configure WebAuthn"
@ -820,6 +830,7 @@ 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."
@ -836,11 +847,14 @@ msgid "Configure what data should be used as unique User Identifier. For most ca
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
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
msgid "Connected."
msgstr "Connected."
@ -927,6 +941,7 @@ msgid "Copy"
msgstr "Copy"
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Copy Key"
msgstr "Copy Key"
@ -988,11 +1003,15 @@ msgstr "Copy recovery link"
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
#: src/user/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
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"
msgstr "Create App password"
@ -1070,6 +1089,8 @@ 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"
msgstr "Create Token"
@ -1106,6 +1127,7 @@ msgid "Created by"
msgstr "Created by"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
msgid "Created {0}"
msgstr "Created {0}"
@ -1191,6 +1213,8 @@ msgstr "Define how notifications are sent to users, like Email or Webhook."
#: 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/tokens/UserTokenList.ts
msgid "Delete"
msgstr "Delete"
@ -1216,6 +1240,7 @@ msgstr "Delete"
#~ msgstr "Delete Session"
#: src/pages/user-settings/UserSelfForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Delete account"
msgstr "Delete account"
@ -1253,6 +1278,7 @@ msgstr "Deny the user access"
#: 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"
@ -1294,6 +1320,7 @@ 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
msgid "Device name"
msgstr "Device name"
@ -1312,14 +1339,17 @@ msgstr "Digits"
#~ msgstr "Disable"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
msgid "Disable Duo authenticator"
msgstr "Disable Duo authenticator"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Disable Static Tokens"
msgstr "Disable Static Tokens"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
msgid "Disable Time-based OTP"
msgstr "Disable Time-based OTP"
@ -1329,6 +1359,8 @@ 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
msgid "Disconnect"
msgstr "Disconnect"
@ -1364,6 +1396,7 @@ msgid "Dummy stage used for testing. Shows a simple continue button and always p
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
msgid "Duo"
msgstr "Duo"
@ -1423,6 +1456,7 @@ 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."
@ -1432,6 +1466,7 @@ msgstr "Either no applications are defined, or you don't have access to any."
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserViewPage.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Email"
msgstr "Email"
@ -1469,6 +1504,7 @@ msgstr "Embedded outpost is not configured correctly."
#~ msgstr "Enable"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
msgid "Enable Duo authenticator"
msgstr "Enable Duo authenticator"
@ -1477,10 +1513,12 @@ msgid "Enable StartTLS"
msgstr "Enable StartTLS"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Enable Static Tokens"
msgstr "Enable Static Tokens"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
msgid "Enable TOTP"
msgstr "Enable TOTP"
@ -1533,10 +1571,12 @@ 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
msgid "Error: unsupported source settings: {0}"
msgstr "Error: unsupported source settings: {0}"
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Error: unsupported stage settings: {0}"
msgstr "Error: unsupported stage settings: {0}"
@ -1622,6 +1662,8 @@ 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"
msgstr "Expiring"
@ -1884,6 +1926,10 @@ 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"
#: src/elements/table/TablePagination.ts
msgid "Go to next page"
msgstr "Go to next page"
@ -1892,6 +1938,10 @@ 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"
#: src/pages/events/RuleForm.ts
#: src/pages/policies/PolicyBindingForm.ts
#: src/pages/policies/PolicyBindingForm.ts
@ -2029,6 +2079,8 @@ msgstr "Icon shown in the browser tab."
#: 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"
msgstr "Identifier"
@ -2134,6 +2186,7 @@ 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"
@ -2274,7 +2327,7 @@ 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/AdminInterface.ts
#: src/interfaces/UserInterface.ts
msgid "Library"
msgstr "Library"
@ -2322,6 +2375,7 @@ msgstr "Load servers"
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/user/user-settings/UserSelfForm.ts
#: src/utils.ts
msgid "Loading"
msgstr "Loading"
@ -2541,11 +2595,12 @@ msgstr "Model deleted"
msgid "Model updated"
msgstr "Model updated"
#: src/interfaces/AdminInterface.ts
msgid "Monitor"
msgstr "Monitor"
#:
#~ msgid "Monitor"
#~ msgstr "Monitor"
#: src/pages/LibraryPage.ts
#: src/user/LibraryPage.ts
msgid "My Applications"
msgstr "My Applications"
@ -2627,6 +2682,7 @@ msgstr "My Applications"
#: src/pages/users/UserForm.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserViewPage.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Name"
msgstr "Name"
@ -2676,10 +2732,12 @@ msgstr "Newly created users are added to this group, if a group is selected."
#: 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."
@ -2747,6 +2805,8 @@ 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
msgid "Not connected."
msgstr "Not connected."
@ -3414,6 +3474,7 @@ msgstr "Required."
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/UserForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
@ -3790,6 +3851,8 @@ 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
msgid "Source {0}"
msgstr "Source {0}"
@ -3900,6 +3963,7 @@ msgid "Static Tokens"
msgstr "Static Tokens"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Static tokens"
msgstr "Static tokens"
@ -3918,12 +3982,18 @@ 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
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
msgid "Status: Enabled"
msgstr "Status: Enabled"
@ -4065,6 +4135,7 @@ 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."
@ -4123,10 +4194,12 @@ 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
msgid "Successfully updated device."
msgstr "Successfully updated device."
@ -4222,6 +4295,7 @@ 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."
@ -4465,6 +4539,7 @@ msgid "Time offset when temporary users should be deleted. This only applies if
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
msgid "Time-based One-Time Passwords"
msgstr "Time-based One-Time Passwords"
@ -4512,6 +4587,7 @@ 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)"
@ -4525,6 +4601,7 @@ 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"
@ -4692,6 +4769,11 @@ msgstr "Up-to-date!"
#: 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/tokens/UserTokenList.ts
msgid "Update"
msgstr "Update"
@ -4775,6 +4857,7 @@ 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"
@ -4789,6 +4872,7 @@ msgid "Update available"
msgstr "Update available"
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Update details"
msgstr "Update details"
@ -4867,6 +4951,7 @@ msgstr "Use this tenant for each domain that doesn't have a dedicated tenant."
#: 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"
msgstr "User"
@ -4884,6 +4969,7 @@ msgid "User Reputation"
msgstr "User Reputation"
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "User Settings"
msgstr "User Settings"
@ -4900,6 +4986,7 @@ 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"
@ -4943,6 +5030,7 @@ 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."
msgstr "User's display name."
@ -4971,6 +5059,7 @@ msgstr "Userinfo URL"
#: src/pages/users/UserForm.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserViewPage.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Username"
msgstr "Username"
@ -5000,6 +5089,10 @@ msgstr "Using flow"
msgid "Using source"
msgstr "Using source"
#: src/pages/users/ServiceAccountForm.ts
msgid "Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List."
msgstr "Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List."
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
msgid "Valid redirect URLs after a successful authorization flow. Also specify any origins here for Implicit flows."
msgstr "Valid redirect URLs after a successful authorization flow. Also specify any origins here for Implicit flows."
@ -5085,6 +5178,7 @@ msgid "WebAuthn Authenticators"
msgstr "WebAuthn Authenticators"
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
msgid "WebAuthn Devices"
msgstr "WebAuthn Devices"
@ -5180,6 +5274,7 @@ msgstr "X509 Subject"
#: 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 "Yes"
msgstr "Yes"
@ -5188,6 +5283,7 @@ 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,6 +34,7 @@ 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 ""
@ -167,6 +168,10 @@ msgstr ""
msgid "Additional user DN, prepended to the Base DN."
msgstr ""
#:
#~ msgid "Admin"
#~ msgstr ""
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
@ -282,6 +287,7 @@ msgstr ""
#: src/pages/LibraryPage.ts
#: src/pages/applications/ApplicationListPage.ts
#: src/pages/outposts/OutpostForm.ts
#: src/user/LibraryPage.ts
msgid "Applications"
msgstr ""
@ -377,6 +383,7 @@ msgstr ""
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
msgid "Authenticator"
msgstr ""
@ -598,6 +605,7 @@ msgid "Certificates"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/settings/UserSettingsPassword.ts
msgid "Change password"
msgstr ""
@ -606,6 +614,7 @@ msgid "Change status"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsPassword.ts
#: src/user/user-settings/settings/UserSettingsPassword.ts
msgid "Change your password"
msgstr ""
@ -782,6 +791,7 @@ msgid "Configuration stage"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
msgid "Configure WebAuthn"
msgstr ""
@ -814,6 +824,7 @@ 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 ""
@ -830,11 +841,14 @@ msgid "Configure what data should be used as unique User Identifier. For most ca
msgstr ""
#: src/pages/user-settings/settings/SourceSettingsOAuth.ts
#: src/user/user-settings/settings/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
msgid "Connected."
msgstr ""
@ -921,6 +935,7 @@ msgid "Copy"
msgstr ""
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Copy Key"
msgstr ""
@ -982,11 +997,15 @@ msgstr ""
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserListPage.ts
#: src/user/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
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"
msgstr ""
@ -1064,6 +1083,8 @@ 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"
msgstr ""
@ -1100,6 +1121,7 @@ msgid "Created by"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
msgid "Created {0}"
msgstr ""
@ -1185,6 +1207,8 @@ msgstr ""
#: 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/tokens/UserTokenList.ts
msgid "Delete"
msgstr ""
@ -1210,6 +1234,7 @@ msgstr ""
#~ msgstr ""
#: src/pages/user-settings/UserSelfForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Delete account"
msgstr ""
@ -1245,6 +1270,7 @@ msgstr ""
#: 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 ""
@ -1286,6 +1312,7 @@ msgid "Device classes which can be used to authenticate."
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
msgid "Device name"
msgstr ""
@ -1304,14 +1331,17 @@ msgstr ""
#~ msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
msgid "Disable Duo authenticator"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Disable Static Tokens"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
msgid "Disable Time-based OTP"
msgstr ""
@ -1321,6 +1351,8 @@ 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
msgid "Disconnect"
msgstr ""
@ -1356,6 +1388,7 @@ msgid "Dummy stage used for testing. Shows a simple continue button and always p
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
msgid "Duo"
msgstr ""
@ -1415,6 +1448,7 @@ 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 ""
@ -1424,6 +1458,7 @@ msgstr ""
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/pages/users/UserViewPage.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Email"
msgstr ""
@ -1461,6 +1496,7 @@ msgstr ""
#~ msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorDuo.ts
msgid "Enable Duo authenticator"
msgstr ""
@ -1469,10 +1505,12 @@ msgid "Enable StartTLS"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Enable Static Tokens"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
msgid "Enable TOTP"
msgstr ""
@ -1525,10 +1563,12 @@ msgid "Error when validating assertion on server: {err}"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Error: unsupported source settings: {0}"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Error: unsupported stage settings: {0}"
msgstr ""
@ -1614,6 +1654,8 @@ 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"
msgstr ""
@ -1876,6 +1918,10 @@ msgstr ""
msgid "Generate Certificate-Key Pair"
msgstr ""
#: src/interfaces/UserInterface.ts
msgid "Go to admin interface"
msgstr ""
#: src/elements/table/TablePagination.ts
msgid "Go to next page"
msgstr ""
@ -1884,6 +1930,10 @@ msgstr ""
msgid "Go to previous page"
msgstr ""
#: src/interfaces/AdminInterface.ts
msgid "Go to user interface"
msgstr ""
#: src/pages/events/RuleForm.ts
#: src/pages/policies/PolicyBindingForm.ts
#: src/pages/policies/PolicyBindingForm.ts
@ -2021,6 +2071,8 @@ msgstr ""
#: 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"
msgstr ""
@ -2126,6 +2178,7 @@ 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 ""
@ -2266,7 +2319,7 @@ msgstr ""
msgid "Let the user identify themselves with their username or Email address."
msgstr ""
#: src/interfaces/AdminInterface.ts
#: src/interfaces/UserInterface.ts
msgid "Library"
msgstr ""
@ -2314,6 +2367,7 @@ msgstr ""
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/user-settings/UserSelfForm.ts
#: src/user/user-settings/UserSelfForm.ts
#: src/utils.ts
msgid "Loading"
msgstr ""
@ -2533,11 +2587,12 @@ msgstr ""
msgid "Model updated"
msgstr ""
#: src/interfaces/AdminInterface.ts
msgid "Monitor"
msgstr ""
#:
#~ msgid "Monitor"
#~ msgstr ""
#: src/pages/LibraryPage.ts
#: src/user/LibraryPage.ts
msgid "My Applications"
msgstr ""
@ -2619,6 +2674,7 @@ msgstr ""
#: src/pages/users/UserForm.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserViewPage.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Name"
msgstr ""
@ -2668,10 +2724,12 @@ msgstr ""
#: 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 ""
@ -2739,6 +2797,8 @@ 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
msgid "Not connected."
msgstr ""
@ -3406,6 +3466,7 @@ msgstr ""
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/ServiceAccountForm.ts
#: src/pages/users/UserForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr ""
@ -3782,6 +3843,8 @@ 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
msgid "Source {0}"
msgstr ""
@ -3892,6 +3955,7 @@ msgid "Static Tokens"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorStatic.ts
msgid "Static tokens"
msgstr ""
@ -3910,12 +3974,18 @@ 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
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
msgid "Status: Enabled"
msgstr ""
@ -4057,6 +4127,7 @@ 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 ""
@ -4115,10 +4186,12 @@ 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
msgid "Successfully updated device."
msgstr ""
@ -4214,6 +4287,7 @@ 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 ""
@ -4450,6 +4524,7 @@ msgid "Time offset when temporary users should be deleted. This only applies if
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorTOTP.ts
msgid "Time-based One-Time Passwords"
msgstr ""
@ -4497,6 +4572,7 @@ msgstr ""
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Token(s)"
msgstr ""
@ -4510,6 +4586,7 @@ msgid "Tokens & App passwords"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Tokens and App passwords"
msgstr ""
@ -4677,6 +4754,11 @@ msgstr ""
#: 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/tokens/UserTokenList.ts
msgid "Update"
msgstr ""
@ -4760,6 +4842,7 @@ msgstr ""
#: src/pages/tokens/TokenListPage.ts
#: src/pages/user-settings/tokens/UserTokenList.ts
#: src/user/user-settings/tokens/UserTokenList.ts
msgid "Update Token"
msgstr ""
@ -4774,6 +4857,7 @@ msgid "Update available"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "Update details"
msgstr ""
@ -4852,6 +4936,7 @@ msgstr ""
#: 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"
msgstr ""
@ -4869,6 +4954,7 @@ msgid "User Reputation"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "User Settings"
msgstr ""
@ -4885,6 +4971,7 @@ msgid "User database + standard password"
msgstr ""
#: src/pages/user-settings/UserSettingsPage.ts
#: src/user/user-settings/UserSettingsPage.ts
msgid "User details"
msgstr ""
@ -4928,6 +5015,7 @@ msgstr ""
#: src/pages/user-settings/UserSelfForm.ts
#: src/pages/users/UserForm.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "User's display name."
msgstr ""
@ -4956,6 +5044,7 @@ msgstr ""
#: src/pages/users/UserForm.ts
#: src/pages/users/UserListPage.ts
#: src/pages/users/UserViewPage.ts
#: src/user/user-settings/UserSelfForm.ts
msgid "Username"
msgstr ""
@ -4985,6 +5074,10 @@ msgstr ""
msgid "Using source"
msgstr ""
#: src/pages/users/ServiceAccountForm.ts
msgid "Valid for 360 days, after which the password will automatically rotate. You can copy the password from the Token List."
msgstr ""
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
msgid "Valid redirect URLs after a successful authorization flow. Also specify any origins here for Implicit flows."
msgstr ""
@ -5070,6 +5163,7 @@ msgid "WebAuthn Authenticators"
msgstr ""
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
#: src/user/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
msgid "WebAuthn Devices"
msgstr ""
@ -5163,6 +5257,7 @@ msgstr ""
#: 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 "Yes"
msgstr ""
@ -5171,6 +5266,7 @@ 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

@ -12,7 +12,7 @@ import "./pages/events/TransportListPage";
import "./pages/flows/FlowListPage";
import "./pages/flows/FlowViewPage";
import "./pages/groups/GroupListPage";
import "./pages/LibraryPage";
import "./user/LibraryPage";
import "./pages/outposts/OutpostListPage";
import "./pages/outposts/ServiceConnectionListPage";
import "./pages/policies/PolicyListPage";
@ -35,8 +35,8 @@ import "./pages/users/UserViewPage";
export const ROUTES: Route[] = [
// Prevent infinite Shell loops
new Route(new RegExp("^/$")).redirect("/library"),
new Route(new RegExp("^#.*")).redirect("/library"),
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("^/administration/overview$"),

13
web/src/routesUser.ts Normal file
View File

@ -0,0 +1,13 @@
import { html } from "lit-html";
import { Route } from "./elements/router/Route";
import "./user/LibraryPage";
import "./user/user-settings/UserSettingsPage";
export const ROUTES: Route[] = [
// Prevent infinite Shell loops
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>`),
];

170
web/src/user/LibraryPage.ts Normal file
View File

@ -0,0 +1,170 @@
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

@ -0,0 +1,100 @@
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

@ -0,0 +1,186 @@
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

@ -0,0 +1,19 @@
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

@ -0,0 +1,50 @@
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

@ -0,0 +1,46 @@
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

@ -0,0 +1,79 @@
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

@ -0,0 +1,100 @@
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

@ -0,0 +1,79 @@
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

@ -0,0 +1,125 @@
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

@ -0,0 +1,20 @@
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,62 @@
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

@ -0,0 +1,156 @@
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 "../../../pages/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>
`,
];
}
}