Compare commits

..

10 Commits

Author SHA1 Message Date
27e9e892e7 web: update the type names for routes.
While writing up the commit message for the previous commit, I realized that I didn't really like
the typenames as they're outlaid in `routeUtils`.  This is much more explicit, describing the
four types of routes we have: ListRoute, ViewRoute, InternalRedirect, ExternalRedirect.
2024-03-14 14:40:19 -07:00
70bf745c0c web: remove a lot of duplication from the Routes
This commit extracts the highly repetitive `new Route(new RegExp(...))` syntax into a mappable
function, leaving only the regular expression and the asynchronous loader behind.  In the process,
it creates a typed description of what kinds of routes we have, and uses type-level pattern matching
to generate the route handlers and redirects.

For example, it establishes that for the purposes of discriminating between redirects and loaders,
the loader type is irrelevant to routing correctly. The loader type *is* still well-typed to prevent
anyone from putting an incompatible loader into a route, but the two different loader types-- ones
that take arguments, and ones that do not-- do not matter to the route builder.

Likewise, the two different kinds of redirects *do* matter, but only because JavaScript makes a
distinction between methods of one argument that can be `call`'ed and methods of more than one
argument that must be `apply`'d.

I suppose I could make this irrelevant by enforcing that the second argument to RawRedirect always
be an Array, but that's not ergonomic, and when the `match()` function has such a lovely and simple
way of distinguishing between the two forms, ergonomics always wins.

