commit
6232333a52
|
@ -3,7 +3,7 @@
|
|||
{% load static %}
|
||||
|
||||
{% block title %}
|
||||
API Browser - {{ config.authentik.branding.title }}
|
||||
API Browser - {{ tenant.branding_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
|
|
|
@ -14,13 +14,6 @@ from authentik.core.api.utils import PassiveSerializer
|
|||
from authentik.lib.config import CONFIG
|
||||
|
||||
|
||||
class FooterLinkSerializer(PassiveSerializer):
|
||||
"""Links returned in Config API"""
|
||||
|
||||
href = CharField(read_only=True)
|
||||
name = CharField(read_only=True)
|
||||
|
||||
|
||||
class Capabilities(models.TextChoices):
|
||||
"""Define capabilities which influence which APIs can/should be used"""
|
||||
|
||||
|
@ -30,10 +23,6 @@ class Capabilities(models.TextChoices):
|
|||
class ConfigSerializer(PassiveSerializer):
|
||||
"""Serialize authentik Config into DRF Object"""
|
||||
|
||||
branding_logo = CharField(read_only=True)
|
||||
branding_title = CharField(read_only=True)
|
||||
ui_footer_links = ListField(child=FooterLinkSerializer(), read_only=True)
|
||||
|
||||
error_reporting_enabled = BooleanField(read_only=True)
|
||||
error_reporting_environment = CharField(read_only=True)
|
||||
error_reporting_send_pii = BooleanField(read_only=True)
|
||||
|
@ -59,12 +48,9 @@ class ConfigView(APIView):
|
|||
"""Retrive public configuration options"""
|
||||
config = ConfigSerializer(
|
||||
{
|
||||
"branding_logo": CONFIG.y("authentik.branding.logo"),
|
||||
"branding_title": CONFIG.y("authentik.branding.title"),
|
||||
"error_reporting_enabled": CONFIG.y("error_reporting.enabled"),
|
||||
"error_reporting_environment": CONFIG.y("error_reporting.environment"),
|
||||
"error_reporting_send_pii": CONFIG.y("error_reporting.send_pii"),
|
||||
"ui_footer_links": CONFIG.y("authentik.footer_links"),
|
||||
"capabilities": self.get_capabilities(),
|
||||
}
|
||||
)
|
||||
|
|
|
@ -100,6 +100,7 @@ from authentik.stages.user_delete.api import UserDeleteStageViewSet
|
|||
from authentik.stages.user_login.api import UserLoginStageViewSet
|
||||
from authentik.stages.user_logout.api import UserLogoutStageViewSet
|
||||
from authentik.stages.user_write.api import UserWriteStageViewSet
|
||||
from authentik.tenants.api import TenantViewSet
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
|
||||
|
@ -111,6 +112,7 @@ router.register("core/groups", GroupViewSet)
|
|||
router.register("core/users", UserViewSet)
|
||||
router.register("core/user_consent", UserConsentViewSet)
|
||||
router.register("core/tokens", TokenViewSet)
|
||||
router.register("core/tenants", TenantViewSet)
|
||||
|
||||
router.register("outposts/instances", OutpostViewSet)
|
||||
router.register("outposts/service_connections/all", ServiceConnectionViewSet)
|
||||
|
|
|
@ -32,7 +32,7 @@ from authentik.core.middleware import (
|
|||
)
|
||||
from authentik.core.models import Token, TokenIntents, User
|
||||
from authentik.events.models import EventAction
|
||||
from authentik.flows.models import Flow, FlowDesignation
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
|
||||
class UserSerializer(ModelSerializer):
|
||||
|
@ -179,8 +179,9 @@ class UserViewSet(ModelViewSet):
|
|||
# pylint: disable=invalid-name, unused-argument
|
||||
def recovery(self, request: Request, pk: int) -> Response:
|
||||
"""Create a temporary link that a user can use to recover their accounts"""
|
||||
tenant: Tenant = request._request.tenant
|
||||
# Check that there is a recovery flow, if not return an error
|
||||
flow = Flow.with_policy(request, designation=FlowDesignation.RECOVERY)
|
||||
flow = tenant.flow_recovery
|
||||
if not flow:
|
||||
raise Http404
|
||||
user: User = self.get_object()
|
||||
|
@ -191,7 +192,8 @@ class UserViewSet(ModelViewSet):
|
|||
)
|
||||
querystring = urlencode({"token": token.key})
|
||||
link = request.build_absolute_uri(
|
||||
reverse_lazy("authentik_flows:default-recovery") + f"?{querystring}"
|
||||
reverse_lazy("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
|
||||
+ f"?{querystring}"
|
||||
)
|
||||
return Response({"link": link})
|
||||
|
||||
|
|
|
@ -42,10 +42,14 @@ class RequestIDMiddleware:
|
|||
if not hasattr(request, "request_id"):
|
||||
request_id = uuid4().hex
|
||||
setattr(request, "request_id", request_id)
|
||||
LOCAL.authentik = {"request_id": request_id}
|
||||
LOCAL.authentik = {
|
||||
"request_id": request_id,
|
||||
"host": request.get_host(),
|
||||
}
|
||||
response = self.get_response(request)
|
||||
response[RESPONSE_HEADER_ID] = request.request_id
|
||||
del LOCAL.authentik["request_id"]
|
||||
del LOCAL.authentik["host"]
|
||||
return response
|
||||
|
||||
|
||||
|
@ -54,4 +58,5 @@ def structlog_add_request_id(logger: Logger, method_name: str, event_dict):
|
|||
"""If threadlocal has authentik defined, add request_id to log"""
|
||||
if hasattr(LOCAL, "authentik"):
|
||||
event_dict["request_id"] = LOCAL.authentik.get("request_id", "")
|
||||
event_dict["host"] = LOCAL.authentik.get("host", "")
|
||||
return event_dict
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>{% block title %}{% trans title|default:config.authentik.branding.title %}{% endblock %}</title>
|
||||
<title>{% block title %}{% trans title|default:tenant.branding_title %}{% endblock %}</title>
|
||||
<link rel="icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}">
|
||||
<link rel="shortcut icon" type="image/png" href="{% static 'dist/assets/icons/icon.png' %}?v={{ ak_version }}">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'dist/patternfly-base.css' %}?v={{ ak_version }}">
|
||||
|
|
|
@ -26,10 +26,7 @@
|
|||
<div class="ak-login-container">
|
||||
<header class="pf-c-login__header">
|
||||
<div class="pf-c-brand ak-brand">
|
||||
<img src="{{ config.authentik.branding.logo }}" alt="authentik icon" />
|
||||
{% if config.authentik.branding.title_show %}
|
||||
<p>{{ config.authentik.branding.title }}</p>
|
||||
{% endif %}
|
||||
<img src="{{ tenant.branding_logo }}" alt="authentik icon" />
|
||||
</div>
|
||||
</header>
|
||||
{% block main_container %}
|
||||
|
@ -49,12 +46,12 @@
|
|||
<footer class="pf-c-login__footer">
|
||||
<p></p>
|
||||
<ul class="pf-c-list pf-m-inline">
|
||||
{% for link in config.authentik.footer_links %}
|
||||
{% for link in footer_links %}
|
||||
<li>
|
||||
<a href="{{ link.href }}">{{ link.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if config.authentik.branding.title != "authentik" %}
|
||||
{% if tenant.branding_title != "authentik" %}
|
||||
<li>
|
||||
<a href="https://goauthentik.io">
|
||||
{% trans 'Powered by authentik' %}
|
||||
|
|
|
@ -142,11 +142,6 @@ class Flow(SerializerModel, PolicyBindingModel):
|
|||
LOGGER.debug("with_policy: no flow found", filters=flow_filter)
|
||||
return None
|
||||
|
||||
def related_flow(self, designation: str, request: HttpRequest) -> Optional["Flow"]:
|
||||
"""Get a related flow with `designation`. Currently this only queries
|
||||
Flows by `designation`, but will eventually use `self` for related lookups."""
|
||||
return Flow.with_policy(request, designation=designation)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Flow {self.name} ({self.slug})"
|
||||
|
||||
|
|
|
@ -16,21 +16,6 @@ urlpatterns = [
|
|||
ToDefaultFlow.as_view(designation=FlowDesignation.INVALIDATION),
|
||||
name="default-invalidation",
|
||||
),
|
||||
path(
|
||||
"-/default/recovery/",
|
||||
ToDefaultFlow.as_view(designation=FlowDesignation.RECOVERY),
|
||||
name="default-recovery",
|
||||
),
|
||||
path(
|
||||
"-/default/enrollment/",
|
||||
ToDefaultFlow.as_view(designation=FlowDesignation.ENROLLMENT),
|
||||
name="default-enrollment",
|
||||
),
|
||||
path(
|
||||
"-/default/unenrollment/",
|
||||
ToDefaultFlow.as_view(designation=FlowDesignation.UNRENOLLMENT),
|
||||
name="default-unenrollment",
|
||||
),
|
||||
path("-/cancel/", CancelView.as_view(), name="cancel"),
|
||||
path(
|
||||
"-/configure/<uuid:stage_uuid>/",
|
||||
|
|
|
@ -44,6 +44,7 @@ from authentik.flows.planner import (
|
|||
)
|
||||
from authentik.lib.utils.reflection import all_subclasses, class_to_path
|
||||
from authentik.lib.utils.urls import is_url_absolute, redirect_with_qs
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
LOGGER = get_logger()
|
||||
# Argument used to redirect user after login
|
||||
|
@ -366,7 +367,17 @@ class ToDefaultFlow(View):
|
|||
designation: Optional[FlowDesignation] = None
|
||||
|
||||
def dispatch(self, request: HttpRequest) -> HttpResponse:
|
||||
flow = Flow.with_policy(request, designation=self.designation)
|
||||
tenant: Tenant = request.tenant
|
||||
flow = None
|
||||
# First, attempt to get default flow from tenant
|
||||
if self.designation == FlowDesignation.AUTHENTICATION:
|
||||
flow = tenant.flow_authentication
|
||||
if self.designation == FlowDesignation.INVALIDATION:
|
||||
flow = tenant.flow_invalidation
|
||||
# If no flow was set, get the first based on slug and policy
|
||||
if not flow:
|
||||
flow = Flow.with_policy(request, designation=self.designation)
|
||||
# If we still don't have a flow, 404
|
||||
if not flow:
|
||||
raise Http404
|
||||
# If user already has a pending plan, clear it so we don't have to later.
|
||||
|
|
|
@ -10,9 +10,6 @@ from urllib.parse import urlparse
|
|||
|
||||
import yaml
|
||||
from django.conf import ImproperlyConfigured
|
||||
from django.http import HttpRequest
|
||||
|
||||
from authentik import __version__
|
||||
|
||||
SEARCH_PATHS = ["authentik/lib/default.yml", "/etc/authentik/config.yml", ""] + glob(
|
||||
"/etc/authentik/config.d/*.yml", recursive=True
|
||||
|
@ -21,11 +18,6 @@ ENV_PREFIX = "AUTHENTIK"
|
|||
ENVIRONMENT = os.getenv(f"{ENV_PREFIX}_ENV", "local")
|
||||
|
||||
|
||||
def context_processor(request: HttpRequest) -> dict[str, Any]:
|
||||
"""Context Processor that injects config object into every template"""
|
||||
return {"config": CONFIG.raw, "ak_version": __version__}
|
||||
|
||||
|
||||
class ConfigLoader:
|
||||
"""Search through SEARCH_PATHS and load configuration. Environment variables starting with
|
||||
`ENV_PREFIX` are also applied.
|
||||
|
|
|
@ -48,9 +48,6 @@ outposts:
|
|||
authentik:
|
||||
avatars: gravatar # gravatar or none
|
||||
geoip: ""
|
||||
branding:
|
||||
title: authentik
|
||||
logo: /static/dist/assets/icons/icon_left_brand.svg
|
||||
# Optionally add links to the footer on the login page
|
||||
footer_links:
|
||||
- name: Documentation
|
||||
|
|
|
@ -6,7 +6,7 @@ class AuthentikManagedConfig(AppConfig):
|
|||
"""authentik Managed app"""
|
||||
|
||||
name = "authentik.managed"
|
||||
label = "authentik_Managed"
|
||||
label = "authentik_managed"
|
||||
verbose_name = "authentik Managed"
|
||||
|
||||
def ready(self) -> None:
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
# Generated by Django 3.2.3 on 2021-05-25 12:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_policies_event_matcher", "0014_alter_eventmatcherpolicy_app"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="eventmatcherpolicy",
|
||||
name="app",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("authentik.admin", "authentik Admin"),
|
||||
("authentik.api", "authentik API"),
|
||||
("authentik.events", "authentik Events"),
|
||||
("authentik.crypto", "authentik Crypto"),
|
||||
("authentik.flows", "authentik Flows"),
|
||||
("authentik.outposts", "authentik Outpost"),
|
||||
("authentik.lib", "authentik lib"),
|
||||
("authentik.policies", "authentik Policies"),
|
||||
("authentik.policies.dummy", "authentik Policies.Dummy"),
|
||||
(
|
||||
"authentik.policies.event_matcher",
|
||||
"authentik Policies.Event Matcher",
|
||||
),
|
||||
("authentik.policies.expiry", "authentik Policies.Expiry"),
|
||||
("authentik.policies.expression", "authentik Policies.Expression"),
|
||||
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
|
||||
("authentik.policies.password", "authentik Policies.Password"),
|
||||
("authentik.policies.reputation", "authentik Policies.Reputation"),
|
||||
("authentik.providers.proxy", "authentik Providers.Proxy"),
|
||||
("authentik.providers.ldap", "authentik Providers.LDAP"),
|
||||
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
|
||||
("authentik.providers.saml", "authentik Providers.SAML"),
|
||||
("authentik.recovery", "authentik Recovery"),
|
||||
("authentik.sources.ldap", "authentik Sources.LDAP"),
|
||||
("authentik.sources.oauth", "authentik Sources.OAuth"),
|
||||
("authentik.sources.plex", "authentik Sources.Plex"),
|
||||
("authentik.sources.saml", "authentik Sources.SAML"),
|
||||
(
|
||||
"authentik.stages.authenticator_duo",
|
||||
"authentik Stages.Authenticator.Duo",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_static",
|
||||
"authentik Stages.Authenticator.Static",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_totp",
|
||||
"authentik Stages.Authenticator.TOTP",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_validate",
|
||||
"authentik Stages.Authenticator.Validate",
|
||||
),
|
||||
(
|
||||
"authentik.stages.authenticator_webauthn",
|
||||
"authentik Stages.Authenticator.WebAuthn",
|
||||
),
|
||||
("authentik.stages.captcha", "authentik Stages.Captcha"),
|
||||
("authentik.stages.consent", "authentik Stages.Consent"),
|
||||
("authentik.stages.deny", "authentik Stages.Deny"),
|
||||
("authentik.stages.dummy", "authentik Stages.Dummy"),
|
||||
("authentik.stages.email", "authentik Stages.Email"),
|
||||
(
|
||||
"authentik.stages.identification",
|
||||
"authentik Stages.Identification",
|
||||
),
|
||||
("authentik.stages.invitation", "authentik Stages.User Invitation"),
|
||||
("authentik.stages.password", "authentik Stages.Password"),
|
||||
("authentik.stages.prompt", "authentik Stages.Prompt"),
|
||||
("authentik.stages.user_delete", "authentik Stages.User Delete"),
|
||||
("authentik.stages.user_login", "authentik Stages.User Login"),
|
||||
("authentik.stages.user_logout", "authentik Stages.User Logout"),
|
||||
("authentik.stages.user_write", "authentik Stages.User Write"),
|
||||
("authentik.tenants", "authentik Tenants"),
|
||||
("authentik.core", "authentik Core"),
|
||||
("authentik.managed", "authentik Managed"),
|
||||
],
|
||||
default="",
|
||||
help_text="Match events created by selected application. When left empty, all applications are matched.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -4,7 +4,7 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% trans 'Permission denied' %} - {{ config.authentik.branding.title }}
|
||||
{% trans 'Permission denied' %} - {{ tenant.branding_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
{% trans 'End session' %} - {{ config.authentik.branding.title }}
|
||||
{% trans 'End session' %} - {{ tenant.branding_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block card_title %}
|
||||
|
|
|
@ -127,6 +127,7 @@ INSTALLED_APPS = [
|
|||
"authentik.stages.user_login",
|
||||
"authentik.stages.user_logout",
|
||||
"authentik.stages.user_write",
|
||||
"authentik.tenants",
|
||||
"rest_framework",
|
||||
"django_filters",
|
||||
"drf_spectacular",
|
||||
|
@ -208,6 +209,7 @@ MIDDLEWARE = [
|
|||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"authentik.core.middleware.RequestIDMiddleware",
|
||||
"authentik.tenants.middleware.TenantMiddleware",
|
||||
"authentik.events.middleware.AuditMiddleware",
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
|
@ -231,7 +233,7 @@ TEMPLATES = [
|
|||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"authentik.lib.config.context_processor",
|
||||
"authentik.tenants.utils.context_processor",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 3.2.3 on 2021-05-29 16:10
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
import authentik.stages.authenticator_validate.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_stages_authenticator_validate", "0007_auto_20210403_0927"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="authenticatorvalidatestage",
|
||||
name="device_classes",
|
||||
field=django.contrib.postgres.fields.ArrayField(
|
||||
base_field=models.TextField(
|
||||
choices=[
|
||||
("static", "Static"),
|
||||
("totp", "TOTP"),
|
||||
("webauthn", "WebAuthn"),
|
||||
("duo", "Duo"),
|
||||
]
|
||||
),
|
||||
default=authentik.stages.authenticator_validate.models.default_device_classes,
|
||||
help_text="Device classes which can be used to authenticate",
|
||||
size=None,
|
||||
),
|
||||
),
|
||||
]
|
0
authentik/tenants/__init__.py
Normal file
0
authentik/tenants/__init__.py
Normal file
76
authentik/tenants/api.py
Normal file
76
authentik/tenants/api.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
"""Serializer for tenant models"""
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.fields import CharField, ListField
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
|
||||
class FooterLinkSerializer(PassiveSerializer):
|
||||
"""Links returned in Config API"""
|
||||
|
||||
href = CharField(read_only=True)
|
||||
name = CharField(read_only=True)
|
||||
|
||||
|
||||
class TenantSerializer(ModelSerializer):
|
||||
"""Tenant Serializer"""
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Tenant
|
||||
fields = [
|
||||
"tenant_uuid",
|
||||
"domain",
|
||||
"default",
|
||||
"branding_title",
|
||||
"branding_logo",
|
||||
"flow_authentication",
|
||||
"flow_invalidation",
|
||||
"flow_recovery",
|
||||
"flow_unenrollment",
|
||||
]
|
||||
|
||||
|
||||
class CurrentTenantSerializer(PassiveSerializer):
|
||||
"""Partial tenant information for styling"""
|
||||
|
||||
matched_domain = CharField(source="domain")
|
||||
branding_title = CharField()
|
||||
branding_logo = CharField()
|
||||
ui_footer_links = ListField(
|
||||
child=FooterLinkSerializer(),
|
||||
read_only=True,
|
||||
default=CONFIG.y("authentik.footer_links"),
|
||||
)
|
||||
|
||||
flow_unenrollment = CharField(source="flow_unenrollment.slug", required=False)
|
||||
|
||||
|
||||
class TenantViewSet(ModelViewSet):
|
||||
"""Tenant Viewset"""
|
||||
|
||||
queryset = Tenant.objects.all()
|
||||
serializer_class = TenantSerializer
|
||||
search_fields = [
|
||||
"domain",
|
||||
"branding_title",
|
||||
]
|
||||
ordering = ["domain"]
|
||||
|
||||
@extend_schema(
|
||||
responses=CurrentTenantSerializer(many=False),
|
||||
)
|
||||
@action(methods=["GET"], detail=False, permission_classes=[AllowAny])
|
||||
# pylint: disable=invalid-name, unused-argument
|
||||
def current(self, request: Request) -> Response:
|
||||
"""Get current tenant"""
|
||||
tenant: Tenant = request._request.tenant
|
||||
return Response(CurrentTenantSerializer(tenant).data)
|
10
authentik/tenants/apps.py
Normal file
10
authentik/tenants/apps.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
"""authentik tenant app"""
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AuthentikTenantsConfig(AppConfig):
|
||||
"""authentik Tenant app"""
|
||||
|
||||
name = "authentik.tenants"
|
||||
label = "authentik_tenants"
|
||||
verbose_name = "authentik Tenants"
|
22
authentik/tenants/middleware.py
Normal file
22
authentik/tenants/middleware.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
"""Inject tenant into current request"""
|
||||
from typing import Callable
|
||||
|
||||
from django.http.request import HttpRequest
|
||||
from django.http.response import HttpResponse
|
||||
|
||||
from authentik.tenants.utils import get_tenant_for_request
|
||||
|
||||
|
||||
class TenantMiddleware:
|
||||
"""Add current tenant to http request"""
|
||||
|
||||
get_response: Callable[[HttpRequest], HttpResponse]
|
||||
|
||||
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request: HttpRequest) -> HttpResponse:
|
||||
if not hasattr(request, "tenant"):
|
||||
tenant = get_tenant_for_request(request)
|
||||
setattr(request, "tenant", tenant)
|
||||
return self.get_response(request)
|
86
authentik/tenants/migrations/0001_initial.py
Normal file
86
authentik/tenants/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
# Generated by Django 3.2.3 on 2021-05-29 16:10
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
("authentik_flows", "0018_oob_flows"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Tenant",
|
||||
fields=[
|
||||
(
|
||||
"tenant_uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
(
|
||||
"domain",
|
||||
models.TextField(
|
||||
help_text="Domain that activates this tenant. Can be a superset, i.e. `a.b` for `aa.b` and `ba.b`"
|
||||
),
|
||||
),
|
||||
("default", models.BooleanField(default=False)),
|
||||
("branding_title", models.TextField(default="authentik")),
|
||||
(
|
||||
"branding_logo",
|
||||
models.TextField(
|
||||
default="/static/dist/assets/icons/icon_left_brand.svg"
|
||||
),
|
||||
),
|
||||
(
|
||||
"flow_authentication",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="tenant_authentication",
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
(
|
||||
"flow_invalidation",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="tenant_invalidation",
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
(
|
||||
"flow_recovery",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="tenant_recovery",
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
(
|
||||
"flow_unenrollment",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="tenant_unenrollment",
|
||||
to="authentik_flows.flow",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Tenant",
|
||||
"verbose_name_plural": "Tenants",
|
||||
},
|
||||
),
|
||||
]
|
40
authentik/tenants/migrations/0002_default.py
Normal file
40
authentik/tenants/migrations/0002_default.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Generated by Django 3.2.3 on 2021-05-29 16:55
|
||||
|
||||
from django.apps.registry import Apps
|
||||
from django.db import migrations
|
||||
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||
|
||||
|
||||
def create_default_tenant(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||
Flow = apps.get_model("authentik_flows", "Flow")
|
||||
Tenant = apps.get_model("authentik_tenants", "Tenant")
|
||||
|
||||
db_alias = schema_editor.connection.alias
|
||||
|
||||
default_authentication = (
|
||||
Flow.objects.using(db_alias).filter(slug="default-authentication-flow").first()
|
||||
)
|
||||
default_invalidation = (
|
||||
Flow.objects.using(db_alias).filter(slug="default-invalidation-flow").first()
|
||||
)
|
||||
|
||||
tenant, _ = Tenant.objects.using(db_alias).update_or_create(
|
||||
domain="authentik-default",
|
||||
default=True,
|
||||
defaults={
|
||||
"flow_authentication": default_authentication,
|
||||
"flow_invalidation": default_invalidation,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_tenants", "0001_initial"),
|
||||
("authentik_flows", "0008_default_flows"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_default_tenant),
|
||||
]
|
0
authentik/tenants/migrations/__init__.py
Normal file
0
authentik/tenants/migrations/__init__.py
Normal file
48
authentik/tenants/models.py
Normal file
48
authentik/tenants/models.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
"""tenant models"""
|
||||
from uuid import uuid4
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from authentik.flows.models import Flow
|
||||
|
||||
|
||||
class Tenant(models.Model):
|
||||
"""Single tenant"""
|
||||
|
||||
tenant_uuid = models.UUIDField(primary_key=True, editable=False, default=uuid4)
|
||||
domain = models.TextField(
|
||||
help_text=_(
|
||||
"Domain that activates this tenant. "
|
||||
"Can be a superset, i.e. `a.b` for `aa.b` and `ba.b`"
|
||||
)
|
||||
)
|
||||
default = models.BooleanField(
|
||||
default=False,
|
||||
)
|
||||
|
||||
branding_title = models.TextField(default="authentik")
|
||||
branding_logo = models.TextField(
|
||||
default="/static/dist/assets/icons/icon_left_brand.svg"
|
||||
)
|
||||
|
||||
flow_authentication = models.ForeignKey(
|
||||
Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_authentication"
|
||||
)
|
||||
flow_invalidation = models.ForeignKey(
|
||||
Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_invalidation"
|
||||
)
|
||||
flow_recovery = models.ForeignKey(
|
||||
Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_recovery"
|
||||
)
|
||||
flow_unenrollment = models.ForeignKey(
|
||||
Flow, null=True, on_delete=models.SET_NULL, related_name="tenant_unenrollment"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.domain
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = _("Tenant")
|
||||
verbose_name_plural = _("Tenants")
|
36
authentik/tenants/tests.py
Normal file
36
authentik/tenants/tests.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
"""Test tenants"""
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
|
||||
class TestTenants(TestCase):
|
||||
"""Test tenants"""
|
||||
|
||||
def test_current_tenant(self):
|
||||
"""Test Current tenant API"""
|
||||
self.assertJSONEqual(
|
||||
force_str(self.client.get(reverse("authentik_api:tenant-current")).content),
|
||||
{
|
||||
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
||||
"branding_title": "authentik",
|
||||
"matched_domain": "authentik-default",
|
||||
"ui_footer_links": CONFIG.y("authentik.footer_links"),
|
||||
},
|
||||
)
|
||||
|
||||
def test_fallback(self):
|
||||
"""Test fallback tenant"""
|
||||
Tenant.objects.all().delete()
|
||||
self.assertJSONEqual(
|
||||
force_str(self.client.get(reverse("authentik_api:tenant-current")).content),
|
||||
{
|
||||
"branding_logo": "/static/dist/assets/icons/icon_left_brand.svg",
|
||||
"branding_title": "authentik",
|
||||
"matched_domain": "fallback",
|
||||
"ui_footer_links": CONFIG.y("authentik.footer_links"),
|
||||
},
|
||||
)
|
30
authentik/tenants/utils.py
Normal file
30
authentik/tenants/utils.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
"""Tenant utilities"""
|
||||
from typing import Any
|
||||
|
||||
from django.db.models import Q
|
||||
from django.http.request import HttpRequest
|
||||
|
||||
from authentik import __version__
|
||||
from authentik.lib.config import CONFIG
|
||||
from authentik.tenants.models import Tenant
|
||||
|
||||
_q_default = Q(default=True)
|
||||
|
||||
|
||||
def get_tenant_for_request(request: HttpRequest) -> Tenant:
|
||||
"""Get tenant object for current request"""
|
||||
db_tenants = Tenant.objects.filter(
|
||||
Q(domain__iendswith=request.get_host()) | _q_default
|
||||
)
|
||||
if not db_tenants.exists():
|
||||
return Tenant(domain="fallback")
|
||||
return db_tenants.first()
|
||||
|
||||
|
||||
def context_processor(request: HttpRequest) -> dict[str, Any]:
|
||||
"""Context Processor that injects tenant object into every template"""
|
||||
return {
|
||||
"tenant": request.tenant,
|
||||
"ak_version": __version__,
|
||||
"footer_links": CONFIG.y("authentik.footer_links"),
|
||||
}
|
401
schema.yml
401
schema.yml
|
@ -1712,6 +1712,231 @@ paths:
|
|||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
/api/v2beta/core/tenants/:
|
||||
get:
|
||||
operationId: core_tenants_list
|
||||
description: Tenant Viewset
|
||||
parameters:
|
||||
- name: ordering
|
||||
required: false
|
||||
in: query
|
||||
description: Which field to use when ordering the results.
|
||||
schema:
|
||||
type: string
|
||||
- name: page
|
||||
required: false
|
||||
in: query
|
||||
description: A page number within the paginated result set.
|
||||
schema:
|
||||
type: integer
|
||||
- name: page_size
|
||||
required: false
|
||||
in: query
|
||||
description: Number of results to return per page.
|
||||
schema:
|
||||
type: integer
|
||||
- name: search
|
||||
required: false
|
||||
in: query
|
||||
description: A search term.
|
||||
schema:
|
||||
type: string
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedTenantList'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
post:
|
||||
operationId: core_tenants_create
|
||||
description: Tenant Viewset
|
||||
tags:
|
||||
- core
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TenantRequest'
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TenantRequest'
|
||||
multipart/form-data:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TenantRequest'
|
||||
required: true
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
responses:
|
||||
'201':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Tenant'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
/api/v2beta/core/tenants/{tenant_uuid}/:
|
||||
get:
|
||||
operationId: core_tenants_retrieve
|
||||
description: Tenant Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: tenant_uuid
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Tenant.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Tenant'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
put:
|
||||
operationId: core_tenants_update
|
||||
description: Tenant Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: tenant_uuid
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Tenant.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TenantRequest'
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TenantRequest'
|
||||
multipart/form-data:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TenantRequest'
|
||||
required: true
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Tenant'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
patch:
|
||||
operationId: core_tenants_partial_update
|
||||
description: Tenant Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: tenant_uuid
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Tenant.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PatchedTenantRequest'
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PatchedTenantRequest'
|
||||
multipart/form-data:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PatchedTenantRequest'
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Tenant'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
delete:
|
||||
operationId: core_tenants_destroy
|
||||
description: Tenant Viewset
|
||||
parameters:
|
||||
- in: path
|
||||
name: tenant_uuid
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: A UUID string identifying this Tenant.
|
||||
required: true
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
responses:
|
||||
'204':
|
||||
description: No response body
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
/api/v2beta/core/tenants/current/:
|
||||
get:
|
||||
operationId: core_tenants_current_retrieve
|
||||
description: Get current tenant
|
||||
tags:
|
||||
- core
|
||||
security:
|
||||
- authentik: []
|
||||
- cookieAuth: []
|
||||
- {}
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CurrentTenant'
|
||||
description: ''
|
||||
'400':
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
'403':
|
||||
$ref: '#/components/schemas/GenericError'
|
||||
/api/v2beta/core/tokens/:
|
||||
get:
|
||||
operationId: core_tokens_list
|
||||
|
@ -15291,6 +15516,7 @@ components:
|
|||
- authentik.stages.user_login
|
||||
- authentik.stages.user_logout
|
||||
- authentik.stages.user_write
|
||||
- authentik.tenants
|
||||
- authentik.core
|
||||
- authentik.managed
|
||||
type: string
|
||||
|
@ -16151,17 +16377,6 @@ components:
|
|||
type: object
|
||||
description: Serialize authentik Config into DRF Object
|
||||
properties:
|
||||
branding_logo:
|
||||
type: string
|
||||
readOnly: true
|
||||
branding_title:
|
||||
type: string
|
||||
readOnly: true
|
||||
ui_footer_links:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/FooterLink'
|
||||
readOnly: true
|
||||
error_reporting_enabled:
|
||||
type: boolean
|
||||
readOnly: true
|
||||
|
@ -16176,13 +16391,10 @@ components:
|
|||
items:
|
||||
$ref: '#/components/schemas/CapabilitiesEnum'
|
||||
required:
|
||||
- branding_logo
|
||||
- branding_title
|
||||
- capabilities
|
||||
- error_reporting_enabled
|
||||
- error_reporting_environment
|
||||
- error_reporting_send_pii
|
||||
- ui_footer_links
|
||||
ConsentChallenge:
|
||||
type: object
|
||||
description: Challenge info for consent screens
|
||||
|
@ -16298,6 +16510,33 @@ components:
|
|||
required:
|
||||
- x_cord
|
||||
- y_cord
|
||||
CurrentTenant:
|
||||
type: object
|
||||
description: Partial tenant information for styling
|
||||
properties:
|
||||
matched_domain:
|
||||
type: string
|
||||
branding_title:
|
||||
type: string
|
||||
branding_logo:
|
||||
type: string
|
||||
ui_footer_links:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/FooterLink'
|
||||
readOnly: true
|
||||
default:
|
||||
- href: https://goauthentik.io/docs/
|
||||
name: Documentation
|
||||
- href: https://goauthentik.io/
|
||||
name: authentik Website
|
||||
flow_unenrollment:
|
||||
type: string
|
||||
required:
|
||||
- branding_logo
|
||||
- branding_title
|
||||
- matched_domain
|
||||
- ui_footer_links
|
||||
DenyStage:
|
||||
type: object
|
||||
description: DenyStage Serializer
|
||||
|
@ -20893,6 +21132,41 @@ components:
|
|||
required:
|
||||
- pagination
|
||||
- results
|
||||
PaginatedTenantList:
|
||||
type: object
|
||||
properties:
|
||||
pagination:
|
||||
type: object
|
||||
properties:
|
||||
next:
|
||||
type: number
|
||||
previous:
|
||||
type: number
|
||||
count:
|
||||
type: number
|
||||
current:
|
||||
type: number
|
||||
total_pages:
|
||||
type: number
|
||||
start_index:
|
||||
type: number
|
||||
end_index:
|
||||
type: number
|
||||
required:
|
||||
- next
|
||||
- previous
|
||||
- count
|
||||
- current
|
||||
- total_pages
|
||||
- start_index
|
||||
- end_index
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Tenant'
|
||||
required:
|
||||
- pagination
|
||||
- results
|
||||
PaginatedTokenList:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -22830,6 +23104,36 @@ components:
|
|||
type: string
|
||||
description: The human-readable name of this device.
|
||||
maxLength: 64
|
||||
PatchedTenantRequest:
|
||||
type: object
|
||||
description: Tenant Serializer
|
||||
properties:
|
||||
domain:
|
||||
type: string
|
||||
description: Domain that activates this tenant. Can be a superset, i.e.
|
||||
`a.b` for `aa.b` and `ba.b`
|
||||
default:
|
||||
type: boolean
|
||||
branding_title:
|
||||
type: string
|
||||
branding_logo:
|
||||
type: string
|
||||
flow_authentication:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
flow_invalidation:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
flow_recovery:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
flow_unenrollment:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
PatchedTokenRequest:
|
||||
type: object
|
||||
description: Token Serializer
|
||||
|
@ -24779,6 +25083,75 @@ components:
|
|||
- task_description
|
||||
- task_finish_timestamp
|
||||
- task_name
|
||||
Tenant:
|
||||
type: object
|
||||
description: Tenant Serializer
|
||||
properties:
|
||||
tenant_uuid:
|
||||
type: string
|
||||
format: uuid
|
||||
readOnly: true
|
||||
domain:
|
||||
type: string
|
||||
description: Domain that activates this tenant. Can be a superset, i.e.
|
||||
`a.b` for `aa.b` and `ba.b`
|
||||
default:
|
||||
type: boolean
|
||||
branding_title:
|
||||
type: string
|
||||
branding_logo:
|
||||
type: string
|
||||
flow_authentication:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
flow_invalidation:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
flow_recovery:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
flow_unenrollment:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
required:
|
||||
- domain
|
||||
- tenant_uuid
|
||||
TenantRequest:
|
||||
type: object
|
||||
description: Tenant Serializer
|
||||
properties:
|
||||
domain:
|
||||
type: string
|
||||
description: Domain that activates this tenant. Can be a superset, i.e.
|
||||
`a.b` for `aa.b` and `ba.b`
|
||||
default:
|
||||
type: boolean
|
||||
branding_title:
|
||||
type: string
|
||||
branding_logo:
|
||||
type: string
|
||||
flow_authentication:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
flow_invalidation:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
flow_recovery:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
flow_unenrollment:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
required:
|
||||
- domain
|
||||
Token:
|
||||
type: object
|
||||
description: Token Serializer
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Config, Configuration, Middleware, ResponseContext, RootApi } from "authentik-api";
|
||||
import { Config, Configuration, CoreApi, CurrentTenant, Middleware, ResponseContext, RootApi, Tenant } from "authentik-api";
|
||||
import { getCookie } from "../utils";
|
||||
import { API_DRAWER_MIDDLEWARE } from "../elements/notifications/APIDrawer";
|
||||
import { MessageMiddleware } from "../elements/messages/Middleware";
|
||||
|
@ -6,7 +6,9 @@ import { MessageMiddleware } from "../elements/messages/Middleware";
|
|||
export class LoggingMiddleware implements Middleware {
|
||||
|
||||
post(context: ResponseContext): Promise<Response | void> {
|
||||
console.debug(`authentik/api: ${context.response.status} ${context.init.method} ${context.url}`);
|
||||
tenant().then(tenant => {
|
||||
console.debug(`authentik/api[${tenant.matchedDomain}]: ${context.response.status} ${context.init.method} ${context.url}`);
|
||||
});
|
||||
return Promise.resolve(context.response);
|
||||
}
|
||||
|
||||
|
@ -20,6 +22,14 @@ export function config(): Promise<Config> {
|
|||
return globalConfigPromise;
|
||||
}
|
||||
|
||||
let globalTenantPromise: Promise<CurrentTenant>;
|
||||
export function tenant(): Promise<CurrentTenant> {
|
||||
if (!globalTenantPromise) {
|
||||
globalTenantPromise = new CoreApi(DEFAULT_CONFIG).coreTenantsCurrentRetrieve();
|
||||
}
|
||||
return globalTenantPromise;
|
||||
}
|
||||
|
||||
export const DEFAULT_CONFIG = new Configuration({
|
||||
basePath: "",
|
||||
headers: {
|
||||
|
|
|
@ -11,10 +11,6 @@ export class AppURLManager {
|
|||
|
||||
export class FlowURLManager {
|
||||
|
||||
static defaultUnenrollment(): string {
|
||||
return "/flows/-/default/unenrollment/";
|
||||
}
|
||||
|
||||
static configure(stageUuid: string, rest: string): string {
|
||||
return `/flows/-/configure/${stageUuid}/${rest}`;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import AKGlobal from "../authentik.css";
|
|||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import { EVENT_SIDEBAR_TOGGLE, TITLE_DEFAULT } from "../constants";
|
||||
import { config } from "../api/Config";
|
||||
import { tenant } from "../api/Config";
|
||||
|
||||
@customElement("ak-page-header")
|
||||
export class PageHeader extends LitElement {
|
||||
|
@ -18,11 +18,11 @@ export class PageHeader extends LitElement {
|
|||
|
||||
@property()
|
||||
set header(value: string) {
|
||||
config().then(config => {
|
||||
tenant().then(tenant => {
|
||||
if (value !== "") {
|
||||
document.title = `${value} - ${config.brandingTitle}`;
|
||||
document.title = `${value} - ${tenant.brandingTitle}`;
|
||||
} else {
|
||||
document.title = config.brandingTitle || TITLE_DEFAULT;
|
||||
document.title = tenant.brandingTitle || TITLE_DEFAULT;
|
||||
}
|
||||
});
|
||||
this._header = value;
|
||||
|
|
|
@ -6,29 +6,26 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
|||
import AKGlobal from "../../authentik.css";
|
||||
|
||||
import { configureSentry } from "../../api/Sentry";
|
||||
import { Config } from "authentik-api";
|
||||
import { CurrentTenant } from "authentik-api";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import { EVENT_SIDEBAR_TOGGLE } from "../../constants";
|
||||
import { tenant } from "../../api/Config";
|
||||
|
||||
// If the viewport is wider than MIN_WIDTH, the sidebar
|
||||
// is shown besides the content, and not overlayed.
|
||||
export const MIN_WIDTH = 1200;
|
||||
|
||||
export const DefaultConfig: Config = {
|
||||
export const DefaultTenant: CurrentTenant = {
|
||||
brandingLogo: " /static/dist/assets/icons/icon_left_brand.svg",
|
||||
brandingTitle: "authentik",
|
||||
|
||||
errorReportingEnabled: false,
|
||||
errorReportingEnvironment: "",
|
||||
errorReportingSendPii: false,
|
||||
uiFooterLinks: [],
|
||||
capabilities: [],
|
||||
matchedDomain: "",
|
||||
};
|
||||
|
||||
@customElement("ak-sidebar-brand")
|
||||
export class SidebarBrand extends LitElement {
|
||||
@property({attribute: false})
|
||||
config: Config = DefaultConfig;
|
||||
tenant: CurrentTenant = DefaultTenant;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
|
@ -68,7 +65,8 @@ export class SidebarBrand extends LitElement {
|
|||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
configureSentry(true).then((c) => {this.config = c;});
|
||||
configureSentry(true);
|
||||
tenant().then(tenant => this.tenant = tenant);
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
|
@ -89,7 +87,7 @@ export class SidebarBrand extends LitElement {
|
|||
` : html``}
|
||||
<a href="#/" class="pf-c-page__header-brand-link">
|
||||
<div class="pf-c-brand ak-brand">
|
||||
<img src="${ifDefined(this.config.brandingLogo)}" alt="authentik icon" loading="lazy" />
|
||||
<img src="${ifDefined(this.tenant.brandingLogo)}" alt="authentik icon" loading="lazy" />
|
||||
</div>
|
||||
</a>`;
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ export abstract class Table<T> extends LitElement {
|
|||
<tr role="row">
|
||||
<td role="cell" colspan="8">
|
||||
<div class="pf-l-bullseye">
|
||||
${inner ? inner : html`<ak-empty-state header="${t`No elements found.`}"></ak-empty-state>`}
|
||||
${inner ? inner : html`<ak-empty-state header="${t`No objects found.`}"></ak-empty-state>`}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -26,8 +26,8 @@ import "./stages/password/PasswordStage";
|
|||
import "./stages/prompt/PromptStage";
|
||||
import "./sources/plex/PlexLoginInit";
|
||||
import { StageHost } from "./stages/base";
|
||||
import { ChallengeChoices, Config, FlowChallengeRequest, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api";
|
||||
import { config, DEFAULT_CONFIG } from "../api/Config";
|
||||
import { ChallengeChoices, CurrentTenant, FlowChallengeRequest, FlowChallengeResponseRequest, FlowsApi, RedirectChallenge, ShellChallenge } from "authentik-api";
|
||||
import { DEFAULT_CONFIG, tenant } from "../api/Config";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import { PFSize } from "../elements/Spinner";
|
||||
|
@ -46,7 +46,7 @@ export class FlowExecutor extends LitElement implements StageHost {
|
|||
loading = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
config?: Config;
|
||||
tenant?: CurrentTenant;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFLogin, PFButton, PFTitle, PFList, PFBackgroundImage, AKGlobal].concat(css`
|
||||
|
@ -85,11 +85,11 @@ export class FlowExecutor extends LitElement implements StageHost {
|
|||
}
|
||||
|
||||
private postUpdate(): void {
|
||||
config().then(config => {
|
||||
tenant().then(tenant => {
|
||||
if (this.challenge?.title) {
|
||||
document.title = `${this.challenge.title} - ${config.brandingTitle}`;
|
||||
document.title = `${this.challenge.title} - ${tenant.brandingTitle}`;
|
||||
} else {
|
||||
document.title = config.brandingTitle || TITLE_DEFAULT;
|
||||
document.title = tenant.brandingTitle || TITLE_DEFAULT;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -115,9 +115,8 @@ export class FlowExecutor extends LitElement implements StageHost {
|
|||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
configureSentry().then((config) => {
|
||||
this.config = config;
|
||||
});
|
||||
configureSentry();
|
||||
tenant().then(tenant => this.tenant = tenant);
|
||||
this.loading = true;
|
||||
new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({
|
||||
flowSlug: this.flowSlug,
|
||||
|
@ -255,7 +254,7 @@ export class FlowExecutor extends LitElement implements StageHost {
|
|||
<div class="ak-login-container">
|
||||
<header class="pf-c-login__header">
|
||||
<div class="pf-c-brand ak-brand">
|
||||
<img src="${ifDefined(this.config?.brandingLogo)}" alt="authentik icon" />
|
||||
<img src="${ifDefined(this.tenant?.brandingLogo)}" alt="authentik icon" />
|
||||
</div>
|
||||
</header>
|
||||
<div class="pf-c-login__main">
|
||||
|
@ -264,12 +263,12 @@ export class FlowExecutor extends LitElement implements StageHost {
|
|||
<footer class="pf-c-login__footer">
|
||||
<p></p>
|
||||
<ul class="pf-c-list pf-m-inline">
|
||||
${until(this.config?.uiFooterLinks?.map((link) => {
|
||||
${until(this.tenant?.uiFooterLinks?.map((link) => {
|
||||
return html`<li>
|
||||
<a href="${link.href || ""}">${link.name}</a>
|
||||
</li>`;
|
||||
}))}
|
||||
${this.config?.brandingTitle != "authentik" ? html`
|
||||
${this.tenant?.brandingTitle != "authentik" ? html`
|
||||
<li><a href="https://goauthentik.io">${t`Powered by authentik`}</a></li>` : html``}
|
||||
</ul>
|
||||
</footer>
|
||||
|
|
|
@ -40,6 +40,9 @@ export class AdminInterface extends Interface {
|
|||
<ak-sidebar-item path="/core/providers" .activeWhen=${[`^/core/providers/(?<id>${ID_REGEX})$`]}>
|
||||
<span slot="label">${t`Providers`}</span>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item path="/core/tenants">
|
||||
<span slot="label">${t`Tenants`}</span>
|
||||
</ak-sidebar-item>
|
||||
</ak-sidebar-item>
|
||||
<ak-sidebar-item
|
||||
.condition=${superUserCondition}>
|
||||
|
|
|
@ -308,6 +308,7 @@ msgstr "Authentication"
|
|||
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
#: src/pages/sources/saml/SAMLSourceForm.ts
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Authentication flow"
|
||||
msgstr "Authentication flow"
|
||||
|
||||
|
@ -422,6 +423,14 @@ msgstr "Binding"
|
|||
msgid "Binding Type"
|
||||
msgstr "Binding Type"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Branding settings"
|
||||
msgstr "Branding settings"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Branding shown in page title and several other places."
|
||||
msgstr "Branding shown in page title and several other places."
|
||||
|
||||
#: src/pages/admin-overview/cards/VersionStatusCard.ts
|
||||
msgid "Build hash: {0}"
|
||||
msgstr "Build hash: {0}"
|
||||
|
@ -522,6 +531,14 @@ msgstr "Changelog"
|
|||
msgid "Characters which are considered as symbols."
|
||||
msgstr "Characters which are considered as symbols."
|
||||
|
||||
#: src/pages/applications/ApplicationViewPage.ts
|
||||
msgid "Check"
|
||||
msgstr "Check"
|
||||
|
||||
#: src/pages/applications/ApplicationViewPage.ts
|
||||
msgid "Check Application access"
|
||||
msgstr "Check Application access"
|
||||
|
||||
#: src/pages/policies/reputation/ReputationPolicyForm.ts
|
||||
msgid "Check IP"
|
||||
msgstr "Check IP"
|
||||
|
@ -530,6 +547,10 @@ msgstr "Check IP"
|
|||
msgid "Check Username"
|
||||
msgstr "Check Username"
|
||||
|
||||
#: src/pages/applications/ApplicationViewPage.ts
|
||||
msgid "Check access"
|
||||
msgstr "Check access"
|
||||
|
||||
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
|
||||
msgid "Check status"
|
||||
msgstr "Check status"
|
||||
|
@ -664,6 +685,10 @@ msgstr "Configure settings relevant to your user profile."
|
|||
msgid "Configure the maximum allowed time drift for an asseration."
|
||||
msgstr "Configure the maximum allowed time drift for an asseration."
|
||||
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
msgid "Configure visual settings and defaults for different domains."
|
||||
msgstr "Configure visual settings and defaults for different domains."
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Configure what data should be used as unique User Identifier. For most cases, the default should be fine."
|
||||
msgstr "Configure what data should be used as unique User Identifier. For most cases, the default should be fine."
|
||||
|
@ -800,6 +825,8 @@ msgstr "Copy download URL"
|
|||
#: src/pages/stages/prompt/PromptListPage.ts
|
||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
|
@ -869,6 +896,10 @@ msgstr "Create Stage"
|
|||
msgid "Create Stage binding"
|
||||
msgstr "Create Stage binding"
|
||||
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
msgid "Create Tenant"
|
||||
msgstr "Create Tenant"
|
||||
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Create Token"
|
||||
msgstr "Create Token"
|
||||
|
@ -928,6 +959,18 @@ msgstr "Date Time"
|
|||
msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
|
||||
msgstr "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Default"
|
||||
msgstr "Default"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Default flows"
|
||||
msgstr "Default flows"
|
||||
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
msgid "Default?"
|
||||
msgstr "Default?"
|
||||
|
||||
#: src/pages/events/TransportListPage.ts
|
||||
msgid "Define how notifications are sent to users, like Email or Webhook."
|
||||
msgstr "Define how notifications are sent to users, like Email or Webhook."
|
||||
|
@ -948,6 +991,7 @@ msgstr "Define how notifications are sent to users, like Email or Webhook."
|
|||
#: src/pages/stages/StageListPage.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
#: src/pages/stages/prompt/PromptListPage.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
|
@ -1071,6 +1115,11 @@ msgstr "Disconnect"
|
|||
msgid "Docker URL"
|
||||
msgstr "Docker URL"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
msgid "Domain"
|
||||
msgstr "Domain"
|
||||
|
||||
#: src/pages/crypto/CertificateKeyPairListPage.ts
|
||||
#: src/pages/providers/saml/SAMLProviderViewPage.ts
|
||||
#: src/pages/sources/saml/SAMLSourceViewPage.ts
|
||||
|
@ -1133,6 +1182,7 @@ msgstr "Each provider has a different issuer, based on the application slug."
|
|||
#: src/pages/sources/saml/SAMLSourceViewPage.ts
|
||||
#: src/pages/stages/StageListPage.ts
|
||||
#: src/pages/stages/prompt/PromptListPage.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
|
@ -1477,6 +1527,14 @@ msgstr "Flow used by an authenticated user to configure this Stage. If empty, us
|
|||
msgid "Flow used for users to authenticate. Currently only identification and password stages are supported."
|
||||
msgstr "Flow used for users to authenticate. Currently only identification and password stages are supported."
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used."
|
||||
msgstr "Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used."
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Flow used to logout. If left empty, the first applicable flow sorted by the slug is used."
|
||||
msgstr "Flow used to logout. If left empty, the first applicable flow sorted by the slug is used."
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderForm.ts
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
|
@ -1625,6 +1683,7 @@ msgstr "Hide service-accounts"
|
|||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/stages/password/PasswordStageForm.ts
|
||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||
|
@ -1643,6 +1702,10 @@ msgstr "ID"
|
|||
msgid "Icon"
|
||||
msgstr "Icon"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Icon shown in sidebar/header and flow executor."
|
||||
msgstr "Icon shown in sidebar/header and flow executor."
|
||||
|
||||
#: src/pages/flows/FlowListPage.ts
|
||||
#: src/pages/system-tasks/SystemTaskListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
|
@ -1664,6 +1727,10 @@ msgstr "If enabled, use the local connection. Required Docker socket/Kubernetes
|
|||
msgid "If left empty, authentik will try to extract the launch URL based on the selected provider."
|
||||
msgstr "If left empty, authentik will try to extract the launch URL based on the selected provider."
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown."
|
||||
msgstr "If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown."
|
||||
|
||||
#: src/pages/stages/invitation/InvitationStageForm.ts
|
||||
msgid "If this flag is set, this Stage will jump to the next Stage when no Invitation is given. By default this Stage will cancel the Flow when no invitation is given."
|
||||
msgstr "If this flag is set, this Stage will jump to the next Stage when no Invitation is given. By default this Stage will cancel the Flow when no invitation is given."
|
||||
|
@ -1725,6 +1792,10 @@ msgstr "Internal host SSL Validation"
|
|||
msgid "Invalidation"
|
||||
msgstr "Invalidation"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Invalidation flow"
|
||||
msgstr "Invalidation flow"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/stages/invitation/InvitationListPage.ts
|
||||
msgid "Invitations"
|
||||
|
@ -1866,6 +1937,7 @@ msgid "Loading"
|
|||
msgstr "Loading"
|
||||
|
||||
#: src/elements/Spinner.ts
|
||||
#: src/pages/applications/ApplicationCheckAccessForm.ts
|
||||
#: src/pages/applications/ApplicationForm.ts
|
||||
#: src/pages/events/RuleForm.ts
|
||||
#: src/pages/events/RuleForm.ts
|
||||
|
@ -1916,9 +1988,14 @@ msgstr "Loading"
|
|||
#: src/pages/stages/email/EmailStageForm.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/stages/password/PasswordStageForm.ts
|
||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||
#: src/pages/stages/prompt/PromptStageForm.ts
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Loading..."
|
||||
msgstr "Loading..."
|
||||
|
||||
|
@ -1949,6 +2026,10 @@ msgstr "Logins"
|
|||
msgid "Logins over the last 24 hours"
|
||||
msgstr "Logins over the last 24 hours"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Logo"
|
||||
msgstr "Logo"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderViewPage.ts
|
||||
msgid "Logout URL"
|
||||
msgstr "Logout URL"
|
||||
|
@ -1986,6 +2067,7 @@ msgstr "Maximum age (in days)"
|
|||
msgid "Members"
|
||||
msgstr "Members"
|
||||
|
||||
#: src/pages/applications/ApplicationCheckAccessForm.ts
|
||||
#: src/pages/events/EventInfo.ts
|
||||
#: src/pages/policies/PolicyTestForm.ts
|
||||
#: src/pages/system-tasks/SystemTaskListPage.ts
|
||||
|
@ -2130,6 +2212,7 @@ msgstr "Need an account?"
|
|||
msgid "New version available!"
|
||||
msgstr "New version available!"
|
||||
|
||||
#: src/pages/applications/ApplicationCheckAccessForm.ts
|
||||
#: src/pages/crypto/CertificateKeyPairListPage.ts
|
||||
#: src/pages/groups/GroupListPage.ts
|
||||
#: src/pages/groups/MemberSelectModal.ts
|
||||
|
@ -2138,6 +2221,7 @@ msgstr "New version available!"
|
|||
#: src/pages/policies/PolicyTestForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
|
@ -2165,10 +2249,6 @@ msgstr "No Stages bound"
|
|||
msgid "No additional data available."
|
||||
msgstr "No additional data available."
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "No elements found."
|
||||
msgstr "No elements found."
|
||||
|
||||
#: src/elements/forms/ModalForm.ts
|
||||
msgid "No form found"
|
||||
msgstr "No form found"
|
||||
|
@ -2178,6 +2258,10 @@ msgstr "No form found"
|
|||
msgid "No matching events could be found."
|
||||
msgstr "No matching events could be found."
|
||||
|
||||
#: src/elements/table/Table.ts
|
||||
msgid "No objects found."
|
||||
msgstr "No objects found."
|
||||
|
||||
#: src/pages/policies/BoundPoliciesList.ts
|
||||
msgid "No policies are currently bound to this object."
|
||||
msgstr "No policies are currently bound to this object."
|
||||
|
@ -2408,6 +2492,7 @@ msgstr "Parent"
|
|||
msgid "Pass policy?"
|
||||
msgstr "Pass policy?"
|
||||
|
||||
#: src/pages/applications/ApplicationCheckAccessForm.ts
|
||||
#: src/pages/events/EventInfo.ts
|
||||
#: src/pages/policies/PolicyTestForm.ts
|
||||
msgid "Passing"
|
||||
|
@ -2673,9 +2758,14 @@ msgid "Recovery"
|
|||
msgstr "Recovery"
|
||||
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Recovery flow"
|
||||
msgstr "Recovery flow"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Recovery flow. If left empty, the first applicable flow sorted by the slug is used."
|
||||
msgstr "Recovery flow. If left empty, the first applicable flow sorted by the slug is used."
|
||||
|
||||
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
|
||||
msgid "Recovery keys"
|
||||
msgstr "Recovery keys"
|
||||
|
@ -2887,6 +2977,10 @@ msgstr "Select an identification method."
|
|||
msgid "Select one of the sources below to login."
|
||||
msgstr "Select one of the sources below to login."
|
||||
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP."
|
||||
msgstr "Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP."
|
||||
|
||||
#: src/pages/groups/MemberSelectModal.ts
|
||||
msgid "Select users to add"
|
||||
msgstr "Select users to add"
|
||||
|
@ -3054,6 +3148,7 @@ msgstr "Source {0}"
|
|||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/sources/SourcesListPage.ts
|
||||
#: src/pages/stages/identification/IdentificationStageForm.ts
|
||||
msgid "Sources"
|
||||
msgstr "Sources"
|
||||
|
||||
|
@ -3288,6 +3383,10 @@ msgstr "Successfully created source."
|
|||
msgid "Successfully created stage."
|
||||
msgstr "Successfully created stage."
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Successfully created tenant."
|
||||
msgstr "Successfully created tenant."
|
||||
|
||||
#: src/pages/user-settings/tokens/UserTokenForm.ts
|
||||
msgid "Successfully created token."
|
||||
msgstr "Successfully created token."
|
||||
|
@ -3321,6 +3420,7 @@ msgstr "Successfully imported flow."
|
|||
msgid "Successfully imported provider."
|
||||
msgstr "Successfully imported provider."
|
||||
|
||||
#: src/pages/applications/ApplicationCheckAccessForm.ts
|
||||
#: src/pages/policies/PolicyTestForm.ts
|
||||
#: src/pages/property-mappings/PropertyMappingTestForm.ts
|
||||
msgid "Successfully sent test-request."
|
||||
|
@ -3427,6 +3527,10 @@ msgstr "Successfully updated source."
|
|||
msgid "Successfully updated stage."
|
||||
msgstr "Successfully updated stage."
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Successfully updated tenant."
|
||||
msgstr "Successfully updated tenant."
|
||||
|
||||
#: src/pages/user-settings/tokens/UserTokenForm.ts
|
||||
msgid "Successfully updated token."
|
||||
msgstr "Successfully updated token."
|
||||
|
@ -3516,6 +3620,16 @@ msgstr "Task finished with warnings"
|
|||
msgid "Template"
|
||||
msgstr "Template"
|
||||
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
msgid "Tenant"
|
||||
msgstr "Tenant"
|
||||
|
||||
#: src/interfaces/AdminInterface.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
msgid "Tenants"
|
||||
msgstr "Tenants"
|
||||
|
||||
#: src/pages/applications/ApplicationViewPage.ts
|
||||
#: src/pages/events/TransportListPage.ts
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
#: src/pages/policies/PolicyListPage.ts
|
||||
|
@ -3609,6 +3723,7 @@ msgid "Timeout"
|
|||
msgstr "Timeout"
|
||||
|
||||
#: src/pages/flows/FlowForm.ts
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Title"
|
||||
msgstr "Title"
|
||||
|
||||
|
@ -3725,6 +3840,10 @@ msgstr "Unbound policies"
|
|||
msgid "Unenrollment"
|
||||
msgstr "Unenrollment"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Unenrollment flow"
|
||||
msgstr "Unenrollment flow"
|
||||
|
||||
#: src/pages/outposts/ServiceConnectionListPage.ts
|
||||
msgid "Unhealthy"
|
||||
msgstr "Unhealthy"
|
||||
|
@ -3774,6 +3893,7 @@ msgstr "Up-to-date!"
|
|||
#: src/pages/sources/saml/SAMLSourceViewPage.ts
|
||||
#: src/pages/stages/StageListPage.ts
|
||||
#: src/pages/stages/prompt/PromptListPage.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/user-settings/UserDetailsPage.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
#: src/pages/user-settings/settings/UserSettingsAuthenticatorWebAuthn.ts
|
||||
|
@ -3859,6 +3979,10 @@ msgstr "Update SAML Source"
|
|||
msgid "Update Stage binding"
|
||||
msgstr "Update Stage binding"
|
||||
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
msgid "Update Tenant"
|
||||
msgstr "Update Tenant"
|
||||
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
msgid "Update Token"
|
||||
msgstr "Update Token"
|
||||
|
@ -3923,8 +4047,13 @@ msgstr "Use the user's username, but deny enrollment when the username already e
|
|||
msgid "Use this redirect URL:"
|
||||
msgstr "Use this redirect URL:"
|
||||
|
||||
#: src/pages/tenants/TenantForm.ts
|
||||
msgid "Use this tenant for each domain that doesn't have a dedicated tenant."
|
||||
msgstr "Use this tenant for each domain that doesn't have a dedicated tenant."
|
||||
|
||||
#: src/elements/events/ObjectChangelog.ts
|
||||
#: src/elements/events/UserEvents.ts
|
||||
#: src/pages/applications/ApplicationCheckAccessForm.ts
|
||||
#: src/pages/events/EventInfo.ts
|
||||
#: src/pages/events/EventListPage.ts
|
||||
#: src/pages/policies/PolicyBindingForm.ts
|
||||
|
@ -4178,6 +4307,7 @@ msgstr ""
|
|||
msgid "X509 Subject"
|
||||
msgstr "X509 Subject"
|
||||
|
||||
#: src/pages/applications/ApplicationCheckAccessForm.ts
|
||||
#: src/pages/crypto/CertificateKeyPairListPage.ts
|
||||
#: src/pages/groups/GroupListPage.ts
|
||||
#: src/pages/groups/MemberSelectModal.ts
|
||||
|
@ -4186,6 +4316,7 @@ msgstr "X509 Subject"
|
|||
#: src/pages/policies/PolicyTestForm.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
#: src/pages/providers/proxy/ProxyProviderViewPage.ts
|
||||
#: src/pages/tenants/TenantListPage.ts
|
||||
#: src/pages/tokens/TokenListPage.ts
|
||||
#: src/pages/user-settings/tokens/UserTokenList.ts
|
||||
#: src/pages/users/UserListPage.ts
|
||||
|
|
|
@ -304,6 +304,7 @@ msgstr ""
|
|||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Authentication flow"
|
||||
msgstr ""
|
||||
|
||||
|
@ -418,6 +419,14 @@ msgstr ""
|
|||
msgid "Binding Type"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Branding settings"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Branding shown in page title and several other places."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Build hash: {0}"
|
||||
msgstr ""
|
||||
|
@ -518,6 +527,14 @@ msgstr ""
|
|||
msgid "Characters which are considered as symbols."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Check"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Check Application access"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Check IP"
|
||||
msgstr ""
|
||||
|
@ -526,6 +543,10 @@ msgstr ""
|
|||
msgid "Check Username"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Check access"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Check status"
|
||||
msgstr ""
|
||||
|
@ -658,6 +679,10 @@ msgstr ""
|
|||
msgid "Configure the maximum allowed time drift for an asseration."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Configure visual settings and defaults for different domains."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Configure what data should be used as unique User Identifier. For most cases, the default should be fine."
|
||||
msgstr ""
|
||||
|
@ -798,6 +823,8 @@ msgstr ""
|
|||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Create"
|
||||
msgstr ""
|
||||
|
||||
|
@ -863,6 +890,10 @@ msgstr ""
|
|||
msgid "Create Stage binding"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Create Tenant"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Create Token"
|
||||
msgstr ""
|
||||
|
@ -922,6 +953,18 @@ msgstr ""
|
|||
msgid "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Default flows"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Default?"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Define how notifications are sent to users, like Email or Webhook."
|
||||
msgstr ""
|
||||
|
@ -946,6 +989,7 @@ msgstr ""
|
|||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1063,6 +1107,11 @@ msgstr ""
|
|||
msgid "Docker URL"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
msgid "Domain"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
|
@ -1128,6 +1177,7 @@ msgstr ""
|
|||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1469,6 +1519,14 @@ msgstr ""
|
|||
msgid "Flow used for users to authenticate. Currently only identification and password stages are supported."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Flow used to logout. If left empty, the first applicable flow sorted by the slug is used."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
|
@ -1620,6 +1678,7 @@ msgstr ""
|
|||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Hold control/command to select multiple items."
|
||||
msgstr ""
|
||||
|
||||
|
@ -1635,6 +1694,10 @@ msgstr ""
|
|||
msgid "Icon"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Icon shown in sidebar/header and flow executor."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
|
@ -1656,6 +1719,10 @@ msgstr ""
|
|||
msgid "If left empty, authentik will try to extract the launch URL based on the selected provider."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "If this flag is set, this Stage will jump to the next Stage when no Invitation is given. By default this Stage will cancel the Flow when no invitation is given."
|
||||
msgstr ""
|
||||
|
@ -1717,6 +1784,10 @@ msgstr ""
|
|||
msgid "Invalidation"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Invalidation flow"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
msgid "Invitations"
|
||||
|
@ -1911,6 +1982,12 @@ msgstr ""
|
|||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
|
@ -1941,6 +2018,10 @@ msgstr ""
|
|||
msgid "Logins over the last 24 hours"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Logo"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Logout URL"
|
||||
msgstr ""
|
||||
|
@ -1981,6 +2062,7 @@ msgstr ""
|
|||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Messages"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2133,6 +2215,8 @@ msgstr ""
|
|||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2157,10 +2241,6 @@ msgstr ""
|
|||
msgid "No additional data available."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "No elements found."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "No form found"
|
||||
msgstr ""
|
||||
|
@ -2170,6 +2250,10 @@ msgstr ""
|
|||
msgid "No matching events could be found."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "No objects found."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "No policies are currently bound to this object."
|
||||
msgstr ""
|
||||
|
@ -2400,6 +2484,7 @@ msgstr ""
|
|||
msgid "Pass policy?"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Passing"
|
||||
|
@ -2664,10 +2749,15 @@ msgstr ""
|
|||
msgid "Recovery"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
msgid "Recovery flow"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Recovery flow. If left empty, the first applicable flow sorted by the slug is used."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Recovery keys"
|
||||
msgstr ""
|
||||
|
@ -2879,6 +2969,10 @@ msgstr ""
|
|||
msgid "Select one of the sources below to login."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Select sources should be shown for users to authenticate with. This only affects web-based sources, not LDAP."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Select users to add"
|
||||
msgstr ""
|
||||
|
@ -3044,6 +3138,7 @@ msgstr ""
|
|||
msgid "Source {0}"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Sources"
|
||||
|
@ -3280,6 +3375,10 @@ msgstr ""
|
|||
msgid "Successfully created stage."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Successfully created tenant."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Successfully created token."
|
||||
msgstr ""
|
||||
|
@ -3313,6 +3412,7 @@ msgstr ""
|
|||
msgid "Successfully imported provider."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Successfully sent test-request."
|
||||
|
@ -3419,6 +3519,10 @@ msgstr ""
|
|||
msgid "Successfully updated stage."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Successfully updated tenant."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Successfully updated token."
|
||||
msgstr ""
|
||||
|
@ -3508,6 +3612,16 @@ msgstr ""
|
|||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Tenant"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
msgid "Tenants"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
|
@ -3596,6 +3710,7 @@ msgstr ""
|
|||
msgid "Timeout"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
@ -3713,6 +3828,10 @@ msgstr ""
|
|||
msgid "Unenrollment"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Unenrollment flow"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Unhealthy"
|
||||
msgstr ""
|
||||
|
@ -3770,6 +3889,7 @@ msgstr ""
|
|||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Update"
|
||||
msgstr ""
|
||||
|
||||
|
@ -3847,6 +3967,10 @@ msgstr ""
|
|||
msgid "Update Stage binding"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Update Tenant"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Update Token"
|
||||
msgstr ""
|
||||
|
@ -3911,6 +4035,11 @@ msgstr ""
|
|||
msgid "Use this redirect URL:"
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
msgid "Use this tenant for each domain that doesn't have a dedicated tenant."
|
||||
msgstr ""
|
||||
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
|
@ -4175,6 +4304,8 @@ msgstr ""
|
|||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
#:
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
|
|
158
web/src/pages/tenants/TenantForm.ts
Normal file
158
web/src/pages/tenants/TenantForm.ts
Normal file
|
@ -0,0 +1,158 @@
|
|||
import { CoreApi, FlowsApi, FlowsInstancesListDesignationEnum, Tenant } from "authentik-api";
|
||||
import { t } from "@lingui/macro";
|
||||
import { customElement } from "lit-element";
|
||||
import { html, TemplateResult } from "lit-html";
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
import "../../elements/forms/HorizontalFormElement";
|
||||
import { first } from "../../utils";
|
||||
import { ModelForm } from "../../elements/forms/ModelForm";
|
||||
import { until } from "lit-html/directives/until";
|
||||
|
||||
@customElement("ak-tenant-form")
|
||||
export class TenantForm extends ModelForm<Tenant, string> {
|
||||
|
||||
loadInstance(pk: string): Promise<Tenant> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreTenantsRetrieve({
|
||||
tenantUuid: pk
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated tenant.`;
|
||||
} else {
|
||||
return t`Successfully created tenant.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: Tenant): Promise<Tenant> => {
|
||||
if (this.instance?.tenantUuid) {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreTenantsUpdate({
|
||||
tenantUuid: this.instance.tenantUuid,
|
||||
tenantRequest: data
|
||||
});
|
||||
} else {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreTenantsCreate({
|
||||
tenantRequest: data
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Domain`}
|
||||
?required=${true}
|
||||
name="domain">
|
||||
<input type="text" value="${first(this.instance?.domain, window.location.host)}" class="pf-c-form-control" required>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="default">
|
||||
<div class="pf-c-check">
|
||||
<input type="checkbox" class="pf-c-check__input" ?checked=${first(this.instance?._default, false)}>
|
||||
<label class="pf-c-check__label">
|
||||
${t`Default`}
|
||||
</label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">${t`Use this tenant for each domain that doesn't have a dedicated tenant.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header">
|
||||
${t`Branding settings`}
|
||||
</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Title`}
|
||||
?required=${true}
|
||||
name="brandingTitle">
|
||||
<input type="text" value="${first(this.instance?.brandingTitle, "authentik")}" class="pf-c-form-control" required>
|
||||
<p class="pf-c-form__helper-text">${t`Branding shown in page title and several other places.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Logo`}
|
||||
?required=${true}
|
||||
name="brandingLogo">
|
||||
<input type="text" value="${first(this.instance?.brandingLogo, "/static/dist/assets/icons/icon_left_brand.svg")}" class="pf-c-form-control" required>
|
||||
<p class="pf-c-form__helper-text">${t`Icon shown in sidebar/header and flow executor.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header">
|
||||
${t`Default flows`}
|
||||
</span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Authentication flow`}
|
||||
name="flowAuthentication">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.flowAuthentication === undefined}>---------</option>
|
||||
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
|
||||
ordering: "pk",
|
||||
designation: FlowsInstancesListDesignationEnum.Authentication,
|
||||
}).then(flows => {
|
||||
return flows.results.map(flow => {
|
||||
const selected = this.instance?.flowAuthentication === flow.pk;
|
||||
return html`<option value=${flow.pk} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
|
||||
});
|
||||
}), html`<option>${t`Loading...`}</option>`)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">${t`Flow used to authenticate users. If left empty, the first applicable flow sorted by the slug is used.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Invalidation flow`}
|
||||
name="flowInvalidation">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.flowInvalidation === undefined}>---------</option>
|
||||
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
|
||||
ordering: "pk",
|
||||
designation: FlowsInstancesListDesignationEnum.Invalidation,
|
||||
}).then(flows => {
|
||||
return flows.results.map(flow => {
|
||||
const selected = this.instance?.flowInvalidation === flow.pk;
|
||||
return html`<option value=${flow.pk} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
|
||||
});
|
||||
}), html`<option>${t`Loading...`}</option>`)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">${t`Flow used to logout. If left empty, the first applicable flow sorted by the slug is used.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Recovery flow`}
|
||||
name="flowRecovery">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.flowRecovery === undefined}>---------</option>
|
||||
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
|
||||
ordering: "pk",
|
||||
designation: FlowsInstancesListDesignationEnum.Recovery,
|
||||
}).then(flows => {
|
||||
return flows.results.map(flow => {
|
||||
const selected = this.instance?.flowRecovery === flow.pk;
|
||||
return html`<option value=${flow.pk} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
|
||||
});
|
||||
}), html`<option>${t`Loading...`}</option>`)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">${t`Recovery flow. If left empty, the first applicable flow sorted by the slug is used.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Unenrollment flow`}
|
||||
name="flowUnenrollment">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.flowUnenrollment === undefined}>---------</option>
|
||||
${until(new FlowsApi(DEFAULT_CONFIG).flowsInstancesList({
|
||||
ordering: "pk",
|
||||
designation: FlowsInstancesListDesignationEnum.Recovery,
|
||||
}).then(flows => {
|
||||
return flows.results.map(flow => {
|
||||
const selected = this.instance?.flowUnenrollment === flow.pk;
|
||||
return html`<option value=${flow.pk} ?selected=${selected}>${flow.name} (${flow.slug})</option>`;
|
||||
});
|
||||
}), html`<option>${t`Loading...`}</option>`)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">${t`If set, users are able to unenroll themselves using this flow. If no flow is set, option is not shown.`}</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
|
||||
}
|
101
web/src/pages/tenants/TenantListPage.ts
Normal file
101
web/src/pages/tenants/TenantListPage.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
import { t } from "@lingui/macro";
|
||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import { AKResponse } from "../../api/Client";
|
||||
import { TablePage } from "../../elements/table/TablePage";
|
||||
|
||||
import "../../elements/forms/DeleteForm";
|
||||
import "../../elements/buttons/SpinnerButton";
|
||||
import { TableColumn } from "../../elements/table/Table";
|
||||
import { PAGE_SIZE } from "../../constants";
|
||||
import { CoreApi, Tenant } from "authentik-api";
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
import "../../elements/forms/ModalForm";
|
||||
import "./TenantForm";
|
||||
|
||||
@customElement("ak-tenant-list")
|
||||
export class TenantListPage extends TablePage<Tenant> {
|
||||
searchEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
pageTitle(): string {
|
||||
return t`Tenants`;
|
||||
}
|
||||
pageDescription(): string {
|
||||
return t`Configure visual settings and defaults for different domains.`;
|
||||
}
|
||||
pageIcon(): string {
|
||||
return "pf-icon pf-icon-tenant";
|
||||
}
|
||||
|
||||
@property()
|
||||
order = "domain";
|
||||
|
||||
apiEndpoint(page: number): Promise<AKResponse<Tenant>> {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreTenantsList({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
pageSize: PAGE_SIZE,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(t`Domain`, "domain"),
|
||||
new TableColumn(t`Default?`, "default"),
|
||||
new TableColumn(""),
|
||||
];
|
||||
}
|
||||
|
||||
row(item: Tenant): TemplateResult[] {
|
||||
return [
|
||||
html`${item.domain}`,
|
||||
html`${item._default ? t`Yes` : t`No`}`,
|
||||
html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">
|
||||
${t`Update`}
|
||||
</span>
|
||||
<span slot="header">
|
||||
${t`Update Tenant`}
|
||||
</span>
|
||||
<ak-tenant-form slot="form" .instancePk=${item.tenantUuid}>
|
||||
</ak-tenant-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
||||
${t`Edit`}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-forms-delete
|
||||
.obj=${item}
|
||||
objectLabel=${t`Tenant`}
|
||||
.delete=${() => {
|
||||
return new CoreApi(DEFAULT_CONFIG).coreTenantsDestroy({
|
||||
tenantUuid: item.tenantUuid
|
||||
});
|
||||
}}>
|
||||
<button slot="trigger" class="pf-c-button pf-m-danger">
|
||||
${t`Delete`}
|
||||
</button>
|
||||
</ak-forms-delete>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">
|
||||
${t`Create`}
|
||||
</span>
|
||||
<span slot="header">
|
||||
${t`Create Tenant`}
|
||||
</span>
|
||||
<ak-tenant-form slot="form">
|
||||
</ak-tenant-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${t`Create`}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
${super.renderToolbar()}
|
||||
`;
|
||||
}
|
||||
}
|
|
@ -8,13 +8,13 @@ import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
|||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
|
||||
import { CoreApi, User } from "authentik-api";
|
||||
import { me } from "../../api/Users";
|
||||
import { FlowURLManager } from "../../api/legacy";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
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";
|
||||
|
||||
@customElement("ak-user-details")
|
||||
export class UserDetailsPage extends LitElement {
|
||||
|
@ -80,10 +80,15 @@ export class UserDetailsPage extends LitElement {
|
|||
<button class="pf-c-button pf-m-primary">
|
||||
${t`Update`}
|
||||
</button>
|
||||
<a class="pf-c-button pf-m-danger"
|
||||
href="${FlowURLManager.defaultUnenrollment()}">
|
||||
${t`Delete account`}
|
||||
</a>
|
||||
${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>
|
||||
|
|
|
@ -25,10 +25,11 @@ import "./pages/stages/invitation/InvitationListPage";
|
|||
import "./pages/stages/prompt/PromptListPage";
|
||||
import "./pages/stages/StageListPage";
|
||||
import "./pages/system-tasks/SystemTaskListPage";
|
||||
import "./pages/tenants/TenantListPage";
|
||||
import "./pages/tokens/TokenListPage";
|
||||
import "./pages/user-settings/UserSettingsPage";
|
||||
import "./pages/users/UserListPage";
|
||||
import "./pages/users/UserViewPage";
|
||||
import "./pages/user-settings/UserSettingsPage";
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
// Prevent infinite Shell loops
|
||||
|
@ -51,6 +52,7 @@ export const ROUTES: Route[] = [
|
|||
}),
|
||||
new Route(new RegExp("^/core/property-mappings$"), html`<ak-property-mapping-list></ak-property-mapping-list>`),
|
||||
new Route(new RegExp("^/core/tokens$"), html`<ak-token-list></ak-token-list>`),
|
||||
new Route(new RegExp("^/core/tenants$"), html`<ak-tenant-list></ak-tenant-list>`),
|
||||
new Route(new RegExp("^/policy/policies$"), html`<ak-policy-list></ak-policy-list>`),
|
||||
new Route(new RegExp("^/identity/groups$"), html`<ak-group-list></ak-group-list>`),
|
||||
new Route(new RegExp("^/identity/users$"), html`<ak-user-list></ak-user-list>`),
|
||||
|
|
Reference in a new issue