This might seem like a curious bit of cleanup, but by exposing the regular expressions as strings,
independent of the Routes that they ultimately represent, we get the power to associate paths to
routeable objects with paths to navigable objects in the Sidebar, *and* ultimately with paths to
navigable objects in a command palette. It might also lead to replacing our Routes infrastructure
with an off-the-shelf router such as [Vaadin.router](https://github.com/vaadin/router) or Justin
Fagnani's preferred [page.js](https://visionmedia.github.io/page.js/)
2024-03-14 14:19:41 -07:00
8b4e0361c4 Merge branch 'main' into dev
* main:
  web: clean up and remove redundant alias '@goauthentik/app' (#8889)
  web/admin: fix markdown table rendering (#8908)
2024-03-14 10:35:46 -07:00
22cb5b7379 Merge branch 'main' into dev
* main:
  web: bump chromedriver from 122.0.5 to 122.0.6 in /tests/wdio (#8902)
  web: bump vite-tsconfig-paths from 4.3.1 to 4.3.2 in /web (#8903)
  core: bump google.golang.org/protobuf from 1.32.0 to 1.33.0 (#8901)
  web: provide InstallID on EnterpriseListPage (#8898)
2024-03-14 08:14:43 -07:00
2d0117d096 Merge branch 'main' into dev
* main:
  api: capabilities: properly set can_save_media when s3 is enabled (#8896)
  web: bump the rollup group in /web with 3 updates (#8891)
  core: bump pydantic from 2.6.3 to 2.6.4 (#8892)
  core: bump twilio from 9.0.0 to 9.0.1 (#8893)
2024-03-13 14:05:11 -07:00
035bda4eac Merge branch 'main' into dev
* main:
  Update _envoy_istio.md (#8888)
  website/docs: new landing page for Providers (#8879)
  web: bump the sentry group in /web with 1 update (#8881)
  web: bump chromedriver from 122.0.4 to 122.0.5 in /tests/wdio (#8884)
  web: bump the eslint group in /tests/wdio with 2 updates (#8883)
  web: bump the eslint group in /web with 2 updates (#8885)
  website: bump @types/react from 18.2.64 to 18.2.65 in /website (#8886)
2024-03-12 13:31:35 -07:00
50906214e5 Merge branch 'main' into dev
* main:
  web: upgrade to lit 3 (#8781)
2024-03-11 11:03:04 -07:00
e505f274b6 Merge branch 'main' into dev
* main:
  web: fix esbuild issue with style sheets (#8856)
2024-03-11 10:28:05 -07:00
fe52f44dca Merge branch 'main' into dev
* main:
  tenants: really ensure default tenant cannot be deleted (#8875)
  core: bump github.com/go-openapi/runtime from 0.27.2 to 0.28.0 (#8867)
  core: bump pytest from 8.0.2 to 8.1.1 (#8868)
  core: bump github.com/go-openapi/strfmt from 0.22.2 to 0.23.0 (#8869)
  core: bump bandit from 1.7.7 to 1.7.8 (#8870)
  core: bump packaging from 23.2 to 24.0 (#8871)
  core: bump ruff from 0.3.1 to 0.3.2 (#8873)
  web: bump the wdio group in /tests/wdio with 3 updates (#8865)
  core: bump requests-oauthlib from 1.3.1 to 1.4.0 (#8866)
  core: bump uvicorn from 0.27.1 to 0.28.0 (#8872)
  core: bump django-filter from 23.5 to 24.1 (#8874)
2024-03-11 10:27:43 -07:00
3146e5a50f web: fix esbuild issue with style sheets
Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious
pain. This fix better identifies the value types (instances) being passed from various sources in
the repo to the three *different* kinds of style processors we're using (the native one, the
polyfill one, and whatever the heck Storybook does internally).

Falling back to using older CSS instantiating techniques one era at a time seems to do the trick.
It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content
(FLoUC), it's the logic with which we're left.

In standard mode, the following warning appears on the console when running a Flow:

```
Autofocus processing was blocked because a document already has a focused element.
```

In compatibility mode, the following **error** appears on the console when running a Flow:

```
crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
    at initDomMutationObservers (crawler-inject.js:1106:18)
    at crawler-inject.js:1114:24
    at Array.forEach (<anonymous>)
    at initDomMutationObservers (crawler-inject.js:1114:10)
    at crawler-inject.js:1549:1
initDomMutationObservers @ crawler-inject.js:1106
(anonymous) @ crawler-inject.js:1114
initDomMutationObservers @ crawler-inject.js:1114
(anonymous) @ crawler-inject.js:1549
```

Despite this error, nothing seems to be broken and flows work as anticipated.
2024-03-08 14:15:55 -08:00
149 changed files with 1694 additions and 3362 deletions

View File

@ -7,6 +7,8 @@ on:
- main
- next
- version-*
paths-ignore:
- website/**
pull_request:
branches:
- main

View File

@ -10,7 +10,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from authentik import __version__, get_build_hash
from authentik.admin.tasks import VERSION_CACHE_KEY, VERSION_NULL, update_latest_version
from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
from authentik.core.api.utils import PassiveSerializer
@ -19,7 +19,6 @@ class VersionSerializer(PassiveSerializer):
version_current = SerializerMethodField()
version_latest = SerializerMethodField()
version_latest_valid = SerializerMethodField()
build_hash = SerializerMethodField()
outdated = SerializerMethodField()
@ -39,10 +38,6 @@ class VersionSerializer(PassiveSerializer):
return __version__
return version_in_cache
def get_version_latest_valid(self, _) -> bool:
"""Check if latest version is valid"""
return cache.get(VERSION_CACHE_KEY) != VERSION_NULL
def get_outdated(self, instance) -> bool:
"""Check if we're running the latest version"""
return parse(self.get_version_current(instance)) < parse(self.get_version_latest(instance))

View File

@ -18,7 +18,6 @@ from authentik.lib.utils.http import get_http_session
from authentik.root.celery import CELERY_APP
LOGGER = get_logger()
VERSION_NULL = "0.0.0"
VERSION_CACHE_KEY = "authentik_latest_version"
VERSION_CACHE_TIMEOUT = 8 * 60 * 60 # 8 hours
# Chop of the first ^ because we want to search the entire string
@ -56,7 +55,7 @@ def clear_update_notifications():
def update_latest_version(self: SystemTask):
"""Update latest version info"""
if CONFIG.get_bool("disable_update_check"):
cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT)
cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT)
self.set_status(TaskStatus.WARNING, "Version check disabled.")
return
try:
@ -83,7 +82,7 @@ def update_latest_version(self: SystemTask):
event_dict["message"] = f"Changelog: {match.group()}"
Event.new(EventAction.UPDATE_AVAILABLE, **event_dict).save()
except (RequestException, IndexError) as exc:
cache.set(VERSION_CACHE_KEY, VERSION_NULL, VERSION_CACHE_TIMEOUT)
cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT)
self.set_error(exc)

View File

@ -46,7 +46,6 @@ class BrandSerializer(ModelSerializer):
fields = [
"brand_uuid",
"domain",
"origin",
"default",
"branding_title",
"branding_logo",
@ -57,7 +56,6 @@ class BrandSerializer(ModelSerializer):
"flow_unenrollment",
"flow_user_settings",
"flow_device_code",
"default_application",
"web_certificate",
"attributes",
]

View File

@ -1,17 +1,12 @@
"""Inject brand into current request"""
from collections.abc import Callable
from typing import TYPE_CHECKING
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.utils.translation import activate
from authentik.brands.utils import get_brand_for_request
from authentik.lib.config import CONFIG
if TYPE_CHECKING:
from authentik.brands.models import Brand
class BrandMiddleware:
@ -30,41 +25,3 @@ class BrandMiddleware:
if locale != "":
activate(locale)
return self.get_response(request)
class BrandHeaderMiddleware:
"""Add headers from currently active brand"""
get_response: Callable[[HttpRequest], HttpResponse]
default_csp_elements: dict[str, list[str]] = {}
def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]):
self.get_response = get_response
self.default_csp_elements = {
"style-src": ["'self'", "'unsafe-inline'"], # Required due to Lit/ShadowDOM
"script-src": ["'self'", "'unsafe-inline'"], # Required for generated scripts
"img-src": ["https:", "http:", "data:"],
"default-src": ["'self'"],
"object-src": ["'none'"],
"connect-src": ["'self'"],
}
if CONFIG.get_bool("error_reporting.enabled"):
self.default_csp_elements["connect-src"].append(
# Required for sentry (TODO: Dynamic)
"https://authentik.error-reporting.a7k.io"
)
if CONFIG.get_bool("debug"):
# Also allow spotlight sidecar connection
self.default_csp_elements["connect-src"].append("http://localhost:8969")
def get_csp(self, request: HttpRequest) -> str:
brand: "Brand" = request.brand
elements = self.default_csp_elements.copy()
if brand.origin != "":
elements["frame-ancestors"] = [brand.origin]
return ";".join(f"{attr} {" ".join(value)}" for attr, value in elements.items())
def __call__(self, request: HttpRequest) -> HttpResponse:
response = self.get_response(request)
response.headers["Content-Security-Policy"] = self.get_csp(request)
return response

View File

@ -1,26 +0,0 @@
# Generated by Django 5.0.3 on 2024-03-21 15:42
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_brands", "0005_tenantuuid_to_branduuid"),
("authentik_core", "0033_alter_user_options"),
]
operations = [
migrations.AddField(
model_name="brand",
name="default_application",
field=models.ForeignKey(
default=None,
help_text="When set, external users will be redirected to this application after authenticating.",
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="authentik_core.application",
),
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 5.0.3 on 2024-03-26 14:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_brands", "0006_brand_default_application"),
]
operations = [
migrations.AddField(
model_name="brand",
name="origin",
field=models.TextField(
blank=True,
help_text="Origin domain that activates this brand. Can be left empty to not allow any origins.",
),
),
]

View File

@ -23,12 +23,6 @@ class Brand(SerializerModel):
"Domain that activates this brand. Can be a superset, i.e. `a.b` for `aa.b` and `ba.b`"
)
)
origin = models.TextField(
help_text=_(
"Origin domain that activates this brand. Can be left empty to not allow any origins."
),
blank=True,
)
default = models.BooleanField(
default=False,
)
@ -57,16 +51,6 @@ class Brand(SerializerModel):
Flow, null=True, on_delete=models.SET_NULL, related_name="brand_device_code"
)
default_application = models.ForeignKey(
"authentik_core.Application",
null=True,
default=None,
on_delete=models.SET_DEFAULT,
help_text=_(
"When set, external users will be redirected to this application after authenticating."
),
)
web_certificate = models.ForeignKey(
CertificateKeyPair,
null=True,

View File

@ -1,15 +1,11 @@
"""Brand utilities"""
from typing import Any
from urllib.parse import urlparse
from django.db.models import F, Q
from django.db.models import Value as V
from django.http import HttpResponse
from django.http.request import HttpRequest
from django.utils.cache import patch_vary_headers
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger
from authentik import get_full_version
from authentik.brands.models import Brand
@ -17,17 +13,13 @@ from authentik.tenants.models import Tenant
_q_default = Q(default=True)
DEFAULT_BRAND = Brand(domain="fallback")
LOGGER = get_logger()
def get_brand_for_request(request: HttpRequest) -> Brand:
"""Get brand object for current request"""
query = Q(host_domain__iendswith=F("domain"))
if "Origin" in request.headers:
query &= Q(Q(origin=request.headers.get("Origin", "")) | Q(origin=""))
db_brands = (
Brand.objects.annotate(host_domain=V(request.get_host()))
.filter(Q(query) | _q_default)
.filter(Q(host_domain__iendswith=F("domain")) | _q_default)
.order_by("default")
)
brands = list(db_brands.all())
@ -50,46 +42,3 @@ def context_processor(request: HttpRequest) -> dict[str, Any]:
"sentry_trace": trace,
"version": get_full_version(),
}
def cors_allow(request: HttpRequest, response: HttpResponse, *allowed_origins: str):
"""Add headers to permit CORS requests from allowed_origins, with or without credentials,
with any headers."""
origin = request.META.get("HTTP_ORIGIN")
if not origin:
return response
# OPTIONS requests don't have an authorization header -> hence
# we can't extract the provider this request is for
# so for options requests we allow the calling origin without checking
allowed = request.method == "OPTIONS"
received_origin = urlparse(origin)
for allowed_origin in allowed_origins:
url = urlparse(allowed_origin)
if (
received_origin.scheme == url.scheme
and received_origin.hostname == url.hostname
and received_origin.port == url.port
):
allowed = True
if not allowed:
LOGGER.warning(
"CORS: Origin is not an allowed origin",
requested=received_origin,
allowed=allowed_origins,
)
return response
# From the CORS spec: The string "*" cannot be used for a resource that supports credentials.
response["Access-Control-Allow-Origin"] = origin
patch_vary_headers(response, ["Origin"])
response["Access-Control-Allow-Credentials"] = "true"
if request.method == "OPTIONS":
if "HTTP_ACCESS_CONTROL_REQUEST_HEADERS" in request.META:
response["Access-Control-Allow-Headers"] = request.META[
"HTTP_ACCESS_CONTROL_REQUEST_HEADERS"
]
response["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
return response

View File

@ -37,7 +37,7 @@ from authentik.lib.utils.file import (
)
from authentik.policies.api.exec import PolicyTestResultSerializer
from authentik.policies.engine import PolicyEngine
from authentik.policies.types import CACHE_PREFIX, PolicyResult
from authentik.policies.types import PolicyResult
from authentik.rbac.decorators import permission_required
from authentik.rbac.filters import ObjectFilter
@ -46,7 +46,7 @@ LOGGER = get_logger()
def user_app_cache_key(user_pk: str) -> str:
"""Cache key where application list for user is saved"""
return f"{CACHE_PREFIX}/app_access/{user_pk}"
return f"goauthentik.io/core/app_access/{user_pk}"
class ApplicationSerializer(ModelSerializer):
@ -214,7 +214,7 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
return super().list(request)
queryset = self._filter_queryset_for_list(self.get_queryset())
paginated_apps = self.paginate_queryset(queryset)
pagined_apps = self.paginate_queryset(queryset)
if "for_user" in request.query_params:
try:
@ -228,18 +228,18 @@ class ApplicationViewSet(UsedByMixin, ModelViewSet):
raise ValidationError({"for_user": "User not found"})
except ValueError as exc:
raise ValidationError from exc
allowed_applications = self._get_allowed_applications(paginated_apps, user=for_user)
allowed_applications = self._get_allowed_applications(pagined_apps, user=for_user)
serializer = self.get_serializer(allowed_applications, many=True)
return self.get_paginated_response(serializer.data)
allowed_applications = []
if not should_cache:
allowed_applications = self._get_allowed_applications(paginated_apps)
allowed_applications = self._get_allowed_applications(pagined_apps)
if should_cache:
allowed_applications = cache.get(user_app_cache_key(self.request.user.pk))
if not allowed_applications:
LOGGER.debug("Caching allowed application list")
allowed_applications = self._get_allowed_applications(paginated_apps)
allowed_applications = self._get_allowed_applications(pagined_apps)
cache.set(
user_app_cache_key(self.request.user.pk),
allowed_applications,

View File

@ -617,9 +617,6 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
"""Get serializer for this model"""
raise NotImplementedError
def __str__(self) -> str:
return f"User-source connection (user={self.user.username}, source={self.source.slug})"
class Meta:
unique_together = (("user", "source"),)

View File

@ -16,9 +16,8 @@ from authentik.core.models import Source, SourceUserMatchingModes, User, UserSou
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION, PostUserEnrollmentStage
from authentik.events.models import Event, EventAction
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import Flow, FlowToken, Stage, in_memory_stage
from authentik.flows.models import Flow, Stage, in_memory_stage
from authentik.flows.planner import (
PLAN_CONTEXT_IS_RESTORED,
PLAN_CONTEXT_PENDING_USER,
PLAN_CONTEXT_REDIRECT,
PLAN_CONTEXT_SOURCE,
@ -36,8 +35,6 @@ from authentik.stages.password.stage import PLAN_CONTEXT_AUTHENTICATION_BACKEND
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
from authentik.stages.user_write.stage import PLAN_CONTEXT_USER_PATH
SESSION_KEY_OVERRIDE_FLOW_TOKEN = "authentik/flows/source_override_flow_token" # nosec
class Action(Enum):
"""Actions that can be decided based on the request
@ -225,43 +222,22 @@ class SourceFlowManager:
**kwargs,
) -> HttpResponse:
"""Prepare Authentication Plan, redirect user FlowExecutor"""
# Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:if-user"
)
kwargs.update(
{
# Since we authenticate the user by their token, they have no backend set
PLAN_CONTEXT_AUTHENTICATION_BACKEND: BACKEND_INBUILT,
PLAN_CONTEXT_SSO: True,
PLAN_CONTEXT_SOURCE: self.source,
PLAN_CONTEXT_REDIRECT: final_redirect,
PLAN_CONTEXT_SOURCES_CONNECTION: connection,
}
)
kwargs.update(self.policy_context)
if SESSION_KEY_OVERRIDE_FLOW_TOKEN in self.request.session:
token: FlowToken = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN)
self._logger.info("Replacing source flow with overridden flow", flow=token.flow.slug)
plan = token.plan
plan.context[PLAN_CONTEXT_IS_RESTORED] = token
plan.context.update(kwargs)
for stage in self.get_stages_to_append(flow):
plan.append_stage(stage)
if stages:
for stage in stages:
plan.append_stage(stage)
self.request.session[SESSION_KEY_PLAN] = plan
flow_slug = token.flow.slug
token.delete()
return redirect_with_qs(
"authentik_core:if-flow",
self.request.GET,
flow_slug=flow_slug,
)
# Ensure redirect is carried through when user was trying to
# authorize application
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "authentik_core:if-user"
)
if PLAN_CONTEXT_REDIRECT not in kwargs:
kwargs[PLAN_CONTEXT_REDIRECT] = final_redirect
if not flow:
return bad_request_message(
self.request,

View File

@ -6,6 +6,7 @@ from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.urls import path
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic import RedirectView
from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.authenticated_sessions import AuthenticatedSessionViewSet
@ -19,12 +20,7 @@ from authentik.core.api.transactional_applications import TransactionalApplicati
from authentik.core.api.users import UserViewSet
from authentik.core.views import apps
from authentik.core.views.debug import AccessDeniedView
from authentik.core.views.interface import (
BrandDefaultRedirectView,
FlowInterfaceView,
InterfaceView,
RootRedirectView,
)
from authentik.core.views.interface import FlowInterfaceView, InterfaceView
from authentik.core.views.session import EndSessionView
from authentik.root.asgi_middleware import SessionMiddleware
from authentik.root.messages.consumer import MessageConsumer
@ -33,11 +29,13 @@ from authentik.root.middleware import ChannelsLoggingMiddleware
urlpatterns = [
path(
"",
login_required(RootRedirectView.as_view()),
login_required(
RedirectView.as_view(pattern_name="authentik_core:if-user", query_string=True)
),
name="root-redirect",
),
path(
# We have to use this format since everything else uses application/o or application/saml
# We have to use this format since everything else uses applications/o or applications/saml
"application/launch/<slug:application_slug>/",
apps.RedirectToAppLaunch.as_view(),
name="application-launch",
@ -45,12 +43,12 @@ urlpatterns = [
# Interfaces
path(
"if/admin/",
ensure_csrf_cookie(BrandDefaultRedirectView.as_view(template_name="if/admin.html")),
ensure_csrf_cookie(InterfaceView.as_view(template_name="if/admin.html")),
name="if-admin",
),
path(
"if/user/",
ensure_csrf_cookie(BrandDefaultRedirectView.as_view(template_name="if/user.html")),
ensure_csrf_cookie(InterfaceView.as_view(template_name="if/user.html")),
name="if-user",
),
path(

View File

@ -3,43 +3,15 @@
from json import dumps
from typing import Any
from django.http import HttpRequest
from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.utils.translation import gettext as _
from django.views.generic.base import RedirectView, TemplateView
from django.shortcuts import get_object_or_404
from django.views.generic.base import TemplateView
from rest_framework.request import Request
from authentik import get_build_hash
from authentik.admin.tasks import LOCAL_VERSION
from authentik.api.v3.config import ConfigView
from authentik.brands.api import CurrentBrandSerializer
from authentik.brands.models import Brand
from authentik.core.models import UserTypes
from authentik.flows.models import Flow
from authentik.policies.denied import AccessDeniedResponse
class RootRedirectView(RedirectView):
"""Root redirect view, redirect to brand's default application if set"""
pattern_name = "authentik_core:if-user"
query_string = True
def redirect_to_app(self, request: HttpRequest):
if request.user.is_authenticated and request.user.type == UserTypes.EXTERNAL:
brand: Brand = request.brand
if brand.default_application:
return redirect(
"authentik_core:application-launch",
application_slug=brand.default_application.slug,
)
return None
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
if redirect_response := RootRedirectView().redirect_to_app(request):
return redirect_response
return super().dispatch(request, *args, **kwargs)
class InterfaceView(TemplateView):
@ -55,22 +27,6 @@ class InterfaceView(TemplateView):
return super().get_context_data(**kwargs)
class BrandDefaultRedirectView(InterfaceView):
"""By default redirect to default app"""
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
if request.user.is_authenticated and request.user.type == UserTypes.EXTERNAL:
brand: Brand = request.brand
if brand.default_application:
return redirect(
"authentik_core:application-launch",
application_slug=brand.default_application.slug,
)
response = AccessDeniedResponse(self.request)
response.error_message = _("Interface can only be accessed by internal users.")
return super().dispatch(request, *args, **kwargs)
class FlowInterfaceView(InterfaceView):
"""Flow interface"""

View File

@ -6,13 +6,13 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet
from authentik.api.authorization import OwnerFilter, OwnerSuperuserPermissions
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.groups import GroupMemberSerializer
from authentik.core.api.used_by import UsedByMixin
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.providers.rac.api.endpoints import EndpointSerializer
from authentik.enterprise.providers.rac.api.providers import RACProviderSerializer
from authentik.enterprise.providers.rac.models import ConnectionToken
from authentik.enterprise.providers.rac.models import ConnectionToken, Endpoint
class ConnectionTokenSerializer(EnterpriseRequiredMixin, ModelSerializer):
@ -23,7 +23,7 @@ class ConnectionTokenSerializer(EnterpriseRequiredMixin, ModelSerializer):
user = GroupMemberSerializer(source="session.user", read_only=True)
class Meta:
model = ConnectionToken
model = Endpoint
fields = [
"pk",
"provider",
@ -49,5 +49,5 @@ class ConnectionTokenViewSet(
filterset_fields = ["endpoint", "session__user", "provider"]
search_fields = ["endpoint__name", "provider__name"]
ordering = ["endpoint__name", "provider__name"]
permission_classes = [OwnerSuperuserPermissions]
permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]

View File

@ -15,7 +15,6 @@ CELERY_BEAT_SCHEDULE = {
TENANT_APPS = [
"authentik.enterprise.audit",
"authentik.enterprise.providers.rac",
"authentik.enterprise.stages.source",
]
MIDDLEWARE = ["authentik.enterprise.middleware.EnterpriseMiddleware"]

View File

@ -1,38 +0,0 @@
"""Source Stage API Views"""
from rest_framework.exceptions import ValidationError
from rest_framework.viewsets import ModelViewSet
from authentik.core.api.used_by import UsedByMixin
from authentik.core.models import Source
from authentik.enterprise.api import EnterpriseRequiredMixin
from authentik.enterprise.stages.source.models import SourceStage
from authentik.flows.api.stages import StageSerializer
class SourceStageSerializer(EnterpriseRequiredMixin, StageSerializer):
"""SourceStage Serializer"""
def validate_source(self, _source: Source) -> Source:
"""Ensure configured source supports web-based login"""
source = Source.objects.filter(pk=_source.pk).select_subclasses().first()
if not source:
raise ValidationError("Invalid source")
login_button = source.ui_login_button(self.context["request"])
if not login_button:
raise ValidationError("Invalid source selected, only web-based sources are supported.")
return source
class Meta:
model = SourceStage
fields = StageSerializer.Meta.fields + ["source", "resume_timeout"]
class SourceStageViewSet(UsedByMixin, ModelViewSet):
"""SourceStage Viewset"""
queryset = SourceStage.objects.all()
serializer_class = SourceStageSerializer
filterset_fields = "__all__"
ordering = ["name"]
search_fields = ["name"]

View File

@ -1,12 +0,0 @@
"""authentik stage app config"""
from authentik.enterprise.apps import EnterpriseConfig
class AuthentikEnterpriseStageSourceConfig(EnterpriseConfig):
"""authentik source stage config"""
name = "authentik.enterprise.stages.source"
label = "authentik_stages_source"
verbose_name = "authentik Enterprise.Stages.Source"
default = True

View File

@ -1,53 +0,0 @@
# Generated by Django 5.0.2 on 2024-02-25 20:44
import authentik.lib.utils.time
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_core", "0033_alter_user_options"),
("authentik_flows", "0027_auto_20231028_1424"),
]
operations = [
migrations.CreateModel(
name="SourceStage",
fields=[
(
"stage_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_flows.stage",
),
),
(
"resume_timeout",
models.TextField(
default="minutes=10",
help_text="Amount of time a user can take to return from the source to continue the flow (Format: hours=-1;minutes=-2;seconds=-3)",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
(
"source",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="authentik_core.source"
),
),
],
options={
"verbose_name": "Source Stage",
"verbose_name_plural": "Source Stages",
},
bases=("authentik_flows.stage",),
),
]

View File

@ -1,45 +0,0 @@
"""Source stage models"""
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.views import View
from rest_framework.serializers import BaseSerializer
from authentik.flows.models import Stage
from authentik.lib.utils.time import timedelta_string_validator
class SourceStage(Stage):
"""Suspend the current flow execution and send the user to a source,
after which this flow execution is resumed."""
source = models.ForeignKey("authentik_core.Source", on_delete=models.CASCADE)
resume_timeout = models.TextField(
default="minutes=10",
validators=[timedelta_string_validator],
help_text=_(
"Amount of time a user can take to return from the source to continue the flow "
"(Format: hours=-1;minutes=-2;seconds=-3)"
),
)
@property
def serializer(self) -> type[BaseSerializer]:
from authentik.enterprise.stages.source.api import SourceStageSerializer
return SourceStageSerializer
@property
def view(self) -> type[View]:
from authentik.enterprise.stages.source.stage import SourceStageView
return SourceStageView
@property
def component(self) -> str:
return "ak-stage-source-form"
class Meta:
verbose_name = _("Source Stage")
verbose_name_plural = _("Source Stages")

View File

@ -1,79 +0,0 @@
"""Source stage logic"""
from typing import Any
from uuid import uuid4
from django.http import HttpRequest, HttpResponse
from django.utils.text import slugify
from django.utils.timezone import now
from guardian.shortcuts import get_anonymous_user
from authentik.core.models import Source, User
from authentik.core.sources.flow_manager import SESSION_KEY_OVERRIDE_FLOW_TOKEN
from authentik.core.types import UILoginButton
from authentik.enterprise.stages.source.models import SourceStage
from authentik.flows.challenge import Challenge, ChallengeResponse
from authentik.flows.models import FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED
from authentik.flows.stage import ChallengeStageView
from authentik.lib.utils.time import timedelta_from_string
PLAN_CONTEXT_RESUME_TOKEN = "resume_token" # nosec
class SourceStageView(ChallengeStageView):
"""Suspend the current flow execution and send the user to a source,
after which this flow execution is resumed."""
login_button: UILoginButton
def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
current_stage: SourceStage = self.executor.current_stage
source: Source = (
Source.objects.filter(pk=current_stage.source_id).select_subclasses().first()
)
if not source:
self.logger.warning("Source does not exist")
return self.executor.stage_invalid("Source does not exist")
self.login_button = source.ui_login_button(self.request)
if not self.login_button:
self.logger.warning("Source does not have a UI login button")
return self.executor.stage_invalid("Invalid source")
restore_token = self.executor.plan.context.get(PLAN_CONTEXT_IS_RESTORED)
override_token = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN)
if restore_token and override_token and restore_token.pk == override_token.pk:
del self.request.session[SESSION_KEY_OVERRIDE_FLOW_TOKEN]
return self.executor.stage_ok()
return super().dispatch(request, *args, **kwargs)
def get_challenge(self, *args, **kwargs) -> Challenge:
resume_token = self.create_flow_token()
self.request.session[SESSION_KEY_OVERRIDE_FLOW_TOKEN] = resume_token
return self.login_button.challenge
def create_flow_token(self) -> FlowToken:
"""Save the current flow state in a token that can be used to resume this flow"""
pending_user: User = self.get_pending_user()
if pending_user.is_anonymous:
pending_user = get_anonymous_user()
current_stage: SourceStage = self.executor.current_stage
identifier = slugify(f"ak-source-stage-{current_stage.name}-{str(uuid4())}")
# Don't check for validity here, we only care if the token exists
tokens = FlowToken.objects.filter(identifier=identifier)
valid_delta = timedelta_from_string(current_stage.resume_timeout)
if not tokens.exists():
return FlowToken.objects.create(
expires=now() + valid_delta,
user=pending_user,
identifier=identifier,
flow=self.executor.flow,
_plan=FlowToken.pickle(self.executor.plan),
)
token = tokens.first()
# Check if token is expired and rotate key if so
if token.is_expired:
token.expire_action()
return token
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
return self.executor.stage_ok()

View File

@ -1,99 +0,0 @@
"""Source stage tests"""
from django.urls import reverse
from authentik.core.tests.utils import create_test_flow, create_test_user
from authentik.enterprise.stages.source.models import SourceStage
from authentik.flows.models import FlowDesignation, FlowStageBinding, FlowToken
from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, FlowPlan
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import SESSION_KEY_PLAN
from authentik.lib.generators import generate_id
from authentik.sources.saml.models import SAMLSource
from authentik.stages.identification.models import IdentificationStage, UserFields
from authentik.stages.password import BACKEND_INBUILT
from authentik.stages.password.models import PasswordStage
from authentik.stages.user_login.models import UserLoginStage
class TestSourceStage(FlowTestCase):
"""Source stage tests"""
def setUp(self):
self.source = SAMLSource.objects.create(
slug=generate_id(),
issuer="authentik",
allow_idp_initiated=True,
pre_authentication_flow=create_test_flow(),
)
def test_source_success(self):
"""Test"""
user = create_test_user()
flow = create_test_flow(FlowDesignation.AUTHENTICATION)
stage = SourceStage.objects.create(name=generate_id(), source=self.source)
FlowStageBinding.objects.create(
target=flow,
stage=IdentificationStage.objects.create(
name=generate_id(),
user_fields=[UserFields.USERNAME],
),
order=0,
)
FlowStageBinding.objects.create(
target=flow,
stage=PasswordStage.objects.create(name=generate_id(), backends=[BACKEND_INBUILT]),
order=5,
)
FlowStageBinding.objects.create(target=flow, stage=stage, order=10)
FlowStageBinding.objects.create(
target=flow,
stage=UserLoginStage.objects.create(
name=generate_id(),
),
order=15,
)
# Get user identification stage
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, flow, component="ak-stage-identification")
# Send username
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
data={"uid_field": user.username},
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, flow, component="ak-stage-password")
# Send password
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
data={"password": user.username},
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(
response,
reverse("authentik_sources_saml:login", kwargs={"source_slug": self.source.slug}),
)
# Hijack flow plan so we don't have to emulate the source
flow_token = FlowToken.objects.filter(
identifier__startswith=f"ak-source-stage-{stage.name.lower()}"
).first()
self.assertIsNotNone(flow_token)
session = self.client.session
plan: FlowPlan = session[SESSION_KEY_PLAN]
plan.context[PLAN_CONTEXT_IS_RESTORED] = flow_token
session[SESSION_KEY_PLAN] = plan
session.save()
# Pretend we've just returned from the source
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), follow=True
)
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))

View File

@ -1,5 +0,0 @@
"""API URLs"""
from authentik.enterprise.stages.source.api import SourceStageViewSet
api_urlpatterns = [("stages/source", SourceStageViewSet)]

View File

@ -452,13 +452,6 @@ class NotificationTransport(SerializerModel):
def send_email(self, notification: "Notification") -> list[str]:
"""Send notification via global email configuration"""
if notification.user.email.strip() == "":
LOGGER.info(
"Discarding notification as user has no email address",
user=notification.user,
notification=notification,
)
return None
subject_prefix = "authentik Notification: "
context = {
"key_value": {

View File

@ -13,7 +13,6 @@ from structlog.stdlib import get_logger
from authentik.core.api.used_by import UsedByMixin
from authentik.core.api.utils import MetaNameSerializer, TypeCreateSerializer
from authentik.core.types import UserSettingSerializer
from authentik.enterprise.apps import EnterpriseConfig
from authentik.flows.api.flows import FlowSetSerializer
from authentik.flows.models import ConfigurableStage, Stage
from authentik.lib.utils.reflection import all_subclasses
@ -76,7 +75,6 @@ class StageViewSet(
"description": subclass.__doc__,
"component": subclass().component,
"model_name": subclass._meta.model_name,
"requires_enterprise": isinstance(subclass._meta.app_config, EnterpriseConfig),
}
)
data = sorted(data, key=lambda x: x["name"])

View File

@ -53,7 +53,6 @@ class TestFlowInspector(APITestCase):
"title": flow.title,
"layout": "stacked",
},
"flow_designation": "authentication",
"type": ChallengeTypes.NATIVE.value,
"password_fields": False,
"primary_action": "Log in",

View File

@ -24,7 +24,6 @@ from sentry_sdk.hub import Hub
from structlog.stdlib import BoundLogger, get_logger
from authentik.brands.models import Brand
from authentik.brands.utils import cors_allow
from authentik.core.models import Application
from authentik.events.models import Event, EventAction, cleanse_dict
from authentik.flows.apps import HIST_FLOW_EXECUTION_STAGE_TIME
@ -156,14 +155,6 @@ class FlowExecutorView(APIView):
return plan
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
response = self.dispatch_wrapper(request, flow_slug)
origins = []
if request.brand.origin != "":
origins.append(request.brand.origin)
cors_allow(request, response, *origins)
return response
def dispatch_wrapper(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
with Hub.current.start_span(
op="authentik.flow.executor.dispatch", description=self.flow.slug
) as span:
@ -478,7 +469,7 @@ class FlowExecutorView(APIView):
class CancelView(View):
"""View which cancels the currently active plan"""
"""View which canels the currently active plan"""
def get(self, request: HttpRequest) -> HttpResponse:
"""View which canels the currently active plan"""

View File

@ -33,8 +33,6 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
# priority than being updated.
if current.spec.selector != reference.spec.selector:
raise NeedsUpdate()
if current.spec.type != reference.spec.type:
raise NeedsUpdate()
super().reconcile(current, reference)
def get_reference_object(self) -> V1Service:

View File

@ -1,9 +1,9 @@
"""authentik oauth provider app config"""
from authentik.blueprints.apps import ManagedAppConfig
from django.apps import AppConfig
class AuthentikProviderOAuth2Config(ManagedAppConfig):
class AuthentikProviderOAuth2Config(AppConfig):
"""authentik oauth provider app config"""
name = "authentik.providers.oauth2"
@ -13,4 +13,3 @@ class AuthentikProviderOAuth2Config(ManagedAppConfig):
"authentik.providers.oauth2.urls_root": "",
"authentik.providers.oauth2.urls": "application/o/",
}
default = True

View File

@ -1,15 +0,0 @@
from hashlib import sha256
from django.contrib.auth.signals import user_logged_out
from django.dispatch import receiver
from django.http import HttpRequest
from authentik.core.models import User
from authentik.providers.oauth2.models import AccessToken
@receiver(user_logged_out)
def user_logged_out_oauth_access_token(sender, request: HttpRequest, user: User, **_):
"""Revoke access tokens upon user logout"""
hashed_session_key = sha256(request.session.session_key.encode("ascii")).hexdigest()
AccessToken.objects.filter(user=user, session_id=hashed_session_key).delete()

View File

@ -4,9 +4,11 @@ import re
from base64 import b64decode
from binascii import Error
from typing import Any
from urllib.parse import urlparse
from django.http import HttpRequest, HttpResponse, JsonResponse
from django.http.response import HttpResponseRedirect
from django.utils.cache import patch_vary_headers
from structlog.stdlib import get_logger
from authentik.core.middleware import CTX_AUTH_VIA, KEY_USER
@ -28,6 +30,49 @@ class TokenResponse(JsonResponse):
self["Pragma"] = "no-cache"
def cors_allow(request: HttpRequest, response: HttpResponse, *allowed_origins: str):
"""Add headers to permit CORS requests from allowed_origins, with or without credentials,
with any headers."""
origin = request.META.get("HTTP_ORIGIN")
if not origin:
return response
# OPTIONS requests don't have an authorization header -> hence
# we can't extract the provider this request is for
# so for options requests we allow the calling origin without checking
allowed = request.method == "OPTIONS"
received_origin = urlparse(origin)
for allowed_origin in allowed_origins:
url = urlparse(allowed_origin)
if (
received_origin.scheme == url.scheme
and received_origin.hostname == url.hostname
and received_origin.port == url.port
):
allowed = True
if not allowed:
LOGGER.warning(
"CORS: Origin is not an allowed origin",
requested=received_origin,
allowed=allowed_origins,
)
return response
# From the CORS spec: The string "*" cannot be used for a resource that supports credentials.
response["Access-Control-Allow-Origin"] = origin
patch_vary_headers(response, ["Origin"])
response["Access-Control-Allow-Credentials"] = "true"
if request.method == "OPTIONS":
if "HTTP_ACCESS_CONTROL_REQUEST_HEADERS" in request.META:
response["Access-Control-Allow-Headers"] = request.META[
"HTTP_ACCESS_CONTROL_REQUEST_HEADERS"
]
response["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
return response
def extract_access_token(request: HttpRequest) -> str | None:
"""
Get the access token using Authorization Request Header Field method.

View File

@ -8,7 +8,6 @@ from django.views import View
from guardian.shortcuts import get_anonymous_user
from structlog.stdlib import get_logger
from authentik.brands.utils import cors_allow
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.core.models import Application
from authentik.providers.oauth2.constants import (
@ -29,6 +28,7 @@ from authentik.providers.oauth2.models import (
ResponseTypes,
ScopeMapping,
)
from authentik.providers.oauth2.utils import cors_allow
LOGGER = get_logger()

View File

@ -20,7 +20,6 @@ from jwt import PyJWK, PyJWT, PyJWTError, decode
from sentry_sdk.hub import Hub
from structlog.stdlib import get_logger
from authentik.brands.utils import cors_allow
from authentik.core.middleware import CTX_AUTH_VIA
from authentik.core.models import (
USER_ATTRIBUTE_EXPIRES,
@ -60,7 +59,7 @@ from authentik.providers.oauth2.models import (
OAuth2Provider,
RefreshToken,
)
from authentik.providers.oauth2.utils import TokenResponse, extract_client_auth
from authentik.providers.oauth2.utils import TokenResponse, cors_allow, extract_client_auth
from authentik.providers.oauth2.views.authorize import FORBIDDEN_URI_SCHEMES
from authentik.sources.oauth.models import OAuthSource
from authentik.stages.password.stage import PLAN_CONTEXT_METHOD, PLAN_CONTEXT_METHOD_ARGS

View File

@ -11,7 +11,6 @@ from django.views import View
from django.views.decorators.csrf import csrf_exempt
from structlog.stdlib import get_logger
from authentik.brands.utils import cors_allow
from authentik.core.exceptions import PropertyMappingExpressionException
from authentik.events.models import Event, EventAction
from authentik.flows.challenge import PermissionDict
@ -29,7 +28,7 @@ from authentik.providers.oauth2.models import (
RefreshToken,
ScopeMapping,
)
from authentik.providers.oauth2.utils import TokenResponse, protected_resource_view
from authentik.providers.oauth2.utils import TokenResponse, cors_allow, protected_resource_view
LOGGER = get_logger()

View File

@ -56,10 +56,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
proxy_provider: ProxyProvider
external_host_name = urlparse(proxy_provider.external_host)
expected_hosts.append(external_host_name.hostname)
if (
external_host_name.scheme == "https"
and self.controller.outpost.config.kubernetes_ingress_secret_name
):
if external_host_name.scheme == "https":
expected_hosts_tls.append(external_host_name.hostname)
expected_hosts.sort()
expected_hosts_tls.sort()
@ -119,10 +116,7 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
):
proxy_provider: ProxyProvider
external_host_name = urlparse(proxy_provider.external_host)
if (
external_host_name.scheme == "https"
and self.controller.outpost.config.kubernetes_ingress_secret_name
):
if external_host_name.scheme == "https":
tls_hosts.append(external_host_name.hostname)
if proxy_provider.mode in [
ProxyMode.FORWARD_SINGLE,
@ -166,15 +160,13 @@ class IngressReconciler(KubernetesObjectReconciler[V1Ingress]):
rules.append(rule)
tls_config = None
if tls_hosts:
tls_config = [
V1IngressTLS(
hosts=tls_hosts,
secret_name=self.controller.outpost.config.kubernetes_ingress_secret_name,
)
]
tls_config = V1IngressTLS(
hosts=tls_hosts,
secret_name=self.controller.outpost.config.kubernetes_ingress_secret_name,
)
spec = V1IngressSpec(
rules=rules,
tls=tls_config,
tls=[tls_config],
)
if self.controller.outpost.config.kubernetes_ingress_class_name:
spec.ingress_class_name = self.controller.outpost.config.kubernetes_ingress_class_name

View File

@ -196,10 +196,8 @@ if CONFIG.get_bool("redis.tls", False):
_redis_protocol_prefix = "rediss://"
_redis_celery_tls_requirements = f"?ssl_cert_reqs={CONFIG.get('redis.tls_reqs')}"
_redis_url = (
f"{_redis_protocol_prefix}"
f"{quote_plus(CONFIG.get('redis.username'))}:"
f"{quote_plus(CONFIG.get('redis.password'))}@"
f"{quote_plus(CONFIG.get('redis.host'))}:"
f"{_redis_protocol_prefix}:"
f"{quote_plus(CONFIG.get('redis.password'))}@{quote_plus(CONFIG.get('redis.host'))}:"
f"{CONFIG.get_int('redis.port')}"
)
@ -241,7 +239,7 @@ MIDDLEWARE = [
"django.middleware.common.CommonMiddleware",
"authentik.root.middleware.CsrfViewMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"authentik.brands.middleware.BrandHeaderMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"authentik.core.middleware.ImpersonateMiddleware",
"django_prometheus.middleware.PrometheusAfterMiddleware",
]

View File

@ -77,7 +77,6 @@ class LDAPSourceSerializer(SourceSerializer):
"group_object_filter",
"group_membership_field",
"object_uniqueness_field",
"password_login_update_internal_password",
"sync_users",
"sync_users_password",
"sync_groups",
@ -119,7 +118,6 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
"group_object_filter",
"group_membership_field",
"object_uniqueness_field",
"password_login_update_internal_password",
"sync_users",
"sync_users_password",
"sync_groups",

View File

@ -41,11 +41,10 @@ class LDAPBackend(InbuiltBackend):
# or has a password, but couldn't be authenticated by ModelBackend.
# This means we check with a bind to see if the LDAP password has changed
if self.auth_user_by_bind(source, user, password):
if source.password_login_update_internal_password:
# Password given successfully binds to LDAP, so we save it in our Database
LOGGER.debug("Updating user's password in DB", user=user)
user.set_password(password, signal=False)
user.save()
# Password given successfully binds to LDAP, so we save it in our Database
LOGGER.debug("Updating user's password in DB", user=user)
user.set_password(password, signal=False)
user.save()
return user
# Password doesn't match
LOGGER.debug("Failed to bind, password invalid")

View File

@ -1,29 +0,0 @@
# Generated by Django 5.0.1 on 2024-01-31 18:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_sources_ldap", "0003_ldapsource_client_certificate_ldapsource_sni_and_more"),
]
operations = [
migrations.AddField(
model_name="ldapsource",
name="password_login_update_internal_password",
field=models.BooleanField(
default=True,
help_text="Update internal authentik password when login succeeds with LDAP",
),
),
migrations.AlterField(
model_name="ldapsource",
name="password_login_update_internal_password",
field=models.BooleanField(
default=False,
help_text="Update internal authentik password when login succeeds with LDAP",
),
),
]

View File

@ -98,11 +98,6 @@ class LDAPSource(Source):
help_text=_("Property mappings used for group creation/updating."),
)
password_login_update_internal_password = models.BooleanField(
default=False,
help_text=_("Update internal authentik password when login succeeds with LDAP"),
)
sync_users = models.BooleanField(default=True)
sync_users_password = models.BooleanField(
default=True,

View File

@ -47,7 +47,7 @@ class SourceType:
def login_challenge(self, source: OAuthSource, request: HttpRequest) -> Challenge:
"""Allow types to return custom challenges"""
return RedirectChallenge(
data={
instance={
"type": ChallengeTypes.REDIRECT.value,
"to": reverse(
"authentik_sources_oauth:oauth-client-login",

View File

@ -54,7 +54,7 @@ class OAuthCallback(OAuthClientMixin, View):
raw_profile=exc.doc,
).from_http(self.request)
return self.handle_login_failure("Could not retrieve profile.")
identifier = self.get_user_id(info=raw_info)
identifier = self.get_user_id(raw_info)
if identifier is None:
return self.handle_login_failure("Could not determine id.")
# Get or create access record
@ -67,7 +67,6 @@ class OAuthCallback(OAuthClientMixin, View):
)
sfm.policy_context = {"oauth_userinfo": raw_info}
return sfm.get_flow(
raw_info=raw_info,
access_token=self.token.get("access_token"),
)
@ -117,7 +116,6 @@ class OAuthSourceFlowManager(SourceFlowManager):
self,
connection: UserOAuthSourceConnection,
access_token: str | None = None,
**_,
) -> UserOAuthSourceConnection:
"""Set the access_token on the connection"""
connection.access_token = access_token

View File

@ -190,7 +190,7 @@ class SAMLSource(Source):
def ui_login_button(self, request: HttpRequest) -> UILoginButton:
return UILoginButton(
challenge=RedirectChallenge(
data={
instance={
"type": ChallengeTypes.REDIRECT.value,
"to": reverse(
"authentik_sources_saml:login",

View File

@ -234,14 +234,12 @@ class ResponseProcessor:
if name_id.attrib["Format"] == SAML_NAME_ID_FORMAT_TRANSIENT:
return self._handle_name_id_transient()
flow_manager = SAMLSourceFlowManager(
return SAMLSourceFlowManager(
self._source,
self._http_request,
name_id.text,
delete_none_values(self.get_attributes()),
)
flow_manager.policy_context["saml_response"] = self._root
return flow_manager
class SAMLSourceFlowManager(SourceFlowManager):

View File

@ -120,9 +120,7 @@ def validate_challenge_code(code: str, stage_view: StageView, user: User) -> Dev
stage=stage_view.executor.current_stage,
device_class=DeviceClasses.TOTP.value,
)
raise ValidationError(
_("Invalid Token. Please ensure the time on your device is accurate and try again.")
)
raise ValidationError(_("Invalid Token"))
return device

View File

@ -12,7 +12,6 @@ class DummyChallenge(Challenge):
"""Dummy challenge"""
component = CharField(default="ak-stage-dummy")
name = CharField()
class DummyChallengeResponse(ChallengeResponse):
@ -36,6 +35,5 @@ class DummyStageView(ChallengeStageView):
data={
"type": ChallengeTypes.NATIVE.value,
"title": self.executor.current_stage.name,
"name": self.executor.current_stage.name,
}
)

View File

@ -118,7 +118,6 @@ class EmailStageView(ChallengeStageView):
"url": self.get_full_url(**{QS_KEY_TOKEN: token.key}),
"user": pending_user,
"expires": token.expires,
"token": token.key,
},
)
send_mails(current_stage, message)

View File

@ -10,7 +10,7 @@ from django.db.models import Q
from django.http import HttpResponse
from django.utils.translation import gettext as _
from drf_spectacular.utils import PolymorphicProxySerializer, extend_schema_field
from rest_framework.fields import BooleanField, CharField, ChoiceField, DictField, ListField
from rest_framework.fields import BooleanField, CharField, DictField, ListField
from rest_framework.serializers import ValidationError
from sentry_sdk.hub import Hub
@ -66,7 +66,6 @@ class IdentificationChallenge(Challenge):
user_fields = ListField(child=CharField(), allow_empty=True, allow_null=True)
password_fields = BooleanField()
application_pre = CharField(required=False)
flow_designation = ChoiceField(FlowDesignation.choices)
enroll_url = CharField(required=False)
recovery_url = CharField(required=False)
@ -195,12 +194,11 @@ class IdentificationStageView(ChallengeStageView):
challenge = IdentificationChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
"component": "ak-stage-identification",
"primary_action": self.get_primary_action(),
"component": "ak-stage-identification",
"user_fields": current_stage.user_fields,
"password_fields": bool(current_stage.password_stage),
"show_source_labels": current_stage.show_source_labels,
"flow_designation": self.executor.flow.designation,
}
)
# If the user has been redirected to us whilst trying to access an
@ -239,9 +237,7 @@ class IdentificationStageView(ChallengeStageView):
ui_login_button = source.ui_login_button(self.request)
if ui_login_button:
button = asdict(ui_login_button)
source_challenge = ui_login_button.challenge
source_challenge.is_valid()
button["challenge"] = source_challenge.data
button["challenge"] = ui_login_button.challenge.data
ui_sources.append(button)
challenge.initial_data["sources"] = ui_sources
return challenge

View File

@ -12,7 +12,6 @@ from rest_framework.exceptions import ValidationError
from authentik.core.middleware import SESSION_KEY_IMPERSONATE_USER
from authentik.core.models import USER_ATTRIBUTE_SOURCES, User, UserSourceConnection, UserTypes
from authentik.core.sources.stage import PLAN_CONTEXT_SOURCES_CONNECTION
from authentik.events.utils import sanitize_item
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import StageView
from authentik.flows.views.executor import FlowExecutorView
@ -48,7 +47,7 @@ class UserWriteStageView(StageView):
# this is just a sanity check to ensure that is removed
if parts[0] == "attributes":
parts = parts[1:]
set_path_in_dict(user.attributes, ".".join(parts), sanitize_item(value))
set_path_in_dict(user.attributes, ".".join(parts), value)
def ensure_user(self) -> tuple[User | None, bool]:
"""Ensure a user exists"""

View File

@ -2594,43 +2594,6 @@
}
}
},
{
"type": "object",
"required": [
"model",
"identifiers"
],
"properties": {
"model": {
"const": "authentik_stages_source.sourcestage"
},
"id": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"absent",
"present",
"created",
"must_created"
],
"default": "present"
},
"conditions": {
"type": "array",
"items": {
"type": "boolean"
}
},
"attrs": {
"$ref": "#/$defs/model_authentik_stages_source.sourcestage"
},
"identifiers": {
"$ref": "#/$defs/model_authentik_stages_source.sourcestage"
}
}
},
{
"type": "object",
"required": [
@ -3294,7 +3257,6 @@
"authentik.enterprise",
"authentik.enterprise.audit",
"authentik.enterprise.providers.rac",
"authentik.enterprise.stages.source",
"authentik.events"
],
"title": "App",
@ -3376,7 +3338,6 @@
"authentik_providers_rac.racprovider",
"authentik_providers_rac.endpoint",
"authentik_providers_rac.racpropertymapping",
"authentik_stages_source.sourcestage",
"authentik_events.event",
"authentik_events.notificationtransport",
"authentik_events.notification",
@ -4347,11 +4308,6 @@
"title": "Object uniqueness field",
"description": "Field which contains a unique Identifier."
},
"password_login_update_internal_password": {
"type": "boolean",
"title": "Password login update internal password",
"description": "Update internal authentik password when login succeeds with LDAP"
},
"sync_users": {
"type": "boolean",
"title": "Sync users"
@ -7609,11 +7565,6 @@
"title": "Domain",
"description": "Domain that activates this brand. Can be a superset, i.e. `a.b` for `aa.b` and `ba.b`"
},
"origin": {
"type": "string",
"title": "Origin",
"description": "Origin domain that activates this brand. Can be left empty to not allow any origins."
},
"default": {
"type": "boolean",
"title": "Default"
@ -7657,11 +7608,6 @@
"type": "integer",
"title": "Flow device code"
},
"default_application": {
"type": "integer",
"title": "Default application",
"description": "When set, external users will be redirected to this application after authenticating."
},
"web_certificate": {
"type": "integer",
"title": "Web certificate",
@ -8072,109 +8018,6 @@
},
"required": []
},
"model_authentik_stages_source.sourcestage": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"flow_set": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
"title": "Name"
},
"slug": {
"type": "string",
"maxLength": 50,
"minLength": 1,
"pattern": "^[-a-zA-Z0-9_]+$",
"title": "Slug",
"description": "Visible in the URL."
},
"title": {
"type": "string",
"minLength": 1,
"title": "Title",
"description": "Shown as the Title in Flow pages."
},
"designation": {
"type": "string",
"enum": [
"authentication",
"authorization",
"invalidation",
"enrollment",
"unenrollment",
"recovery",
"stage_configuration"
],
"title": "Designation",
"description": "Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik."
},
"policy_engine_mode": {
"type": "string",
"enum": [
"all",
"any"
],
"title": "Policy engine mode"
},
"compatibility_mode": {
"type": "boolean",
"title": "Compatibility mode",
"description": "Enable compatibility mode, increases compatibility with password managers on mobile devices."
},
"layout": {
"type": "string",
"enum": [
"stacked",
"content_left",
"content_right",
"sidebar_left",
"sidebar_right"
],
"title": "Layout"
},
"denied_action": {
"type": "string",
"enum": [
"message_continue",
"message",
"continue"
],
"title": "Denied action",
"description": "Configure what should happen when a flow denies access to a user."
}
},
"required": [
"name",
"slug",
"title",
"designation"
]
},
"title": "Flow set"
},
"source": {
"type": "integer",
"title": "Source"
},
"resume_timeout": {
"type": "string",
"minLength": 1,
"title": "Resume timeout",
"description": "Amount of time a user can take to return from the source to continue the flow (Format: hours=-1;minutes=-2;seconds=-3)"
}
},
"required": []
},
"model_authentik_events.event": {
"type": "object",
"properties": {

2
go.mod
View File

@ -30,7 +30,7 @@ require (
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2024022.5
goauthentik.io/api/v3 v3.2024022.1
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.18.0
golang.org/x/sync v0.6.0

4
go.sum
View File

@ -280,8 +280,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
goauthentik.io/api/v3 v3.2024022.5 h1:z1ZaVY/UpwpHAghf/PyYRSOQT7U9g8E2N23YlRB5BJQ=
goauthentik.io/api/v3 v3.2024022.5/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2024022.1 h1:ydYi3X/OSnu4LumUN+oCe6vvGDXil1Xn186hC9FQb4Q=
goauthentik.io/api/v3 v3.2024022.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

View File

@ -10,17 +10,12 @@ const CodePasswordSeparator = ";"
var alphaNum = regexp.MustCompile(`^[a-zA-Z0-9]*$`)
// Sets the secret answers for the flow executor for protocols that only support username/password
// according to used options
func (fe *FlowExecutor) SetSecrets(password string, mfaCodeBased bool) {
if fe.Answers[StageAuthenticatorValidate] != "" || fe.Answers[StagePassword] != "" {
return
}
fe.Answers[StagePassword] = password
if !mfaCodeBased {
// If code-based MFA is disabled StageAuthenticatorValidate answer is set to password.
// This allows flows with a mfa stage only.
fe.Answers[StageAuthenticatorValidate] = password
// CheckPasswordInlineMFA For protocols that only support username/password, check if the password
// contains the TOTP code
func (fe *FlowExecutor) CheckPasswordInlineMFA() {
password := fe.Answers[StagePassword]
// We already have an authenticator answer
if fe.Answers[StageAuthenticatorValidate] != "" {
return
}
// password doesn't contain the separator

View File

@ -23,7 +23,10 @@ func (db *DirectBinder) Bind(username string, req *bind.Request) (ldap.LDAPResul
fe.Params.Add("goauthentik.io/outpost/ldap", "true")
fe.Answers[flow.StageIdentification] = username
fe.SetSecrets(req.BindPW, db.si.GetMFASupport())
fe.Answers[flow.StagePassword] = req.BindPW
if db.si.GetMFASupport() {
fe.CheckPasswordInlineMFA()
}
passed, err := fe.Execute()
flags := flags.UserFlags{

View File

@ -56,7 +56,7 @@ func TestProxy_Redirect_Subdirectory(t *testing.T) {
loc, _ := rr.Result().Location()
assert.Equal(
t,
"https://ext.t.goauthentik.io/subdir/outpost.goauthentik.io/start?rd=https%3A%2F%2Fext.t.goauthentik.io%2Fsubdir%2Ffoo",
"https://ext.t.goauthentik.io/subdir/outpost.goauthentik.io/start?rd=https%3A%2F%2Fext.t.goauthentik.io%2Ffoo",
loc.String(),
)
}

View File

@ -32,8 +32,8 @@ func (a *Application) getStore(p api.ProxyOutpostConfig, externalHost *url.URL)
}
if a.isEmbedded {
client := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port),
Username: config.Get().Redis.Username,
Addr: fmt.Sprintf("%s:%d", config.Get().Redis.Host, config.Get().Redis.Port),
// Username: config.Get().Redis.Password,
Password: config.Get().Redis.Password,
DB: config.Get().Redis.DB,
})

View File

@ -3,6 +3,7 @@ package application
import (
"net/http"
"net/url"
"path"
"strconv"
"strings"
@ -10,12 +11,22 @@ import (
"goauthentik.io/internal/outpost/proxyv2/constants"
)
func urlJoin(originalUrl string, newPath string) string {
u, err := url.JoinPath(originalUrl, newPath)
func urlPathSet(originalUrl string, newPath string) string {
u, err := url.Parse(originalUrl)
if err != nil {
return originalUrl
}
return u
u.Path = newPath
return u.String()
}
func urlJoin(originalUrl string, newPath string) string {
u, err := url.Parse(originalUrl)
if err != nil {
return originalUrl
}
u.Path = path.Join(u.Path, newPath)
return u.String()
}
func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
@ -35,7 +46,7 @@ func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
}
}
redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.Path)
redirectUrl := urlPathSet(a.proxyConfig.ExternalHost, r.URL.Path)
if a.Mode() == api.PROXYMODE_FORWARD_DOMAIN {
dom := strings.TrimPrefix(*a.proxyConfig.CookieDomain, ".")

View File

@ -21,7 +21,10 @@ func (rs *RadiusServer) Handle_AccessRequest(w radius.ResponseWriter, r *RadiusR
fe.Params.Add("goauthentik.io/outpost/radius", "true")
fe.Answers[flow.StageIdentification] = username
fe.SetSecrets(rfc2865.UserPassword_GetString(r.Packet), r.pi.MFASupport)
fe.Answers[flow.StagePassword] = rfc2865.UserPassword_GetString(r.Packet)
if r.pi.MFASupport {
fe.CheckPasswordInlineMFA()
}
passed, err := fe.Execute()
if err != nil {

View File

@ -14,27 +14,26 @@ import (
)
func (ws *WebServer) configureStatic() {
staticRouter := ws.lh.NewRoute().Subrouter()
staticRouter.Use(ws.staticHeaderMiddleware)
staticRouter.Use(web.DisableIndex)
statRouter := ws.lh.NewRoute().Subrouter()
statRouter.Use(ws.staticHeaderMiddleware)
indexLessRouter := statRouter.NewRoute().Subrouter()
indexLessRouter.Use(web.DisableIndex)
distFs := http.FileServer(http.Dir("./web/dist"))
distHandler := http.StripPrefix("/static/dist/", distFs)
authentikHandler := http.StripPrefix("/static/authentik/", http.FileServer(http.Dir("./web/authentik")))
helpHandler := http.FileServer(http.Dir("./website/help/"))
indexLessRouter.PathPrefix("/static/dist/").Handler(distHandler)
indexLessRouter.PathPrefix("/static/authentik/").Handler(authentikHandler)
// Root file paths, from which they should be accessed
staticRouter.PathPrefix("/static/dist/").Handler(http.StripPrefix("/static/dist/", distFs))
staticRouter.PathPrefix("/static/authentik/").Handler(authentikHandler)
// Also serve assets folder in specific interfaces since fonts in patternfly are imported
// with a relative path
staticRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Prevent font-loading issues on safari, which loads fonts relatively to the URL the browser is on
indexLessRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs)).ServeHTTP(rw, r)
})
staticRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs))
staticRouter.PathPrefix("/if/user/assets").Handler(http.StripPrefix("/if/user", distFs))
staticRouter.PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
indexLessRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs))
indexLessRouter.PathPrefix("/if/user/assets").Handler(http.StripPrefix("/if/user", distFs))
indexLessRouter.PathPrefix("/if/rac/{app_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
web.DisableIndex(http.StripPrefix(fmt.Sprintf("/if/rac/%s", vars["app_slug"]), distFs)).ServeHTTP(rw, r)
@ -43,13 +42,12 @@ func (ws *WebServer) configureStatic() {
// Media files, if backend is file
if config.Get().Storage.Media.Backend == "file" {
fsMedia := http.FileServer(http.Dir(config.Get().Storage.Media.File.Path))
staticRouter.PathPrefix("/media/").Handler(http.StripPrefix("/media", fsMedia))
indexLessRouter.PathPrefix("/media/").Handler(http.StripPrefix("/media", fsMedia))
}
staticRouter.PathPrefix("/if/help/").Handler(http.StripPrefix("/if/help/", http.FileServer(http.Dir("./website/help/"))))
staticRouter.PathPrefix("/help").Handler(http.RedirectHandler("/if/help/", http.StatusMovedPermanently))
statRouter.PathPrefix("/if/help/").Handler(http.StripPrefix("/if/help/", helpHandler))
statRouter.PathPrefix("/help").Handler(http.RedirectHandler("/if/help/", http.StatusMovedPermanently))
// Static misc files
ws.lh.Path("/robots.txt").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header()["Content-Type"] = []string{"text/plain"}
rw.WriteHeader(200)

View File

@ -64,7 +64,6 @@ def release_lock(cursor: Cursor):
"""Release database lock"""
if not LOCKED:
return
LOGGER.info("releasing database lock")
cursor.execute("SELECT pg_advisory_unlock(%s)", (ADV_LOCK_UID,))

View File

@ -1,12 +0,0 @@
from lifecycle.migrate import BaseMigration
class Migration(BaseMigration):
def needs_migration(self) -> bool:
self.cur.execute(
"SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'template';"
)
return not bool(self.cur.rowcount)
def run(self):
self.cur.execute("CREATE SCHEMA IF NOT EXISTS template; COMMIT;")

View File

@ -116,7 +116,6 @@ class Migration(BaseMigration):
host=CONFIG.get("redis.host"),
port=6379,
db=db,
username=CONFIG.get("redis.username"),
password=CONFIG.get("redis.password"),
)
redis.flushall()

View File

@ -19,7 +19,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-05 00:07+0000\n"
"POT-Creation-Date: 2024-03-01 00:07+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2024\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
@ -802,10 +802,6 @@ msgstr "Jeton du flux"
msgid "Flow Tokens"
msgstr "Jetons du flux"
#: authentik/flows/views/executor.py
msgid "Invalid next URL"
msgstr "URL suivante invalide"
#: authentik/lib/utils/time.py
#, python-format
msgid "%(value)s is not in the correct format of 'hours=3;minutes=1'."

275
poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "aiohttp"
@ -392,33 +392,33 @@ files = [
[[package]]
name = "black"
version = "24.3.0"
version = "24.2.0"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"},
{file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"},
{file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"},
{file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"},
{file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"},
{file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"},
{file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"},
{file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"},
{file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"},
{file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"},
{file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"},
{file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"},
{file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"},
{file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"},
{file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"},
{file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"},
{file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"},
{file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"},
{file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"},
{file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"},
{file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"},
{file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"},
{file = "black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29"},
{file = "black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430"},
{file = "black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f"},
{file = "black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a"},
{file = "black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd"},
{file = "black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2"},
{file = "black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92"},
{file = "black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23"},
{file = "black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b"},
{file = "black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9"},
{file = "black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693"},
{file = "black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982"},
{file = "black-24.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4"},
{file = "black-24.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218"},
{file = "black-24.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0"},
{file = "black-24.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"},
{file = "black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8"},
{file = "black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8"},
{file = "black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540"},
{file = "black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31"},
{file = "black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6"},
{file = "black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894"},
]
[package.dependencies]
@ -455,19 +455,19 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"]
[[package]]
name = "botocore"
version = "1.34.63"
version = "1.34.15"
description = "Low-level, data-driven core of boto 3."
optional = false
python-versions = ">= 3.8"
files = [
{file = "botocore-1.34.63-py3-none-any.whl", hash = "sha256:8a6cbc3a5c5988725c00815f8f7f6baf81980b19d9a2ee414b031e726759dba9"},
{file = "botocore-1.34.63.tar.gz", hash = "sha256:2237743fc3ed68319bc358b451e7c13a02110242b1522b839806fd64fcee45fb"},
{file = "botocore-1.34.15-py3-none-any.whl", hash = "sha256:16bcf871e67ef0177593f06e9e5bae4db51c9a9a2e953cb14feeb42d53441a85"},
{file = "botocore-1.34.15.tar.gz", hash = "sha256:c3c3404962a6d9d5e1634bd70ed53b8eff1ff17ee9d7a6240e9e8c94db48ad6f"},
]
[package.dependencies]
jmespath = ">=0.7.1,<2.0.0"
python-dateutil = ">=2.1,<3.0.0"
urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}
urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""}
[package.extras]
crt = ["awscrt (==0.19.19)"]
@ -919,63 +919,63 @@ files = [
[[package]]
name = "coverage"
version = "7.4.4"
version = "7.4.3"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"},
{file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"},
{file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"},
{file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"},
{file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"},
{file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"},
{file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"},
{file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"},
{file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"},
{file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"},
{file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"},
{file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"},
{file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"},
{file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"},
{file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"},
{file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"},
{file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"},
{file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"},
{file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"},
{file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"},
{file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"},
{file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"},
{file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"},
{file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"},
{file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"},
{file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"},
{file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"},
{file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"},
{file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"},
{file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"},
{file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"},
{file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"},
{file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"},
{file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"},
{file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"},
{file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"},
{file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"},
{file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"},
{file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"},
{file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"},
{file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"},
{file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"},
{file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"},
{file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"},
{file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"},
{file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"},
{file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"},
{file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"},
{file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"},
{file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"},
{file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"},
{file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"},
{file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"},
{file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"},
{file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"},
{file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"},
{file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"},
{file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"},
{file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"},
{file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"},
{file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"},
{file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"},
{file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"},
{file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"},
{file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"},
{file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"},
{file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"},
{file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"},
{file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"},
{file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"},
{file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"},
{file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"},
{file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"},
{file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"},
{file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"},
{file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"},
{file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"},
{file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"},
{file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"},
{file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"},
{file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"},
{file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"},
{file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"},
{file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"},
]
[package.extras]
@ -1389,13 +1389,13 @@ files = [
[[package]]
name = "duo-client"
version = "5.3.0"
version = "5.2.0"
description = "Reference client for Duo Security APIs"
optional = false
python-versions = "*"
files = [
{file = "duo_client-5.3.0-py3-none-any.whl", hash = "sha256:85614bb684cef96285268aef0c1e858df939f6e8a190fb2c707d700bb0215766"},
{file = "duo_client-5.3.0.tar.gz", hash = "sha256:afa5ef98a42f06965a2702ca41dba9c85c483abd945e0a440f0ec4871b7593bf"},
{file = "duo_client-5.2.0-py3-none-any.whl", hash = "sha256:da3237e34300665c40ba5215f1e6656fec1a0136295917541aa973e7fcbf027e"},
{file = "duo_client-5.2.0.tar.gz", hash = "sha256:f82361740792b06303f9721e7ba593916080461769396b4f73c0502c0bfcee44"},
]
[package.dependencies]
@ -1712,13 +1712,13 @@ files = [
[[package]]
name = "importlib-metadata"
version = "7.1.0"
version = "7.0.2"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"},
{file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"},
{file = "importlib_metadata-7.0.2-py3-none-any.whl", hash = "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100"},
{file = "importlib_metadata-7.0.2.tar.gz", hash = "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792"},
]
[package.dependencies]
@ -1727,7 +1727,7 @@ zipp = ">=0.5"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
[[package]]
name = "incremental"
@ -3351,13 +3351,13 @@ test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "tes
[[package]]
name = "requests-oauthlib"
version = "2.0.0"
version = "1.4.0"
description = "OAuthlib authentication support for Requests."
optional = false
python-versions = ">=3.4"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
files = [
{file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"},
{file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"},
{file = "requests-oauthlib-1.4.0.tar.gz", hash = "sha256:acee623221e4a39abcbb919312c8ff04bd44e7e417087fb4bd5e2a2f53d5e79a"},
{file = "requests_oauthlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:7a3130d94a17520169e38db6c8d75f2c974643788465ecc2e4b36d288bf13033"},
]
[package.dependencies]
@ -3509,28 +3509,28 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.3.4"
version = "0.3.2"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"},
{file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"},
{file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"},
{file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"},
{file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"},
{file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"},
{file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"},
{file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"},
{file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77f2612752e25f730da7421ca5e3147b213dca4f9a0f7e0b534e9562c5441f01"},
{file = "ruff-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9966b964b2dd1107797be9ca7195002b874424d1d5472097701ae8f43eadef5d"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b83d17ff166aa0659d1e1deaf9f2f14cbe387293a906de09bc4860717eb2e2da"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb875c6cc87b3703aeda85f01c9aebdce3d217aeaca3c2e52e38077383f7268a"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be75e468a6a86426430373d81c041b7605137a28f7014a72d2fc749e47f572aa"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:967978ac2d4506255e2f52afe70dda023fc602b283e97685c8447d036863a302"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1231eacd4510f73222940727ac927bc5d07667a86b0cbe822024dd00343e77e9"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c6d613b19e9a8021be2ee1d0e27710208d1603b56f47203d0abbde906929a9b"},
{file = "ruff-0.3.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8439338a6303585d27b66b4626cbde89bb3e50fa3cae86ce52c1db7449330a7"},
{file = "ruff-0.3.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:de8b480d8379620cbb5ea466a9e53bb467d2fb07c7eca54a4aa8576483c35d36"},
{file = "ruff-0.3.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b74c3de9103bd35df2bb05d8b2899bf2dbe4efda6474ea9681280648ec4d237d"},
{file = "ruff-0.3.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f380be9fc15a99765c9cf316b40b9da1f6ad2ab9639e551703e581a5e6da6745"},
{file = "ruff-0.3.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0ac06a3759c3ab9ef86bbeca665d31ad3aa9a4b1c17684aadb7e61c10baa0df4"},
{file = "ruff-0.3.2-py3-none-win32.whl", hash = "sha256:9bd640a8f7dd07a0b6901fcebccedadeb1a705a50350fb86b4003b805c81385a"},
{file = "ruff-0.3.2-py3-none-win_amd64.whl", hash = "sha256:0c1bdd9920cab5707c26c8b3bf33a064a4ca7842d91a99ec0634fec68f9f4037"},
{file = "ruff-0.3.2-py3-none-win_arm64.whl", hash = "sha256:5f65103b1d76e0d600cabd577b04179ff592064eaa451a70a81085930e907d0b"},
{file = "ruff-0.3.2.tar.gz", hash = "sha256:fa78ec9418eb1ca3db392811df3376b46471ae93792a81af2d1cbb0e5dcb5142"},
]
[[package]]
@ -3570,13 +3570,13 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]}
[[package]]
name = "sentry-sdk"
version = "1.43.0"
version = "1.41.0"
description = "Python client for Sentry (https://sentry.io)"
optional = false
python-versions = "*"
files = [
{file = "sentry-sdk-1.43.0.tar.gz", hash = "sha256:41df73af89d22921d8733714fb0fc5586c3461907e06688e6537d01a27e0e0f6"},
{file = "sentry_sdk-1.43.0-py2.py3-none-any.whl", hash = "sha256:8d768724839ca18d7b4c7463ef7528c40b7aa2bfbf7fe554d5f9a7c044acfd36"},
{file = "sentry-sdk-1.41.0.tar.gz", hash = "sha256:4f2d6c43c07925d8cd10dfbd0970ea7cb784f70e79523cca9dbcd72df38e5a46"},
{file = "sentry_sdk-1.41.0-py2.py3-none-any.whl", hash = "sha256:be4f8f4b29a80b6a3b71f0f31487beb9e296391da20af8504498a328befed53f"},
]
[package.dependencies]
@ -3590,7 +3590,6 @@ asyncpg = ["asyncpg (>=0.23)"]
beam = ["apache-beam (>=2.12)"]
bottle = ["bottle (>=0.12.13)"]
celery = ["celery (>=3)"]
celery-redbeat = ["celery-redbeat (>=2)"]
chalice = ["chalice (>=1.16.0)"]
clickhouse-driver = ["clickhouse-driver (>=0.2.0)"]
django = ["django (>=1.8)"]
@ -3601,7 +3600,6 @@ grpcio = ["grpcio (>=1.21.1)"]
httpx = ["httpx (>=0.16.0)"]
huey = ["huey (>=2)"]
loguru = ["loguru (>=0.5)"]
openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"]
opentelemetry = ["opentelemetry-distro (>=0.35b0)"]
opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"]
pure-eval = ["asttokens", "executing", "pure-eval"]
@ -3820,13 +3818,13 @@ wsproto = ">=0.14"
[[package]]
name = "twilio"
version = "9.0.2"
version = "9.0.1"
description = "Twilio API client and TwiML generator"
optional = false
python-versions = ">=3.7.0"
files = [
{file = "twilio-9.0.2-py2.py3-none-any.whl", hash = "sha256:db89a8326f92240cdd8dc1dafb1d3f69169576243be9b9cbf5cf7778ecab0eed"},
{file = "twilio-9.0.2.tar.gz", hash = "sha256:9450b7a9987c32146d1760c8680f92baa76b4ba570543758a4937da48ae46d77"},
{file = "twilio-9.0.1-py2.py3-none-any.whl", hash = "sha256:7df45f314140b5931199420a2a00f0466d2fd16aff2f8c3e1589a47adc9deecb"},
{file = "twilio-9.0.1.tar.gz", hash = "sha256:4ffb63342dff9a5b24dd3f8d33e8fac2ceb55dcd10936d50456cbb44e1834e72"},
]
[package.dependencies]
@ -3964,33 +3962,48 @@ files = [
[[package]]
name = "urllib3"
version = "2.2.1"
version = "1.26.18"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.8"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
files = [
{file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
{file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
{file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"},
{file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"},
]
[package.dependencies]
pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""}
certifi = {version = "*", optional = true, markers = "extra == \"secure\""}
cryptography = {version = ">=1.3.4", optional = true, markers = "extra == \"secure\""}
idna = {version = ">=2.0.0", optional = true, markers = "extra == \"secure\""}
pyOpenSSL = {version = ">=0.14", optional = true, markers = "extra == \"secure\""}
PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""}
urllib3-secure-extra = {version = "*", optional = true, markers = "extra == \"secure\""}
[package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "urllib3-secure-extra"
version = "0.1.0"
description = "Marker library to detect whether urllib3 was installed with the deprecated [secure] extra"
optional = false
python-versions = "*"
files = [
{file = "urllib3-secure-extra-0.1.0.tar.gz", hash = "sha256:ee9409cbfeb4b8609047be4c32fb4317870c602767e53fd8a41005ebe6a41dff"},
{file = "urllib3_secure_extra-0.1.0-py2.py3-none-any.whl", hash = "sha256:f7adcb108b4d12a4b26b99eb60e265d087f435052a76aefa396b6ee85e9a6ef9"},
]
[[package]]
name = "uvicorn"
version = "0.29.0"
version = "0.28.0"
description = "The lightning-fast ASGI server."
optional = false
python-versions = ">=3.8"
files = [
{file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"},
{file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"},
{file = "uvicorn-0.28.0-py3-none-any.whl", hash = "sha256:6623abbbe6176204a4226e67607b4d52cc60ff62cda0ff177613645cefa2ece1"},
{file = "uvicorn-0.28.0.tar.gz", hash = "sha256:cab4473b5d1eaeb5a0f6375ac4bc85007ffc75c3cc1768816d9e5d589857b067"},
]
[package.dependencies]
@ -4538,4 +4551,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "~3.12"
content-hash = "04ef13e2692c158e5eda1e89876a215746c56a891a161d8434d808dc12c0fc7a"
content-hash = "825f1d552ba34206f7bfd55b70bfb42bc5d769605f59410703828ae787cd0baf"

View File

@ -140,7 +140,7 @@ twilio = "*"
twisted = "*"
ua-parser = "*"
# Pinned because of botocore https://github.com/orgs/python-poetry/discussions/7937
urllib3 = { extras = ["secure"], version = "<3" }
urllib3 = { extras = ["secure"], version = "<2" }
uvicorn = { extras = ["standard"], version = "*" }
watchdog = "*"
webauthn = "*"

View File

@ -18512,7 +18512,6 @@ paths:
- authentik_stages_password.passwordstage
- authentik_stages_prompt.prompt
- authentik_stages_prompt.promptstage
- authentik_stages_source.sourcestage
- authentik_stages_user_delete.userdeletestage
- authentik_stages_user_login.userloginstage
- authentik_stages_user_logout.userlogoutstage
@ -18588,7 +18587,6 @@ paths:
* `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification
@ -18802,7 +18800,6 @@ paths:
- authentik_stages_password.passwordstage
- authentik_stages_prompt.prompt
- authentik_stages_prompt.promptstage
- authentik_stages_source.sourcestage
- authentik_stages_user_delete.userdeletestage
- authentik_stages_user_login.userloginstage
- authentik_stages_user_logout.userlogoutstage
@ -18878,7 +18875,6 @@ paths:
* `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification
@ -19926,10 +19922,6 @@ paths:
description: Number of results to return per page.
schema:
type: integer
- in: query
name: password_login_update_internal_password
schema:
type: boolean
- in: query
name: peer_certificate
schema:
@ -27880,289 +27872,6 @@ paths:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/source/:
get:
operationId: stages_source_list
description: SourceStage Viewset
parameters:
- in: query
name: name
schema:
type: string
- 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
- in: query
name: resume_timeout
schema:
type: string
- name: search
required: false
in: query
description: A search term.
schema:
type: string
- in: query
name: source
schema:
type: string
format: uuid
- in: query
name: stage_uuid
schema:
type: string
format: uuid
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedSourceStageList'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
post:
operationId: stages_source_create
description: SourceStage Viewset
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStageRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/source/{stage_uuid}/:
get:
operationId: stages_source_retrieve
description: SourceStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Source Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
put:
operationId: stages_source_update
description: SourceStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Source Stage.
required: true
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStageRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
patch:
operationId: stages_source_partial_update
description: SourceStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Source Stage.
required: true
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedSourceStageRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SourceStage'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
delete:
operationId: stages_source_destroy
description: SourceStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Source Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'204':
description: No response body
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/source/{stage_uuid}/used_by/:
get:
operationId: stages_source_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this Source Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationError'
description: ''
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/GenericError'
description: ''
/stages/user_delete/:
get:
operationId: stages_user_delete_list
@ -29933,7 +29642,6 @@ components:
- authentik.enterprise
- authentik.enterprise.audit
- authentik.enterprise.providers.rac
- authentik.enterprise.stages.source
- authentik.events
type: string
description: |-
@ -29988,7 +29696,6 @@ components:
* `authentik.enterprise` - authentik Enterprise
* `authentik.enterprise.audit` - authentik Enterprise.Audit
* `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC
* `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source
* `authentik.events` - authentik Events
AppleChallengeResponseRequest:
type: object
@ -31334,10 +31041,6 @@ components:
type: string
description: Domain that activates this brand. Can be a superset, i.e. `a.b`
for `aa.b` and `ba.b`
origin:
type: string
description: Origin domain that activates this brand. Can be left empty
to not allow any origins.
default:
type: boolean
branding_title:
@ -31370,12 +31073,6 @@ components:
type: string
format: uuid
nullable: true
default_application:
type: string
format: uuid
nullable: true
description: When set, external users will be redirected to this application
after authenticating.
web_certificate:
type: string
format: uuid
@ -31394,10 +31091,6 @@ components:
minLength: 1
description: Domain that activates this brand. Can be a superset, i.e. `a.b`
for `aa.b` and `ba.b`
origin:
type: string
description: Origin domain that activates this brand. Can be left empty
to not allow any origins.
default:
type: boolean
branding_title:
@ -31433,12 +31126,6 @@ components:
type: string
format: uuid
nullable: true
default_application:
type: string
format: uuid
nullable: true
description: When set, external users will be redirected to this application
after authenticating.
web_certificate:
type: string
format: uuid
@ -31806,7 +31493,8 @@ components:
pk:
type: string
format: uuid
title: Connection token uuid
readOnly: true
title: Pbm uuid
provider:
type: integer
provider_obj:
@ -31816,6 +31504,7 @@ components:
endpoint:
type: string
format: uuid
readOnly: true
endpoint_obj:
allOf:
- $ref: '#/components/schemas/Endpoint'
@ -31827,6 +31516,7 @@ components:
required:
- endpoint
- endpoint_obj
- pk
- provider
- provider_obj
- user
@ -31834,17 +31524,9 @@ components:
type: object
description: ConnectionToken Serializer
properties:
pk:
type: string
format: uuid
title: Connection token uuid
provider:
type: integer
endpoint:
type: string
format: uuid
required:
- endpoint
- provider
ConsentChallenge:
type: object
@ -32375,10 +32057,7 @@ components:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
name:
type: string
required:
- name
- type
DummyChallengeResponseRequest:
type: object
@ -33060,7 +32739,6 @@ components:
* `authentik.enterprise` - authentik Enterprise
* `authentik.enterprise.audit` - authentik Enterprise.Audit
* `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC
* `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source
* `authentik.events` - authentik Events
model:
allOf:
@ -33138,7 +32816,6 @@ components:
* `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification
@ -33261,7 +32938,6 @@ components:
* `authentik.enterprise` - authentik Enterprise
* `authentik.enterprise.audit` - authentik Enterprise.Audit
* `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC
* `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source
* `authentik.events` - authentik Events
model:
allOf:
@ -33339,7 +33015,6 @@ components:
* `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification
@ -34361,8 +34036,6 @@ components:
type: boolean
application_pre:
type: string
flow_designation:
$ref: '#/components/schemas/FlowDesignationEnum'
enroll_url:
type: string
recovery_url:
@ -34378,7 +34051,6 @@ components:
show_source_labels:
type: boolean
required:
- flow_designation
- password_fields
- primary_action
- show_source_labels
@ -35249,10 +34921,6 @@ components:
object_uniqueness_field:
type: string
description: Field which contains a unique Identifier.
password_login_update_internal_password:
type: boolean
description: Update internal authentik password when login succeeds with
LDAP
sync_users:
type: boolean
sync_users_password:
@ -35394,10 +35062,6 @@ components:
type: string
minLength: 1
description: Field which contains a unique Identifier.
password_login_update_internal_password:
type: boolean
description: Update internal authentik password when login succeeds with
LDAP
sync_users:
type: boolean
sync_users_password:
@ -35666,7 +35330,6 @@ components:
- authentik_providers_rac.racprovider
- authentik_providers_rac.endpoint
- authentik_providers_rac.racpropertymapping
- authentik_stages_source.sourcestage
- authentik_events.event
- authentik_events.notificationtransport
- authentik_events.notification
@ -35743,7 +35406,6 @@ components:
* `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification
@ -37678,18 +37340,6 @@ components:
required:
- pagination
- results
PaginatedSourceStageList:
type: object
properties:
pagination:
$ref: '#/components/schemas/Pagination'
results:
type: array
items:
$ref: '#/components/schemas/SourceStage'
required:
- pagination
- results
PaginatedStageList:
type: object
properties:
@ -38538,10 +38188,6 @@ components:
minLength: 1
description: Domain that activates this brand. Can be a superset, i.e. `a.b`
for `aa.b` and `ba.b`
origin:
type: string
description: Origin domain that activates this brand. Can be left empty
to not allow any origins.
default:
type: boolean
branding_title:
@ -38577,12 +38223,6 @@ components:
type: string
format: uuid
nullable: true
default_application:
type: string
format: uuid
nullable: true
description: When set, external users will be redirected to this application
after authenticating.
web_certificate:
type: string
format: uuid
@ -38636,15 +38276,8 @@ components:
type: object
description: ConnectionToken Serializer
properties:
pk:
type: string
format: uuid
title: Connection token uuid
provider:
type: integer
endpoint:
type: string
format: uuid
PatchedConsentStageRequest:
type: object
description: ConsentStage Serializer
@ -38950,7 +38583,6 @@ components:
* `authentik.enterprise` - authentik Enterprise
* `authentik.enterprise.audit` - authentik Enterprise.Audit
* `authentik.enterprise.providers.rac` - authentik Enterprise.Providers.RAC
* `authentik.enterprise.stages.source` - authentik Enterprise.Stages.Source
* `authentik.events` - authentik Events
model:
allOf:
@ -39028,7 +38660,6 @@ components:
* `authentik_providers_rac.racprovider` - RAC Provider
* `authentik_providers_rac.endpoint` - RAC Endpoint
* `authentik_providers_rac.racpropertymapping` - RAC Property Mapping
* `authentik_stages_source.sourcestage` - Source Stage
* `authentik_events.event` - Event
* `authentik_events.notificationtransport` - Notification Transport
* `authentik_events.notification` - Notification
@ -39482,10 +39113,6 @@ components:
type: string
minLength: 1
description: Field which contains a unique Identifier.
password_login_update_internal_password:
type: boolean
description: Update internal authentik password when login succeeds with
LDAP
sync_users:
type: boolean
sync_users_password:
@ -40615,25 +40242,6 @@ components:
impersonation:
type: boolean
description: Globally enable/disable impersonation.
PatchedSourceStageRequest:
type: object
description: SourceStage Serializer
properties:
name:
type: string
minLength: 1
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowSetRequest'
source:
type: string
format: uuid
resume_timeout:
type: string
minLength: 1
description: 'Amount of time a user can take to return from the source to
continue the flow (Format: hours=-1;minutes=-2;seconds=-3)'
PatchedStaticDeviceRequest:
type: object
description: Serializer for static authenticator devices
@ -43978,74 +43586,6 @@ components:
required:
- name
- slug
SourceStage:
type: object
description: SourceStage Serializer
properties:
pk:
type: string
format: uuid
readOnly: true
title: Stage uuid
name:
type: string
component:
type: string
description: Get object type so that we know how to edit the object
readOnly: true
verbose_name:
type: string
description: Return object's verbose_name
readOnly: true
verbose_name_plural:
type: string
description: Return object's plural verbose_name
readOnly: true
meta_model_name:
type: string
description: Return internal model name
readOnly: true
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowSet'
source:
type: string
format: uuid
resume_timeout:
type: string
description: 'Amount of time a user can take to return from the source to
continue the flow (Format: hours=-1;minutes=-2;seconds=-3)'
required:
- component
- meta_model_name
- name
- pk
- source
- verbose_name
- verbose_name_plural
SourceStageRequest:
type: object
description: SourceStage Serializer
properties:
name:
type: string
minLength: 1
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowSetRequest'
source:
type: string
format: uuid
resume_timeout:
type: string
minLength: 1
description: 'Amount of time a user can take to return from the source to
continue the flow (Format: hours=-1;minutes=-2;seconds=-3)'
required:
- name
- source
SourceType:
type: object
description: Serializer for SourceType
@ -45644,10 +45184,6 @@ components:
type: string
description: Get latest version from cache
readOnly: true
version_latest_valid:
type: boolean
description: Check if latest version is valid
readOnly: true
build_hash:
type: string
description: Get build hash, if version is not latest or released
@ -45661,7 +45197,6 @@ components:
- outdated
- version_current
- version_latest
- version_latest_valid
WebAuthnDevice:
type: object
description: Serializer for WebAuthn authenticator devices

View File

@ -128,7 +128,6 @@ class TestSourceLDAPSamba(SeleniumTestCase):
base_dn="dc=test,dc=goauthentik,dc=io",
additional_user_dn="ou=users",
additional_group_dn="ou=groups",
password_login_update_internal_password=True,
)
source.property_mappings.set(
LDAPPropertyMapping.objects.filter(

View File

@ -10,7 +10,6 @@ from kubernetes.client.exceptions import OpenApiException
from authentik.core.tests.utils import create_test_flow
from authentik.lib.config import CONFIG
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
from authentik.outposts.controllers.k8s.service import ServiceReconciler
from authentik.outposts.controllers.k8s.triggers import NeedsUpdate
from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
from authentik.outposts.tasks import outpost_connection_discovery
@ -92,35 +91,6 @@ class OutpostKubernetesTests(TestCase):
deployment_reconciler.delete(deployment_reconciler.get_reference_object())
@pytest.mark.timeout(120)
def test_service_reconciler(self):
"""test that service requires update"""
controller = ProxyKubernetesController(self.outpost, self.service_connection)
service_reconciler = ServiceReconciler(controller)
self.assertIsNotNone(service_reconciler.retrieve())
config = self.outpost.config
config.kubernetes_service_type = "NodePort"
config.kubernetes_json_patches = {
"service": [
{
"op": "add",
"path": "/spec/ipFamilyPolicy",
"value": "PreferDualStack",
}
]
}
self.outpost.config = config
with self.assertRaises(NeedsUpdate):
service_reconciler.reconcile(
service_reconciler.retrieve(),
service_reconciler.get_reference_object(),
)
service_reconciler.delete(service_reconciler.get_reference_object())
@pytest.mark.timeout(120)
def test_controller_rename(self):
"""test that objects get deleted and re-created with new names"""

View File

@ -6,15 +6,15 @@
"": {
"name": "@goauthentik/web-tests",
"dependencies": {
"chromedriver": "^123.0.0"
"chromedriver": "^122.0.6"
},
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"@wdio/cli": "^8.35.1",
"@wdio/local-runner": "^8.35.1",
"@wdio/mocha-framework": "^8.35.0",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@wdio/cli": "^8.33.1",
"@wdio/local-runner": "^8.33.1",
"@wdio/mocha-framework": "^8.33.1",
"@wdio/spec-reporter": "^8.32.4",
"eslint": "^8.57.0",
"eslint-config-google": "^0.14.0",
@ -22,7 +22,7 @@
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.3",
"typescript": "^5.4.2",
"wdio-wait-for": "^3.0.11"
},
"engines": {
@ -889,13 +889,10 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.11.28",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz",
"integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==",
"devOptional": true,
"dependencies": {
"undici-types": "~5.26.4"
}
"version": "20.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.0.tgz",
"integrity": "sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==",
"devOptional": true
},
"node_modules/@types/normalize-package-data": {
"version": "2.4.4",
@ -955,16 +952,16 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz",
"integrity": "sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz",
"integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
"@typescript-eslint/scope-manager": "7.4.0",
"@typescript-eslint/type-utils": "7.4.0",
"@typescript-eslint/utils": "7.4.0",
"@typescript-eslint/visitor-keys": "7.4.0",
"@typescript-eslint/scope-manager": "7.2.0",
"@typescript-eslint/type-utils": "7.2.0",
"@typescript-eslint/utils": "7.2.0",
"@typescript-eslint/visitor-keys": "7.2.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@ -973,7 +970,7 @@
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -990,19 +987,19 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.4.0.tgz",
"integrity": "sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
"integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.4.0",
"@typescript-eslint/types": "7.4.0",
"@typescript-eslint/typescript-estree": "7.4.0",
"@typescript-eslint/visitor-keys": "7.4.0",
"@typescript-eslint/scope-manager": "7.2.0",
"@typescript-eslint/types": "7.2.0",
"@typescript-eslint/typescript-estree": "7.2.0",
"@typescript-eslint/visitor-keys": "7.2.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -1018,16 +1015,16 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz",
"integrity": "sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz",
"integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.4.0",
"@typescript-eslint/visitor-keys": "7.4.0"
"@typescript-eslint/types": "7.2.0",
"@typescript-eslint/visitor-keys": "7.2.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -1035,18 +1032,18 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz",
"integrity": "sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz",
"integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "7.4.0",
"@typescript-eslint/utils": "7.4.0",
"@typescript-eslint/typescript-estree": "7.2.0",
"@typescript-eslint/utils": "7.2.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -1062,12 +1059,12 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.4.0.tgz",
"integrity": "sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz",
"integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==",
"dev": true,
"engines": {
"node": "^18.18.0 || >=20.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -1075,13 +1072,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz",
"integrity": "sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz",
"integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.4.0",
"@typescript-eslint/visitor-keys": "7.4.0",
"@typescript-eslint/types": "7.2.0",
"@typescript-eslint/visitor-keys": "7.2.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -1090,7 +1087,7 @@
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -1127,21 +1124,21 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.4.0.tgz",
"integrity": "sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz",
"integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "7.4.0",
"@typescript-eslint/types": "7.4.0",
"@typescript-eslint/typescript-estree": "7.4.0",
"@typescript-eslint/scope-manager": "7.2.0",
"@typescript-eslint/types": "7.2.0",
"@typescript-eslint/typescript-estree": "7.2.0",
"semver": "^7.5.4"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -1152,16 +1149,16 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz",
"integrity": "sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz",
"integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.4.0",
"@typescript-eslint/types": "7.2.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
@ -1189,19 +1186,19 @@
}
},
"node_modules/@wdio/cli": {
"version": "8.35.1",
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.35.1.tgz",
"integrity": "sha512-cdFmd6P/eQJdP2lChQ+Fa9b1c2p0bDIPmetVHGCuHiW8ZPkanrvBFtHMUhMu44a1koni9LvN/hu7vIJ/aAC+Rg==",
"version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.33.1.tgz",
"integrity": "sha512-Ngt5R6YAmErkSKnWLWt1JilLIKDPIB0P93bzQhb9bQhmg1arFBcl75uiwe6kf6T355vzcNslMaEJyeuqGChmCg==",
"dev": true,
"dependencies": {
"@types/node": "^20.1.1",
"@vitest/snapshot": "^1.2.1",
"@wdio/config": "8.35.0",
"@wdio/globals": "8.35.1",
"@wdio/config": "8.33.1",
"@wdio/globals": "8.33.1",
"@wdio/logger": "8.28.0",
"@wdio/protocols": "8.32.0",
"@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0",
"@wdio/utils": "8.33.1",
"async-exit-hook": "^2.0.1",
"chalk": "^5.2.0",
"chokidar": "^3.5.3",
@ -1216,7 +1213,7 @@
"lodash.union": "^4.6.0",
"read-pkg-up": "10.0.0",
"recursive-readdir": "^2.2.3",
"webdriverio": "8.35.1",
"webdriverio": "8.33.1",
"yargs": "^17.7.2"
},
"bin": {
@ -1239,14 +1236,14 @@
}
},
"node_modules/@wdio/config": {
"version": "8.35.0",
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.35.0.tgz",
"integrity": "sha512-I36sBPMl/+LCyQ3Pwb8gGQM6KxwmUfhOPp16TxN21Qo/Bc0fZfyGIg6KevmRu4DuqpGUm5MMVSfyPhLUkMk3Cg==",
"version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.33.1.tgz",
"integrity": "sha512-JB7+tRkEsDJ4QAgJIZ3AaZvlp8pfBH6A5cKcGsaOuLVYMnsRPVkEGQc6n2akN9EPlDA2UjyrPOX6KZHbsSty7w==",
"dev": true,
"dependencies": {
"@wdio/logger": "8.28.0",
"@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0",
"@wdio/utils": "8.33.1",
"decamelize": "^6.0.0",
"deepmerge-ts": "^5.0.0",
"glob": "^10.2.2",
@ -1257,28 +1254,28 @@
}
},
"node_modules/@wdio/globals": {
"version": "8.35.1",
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.35.1.tgz",
"integrity": "sha512-T3IUFcKXRU9WWleAV72DGFWUiXSSr8SBvpc2cUJrvZ5Je9R2gEsrts5eHCY7amXtfeylfMgy5EayGMajgcna6A==",
"version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.33.1.tgz",
"integrity": "sha512-1ud9oq7n9MMNywS/FoMRRWqW6uhcoxgnpXoGeLE2Tr+4f937ABOl+sfZgjycXujyvR7yTL8AROOYajp1Yuv1Xg==",
"dev": true,
"engines": {
"node": "^16.13 || >=18"
},
"optionalDependencies": {
"expect-webdriverio": "^4.11.2",
"webdriverio": "8.35.1"
"webdriverio": "8.33.1"
}
},
"node_modules/@wdio/local-runner": {
"version": "8.35.1",
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.35.1.tgz",
"integrity": "sha512-PG+bADoY5VoWPmAfRi030rtxbFj68MVPlcwEN0dN1lDdYKz1ATzzGUK12sqCgGz1ktcC7sQzmJZVBklzbvn3mQ==",
"version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.33.1.tgz",
"integrity": "sha512-eQp12wHIkyh5zl9fun1qjv5Qvf4mCHPgLs5sKbfo3OK4LadzmD4/QNvDG8DYq/9cyuhVvnHgbLQ3XAnkoPde3w==",
"dev": true,
"dependencies": {
"@types/node": "^20.1.0",
"@wdio/logger": "8.28.0",
"@wdio/repl": "8.24.12",
"@wdio/runner": "8.35.1",
"@wdio/runner": "8.33.1",
"@wdio/types": "8.32.4",
"async-exit-hook": "^2.0.1",
"split2": "^4.1.0",
@ -1316,16 +1313,16 @@
}
},
"node_modules/@wdio/mocha-framework": {
"version": "8.35.0",
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.35.0.tgz",
"integrity": "sha512-riO3aMgvGdFFRMpyMk5m480V+mi5EcKk6cjT1TB9L5XEN7Mo/8qthBw9CLgFCZkr4KlR40hgPKSZFHE0rH2GpQ==",
"version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.33.1.tgz",
"integrity": "sha512-CxYLE22+tgnMnruElvDGJGR+dE0pxvMZ95agIUYYen69DJ705a74XtTR6zX9COWu6RooBezHgEs3fXev0XL79Q==",
"dev": true,
"dependencies": {
"@types/mocha": "^10.0.0",
"@types/node": "^20.1.0",
"@wdio/logger": "8.28.0",
"@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0",
"@wdio/utils": "8.33.1",
"mocha": "^10.0.0"
},
"engines": {
@ -1367,22 +1364,22 @@
}
},
"node_modules/@wdio/runner": {
"version": "8.35.1",
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.35.1.tgz",
"integrity": "sha512-5F6cbOYeZjF34Vsnycp5JPnDljI52fmyxsV2O/L3h6F2+83YXpbsqBplw/2G24JtIUudV7VOY/38bUicn1OyXg==",
"version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.33.1.tgz",
"integrity": "sha512-i0eRwMCePKkQocWsdkPQpBb1jELyNR5JCwnmOgM3g9fQI6KAf5D4oEUkNDFL/vD4UtgbSRmux7b7j5G01VvuqQ==",
"dev": true,
"dependencies": {
"@types/node": "^20.11.28",
"@wdio/config": "8.35.0",
"@wdio/globals": "8.35.1",
"@types/node": "^20.1.0",
"@wdio/config": "8.33.1",
"@wdio/globals": "8.33.1",
"@wdio/logger": "8.28.0",
"@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0",
"deepmerge-ts": "^5.1.0",
"expect-webdriverio": "^4.12.0",
"gaze": "^1.1.3",
"webdriver": "8.35.0",
"webdriverio": "8.35.1"
"@wdio/utils": "8.33.1",
"deepmerge-ts": "^5.0.0",
"expect-webdriverio": "^4.11.2",
"gaze": "^1.1.2",
"webdriver": "8.33.1",
"webdriverio": "8.33.1"
},
"engines": {
"node": "^16.13 || >=18"
@ -1429,9 +1426,9 @@
}
},
"node_modules/@wdio/utils": {
"version": "8.35.0",
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.35.0.tgz",
"integrity": "sha512-9KCyn4aS+9tWfthnUkNFVe52AM6QrLGAeIxgGxNlzTAcQGl7jjwdDM7aSK0RjLkWI3a/88DRH21mN/t2LGDmPQ==",
"version": "8.33.1",
"resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.33.1.tgz",
"integrity": "sha512-W0ArrZbs4M23POv8+FPsgHDFxg+wwklfZgLSsjVq2kpCmBCfIPxKSAOgTo/XrcH4We/OnshgBzxLcI+BHDgi4w==",
"dev": true,
"dependencies": {
"@puppeteer/browsers": "^1.6.0",
@ -2084,9 +2081,9 @@
}
},
"node_modules/chromedriver": {
"version": "123.0.0",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-123.0.0.tgz",
"integrity": "sha512-OE9mpxXwbFy5LncAisqXm1aEzuLPtEMORIxyYIn9uT7N8rquJWyoip6w9Rytub3o2gnynW9+PFOTPVTldaYrtw==",
"version": "122.0.6",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-122.0.6.tgz",
"integrity": "sha512-Q0r+QlUtiJWMQ5HdYaFa0CtBmLFq3n5JWfmq9mOC00UMBvWxku09gUkvBt457QnYfTM/XHqY/HTFOxHvATnTmA==",
"hasInstallScript": true,
"dependencies": {
"@testim/chrome-version": "^1.1.4",
@ -2691,9 +2688,9 @@
}
},
"node_modules/devtools-protocol": {
"version": "0.0.1273771",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1273771.tgz",
"integrity": "sha512-QDbb27xcTVReQQW/GHJsdQqGKwYBE7re7gxehj467kKP2DKuYBUj6i2k5LRiAC66J1yZG/9gsxooz/s9pcm0Og==",
"version": "0.0.1263784",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1263784.tgz",
"integrity": "sha512-k0SCZMwj587w4F8QYbP5iIbSonL6sd3q8aVJch036r9Tv2t9b5/Oq7AiJ/FJvRuORm/pJNXZtrdNNWlpRnl56A==",
"dev": true
},
"node_modules/diff": {
@ -3413,9 +3410,9 @@
}
},
"node_modules/expect-webdriverio": {
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.12.1.tgz",
"integrity": "sha512-jTfyC2bJbPNw4c8MlEwZNX7SjtPbZ73ysJvr/OGKA9mSKC+toyjU2eMNzHlt9WZO5+wl0RDS1dR7VxHXeu7+zA==",
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.11.2.tgz",
"integrity": "sha512-PK8lrQmRhK8NRtE8i/CJsnKS/QGrHULQW5EfmyKxIiLHnXd7t8dX0dWJn/fiYVtrPKBUyXSL6h52QqvZVc9yGQ==",
"dev": true,
"dependencies": {
"@vitest/snapshot": "^1.2.2",
@ -3699,9 +3696,9 @@
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [
{
"type": "individual",
@ -8620,9 +8617,9 @@
}
},
"node_modules/typescript": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
"integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@ -8657,12 +8654,6 @@
"through": "^2.3.8"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"devOptional": true
},
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@ -8886,18 +8877,18 @@
}
},
"node_modules/webdriver": {
"version": "8.35.0",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.35.0.tgz",
"integrity": "sha512-D13EroddIXDqdq3jgO8j6sorgTWqTwEiTqwlDoJizpRIgHGBy+UjkNM7XW1yVcvt8gsD2Dei2LQth2tJEnu5Ng==",
"version": "8.33.1",
"resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.33.1.tgz",
"integrity": "sha512-QREF4c08omN9BPh3QDmz5h+OEvjdzDliuEcrDuXoDnHSMxIj1rsonzsgRaM2PXhFZuPeMIiKZYqc7Qg9BGbh6A==",
"dev": true,
"dependencies": {
"@types/node": "^20.1.0",
"@types/ws": "^8.5.3",
"@wdio/config": "8.35.0",
"@wdio/config": "8.33.1",
"@wdio/logger": "8.28.0",
"@wdio/protocols": "8.32.0",
"@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0",
"@wdio/utils": "8.33.1",
"deepmerge-ts": "^5.1.0",
"got": "^12.6.1",
"ky": "^0.33.0",
@ -8908,23 +8899,23 @@
}
},
"node_modules/webdriverio": {
"version": "8.35.1",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.35.1.tgz",
"integrity": "sha512-YAuKR4JERGiMqCJmm5fEVZ160iiFPyupwALqfXfzrYVcEmKltKPFY/oUCArmi6Uzqd+Sa2Kp9WZtz2Eu1R76JA==",
"version": "8.33.1",
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.33.1.tgz",
"integrity": "sha512-1DsF8sx1a46AoVYCUpEwJYU74iBAW/U2H5r6p+60ct7dIiFmxmc4uCbOqtf7NLOTgrIzAOaRnT0EsrRICpg5Qw==",
"dev": true,
"dependencies": {
"@types/node": "^20.1.0",
"@wdio/config": "8.35.0",
"@wdio/config": "8.33.1",
"@wdio/logger": "8.28.0",
"@wdio/protocols": "8.32.0",
"@wdio/repl": "8.24.12",
"@wdio/types": "8.32.4",
"@wdio/utils": "8.35.0",
"@wdio/utils": "8.33.1",
"archiver": "^7.0.0",
"aria-query": "^5.0.0",
"css-shorthand-properties": "^1.1.1",
"css-value": "^0.0.1",
"devtools-protocol": "^0.0.1273771",
"devtools-protocol": "^0.0.1263784",
"grapheme-splitter": "^1.0.2",
"import-meta-resolve": "^4.0.0",
"is-plain-obj": "^4.1.0",
@ -8936,7 +8927,7 @@
"resq": "^1.9.1",
"rgb2hex": "0.2.5",
"serialize-error": "^11.0.1",
"webdriver": "8.35.0"
"webdriver": "8.33.1"
},
"engines": {
"node": "^16.13 || >=18"

View File

@ -4,11 +4,11 @@
"type": "module",
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"@wdio/cli": "^8.35.1",
"@wdio/local-runner": "^8.35.1",
"@wdio/mocha-framework": "^8.35.0",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@wdio/cli": "^8.33.1",
"@wdio/local-runner": "^8.33.1",
"@wdio/mocha-framework": "^8.33.1",
"@wdio/spec-reporter": "^8.32.4",
"eslint": "^8.57.0",
"eslint-config-google": "^0.14.0",
@ -16,7 +16,7 @@
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.3",
"typescript": "^5.4.2",
"wdio-wait-for": "^3.0.11"
},
"scripts": {
@ -32,6 +32,6 @@
"node": ">=20"
},
"dependencies": {
"chromedriver": "^123.0.0"
"chromedriver": "^122.0.6"
}
}

View File

@ -55,15 +55,15 @@ for (const [source, rawdest, strip] of otherFiles) {
// This starts the definitions used for esbuild: Our targets, our arguments, the function for running a build, and three
// options for building: watching, building, and building the proxy.
// Ordered by largest to smallest interface to build even faster
const interfaces = [
["admin/AdminInterface/AdminInterface.ts", "admin"],
["user/UserInterface.ts", "user"],
["flow/FlowInterface.ts", "flow"],
["standalone/api-browser/index.ts", "standalone/api-browser"],
["enterprise/rac/index.ts", "enterprise/rac"],
["standalone/loading/index.ts", "standalone/loading"],
["polyfill/poly.ts", "."],
["standalone/loading/index.ts", "standalone/loading"],
["flow/FlowInterface.ts", "flow"],
["user/UserInterface.ts", "user"],
["enterprise/rac/index.ts", "enterprise/rac"],
["standalone/api-browser/index.ts", "standalone/api-browser"],
["admin/AdminInterface/AdminInterface.ts", "admin"],
];
const baseArgs = {
@ -80,32 +80,29 @@ const baseArgs = {
format: "esm",
};
async function buildOneSource(source, dest) {
const DIST = path.join(__dirname, "./dist", dest);
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
try {
const start = Date.now();
await esbuild.build({
...baseArgs,
entryPoints: [`./src/${source}`],
outdir: DIST,
});
const end = Date.now();
console.log(
`[${new Date(end).toISOString()}] Finished build for target ${source} in ${
Date.now() - start
}ms`,
);
} catch (exc) {
console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`);
function buildAuthentik(interfaces) {
for (const [source, dest] of interfaces) {
const DIST = path.join(__dirname, "./dist", dest);
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
try {
const start = Date.now();
esbuild.buildSync({
...baseArgs,
entryPoints: [`./src/${source}`],
outdir: DIST,
});
const end = Date.now();
console.log(
`[${new Date(end).toISOString()}] Finished build for target ${source} in ${Date.now() - start}ms`,
);
} catch (exc) {
console.error(
`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`,
);
}
}
}
async function buildAuthentik(interfaces) {
await Promise.allSettled(interfaces.map(([source, dest]) => buildOneSource(source, dest)));
}
let timeoutId = null;
function debouncedBuild() {
if (timeoutId !== null) {
@ -141,11 +138,9 @@ if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] ===
});
} else if (process.argv.length > 2 && (process.argv[2] === "-p" || process.argv[2] === "--proxy")) {
// There's no watch-for-proxy, sorry.
await buildAuthentik(
interfaces.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)),
);
buildAuthentik(interfaces.slice(0, 2));
process.exit(0);
} else {
// And the fallback: just build it.
await buildAuthentik(interfaces);
buildAuthentik(interfaces);
}

1228
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -38,23 +38,23 @@
"@codemirror/theme-one-dark": "^6.1.2",
"@formatjs/intl-listformat": "^7.5.5",
"@fortawesome/fontawesome-free": "^6.5.1",
"@goauthentik/api": "^2024.2.2-1711369360",
"@goauthentik/api": "^2024.2.2-1709583949",
"@lit-labs/task": "^3.1.0",
"@lit/reactive-element": "^2.0.4",
"@lit/context": "^1.1.0",
"@lit/localize": "^0.12.1",
"@lit/reactive-element": "^2.0.4",
"@open-wc/lit-helpers": "^0.7.0",
"@patternfly/elements": "^2.4.0",
"@patternfly/patternfly": "^4.224.2",
"@sentry/browser": "^7.108.0",
"@sentry/browser": "^7.106.1",
"@webcomponents/webcomponentsjs": "^2.8.0",
"base64-js": "^1.5.1",
"chart.js": "^4.4.2",
"chartjs-adapter-moment": "^1.0.1",
"codemirror": "^6.0.1",
"construct-style-sheets-polyfill": "^3.1.0",
"core-js": "^3.36.1",
"country-flag-icons": "^1.5.10",
"core-js": "^3.36.0",
"country-flag-icons": "^1.5.9",
"fuse.js": "^7.0.0",
"guacamole-common-js": "^1.5.0",
"lit": "^3.1.2",
@ -68,19 +68,18 @@
"yaml": "^2.4.1"
},
"devDependencies": {
"@babel/core": "^7.24.3",
"@babel/core": "^7.24.0",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.24.1",
"@babel/plugin-transform-private-methods": "^7.24.1",
"@babel/plugin-transform-private-property-in-object": "^7.24.1",
"@babel/plugin-transform-runtime": "^7.24.3",
"@babel/preset-env": "^7.24.3",
"@babel/preset-typescript": "^7.24.1",
"@babel/plugin-proposal-decorators": "^7.24.0",
"@babel/plugin-transform-private-methods": "^7.23.3",
"@babel/plugin-transform-private-property-in-object": "^7.23.4",
"@babel/plugin-transform-runtime": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"@babel/preset-typescript": "^7.23.3",
"@hcaptcha/types": "^1.0.3",
"@jeysal/storybook-addon-css-user-preferences": "^0.2.0",
"@lit/localize-tools": "^0.7.2",
"@rollup/plugin-replace": "^5.0.5",
"@spotlightjs/spotlight": "^1.2.15",
"@spotlightjs/spotlight": "^1.2.12",
"@storybook/addon-essentials": "^7.6.17",
"@storybook/addon-links": "^7.6.17",
"@storybook/api": "^7.6.17",
@ -91,16 +90,19 @@
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/chart.js": "^2.9.41",
"@types/codemirror": "5.60.15",
"@types/grecaptcha": "^3.0.9",
"@types/grecaptcha": "^3.0.8",
"@types/guacamole-common-js": "1.5.2",
"@types/showdown": "^2.0.6",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@rollup/plugin-replace": "^5.0.5",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-tsconfig-paths": "^1.0.3",
"chokidar": "^3.6.0",
"cross-env": "^7.0.3",
"esbuild": "^0.20.2",
"esbuild": "^0.20.1",
"eslint": "^8.57.0",
"eslint-config-google": "^0.14.0",
"eslint-plugin-custom-elements": "0.0.8",
@ -115,14 +117,12 @@
"pseudolocale": "^2.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup-plugin-modify": "^3.0.0",
"rollup-plugin-postcss-lit": "^2.1.0",
"storybook": "^7.6.17",
"storybook-addon-mock": "^4.3.0",
"ts-lit-plugin": "^2.0.2",
"tslib": "^2.6.2",
"turnstile-types": "^1.2.0",
"typescript": "^5.4.3",
"typescript": "^5.4.2",
"vite-tsconfig-paths": "^4.3.2"
},
"optionalDependencies": {

View File

@ -1,155 +1,266 @@
import "@goauthentik/admin/admin-overview/AdminOverviewPage";
import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
import { RawRoute, makeRoute } from "@goauthentik/elements/router/routeUtils";
import { html } from "lit";
export const ROUTES: Route[] = [
export const _ROUTES: RawRoute[] = [
// Prevent infinite Shell loops
new Route(new RegExp("^/$")).redirect("/administration/overview"),
new Route(new RegExp("^#.*")).redirect("/administration/overview"),
new Route(new RegExp("^/library$")).redirect("/if/user/", true),
["^/$", "/administration/overview"],
["^#.*", "/administration/overview"],
["^/library$", ["/if/user/", true]],
// statically imported since this is the default route
new Route(new RegExp("^/administration/overview$"), async () => {
return html`<ak-admin-overview></ak-admin-overview>`;
}),
new Route(new RegExp("^/administration/dashboard/users$"), async () => {
await import("@goauthentik/admin/admin-overview/DashboardUserPage");
return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`;
}),
new Route(new RegExp("^/administration/system-tasks$"), async () => {
await import("@goauthentik/admin/system-tasks/SystemTaskListPage");
return html`<ak-system-task-list></ak-system-task-list>`;
}),
new Route(new RegExp("^/core/providers$"), async () => {
await import("@goauthentik/admin/providers/ProviderListPage");
return html`<ak-provider-list></ak-provider-list>`;
}),
new Route(new RegExp(`^/core/providers/(?<id>${ID_REGEX})$`), async (args) => {
await import("@goauthentik/admin/providers/ProviderViewPage");
return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`;
}),
new Route(new RegExp("^/core/applications$"), async () => {
await import("@goauthentik/admin/applications/ApplicationListPage");
return html`<ak-application-list></ak-application-list>`;
}),
new Route(new RegExp(`^/core/applications/(?<slug>${SLUG_REGEX})$`), async (args) => {
await import("@goauthentik/admin/applications/ApplicationViewPage");
return html`<ak-application-view .applicationSlug=${args.slug}></ak-application-view>`;
}),
new Route(new RegExp("^/core/sources$"), async () => {
await import("@goauthentik/admin/sources/SourceListPage");
return html`<ak-source-list></ak-source-list>`;
}),
new Route(new RegExp(`^/core/sources/(?<slug>${SLUG_REGEX})$`), async (args) => {
await import("@goauthentik/admin/sources/SourceViewPage");
return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`;
}),
new Route(new RegExp("^/core/property-mappings$"), async () => {
await import("@goauthentik/admin/property-mappings/PropertyMappingListPage");
return html`<ak-property-mapping-list></ak-property-mapping-list>`;
}),
new Route(new RegExp("^/core/tokens$"), async () => {
await import("@goauthentik/admin/tokens/TokenListPage");
return html`<ak-token-list></ak-token-list>`;
}),
new Route(new RegExp("^/core/brands"), async () => {
await import("@goauthentik/admin/brands/BrandListPage");
return html`<ak-brand-list></ak-brand-list>`;
}),
new Route(new RegExp("^/policy/policies$"), async () => {
await import("@goauthentik/admin/policies/PolicyListPage");
return html`<ak-policy-list></ak-policy-list>`;
}),
new Route(new RegExp("^/policy/reputation$"), async () => {
await import("@goauthentik/admin/policies/reputation/ReputationListPage");
return html`<ak-policy-reputation-list></ak-policy-reputation-list>`;
}),
new Route(new RegExp("^/identity/groups$"), async () => {
await import("@goauthentik/admin/groups/GroupListPage");
return html`<ak-group-list></ak-group-list>`;
}),
new Route(new RegExp(`^/identity/groups/(?<uuid>${UUID_REGEX})$`), async (args) => {
await import("@goauthentik/admin/groups/GroupViewPage");
return html`<ak-group-view .groupId=${args.uuid}></ak-group-view>`;
}),
new Route(new RegExp("^/identity/users$"), async () => {
await import("@goauthentik/admin/users/UserListPage");
return html`<ak-user-list></ak-user-list>`;
}),
new Route(new RegExp(`^/identity/users/(?<id>${ID_REGEX})$`), async (args) => {
await import("@goauthentik/admin/users/UserViewPage");
return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`;
}),
new Route(new RegExp("^/identity/roles$"), async () => {
await import("@goauthentik/admin/roles/RoleListPage");
return html`<ak-role-list></ak-role-list>`;
}),
new Route(new RegExp(`^/identity/roles/(?<id>${UUID_REGEX})$`), async (args) => {
await import("@goauthentik/admin/roles/RoleViewPage");
return html`<ak-role-view roleId=${args.id}></ak-role-view>`;
}),
new Route(new RegExp("^/flow/stages/invitations$"), async () => {
await import("@goauthentik/admin/stages/invitation/InvitationListPage");
return html`<ak-stage-invitation-list></ak-stage-invitation-list>`;
}),
new Route(new RegExp("^/flow/stages/prompts$"), async () => {
await import("@goauthentik/admin/stages/prompt/PromptListPage");
return html`<ak-stage-prompt-list></ak-stage-prompt-list>`;
}),
new Route(new RegExp("^/flow/stages$"), async () => {
await import("@goauthentik/admin/stages/StageListPage");
return html`<ak-stage-list></ak-stage-list>`;
}),
new Route(new RegExp("^/flow/flows$"), async () => {
await import("@goauthentik/admin/flows/FlowListPage");
return html`<ak-flow-list></ak-flow-list>`;
}),
new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`), async (args) => {
await import("@goauthentik/admin/flows/FlowViewPage");
return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`;
}),
new Route(new RegExp("^/events/log$"), async () => {
await import("@goauthentik/admin/events/EventListPage");
return html`<ak-event-list></ak-event-list>`;
}),
new Route(new RegExp(`^/events/log/(?<id>${UUID_REGEX})$`), async (args) => {
await import("@goauthentik/admin/events/EventViewPage");
return html`<ak-event-view .eventID=${args.id}></ak-event-view>`;
}),
new Route(new RegExp("^/events/transports$"), async () => {
await import("@goauthentik/admin/events/TransportListPage");
return html`<ak-event-transport-list></ak-event-transport-list>`;
}),
new Route(new RegExp("^/events/rules$"), async () => {
await import("@goauthentik/admin/events/RuleListPage");
return html`<ak-event-rule-list></ak-event-rule-list>`;
}),
new Route(new RegExp("^/outpost/outposts$"), async () => {
await import("@goauthentik/admin/outposts/OutpostListPage");
return html`<ak-outpost-list></ak-outpost-list>`;
}),
new Route(new RegExp("^/outpost/integrations$"), async () => {
await import("@goauthentik/admin/outposts/ServiceConnectionListPage");
return html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`;
}),
new Route(new RegExp("^/crypto/certificates$"), async () => {
await import("@goauthentik/admin/crypto/CertificateKeyPairListPage");
return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`;
}),
new Route(new RegExp("^/admin/settings$"), async () => {
await import("@goauthentik/admin/admin-settings/AdminSettingsPage");
return html`<ak-admin-settings></ak-admin-settings>`;
}),
new Route(new RegExp("^/blueprints/instances$"), async () => {
await import("@goauthentik/admin/blueprints/BlueprintListPage");
return html`<ak-blueprint-list></ak-blueprint-list>`;
}),
new Route(new RegExp("^/debug$"), async () => {
await import("@goauthentik/admin/DebugPage");
return html`<ak-admin-debug-page></ak-admin-debug-page>`;
}),
new Route(new RegExp("^/enterprise/licenses$"), async () => {
await import("@goauthentik/admin/enterprise/EnterpriseLicenseListPage");
return html`<ak-enterprise-license-list></ak-enterprise-license-list>`;
}),
[
"^/administration/overview$",
async () => {
return html`<ak-admin-overview></ak-admin-overview>`;
},
],
[
"^/administration/dashboard/users$",
async () => {
await import("@goauthentik/admin/admin-overview/DashboardUserPage");
return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`;
},
],
[
"^/administration/system-tasks$",
async () => {
await import("@goauthentik/admin/system-tasks/SystemTaskListPage");
return html`<ak-system-task-list></ak-system-task-list>`;
},
],
[
"^/core/providers$",
async () => {
await import("@goauthentik/admin/providers/ProviderListPage");
return html`<ak-provider-list></ak-provider-list>`;
},
],
[
`^/core/providers/(?<id>${ID_REGEX}])$`,
async (args) => {
await import("@goauthentik/admin/providers/ProviderViewPage");
return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`;
},
],
[
"^/core/applications$",
async () => {
await import("@goauthentik/admin/applications/ApplicationListPage");
return html`<ak-application-list></ak-application-list>`;
},
],
[
`^/core/applications/(?<slug>${SLUG_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/applications/ApplicationViewPage");
return html`<ak-application-view .applicationSlug=${args.slug}></ak-application-view>`;
},
],
[
"^/core/sources$",
async () => {
await import("@goauthentik/admin/sources/SourceListPage");
return html`<ak-source-list></ak-source-list>`;
},
],
[
`^/core/sources/(?<slug>${SLUG_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/sources/SourceViewPage");
return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`;
},
],
[
"^/core/property-mappings$",
async () => {
await import("@goauthentik/admin/property-mappings/PropertyMappingListPage");
return html`<ak-property-mapping-list></ak-property-mapping-list>`;
},
],
[
"^/core/tokens$",
async () => {
await import("@goauthentik/admin/tokens/TokenListPage");
return html`<ak-token-list></ak-token-list>`;
},
],
[
"^/core/brands",
async () => {
await import("@goauthentik/admin/brands/BrandListPage");
return html`<ak-brand-list></ak-brand-list>`;
},
],
[
"^/policy/policies$",
async () => {
await import("@goauthentik/admin/policies/PolicyListPage");
return html`<ak-policy-list></ak-policy-list>`;
},
],
[
"^/policy/reputation$",
async () => {
await import("@goauthentik/admin/policies/reputation/ReputationListPage");
return html`<ak-policy-reputation-list></ak-policy-reputation-list>`;
},
],
[
"^/identity/groups$",
async () => {
await import("@goauthentik/admin/groups/GroupListPage");
return html`<ak-group-list></ak-group-list>`;
},
],
[
`^/identity/groups/(?<uuid>${UUID_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/groups/GroupViewPage");
return html`<ak-group-view .groupId=${args.uuid}></ak-group-view>`;
},
],
[
"^/identity/users$",
async () => {
await import("@goauthentik/admin/users/UserListPage");
return html`<ak-user-list></ak-user-list>`;
},
],
[
`^/identity/users/(?<id>${ID_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/users/UserViewPage");
return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`;
},
],
[
"^/identity/roles$",
async () => {
await import("@goauthentik/admin/roles/RoleListPage");
return html`<ak-role-list></ak-role-list>`;
},
],
[
`^/identity/roles/(?<id>${UUID_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/roles/RoleViewPage");
return html`<ak-role-view roleId=${args.id}></ak-role-view>`;
},
],
[
"^/flow/stages/invitations$",
async () => {
await import("@goauthentik/admin/stages/invitation/InvitationListPage");
return html`<ak-stage-invitation-list></ak-stage-invitation-list>`;
},
],
[
"^/flow/stages/prompts$",
async () => {
await import("@goauthentik/admin/stages/prompt/PromptListPage");
return html`<ak-stage-prompt-list></ak-stage-prompt-list>`;
},
],
[
"^/flow/stages$",
async () => {
await import("@goauthentik/admin/stages/StageListPage");
return html`<ak-stage-list></ak-stage-list>`;
},
],
[
"^/flow/flows$",
async () => {
await import("@goauthentik/admin/flows/FlowListPage");
return html`<ak-flow-list></ak-flow-list>`;
},
],
[
`^/flow/flows/(?<slug>${SLUG_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/flows/FlowViewPage");
return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`;
},
],
[
"^/events/log$",
async () => {
await import("@goauthentik/admin/events/EventListPage");
return html`<ak-event-list></ak-event-list>`;
},
],
[
`^/events/log/(?<id>${UUID_REGEX})$`,
async (args) => {
await import("@goauthentik/admin/events/EventViewPage");
return html`<ak-event-view .eventID=${args.id}></ak-event-view>`;
},
],
[
"^/events/transports$",
async () => {
await import("@goauthentik/admin/events/TransportListPage");
return html`<ak-event-transport-list></ak-event-transport-list>`;
},
],
[
"^/events/rules$",
async () => {
await import("@goauthentik/admin/events/RuleListPage");
return html`<ak-event-rule-list></ak-event-rule-list>`;
},
],
[
"^/outpost/outposts$",
async () => {
await import("@goauthentik/admin/outposts/OutpostListPage");
return html`<ak-outpost-list></ak-outpost-list>`;
},
],
[
"^/outpost/integrations$",
async () => {
await import("@goauthentik/admin/outposts/ServiceConnectionListPage");
return html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`;
},
],
[
"^/crypto/certificates$",
async () => {
await import("@goauthentik/admin/crypto/CertificateKeyPairListPage");
return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`;
},
],
[
"^/admin/settings$",
async () => {
await import("@goauthentik/admin/admin-settings/AdminSettingsPage");
return html`<ak-admin-settings></ak-admin-settings>`;
},
],
[
"^/blueprints/instances$",
async () => {
await import("@goauthentik/admin/blueprints/BlueprintListPage");
return html`<ak-blueprint-list></ak-blueprint-list>`;
},
],
[
"^/debug$",
async () => {
await import("@goauthentik/admin/DebugPage");
return html`<ak-admin-debug-page></ak-admin-debug-page>`;
},
],
[
"^/enterprise/licenses$",
async () => {
await import("@goauthentik/admin/enterprise/EnterpriseLicenseListPage");
return html`<ak-enterprise-license-list></ak-enterprise-license-list>`;
},
],
];
export const ROUTES = _ROUTES.map(makeRoute);

View File

@ -31,15 +31,9 @@ export class VersionStatusCard extends AdminStatusCard<Version> {
message: html`${msg(str`${value.versionLatest} is available!`)}`,
});
}
if (value.versionLatestValid) {
return Promise.resolve<AdminStatus>({
icon: "fa fa-check-circle pf-m-success",
message: html`${msg("Up-to-date!")}`,
});
}
return Promise.resolve<AdminStatus>({
icon: "fa fa-question-circle",
message: html`${msg("Latest version unknown")}`,
icon: "fa fa-check-circle pf-m-success",
message: html`${msg("Up-to-date!")}`,
});
}

View File

@ -15,13 +15,7 @@ import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import {
Application,
Brand,
CoreApi,
CoreApplicationsListRequest,
FlowsInstancesListDesignationEnum,
} from "@goauthentik/api";
import { Brand, CoreApi, FlowsInstancesListDesignationEnum } from "@goauthentik/api";
@customElement("ak-brand-form")
export class BrandForm extends ModelForm<Brand, string> {
@ -143,46 +137,6 @@ export class BrandForm extends ModelForm<Brand, string> {
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("External user settings")} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${msg("Default application")}
name="defaultApplication"
>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Application[]> => {
const args: CoreApplicationsListRequest = {
ordering: "name",
superuserFullList: true,
};
if (query !== undefined) {
args.search = query;
}
const users = await new CoreApi(
DEFAULT_CONFIG,
).coreApplicationsList(args);
return users.results;
}}
.renderElement=${(item: Application): string => {
return item.name;
}}
.renderDescription=${(item: Application): TemplateResult => {
return html`${item.slug}`;
}}
.value=${(item: Application | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: Application): boolean => {
return item.pk === this.instance?.defaultApplication;
}}
>
</ak-search-select>
</ak-form-element-horizontal>
</div>
</ak-form-group>
<ak-form-group>
<span slot="header"> ${msg("Default flows")} </span>
<div slot="body" class="pf-c-form">

View File

@ -13,14 +13,13 @@ import { TablePage } from "@goauthentik/elements/table/TablePage";
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
import { msg } from "@lit/localize";
import { TemplateResult, html, nothing } from "lit";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { Brand, CoreApi, RbacPermissionsAssignedByUsersListModelEnum } from "@goauthentik/api";
import { WithBrandConfig } from "@goauthentik/authentik/elements/Interface/brandProvider";
@customElement("ak-brand-list")
export class BrandListPage extends WithBrandConfig(TablePage<Brand>) {
export class BrandListPage extends TablePage<Brand> {
searchEnabled(): boolean {
return true;
}
@ -85,9 +84,7 @@ export class BrandListPage extends WithBrandConfig(TablePage<Brand>) {
row(item: Brand): TemplateResult[] {
return [
html`${item.domain}${this.brand.matchedDomain === item.domain ? html`
&nbsp;<ak-status-label ?good=${false} type="info" bad-label=${msg("Active")}></ak-status-label>
` : nothing}`,
html`${item.domain}`,
html`${item.brandingTitle}`,
html`<ak-status-label ?good=${item._default}></ak-status-label>`,
html`<ak-forms-modal>

View File

@ -150,6 +150,10 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
renderSectionBefore(): TemplateResult {
return html`
<div class="pf-c-banner pf-m-info">
${msg("Enterprise is in preview.")}
<a href="mailto:hello@goauthentik.io">${msg("Send us feedback!")}</a>
</div>
<section class="pf-c-page__main-section pf-m-no-padding-bottom">
<div
class="pf-l-grid pf-m-gutter pf-m-all-6-col-on-sm pf-m-all-4-col-on-md pf-m-all-3-col-on-lg pf-m-all-3-col-on-xl"

View File

@ -175,7 +175,7 @@ export class OAuth2ProviderViewPage extends AKElement {
</div>`}
<div class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter">
<div
class="pf-c-card pf-l-grid__item pf-l-grid__item pf-m-12-col pf-m-4-col-on-xl pf-m-4-col-on-2xl"
class="pf-c-card pf-l-grid__item pf-l-grid__item pf-m-12-col pf-m-3-col-on-xl pf-m-3-col-on-2xl"
>
<div class="pf-c-card__body">
<dl class="pf-c-description-list">
@ -255,7 +255,7 @@ export class OAuth2ProviderViewPage extends AKElement {
</ak-forms-modal>
</div>
</div>
<div class="pf-c-card pf-l-grid__item pf-m-8-col">
<div class="pf-c-card pf-l-grid__item pf-m-7-col">
<div class="pf-c-card__body">
<form class="pf-c-form">
<div class="pf-c-form__group">

View File

@ -13,7 +13,7 @@ import { customElement, property } from "lit/decorators.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import { ConnectionToken, RACProvider, RacApi } from "@goauthentik/api";
import { ConnectionToken, Endpoint, RACProvider, RacApi } from "@goauthentik/api";
@customElement("ak-rac-connection-token-list")
export class ConnectionTokenListPage extends Table<ConnectionToken> {
@ -53,20 +53,20 @@ export class ConnectionTokenListPage extends Table<ConnectionToken> {
return html`<ak-forms-delete-bulk
objectLabel=${msg("Connection Token(s)")}
.objects=${this.selectedElements}
.metadata=${(item: ConnectionToken) => {
.metadata=${(item: Endpoint) => {
return [
{ key: msg("Endpoint"), value: item.endpointObj.name },
{ key: msg("User"), value: item.user.username },
{ key: msg("Name"), value: item.name },
{ key: msg("Host"), value: item.host },
];
}}
.usedBy=${(item: ConnectionToken) => {
.usedBy=${(item: Endpoint) => {
return new RacApi(DEFAULT_CONFIG).racConnectionTokensUsedByList({
connectionTokenUuid: item.pk || "",
connectionTokenUuid: item.pk,
});
}}
.delete=${(item: ConnectionToken) => {
.delete=${(item: Endpoint) => {
return new RacApi(DEFAULT_CONFIG).racConnectionTokensDestroy({
connectionTokenUuid: item.pk || "",
connectionTokenUuid: item.pk,
});
}}
>

View File

@ -88,11 +88,7 @@ export class RACProviderViewPage extends AKElement {
<section slot="page-overview" data-tab-title="${msg("Overview")}">
${this.renderTabOverview()}
</section>
<section
slot="page-connections"
data-tab-title="${msg("Connections")}"
class="pf-c-page__main-section pf-m-no-padding-mobile"
>
<section slot="page-connections" data-tab-title="${msg("Connections")}">
<div class="pf-c-card">
<div class="pf-c-card__body">
<ak-rac-connection-token-list

View File

@ -86,28 +86,6 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
<span class="pf-c-switch__label">${msg("Enabled")}</span>
</label>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="passwordLoginUpdateInternalPassword">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${first(this.instance?.passwordLoginUpdateInternalPassword, false)}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label"
>${msg("Update internal password on login")}</span
>
</label>
<p class="pf-c-form__helper-text">
${msg(
"When the user logs in to authentik using this source password backend, update their credentials in authentik.",
)}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="syncUsers">
<label class="pf-c-switch">
<input

View File

@ -15,7 +15,6 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm";
import "@goauthentik/admin/stages/invitation/InvitationStageForm";
import "@goauthentik/admin/stages/password/PasswordStageForm";
import "@goauthentik/admin/stages/prompt/PromptStageForm";
import "@goauthentik/admin/stages/source/SourceStageForm";
import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm";
import "@goauthentik/admin/stages/user_login/UserLoginStageForm";
import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm";

View File

@ -1,4 +1,3 @@
import "@goauthentik/admin/common/ak-license-notice";
import { StageBindingForm } from "@goauthentik/admin/flows/StageBindingForm";
import "@goauthentik/admin/stages/authenticator_duo/AuthenticatorDuoStageForm";
import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm";
@ -15,14 +14,12 @@ import "@goauthentik/admin/stages/identification/IdentificationStageForm";
import "@goauthentik/admin/stages/invitation/InvitationStageForm";
import "@goauthentik/admin/stages/password/PasswordStageForm";
import "@goauthentik/admin/stages/prompt/PromptStageForm";
import "@goauthentik/admin/stages/source/SourceStageForm";
import "@goauthentik/admin/stages/user_delete/UserDeleteStageForm";
import "@goauthentik/admin/stages/user_login/UserLoginStageForm";
import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm";
import "@goauthentik/admin/stages/user_write/UserWriteStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { AKElement } from "@goauthentik/elements/Base";
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
import "@goauthentik/elements/forms/ProxyForm";
import "@goauthentik/elements/wizard/FormWizardPage";
import { FormWizardPage } from "@goauthentik/elements/wizard/FormWizardPage";
@ -31,7 +28,7 @@ import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
import { msg, str } from "@lit/localize";
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
import { CSSResult, TemplateResult, html, nothing } from "lit";
import { CSSResult, TemplateResult, html } from "lit";
import { property } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -42,7 +39,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { FlowStageBinding, Stage, StagesApi, TypeCreate } from "@goauthentik/api";
@customElement("ak-stage-wizard-initial")
export class InitialStageWizardPage extends WithLicenseSummary(WizardPage) {
export class InitialStageWizardPage extends WizardPage {
@property({ attribute: false })
stageTypes: TypeCreate[] = [];
sidebarLabel = () => msg("Select type");
@ -65,7 +62,6 @@ export class InitialStageWizardPage extends WithLicenseSummary(WizardPage) {
render(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
${this.stageTypes.map((type) => {
const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense;
return html`<div class="pf-c-radio">
<input
class="pf-c-radio__input"
@ -86,15 +82,11 @@ export class InitialStageWizardPage extends WithLicenseSummary(WizardPage) {
);
this.host.isValid = true;
}}
?disabled=${requiresEnterprise}
/>
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
>${type.name}</label
>
<span class="pf-c-radio__description">${type.description}${
requiresEnterprise ? html`<ak-license-notice></ak-license-notice>` : nothing
}</span>
</span>
<span class="pf-c-radio__description">${type.description}</span>
</div>`;
})}
</form>`;

View File

@ -29,8 +29,6 @@ class PreviewStageHost implements StageHost {
flowSlug = undefined;
loading = false;
brand = undefined;
frameMode = false;
async submit(payload: unknown): Promise<boolean> {
this.promptForm.previewResult = payload;
return false;

View File

@ -1,99 +0,0 @@
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect/index";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import {
Source,
SourceStage,
SourcesAllListRequest,
SourcesApi,
StagesApi,
} from "@goauthentik/api";
@customElement("ak-stage-source-form")
export class SourceStageForm extends BaseStageForm<SourceStage> {
loadInstance(pk: string): Promise<SourceStage> {
return new StagesApi(DEFAULT_CONFIG).stagesSourceRetrieve({
stageUuid: pk,
});
}
async send(data: SourceStage): Promise<SourceStage> {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesSourceUpdate({
stageUuid: this.instance.pk || "",
sourceStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesSourceCreate({
sourceStageRequest: data,
});
}
}
renderForm(): TemplateResult {
return html`
<span> ${msg("TODO.")} </span>
<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Source")} ?required=${true} name="source">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Source[]> => {
const args: SourcesAllListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const users = await new SourcesApi(DEFAULT_CONFIG).sourcesAllList(args);
return users.results;
}}
.renderElement=${(source: Source): string => {
return source.name;
}}
.renderDescription=${(source: Source): TemplateResult => {
return html`${source.verboseName}`;
}}
.value=${(source: Source | undefined): string | undefined => {
return source?.pk;
}}
.selected=${(source: Source): boolean => {
return source.pk === this.instance?.source;
}}
>
</ak-search-select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Resume timeout")}
?required=${true}
name="resumeTimeout"
>
<input
type="text"
value="${ifDefined(this.instance?.resumeTimeout || "minutes=10")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg(
"Amount of time a user can take to return from the source to continue the flow.",
)}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
`;
}
}

View File

@ -61,18 +61,16 @@ export function brand(): Promise<CurrentBrand> {
return globalBrandPromise;
}
export function getMetaContent(key: string): string | undefined {
export function getMetaContent(key: string): string {
const metaEl = document.querySelector<HTMLMetaElement>(`meta[name=${key}]`);
return metaEl?.content;
if (!metaEl) return "";
return metaEl.content;
}
export const DEFAULT_CONFIG = new Configuration({
basePath:
(process.env.AK_API_BASE_PATH ||
getMetaContent("authentik-api") ||
window.location.origin) + "/api/v3",
basePath: (process.env.AK_API_BASE_PATH || window.location.origin) + "/api/v3",
headers: {
"sentry-trace": getMetaContent("sentry-trace") || "",
"sentry-trace": getMetaContent("sentry-trace"),
},
middleware: [
new CSRFMiddleware(),

View File

@ -329,7 +329,3 @@ input[type="date"]::-webkit-calendar-picker-indicator {
.pf-c-tree-view__content:focus-within {
--pf-c-tree-view__node--hover--BackgroundColor: var(--ak-dark-background-light-ish);
}
/* stepper */
.pf-c-progress-stepper__step-title {
--pf-c-progress-stepper__step-title--Color: var(--ak-dark-foreground);
}

View File

@ -1,6 +1,5 @@
import { PFSize } from "@goauthentik/common/enums.js";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/Spinner";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";

View File

@ -0,0 +1,41 @@
import { Route, RouteArgs } from "@goauthentik/elements/router/Route";
import { P, match } from "ts-pattern";
import { TemplateResult } from "lit";
type ListRoute = () => Promise<TemplateResult<1>>;
type ViewRoute = (_1: RouteArgs) => Promise<TemplateResult<1>>;
type InternalRedirect = string;
type ExternalRedirect = [string, boolean];
type RouteInvoke = ViewRoute | ListRoute;
type RedirectRoute = [string, InternalRedirect | ExternalRedirect];
type PageRoute = [string, RouteInvoke];
export type RawRoute = PageRoute | RedirectRoute;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const isLoader = (v: any): v is RouteInvoke => typeof v === "function";
// When discriminating between redirects and loaders, the loader type is irrelevant to routing
// correctly. The loader type *is* still well-typed to prevent an incompatible loader being added to
// a route, but the two different loader types-- ones that take arguments, and ones that do not-- do
// not matter to the route builder.
// On the other hand, the two different kinds of redirects *do* matter, but only because JavaScript
// makes a distinction between methods of one argument that can be `call`'ed and methods of more
// than one argument that must be `apply`'d. (Spread arguments are converted to call or apply as
// needed).
// prettier-ignore
export function makeRoute(route: RawRoute): Route {
return match(route)
.with([P.string, P.when(isLoader)],
([path, loader]) => new Route(new RegExp(path), loader))
.with([P.string, P.string],
([path, redirect]) => new Route(new RegExp(path)).redirect(redirect))
.with([P.string, [P.string, P.boolean]],
([path, redirect]) => new Route(new RegExp(path)).redirect(...redirect))
.exhaustive();
}

View File

@ -1,4 +1,3 @@
import { ensureCSSStyleSheet } from "@goauthentik/authentik/elements/utils/ensureCSSStyleSheet";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import {
EVENT_FLOW_ADVANCE,
@ -33,7 +32,6 @@ import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import {
CapabilitiesEnum,
ChallengeChoices,
ChallengeTypes,
ContextualFlowInfo,
@ -77,8 +75,7 @@ export class FlowExecutor extends Interface implements StageHost {
@state()
flowInfo?: ContextualFlowInfo;
@state()
frameMode = window !== window.top;
ws: WebsocketClient;
static get styles(): CSSResult[] {
return [PFBase, PFLogin, PFDrawer, PFButton, PFTitle, PFList, PFBackgroundImage].concat(css`
@ -142,25 +139,6 @@ export class FlowExecutor extends Interface implements StageHost {
:host([theme="dark"]) .pf-c-login.sidebar_right .pf-c-list {
color: var(--ak-dark-foreground);
}
/* frame design */
.pf-c-login.frame {
padding: 0;
min-height: initial !important;
}
.pf-c-login.frame .pf-c-login__main {
margin-bottom: 0;
height: 100%;
}
.pf-c-login.frame .ak-login-container {
height: 100vh;
width: 100vw;
}
.pf-c-login.frame .pf-c-login__footer {
display: none;
}
.pf-c-login.frame .pf-c-login__footer .pf-c-list {
padding: 0;
}
.pf-c-brand {
padding-top: calc(
var(--pf-c-login__main-footer-links--PaddingTop) +
@ -182,23 +160,9 @@ export class FlowExecutor extends Interface implements StageHost {
constructor() {
super();
if (this.frameMode) {
// PatternFly sets html and body to 100% height, which we don't
// want in this case since the iframe should only take the space it needs
document.adoptedStyleSheets = [
...document.adoptedStyleSheets,
ensureCSSStyleSheet(css`
body,
html {
height: unset !important;
}
`),
];
} else {
new WebsocketClient();
}
this.ws = new WebsocketClient();
if (window.location.search.includes("inspector")) {
this.inspectorOpen = true;
this.inspectorOpen = !this.inspectorOpen;
}
this.addEventListener(EVENT_FLOW_INSPECTOR_TOGGLE, () => {
this.inspectorOpen = !this.inspectorOpen;
@ -249,9 +213,6 @@ export class FlowExecutor extends Interface implements StageHost {
async firstUpdated(): Promise<void> {
configureSentry();
if (this.config?.capabilities.includes(CapabilitiesEnum.CanDebug)) {
this.inspectorOpen = true;
}
this.loading = true;
try {
const challenge = await new FlowsApi(DEFAULT_CONFIG).flowsExecutorGet({
@ -472,11 +433,9 @@ export class FlowExecutor extends Interface implements StageHost {
}
renderChallengeWrapper(): TemplateResult {
const logo = this.frameMode
? nothing
: html`<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="${first(this.brand?.brandingLogo, "")}" alt="authentik Logo" />
</div>`;
const logo = html`<div class="pf-c-login__main-header pf-c-brand ak-brand">
<img src="${first(this.brand?.brandingLogo, "")}" alt="authentik Logo" />
</div>`;
if (!this.challenge) {
return html`${logo}<ak-empty-state ?loading=${true} header=${msg("Loading")}>
</ak-empty-state>`;
@ -519,26 +478,7 @@ export class FlowExecutor extends Interface implements StageHost {
}
render(): TemplateResult {
if (this.frameMode) {
return html`<ak-locale-context>
<div class="pf-c-login frame">
<div class="ak-login-container">
<div class="pf-c-login__main">${this.renderChallengeWrapper()}</div>
<footer class="pf-c-login__footer">
<ul class="pf-c-list pf-m-inline">
<li>
<a
href="https://goauthentik.io?utm_source=authentik&amp;utm_medium=flow"
>${msg("Powered by authentik")}</a
>
</li>
</ul>
</footer>
</div>
</div>
</ak-locale-context>`;
}
return html`<ak-locale-context>
return html` <ak-locale-context>
<div class="pf-c-background-image"></div>
<div class="pf-c-page__drawer">
<div class="pf-c-drawer ${this.inspectorOpen ? "pf-m-expanded" : "pf-m-collapsed"}">

View File

@ -37,10 +37,6 @@ export class FlowInspector extends AKElement {
PFDescriptionList,
PFProgressStepper,
css`
.pf-c-drawer__body {
min-height: 100vh;
max-height: 100vh;
}
code.break {
word-break: break-all;
}
@ -49,6 +45,9 @@ export class FlowInspector extends AKElement {
overflow-x: hidden;
white-space: break-spaces;
}
.pf-c-notification-drawer__body {
overflow-x: hidden;
}
`,
];
}
@ -114,7 +113,6 @@ export class FlowInspector extends AKElement {
return this.renderAccessDenied();
}
if (!this.state) {
this.advanceHandler();
return html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
</ak-empty-state>`;
}

View File

@ -49,27 +49,8 @@ export class RedirectStage extends BaseStage<RedirectChallenge, FlowChallengeRes
"authentik/stages/redirect: redirecting to url from server",
this.challenge.to,
);
window.location.assign(this.challenge.to);
this.startedRedirect = true;
if (this.host.frameMode && window.top) {
try {
window.top.location.assign(this.challenge.to);
} catch {
window.top.postMessage(
{
source: "goauthentik.io",
context: "flow-executor",
component: this.challenge.component,
to: this.challenge.to,
},
document.location.ancestorOrigins[0],
);
}
if (this.challenge.to.startsWith("http")) {
return;
}
} else {
window.location.assign(this.challenge.to);
}
}
renderLoading(): TemplateResult {

View File

@ -1,58 +0,0 @@
import type { StoryObj } from "@storybook/web-components";
import { html } from "lit";
import "@patternfly/patternfly/components/Login/login.css";
import { AuthenticatorTOTPChallenge, ChallengeChoices, UiThemeEnum } from "@goauthentik/api";
import "../../../stories/flow-interface";
import "./AuthenticatorTOTPStage";
export default {
title: "Flow / Stages / AuthenticatorTOTPStage",
};
export const LoadingNoChallenge = () => {
return html`<ak-storybook-interface theme=${UiThemeEnum.Dark}>
<div class="pf-c-login">
<div class="pf-c-login__container">
<div class="pf-c-login__main">
<ak-stage-authenticator-totp></ak-stage-authenticator-totp>
</div>
</div>
</div>
</ak-storybook-interface>`;
};
export const Challenge: StoryObj = {
render: ({ theme, challenge }) => {
return html`<ak-storybook-interface theme=${theme}>
<div class="pf-c-login">
<div class="pf-c-login__container">
<div class="pf-c-login__main">
<ak-stage-authenticator-totp
.challenge=${challenge}
></ak-stage-authenticator-totp>
</div>
</div></div
></ak-storybook-interface>`;
},
args: {
theme: "automatic",
challenge: {
type: ChallengeChoices.Native,
pendingUser: "foo",
pendingUserAvatar: "https://picsum.photos/64",
configUrl: "",
} as AuthenticatorTOTPChallenge,
},
argTypes: {
theme: {
options: [UiThemeEnum.Automatic, UiThemeEnum.Light, UiThemeEnum.Dark],
control: {
type: "select",
},
},
},
};

View File

@ -106,11 +106,6 @@ export class AuthenticatorTOTPStage extends BaseStage<
</button>
</div>
</ak-form-element>
<p>
${msg(
"Please scan the QR code above using the Microsoft Authenticator, Google Authenticator, or other authenticator apps on your device, and enter the code the device displays below to finish setting up the MFA device.",
)}
</p>
<ak-form-element
label="${msg("Code")}"
?required="${true}"

View File

@ -47,10 +47,6 @@ export class AuthenticatorValidateStage
return this.host.brand;
}
get frameMode(): boolean {
return this.host.frameMode;
}
@state()
_selectedDeviceChallenge?: DeviceChallenge;

View File

@ -16,7 +16,6 @@ export interface StageHost {
challenge?: unknown;
flowSlug?: string;
loading: boolean;
frameMode: boolean;
submit(payload: unknown, options?: SubmitOptions): Promise<boolean>;
readonly brand?: CurrentBrand;

Some files were not shown because too many files have changed in this diff Show More