Compare commits

..

3 Commits

Author SHA1 Message Date
ab42a62916 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-03-07 19:23:34 +01:00
ef8d2bdd40 wip
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-03-07 16:24:55 +01:00
32f4e08eac writting down thoughts
Signed-off-by: Marc 'risson' Schmitt <marc.schmitt@risson.space>
2025-03-07 14:04:21 +01:00
113 changed files with 1085 additions and 2507 deletions

View File

@ -5,7 +5,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socioeconomic status,
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

View File

@ -3,8 +3,7 @@
# Stage 1: Build website
FROM --platform=${BUILDPLATFORM} docker.io/library/node:22 AS website-builder
ENV NODE_ENV=production \
GIT_UNAVAILABLE=true
ENV NODE_ENV=production
WORKDIR /work/website

View File

@ -4,7 +4,7 @@
PWD = $(shell pwd)
UID = $(shell id -u)
GID = $(shell id -g)
NPM_VERSION = $(shell python -m scripts.generate_semver)
NPM_VERSION = $(shell poetry run python -m scripts.generate_semver)
PY_SOURCES = authentik tests scripts lifecycle .github
DOCKER_IMAGE ?= "authentik:test"
@ -145,7 +145,7 @@ gen-client-py: gen-clean-py ## Build and install the authentik API for Python
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
docker.io/openapitools/openapi-generator-cli:v7.11.0 generate \
docker.io/openapitools/openapi-generator-cli:v7.4.0 generate \
-i /local/schema.yml \
-g python \
-o /local/${GEN_API_PY} \

View File

@ -5,7 +5,6 @@ from collections.abc import Iterable
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, ReadOnlyField, SerializerMethodField
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request
@ -155,17 +154,6 @@ class SourceViewSet(
matching_sources.append(source_settings.validated_data)
return Response(matching_sources)
def destroy(self, request: Request, *args, **kwargs):
"""Prevent deletion of built-in sources"""
instance: Source = self.get_object()
if instance.managed == Source.MANAGED_INBUILT:
raise ValidationError(
{"detail": "Built-in sources cannot be deleted"}, code="protected"
)
return super().destroy(request, *args, **kwargs)
class UserSourceConnectionSerializer(SourceSerializer):
"""User source connection"""

View File

@ -32,5 +32,5 @@ class AuthentikCoreConfig(ManagedAppConfig):
"name": "authentik Built-in",
"slug": "authentik-built-in",
},
managed=Source.MANAGED_INBUILT,
managed="goauthentik.io/sources/inbuilt",
)

View File

@ -678,8 +678,6 @@ class SourceGroupMatchingModes(models.TextChoices):
class Source(ManagedModel, SerializerModel, PolicyBindingModel):
"""Base Authentication source, i.e. an OAuth Provider, SAML Remote or LDAP Server"""
MANAGED_INBUILT = "goauthentik.io/sources/inbuilt"
name = models.TextField(help_text=_("Source's display Name."))
slug = models.SlugField(help_text=_("Internal source name, used in URLs."), unique=True)

View File

@ -9,7 +9,7 @@ class AuthentikEnterpriseAuditConfig(EnterpriseConfig):
"""Enterprise app config"""
name = "authentik.enterprise.audit"
label = "authentik_enterprise_audit"
label = "authentik_audit"
verbose_name = "authentik Enterprise.Audit"
default = True

View File

@ -0,0 +1,107 @@
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from authentik.lib.models import SerializerModel
from django.db import models
from uuid import uuid4
from authentik.core.models import Group, User
# # Names
# Lifecycle
# Access reviews
# Access lifecycle
# Governance
# Audit
# Compliance
# Lifecycle
# Lifecycle review
# Review
# Access review
# Compliance review
# X Scheduled review
# Only some objects supported?
#
# For disabling support:
# Application
# Provider
# Outpost (simply setting the list of providers to empty in the outpost itself)
# Flow
# Users
# Groups <- will get tricky
# Roles
# Sources
# Tokens (api, app_pass)
# Brands
# Outpost integrations
#
# w/o disabling support
# System Settings
# everything else
# would need to show in an audit dashboard cause not all have pages to get details
# "default" policy for objects, by default, everlasting
class AuditPolicyFailAction(models.TextChoices):
# For preview
NOTHING = "nothing"
# Disable the thing failing, HOW
DISABLE = "disable"
# Emit events
WARN = "warn"
class LifecycleRule(SerializerModel):
pass
class ReviewRule(SerializerModel):
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
# Check every 6 months, allow for daily/weekly/first of month, etc.
interval = models.TextField() # timedelta
# Preventive notification
reminder_interval = models.TextField() # timedelta
# Must be checked by these
groups = models.ManyToManyField(Group)
users = models.ManyToManyField(User)
# How many of the above must approve
required_approvals = models.IntegerField(default=1)
# How long to wait before executing fail action
grace_period = models.TextField() # timedelta
# What to do if not reviewed in time
fail_action = models.CharField(choices=AuditPolicyFailAction)
class AuditPolicyBinding(SerializerModel):
id = models.UUIDField(primary_key=True, editable=False, default=uuid4)
# Many to many ? Bind users/groups here instead of on the policy ?
policy = models.ForeignKey(AuditPolicy, on_delete=models.PROTECT)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.TextField(blank=True) # optional to apply on all objects of specific type
content_object = GenericForeignKey("content_type", "object_id")
# valid -> waiting review -> valid
# valid -> waiting review -> review overdue -> valid
# valid -> waiting review -> review overdue -> failed -> valid
# look at django-fsm or django-viewflow
status = models.TextField()
class Meta:
indexes = (
models.Index(fields=["content_type"]),
models.Index(fields=["content_type", "object_id"]),
)
class AuditHistory:
pass

View File

@ -1,6 +1,5 @@
"""Base Kubernetes Reconciler"""
import re
from dataclasses import asdict
from json import dumps
from typing import TYPE_CHECKING, Generic, TypeVar
@ -68,8 +67,7 @@ class KubernetesObjectReconciler(Generic[T]):
@property
def name(self) -> str:
"""Get the name of the object this reconciler manages"""
base_name = (
return (
self.controller.outpost.config.object_naming_template
% {
"name": slugify(self.controller.outpost.name),
@ -77,16 +75,6 @@ class KubernetesObjectReconciler(Generic[T]):
}
).lower()
formatted = slugify(base_name)
formatted = re.sub(r"[^a-z0-9-]", "-", formatted)
formatted = re.sub(r"-+", "-", formatted)
formatted = formatted[:63]
if not formatted:
formatted = f"outpost-{self.controller.outpost.uuid.hex}"[:63]
return formatted
def get_patched_reference_object(self) -> T:
"""Get patched reference object"""
reference = self.get_reference_object()
@ -124,6 +112,7 @@ class KubernetesObjectReconciler(Generic[T]):
try:
current = self.retrieve()
except (OpenApiException, HTTPError) as exc:
if isinstance(exc, ApiException) and exc.status == HttpResponseNotFound.status_code:
self.logger.debug("Failed to get current, triggering recreate")
raise NeedsRecreate from exc
@ -167,6 +156,7 @@ class KubernetesObjectReconciler(Generic[T]):
self.delete(current)
self.logger.debug("Removing")
except (OpenApiException, HTTPError) as exc:
if isinstance(exc, ApiException) and exc.status == HttpResponseNotFound.status_code:
self.logger.debug("Failed to get current, assuming non-existent")
return

View File

@ -61,14 +61,9 @@ class KubernetesController(BaseController):
client: KubernetesClient
connection: KubernetesServiceConnection
def __init__(
self,
outpost: Outpost,
connection: KubernetesServiceConnection,
client: KubernetesClient | None = None,
) -> None:
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection) -> None:
super().__init__(outpost, connection)
self.client = client if client else KubernetesClient(connection)
self.client = KubernetesClient(connection)
self.reconcilers = {
SecretReconciler.reconciler_name(): SecretReconciler,
DeploymentReconciler.reconciler_name(): DeploymentReconciler,

View File

@ -1,44 +0,0 @@
"""Kubernetes controller tests"""
from django.test import TestCase
from authentik.blueprints.tests import reconcile_app
from authentik.lib.generators import generate_id
from authentik.outposts.apps import MANAGED_OUTPOST
from authentik.outposts.controllers.k8s.deployment import DeploymentReconciler
from authentik.outposts.controllers.kubernetes import KubernetesController
from authentik.outposts.models import KubernetesServiceConnection, Outpost, OutpostType
class KubernetesControllerTests(TestCase):
"""Kubernetes controller tests"""
@reconcile_app("authentik_outposts")
def setUp(self) -> None:
self.outpost = Outpost.objects.create(
name="test",
type=OutpostType.PROXY,
)
self.integration = KubernetesServiceConnection(name="test")
def test_gen_name(self):
"""Ensure the generated name is valid"""
controller = KubernetesController(
Outpost.objects.filter(managed=MANAGED_OUTPOST).first(),
self.integration,
# Pass something not-none as client so we don't
# attempt to connect to K8s as that's not needed
client=self,
)
rec = DeploymentReconciler(controller)
self.assertEqual(rec.name, "ak-outpost-authentik-embedded-outpost")
controller.outpost.name = generate_id()
self.assertLess(len(rec.name), 64)
# Test custom naming template
_cfg = controller.outpost.config
_cfg.object_naming_template = ""
controller.outpost.config = _cfg
self.assertEqual(rec.name, f"outpost-{controller.outpost.uuid.hex}")
self.assertLess(len(rec.name), 64)

View File

@ -254,10 +254,10 @@ class OAuthAuthorizationParams:
raise AuthorizeError(self.redirect_uri, "invalid_scope", self.grant_type, self.state)
if SCOPE_OFFLINE_ACCESS in self.scope:
# https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
# Don't explicitly request consent with offline_access, as the spec allows for
# "other conditions for processing the request permitting offline access to the
# requested resources are in place"
# which we interpret as "the admin picks an authorization flow with or without consent"
if PROMPT_CONSENT not in self.prompt:
# Instead of ignoring the `offline_access` scope when `prompt`
# isn't set to `consent`, we set override it ourselves
self.prompt.add(PROMPT_CONSENT)
if self.response_type not in [
ResponseTypes.CODE,
ResponseTypes.CODE_TOKEN,

View File

@ -1,9 +1,9 @@
"""RAC app config"""
from authentik.blueprints.apps import ManagedAppConfig
from django.apps import AppConfig
class AuthentikProviderRAC(ManagedAppConfig):
class AuthentikProviderRAC(AppConfig):
"""authentik rac app config"""
name = "authentik.providers.rac"

View File

@ -4,7 +4,8 @@ from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.contrib.auth.signals import user_logged_out
from django.core.cache import cache
from django.db.models.signals import post_delete, post_save, pre_delete
from django.db.models import Model
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from django.http import HttpRequest
@ -45,8 +46,12 @@ def pre_delete_connection_token_disconnect(sender, instance: ConnectionToken, **
)
@receiver([post_save, post_delete], sender=Endpoint)
def post_save_post_delete_endpoint(**_):
"""Clear user's endpoint cache upon endpoint creation or deletion"""
@receiver(post_save, sender=Endpoint)
def post_save_endpoint(sender: type[Model], instance, created: bool, **_):
"""Clear user's endpoint cache upon endpoint creation"""
if not created: # pragma: no cover
return
# Delete user endpoint cache
keys = cache.keys(user_endpoint_cache_key("*"))
cache.delete_many(keys)

View File

@ -28,7 +28,6 @@ class SCIMProviderSerializer(ProviderSerializer):
"url",
"verify_certificates",
"token",
"compatibility_mode",
"exclude_users_service_account",
"filter_group",
"dry_run",

View File

@ -22,7 +22,7 @@ from authentik.lib.sync.outgoing.exceptions import (
from authentik.lib.utils.http import get_http_session
from authentik.providers.scim.clients.exceptions import SCIMRequestException
from authentik.providers.scim.clients.schema import ServiceProviderConfiguration
from authentik.providers.scim.models import SCIMCompatibilityMode, SCIMProvider
from authentik.providers.scim.models import SCIMProvider
if TYPE_CHECKING:
from django.db.models import Model
@ -90,14 +90,9 @@ class SCIMClient[TModel: "Model", TConnection: "Model", TSchema: "BaseModel"](
"""Get Service provider config"""
default_config = ServiceProviderConfiguration.default()
try:
config = ServiceProviderConfiguration.model_validate(
return ServiceProviderConfiguration.model_validate(
self._request("GET", "/ServiceProviderConfig")
)
if self.provider.compatibility_mode == SCIMCompatibilityMode.AWS:
config.patch.supported = False
if self.provider.compatibility_mode == SCIMCompatibilityMode.SLACK:
config.filter.supported = True
return config
except (ValidationError, SCIMRequestException, NotFoundSyncException) as exc:
self.logger.warning("failed to get ServiceProviderConfig", exc=exc)
return default_config

View File

@ -1,12 +1,10 @@
"""User client"""
from django.db import transaction
from django.utils.http import urlencode
from pydantic import ValidationError
from authentik.core.models import User
from authentik.lib.sync.mapper import PropertyMappingManager
from authentik.lib.sync.outgoing.exceptions import ObjectExistsSyncException, StopSync
from authentik.lib.sync.outgoing.exceptions import StopSync
from authentik.policies.utils import delete_none_values
from authentik.providers.scim.clients.base import SCIMClient
from authentik.providers.scim.clients.schema import SCIM_USER_SCHEMA
@ -57,35 +55,18 @@ class SCIMUserClient(SCIMClient[User, SCIMProviderUser, SCIMUserSchema]):
def create(self, user: User):
"""Create user from scratch and create a connection object"""
scim_user = self.to_schema(user, None)
with transaction.atomic():
try:
response = self._request(
"POST",
"/Users",
json=scim_user.model_dump(
mode="json",
exclude_unset=True,
),
)
except ObjectExistsSyncException as exc:
if not self._config.filter.supported:
raise exc
users = self._request(
"GET", f"/Users?{urlencode({'filter': f'userName eq {scim_user.userName}'})}"
)
users_res = users.get("Resources", [])
if len(users_res) < 1:
raise exc
return SCIMProviderUser.objects.create(
provider=self.provider, user=user, scim_id=users_res[0]["id"]
)
else:
scim_id = response.get("id")
if not scim_id or scim_id == "":
raise StopSync("SCIM Response with missing or invalid `id`")
return SCIMProviderUser.objects.create(
provider=self.provider, user=user, scim_id=scim_id
)
response = self._request(
"POST",
"/Users",
json=scim_user.model_dump(
mode="json",
exclude_unset=True,
),
)
scim_id = response.get("id")
if not scim_id or scim_id == "":
raise StopSync("SCIM Response with missing or invalid `id`")
return SCIMProviderUser.objects.create(provider=self.provider, user=user, scim_id=scim_id)
def update(self, user: User, connection: SCIMProviderUser):
"""Update existing user"""

View File

@ -1,24 +0,0 @@
# Generated by Django 5.0.12 on 2025-03-07 23:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_providers_scim", "0011_scimprovider_dry_run"),
]
operations = [
migrations.AddField(
model_name="scimprovider",
name="compatibility_mode",
field=models.CharField(
choices=[("default", "Default"), ("aws", "AWS"), ("slack", "Slack")],
default="default",
help_text="Alter authentik behavior for vendor-specific SCIM implementations.",
max_length=30,
verbose_name="SCIM Compatibility Mode",
),
),
]

View File

@ -57,14 +57,6 @@ class SCIMProviderGroup(SerializerModel):
return f"SCIM Provider Group {self.group_id} to {self.provider_id}"
class SCIMCompatibilityMode(models.TextChoices):
"""SCIM compatibility mode"""
DEFAULT = "default", _("Default")
AWS = "aws", _("AWS")
SLACK = "slack", _("Slack")
class SCIMProvider(OutgoingSyncProvider, BackchannelProvider):
"""SCIM 2.0 provider to create users and groups in external applications"""
@ -85,14 +77,6 @@ class SCIMProvider(OutgoingSyncProvider, BackchannelProvider):
help_text=_("Property mappings used for group creation/updating."),
)
compatibility_mode = models.CharField(
max_length=30,
choices=SCIMCompatibilityMode.choices,
default=SCIMCompatibilityMode.DEFAULT,
verbose_name=_("SCIM Compatibility Mode"),
help_text=_("Alter authentik behavior for vendor-specific SCIM implementations."),
)
@property
def icon_url(self) -> str | None:
return static("authentik/sources/scim.png")

View File

@ -68,6 +68,8 @@ class OAuth2Client(BaseOAuthClient):
error_desc = self.get_request_arg("error_description", None)
return {"error": error_desc or error or _("No token received.")}
args = {
"client_id": self.get_client_id(),
"client_secret": self.get_client_secret(),
"redirect_uri": callback,
"code": code,
"grant_type": "authorization_code",

View File

@ -28,7 +28,7 @@ def update_well_known_jwks(self: SystemTask):
LOGGER.warning("Failed to update well_known", source=source, exc=exc, text=text)
messages.append(f"Failed to update OIDC configuration for {source.slug}")
continue
config: dict = well_known_config.json()
config = well_known_config.json()
try:
dirty = False
source_attr_key = (
@ -40,9 +40,7 @@ def update_well_known_jwks(self: SystemTask):
for source_attr, config_key in source_attr_key:
# Check if we're actually changing anything to only
# save when something has changed
if config_key not in config:
continue
if getattr(source, source_attr, "") != config.get(config_key, ""):
if getattr(source, source_attr, "") != config[config_key]:
dirty = True
setattr(source, source_attr, config[config_key])
except (IndexError, KeyError) as exc:

View File

@ -25,10 +25,8 @@ class RedditOAuth2Client(UserprofileHeaderAuthClient):
def get_access_token(self, **request_kwargs):
"Fetch access token from callback request."
request_kwargs["auth"] = HTTPBasicAuth(
self.source.consumer_key, self.source.consumer_secret
)
return super().get_access_token(**request_kwargs)
auth = HTTPBasicAuth(self.source.consumer_key, self.source.consumer_secret)
return super().get_access_token(auth=auth)
class RedditOAuth2Callback(OAuthCallback):

View File

@ -1,54 +0,0 @@
# Generated by Django 5.0.12 on 2025-02-27 04:32
import authentik.lib.utils.time
from authentik.lib.utils.time import timedelta_from_string
from django.db import migrations, models
def convert_integer_to_string_format(apps, schema_editor):
db_alias = schema_editor.connection.alias
EmailStage = apps.get_model("authentik_stages_email", "EmailStage")
for stage in EmailStage.objects.using(db_alias).all():
stage.token_expiry = f"minutes={stage.token_expiry}"
stage.save(using=db_alias)
def convert_string_to_integer_format(apps, schema_editor):
db_alias = schema_editor.connection.alias
EmailStage = apps.get_model("authentik_stages_email", "EmailStage")
for stage in EmailStage.objects.using(db_alias).all():
# Check if token_expiry is a string
if isinstance(stage.token_expiry, str):
try:
# Use the timedelta_from_string utility to convert to timedelta
# then convert to minutes by dividing seconds by 60
td = timedelta_from_string(stage.token_expiry)
minutes_value = int(td.total_seconds() / 60)
stage.token_expiry = minutes_value
stage.save(using=db_alias)
except (ValueError, TypeError):
# If the string can't be parsed or converted properly, skip
pass
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_email", "0004_emailstage_activate_user_on_success"),
]
operations = [
migrations.AlterField(
model_name="emailstage",
name="token_expiry",
field=models.TextField(
default="minutes=30",
help_text="Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
migrations.RunPython(
convert_integer_to_string_format,
convert_string_to_integer_format,
),
]

View File

@ -14,7 +14,6 @@ from structlog.stdlib import get_logger
from authentik.flows.models import Stage
from authentik.lib.config import CONFIG
from authentik.lib.utils.time import timedelta_string_validator
LOGGER = get_logger()
@ -75,10 +74,8 @@ class EmailStage(Stage):
default=False, help_text=_("Activate users upon completion of stage.")
)
token_expiry = models.TextField(
default="minutes=30",
validators=[timedelta_string_validator],
help_text=_("Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."),
token_expiry = models.IntegerField(
default=30, help_text=_("Time in minutes the token sent is valid.")
)
subject = models.TextField(default="authentik")
template = models.TextField(default=EmailTemplates.PASSWORD_RESET)

View File

@ -22,7 +22,6 @@ from authentik.flows.planner import PLAN_CONTEXT_IS_RESTORED, PLAN_CONTEXT_PENDI
from authentik.flows.stage import ChallengeStageView
from authentik.flows.views.executor import QS_KEY_TOKEN, QS_QUERY
from authentik.lib.utils.errors import exception_to_string
from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.email.models import EmailStage
from authentik.stages.email.tasks import send_mails
from authentik.stages.email.utils import TemplateEmailMessage
@ -74,8 +73,8 @@ class EmailStageView(ChallengeStageView):
"""Get token"""
pending_user = self.get_pending_user()
current_stage: EmailStage = self.executor.current_stage
valid_delta = timedelta_from_string(current_stage.token_expiry) + timedelta(
minutes=1
valid_delta = timedelta(
minutes=current_stage.token_expiry + 1
) # + 1 because django timesince always rounds down
identifier = slugify(f"ak-email-stage-{current_stage.name}-{str(uuid4())}")
# Don't check for validity here, we only care if the token exists

View File

@ -57,7 +57,7 @@ entries:
use_ssl: false
timeout: 10
from_address: system@authentik.local
token_expiry: minutes=30
token_expiry: 30
subject: authentik
template: email/password_reset.html
activate_user_on_success: true

View File

@ -6661,16 +6661,6 @@
"title": "Token",
"description": "Authentication token"
},
"compatibility_mode": {
"type": "string",
"enum": [
"default",
"aws",
"slack"
],
"title": "SCIM Compatibility Mode",
"description": "Alter authentik behavior for vendor-specific SCIM implementations."
},
"exclude_users_service_account": {
"type": "boolean",
"title": "Exclude users service account"
@ -11379,10 +11369,11 @@
"title": "From address"
},
"token_expiry": {
"type": "string",
"minLength": 1,
"type": "integer",
"minimum": -2147483648,
"maximum": 2147483647,
"title": "Token expiry",
"description": "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
"description": "Time in minutes the token sent is valid."
},
"subject": {
"type": "string",

10
go.mod
View File

@ -6,7 +6,7 @@ toolchain go1.24.0
require (
beryju.io/ldap v0.1.0
github.com/coreos/go-oidc/v3 v3.13.0
github.com/coreos/go-oidc/v3 v3.12.0
github.com/getsentry/sentry-go v0.31.1
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
github.com/go-ldap/ldap/v3 v3.4.10
@ -29,7 +29,7 @@ require (
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
github.com/wwt/guac v1.3.2
goauthentik.io/api/v3 v3.2025021.4
goauthentik.io/api/v3 v3.2025021.2
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
golang.org/x/oauth2 v0.28.0
golang.org/x/sync v0.12.0
@ -76,9 +76,9 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

22
go.sum
View File

@ -55,8 +55,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8=
github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo=
github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -299,8 +299,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.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
goauthentik.io/api/v3 v3.2025021.4 h1:KFap2KW+8CwhOxjBkRnRB4flvuHEMw24+fZei9dOhzw=
goauthentik.io/api/v3 v3.2025021.4/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
goauthentik.io/api/v3 v3.2025021.2 h1:9y87piH47omtkWxQpKZaKai/+jh+cJdLxj5MC2Y/ZLI=
goauthentik.io/api/v3 v3.2025021.2/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=
@ -313,8 +313,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -386,9 +386,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -450,8 +449,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -472,9 +471,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@ -9,7 +9,7 @@
"version": "0.0.0",
"license": "MIT",
"devDependencies": {
"aws-cdk": "^2.1004.0",
"aws-cdk": "^2.1003.0",
"cross-env": "^7.0.3"
},
"engines": {
@ -17,9 +17,9 @@
}
},
"node_modules/aws-cdk": {
"version": "2.1004.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1004.0.tgz",
"integrity": "sha512-3E5ICmSc7ZCZCwLX7NY+HFmmdUYgRaL+67h/BDoDQmkhx9StC8wG4xgzHFY9k8WQS0+ib/MP28f2d9yzHtQLlQ==",
"version": "2.1003.0",
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1003.0.tgz",
"integrity": "sha512-FORPDGW8oUg4tXFlhX+lv/j+152LO9wwi3/CwNr1WY3c3HwJUtc0fZGb2B3+Fzy6NhLWGHJclUsJPEhjEt8Nhg==",
"dev": true,
"license": "Apache-2.0",
"bin": {

View File

@ -10,7 +10,7 @@
"node": ">=20"
},
"devDependencies": {
"aws-cdk": "^2.1004.0",
"aws-cdk": "^2.1003.0",
"cross-env": "^7.0.3"
}
}

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
"POT-Creation-Date: 2025-03-02 00:10+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -1883,18 +1883,6 @@ msgstr ""
msgid "SAML Providers from Metadata"
msgstr ""
#: authentik/providers/scim/models.py
msgid "Default"
msgstr ""
#: authentik/providers/scim/models.py
msgid "AWS"
msgstr ""
#: authentik/providers/scim/models.py
msgid "Slack"
msgstr ""
#: authentik/providers/scim/models.py
msgid "Base URL to SCIM requests, usually ends in /v2"
msgstr ""
@ -1903,14 +1891,6 @@ msgstr ""
msgid "Authentication token"
msgstr ""
#: authentik/providers/scim/models.py
msgid "SCIM Compatibility Mode"
msgstr ""
#: authentik/providers/scim/models.py
msgid "Alter authentik behavior for vendor-specific SCIM implementations."
msgstr ""
#: authentik/providers/scim/models.py
msgid "SCIM Provider"
msgstr ""
@ -2555,7 +2535,6 @@ msgid ""
msgstr ""
#: authentik/stages/authenticator_email/models.py
#: authentik/stages/email/models.py
msgid "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
msgstr ""
@ -2894,6 +2873,10 @@ msgstr ""
msgid "Activate users upon completion of stage."
msgstr ""
#: authentik/stages/email/models.py
msgid "Time in minutes the token sent is valid."
msgstr ""
#: authentik/stages/email/models.py
msgid "Email Stage"
msgstr ""

View File

@ -9,9 +9,9 @@
# Kyllian Delaye-Maillot, 2023
# Manuel Viens, 2023
# Mordecai, 2023
# Charles Leclerc, 2024
# nerdinator <florian.dupret@gmail.com>, 2024
# Tina, 2024
# Charles Leclerc, 2025
# Marc Schmitt, 2025
#
#, fuzzy
@ -19,7 +19,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
"POT-Creation-Date: 2025-03-02 00:10+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: Marc Schmitt, 2025\n"
"Language-Team: French (https://app.transifex.com/authentik/teams/119923/fr/)\n"
@ -2097,18 +2097,6 @@ msgstr "Fournisseur SAML depuis métadonnées"
msgid "SAML Providers from Metadata"
msgstr "Fournisseurs SAML depuis métadonnées"
#: authentik/providers/scim/models.py
msgid "Default"
msgstr "Par défaut"
#: authentik/providers/scim/models.py
msgid "AWS"
msgstr "AWS"
#: authentik/providers/scim/models.py
msgid "Slack"
msgstr "Slack"
#: authentik/providers/scim/models.py
msgid "Base URL to SCIM requests, usually ends in /v2"
msgstr "URL de base pour les requêtes SCIM, se terminant généralement par /v2"
@ -2117,16 +2105,6 @@ msgstr "URL de base pour les requêtes SCIM, se terminant généralement par /v2
msgid "Authentication token"
msgstr "Jeton d'authentification"
#: authentik/providers/scim/models.py
msgid "SCIM Compatibility Mode"
msgstr "Mode de compatibilité SCIM"
#: authentik/providers/scim/models.py
msgid "Alter authentik behavior for vendor-specific SCIM implementations."
msgstr ""
"Change le comportement d'authentik en fonction des spécificités "
"d'implémentations des fournisseurs SCIM."
#: authentik/providers/scim/models.py
msgid "SCIM Provider"
msgstr "Fournisseur SCIM"
@ -2819,7 +2797,6 @@ msgstr ""
"les paramètres de connexion ci-dessous seront ignorés."
#: authentik/stages/authenticator_email/models.py
#: authentik/stages/email/models.py
msgid "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
msgstr ""
"Durée de validité du jeton envoyé (Format : hours=3,minutes=17,seconds=300)."
@ -3191,6 +3168,10 @@ msgstr "Confirmation du Compte"
msgid "Activate users upon completion of stage."
msgstr "Activer les utilisateurs à la complétion de l'étape."
#: authentik/stages/email/models.py
msgid "Time in minutes the token sent is valid."
msgstr "Temps en minutes durant lequel le jeton envoyé est valide."
#: authentik/stages/email/models.py
msgid "Email Stage"
msgstr "Étape Email"

Binary file not shown.

View File

@ -7,7 +7,7 @@
# Chen Zhikai, 2022
# 刘松, 2022
# Tianhao Chai <cth451@gmail.com>, 2024
# Jens L. <jens@goauthentik.io>, 2025
# Jens L. <jens@goauthentik.io>, 2024
# deluxghost, 2025
#
#, fuzzy
@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
"POT-Creation-Date: 2025-03-02 00:10+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2025\n"
"Language-Team: Chinese Simplified (https://app.transifex.com/authentik/teams/119923/zh-Hans/)\n"
@ -1909,18 +1909,6 @@ msgstr "来自元数据的 SAML 提供程序"
msgid "SAML Providers from Metadata"
msgstr "来自元数据的 SAML 提供程序"
#: authentik/providers/scim/models.py
msgid "Default"
msgstr "默认"
#: authentik/providers/scim/models.py
msgid "AWS"
msgstr "AWS"
#: authentik/providers/scim/models.py
msgid "Slack"
msgstr "Slack"
#: authentik/providers/scim/models.py
msgid "Base URL to SCIM requests, usually ends in /v2"
msgstr "SCIM 请求的基础 URL通常以 /v2 结尾"
@ -1929,14 +1917,6 @@ msgstr "SCIM 请求的基础 URL通常以 /v2 结尾"
msgid "Authentication token"
msgstr "身份验证令牌"
#: authentik/providers/scim/models.py
msgid "SCIM Compatibility Mode"
msgstr "SCIM 兼容模式"
#: authentik/providers/scim/models.py
msgid "Alter authentik behavior for vendor-specific SCIM implementations."
msgstr "更改 authentik 的行为,以兼容特定厂商的 SCIM 实现。"
#: authentik/providers/scim/models.py
msgid "SCIM Provider"
msgstr "SCIM 提供程序"
@ -2591,7 +2571,6 @@ msgid ""
msgstr "启用后,将使用全局电子邮件连接设置,下面的连接设置将被忽略。"
#: authentik/stages/authenticator_email/models.py
#: authentik/stages/email/models.py
msgid "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
msgstr "发出令牌有效的时间格式hours=3,minutes=17,seconds=300。"
@ -2941,6 +2920,10 @@ msgstr "账户确认"
msgid "Activate users upon completion of stage."
msgstr "完成阶段后激活用户。"
#: authentik/stages/email/models.py
msgid "Time in minutes the token sent is valid."
msgstr "发出令牌的有效时间(单位为分钟)。"
#: authentik/stages/email/models.py
msgid "Email Stage"
msgstr "电子邮件阶段"

Binary file not shown.

View File

@ -6,7 +6,7 @@
# Translators:
# Chen Zhikai, 2022
# 刘松, 2022
# Jens L. <jens@goauthentik.io>, 2025
# Jens L. <jens@goauthentik.io>, 2024
# deluxghost, 2025
#
#, fuzzy
@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-13 00:10+0000\n"
"POT-Creation-Date: 2025-03-02 00:10+0000\n"
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
"Last-Translator: deluxghost, 2025\n"
"Language-Team: Chinese (China) (https://app.transifex.com/authentik/teams/119923/zh_CN/)\n"
@ -1908,18 +1908,6 @@ msgstr "来自元数据的 SAML 提供程序"
msgid "SAML Providers from Metadata"
msgstr "来自元数据的 SAML 提供程序"
#: authentik/providers/scim/models.py
msgid "Default"
msgstr "默认"
#: authentik/providers/scim/models.py
msgid "AWS"
msgstr "AWS"
#: authentik/providers/scim/models.py
msgid "Slack"
msgstr "Slack"
#: authentik/providers/scim/models.py
msgid "Base URL to SCIM requests, usually ends in /v2"
msgstr "SCIM 请求的基础 URL通常以 /v2 结尾"
@ -1928,14 +1916,6 @@ msgstr "SCIM 请求的基础 URL通常以 /v2 结尾"
msgid "Authentication token"
msgstr "身份验证令牌"
#: authentik/providers/scim/models.py
msgid "SCIM Compatibility Mode"
msgstr "SCIM 兼容模式"
#: authentik/providers/scim/models.py
msgid "Alter authentik behavior for vendor-specific SCIM implementations."
msgstr "更改 authentik 的行为,以兼容特定厂商的 SCIM 实现。"
#: authentik/providers/scim/models.py
msgid "SCIM Provider"
msgstr "SCIM 提供程序"
@ -2590,7 +2570,6 @@ msgid ""
msgstr "启用后,将使用全局电子邮件连接设置,下面的连接设置将被忽略。"
#: authentik/stages/authenticator_email/models.py
#: authentik/stages/email/models.py
msgid "Time the token sent is valid (Format: hours=3,minutes=17,seconds=300)."
msgstr "发出令牌有效的时间格式hours=3,minutes=17,seconds=300。"
@ -2940,6 +2919,10 @@ msgstr "账户确认"
msgid "Activate users upon completion of stage."
msgstr "完成阶段后激活用户。"
#: authentik/stages/email/models.py
msgid "Time in minutes the token sent is valid."
msgstr "发出令牌的有效时间(单位为分钟)。"
#: authentik/stages/email/models.py
msgid "Email Stage"
msgstr "电子邮件阶段"

169
poetry.lock generated
View File

@ -358,18 +358,18 @@ visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"]
[[package]]
name = "aws-cdk-asset-awscli-v1"
version = "2.2.227"
version = "2.2.212"
description = "A library that contains the AWS CLI for use in Lambda Layers"
optional = false
python-versions = "~=3.8"
groups = ["dev"]
files = [
{file = "aws_cdk.asset_awscli_v1-2.2.227-py3-none-any.whl", hash = "sha256:5160cd515d94d0da252806cd853a3e861e1b76aa553e1c90aec2bd712fa3df1b"},
{file = "aws_cdk_asset_awscli_v1-2.2.227.tar.gz", hash = "sha256:0fa4cf382e712121b8bbe11532854018abdba19964ded9c0aa9aace4383816b6"},
{file = "aws_cdk.asset_awscli_v1-2.2.212-py3-none-any.whl", hash = "sha256:12161e2d528698957bc2c0f53d2f5e81de54f8ad0e4b94316634bdc1db50f539"},
{file = "aws_cdk_asset_awscli_v1-2.2.212.tar.gz", hash = "sha256:3a4374562f37c9cd3f59cb45173a18ef0f781c0f1df187773662a1dd14cc18fd"},
]
[package.dependencies]
jsii = ">=1.108.0,<2.0.0"
jsii = ">=1.105.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<4.3.0"
@ -409,22 +409,22 @@ typeguard = ">=2.13.3,<4.3.0"
[[package]]
name = "aws-cdk-lib"
version = "2.184.0"
version = "2.182.0"
description = "Version 2 of the AWS Cloud Development Kit library"
optional = false
python-versions = "~=3.9"
python-versions = "~=3.8"
groups = ["dev"]
files = [
{file = "aws_cdk_lib-2.184.0-py3-none-any.whl", hash = "sha256:b714691e5a53d7d6dc3ca9a637c603921ccb31d31ec5cc206c96ae132be410ca"},
{file = "aws_cdk_lib-2.184.0.tar.gz", hash = "sha256:40b26e3eb7de23260c74a7cbbf8345104bb28301586859d2c5ea5cce108db9c0"},
{file = "aws_cdk_lib-2.182.0-py3-none-any.whl", hash = "sha256:73b46fb789c7fe138f5ec15afa23c588fee706827edd056dd5fa8571e3d725dd"},
{file = "aws_cdk_lib-2.182.0.tar.gz", hash = "sha256:907a2969d7c48605f597b47e47adb0b58ac17bb8de71d4a97761513c76cb3aa8"},
]
[package.dependencies]
"aws-cdk.asset-awscli-v1" = ">=2.2.227,<3.0.0"
"aws-cdk.asset-awscli-v1" = ">=2.2.208,<3.0.0"
"aws-cdk.asset-node-proxy-agent-v6" = ">=2.1.0,<3.0.0"
"aws-cdk.cloud-assembly-schema" = ">=40.7.0,<41.0.0"
"aws-cdk.cloud-assembly-schema" = ">=40.6.0,<41.0.0"
constructs = ">=10.0.0,<11.0.0"
jsii = ">=1.109.0,<2.0.0"
jsii = ">=1.106.0,<2.0.0"
publication = ">=0.0.3"
typeguard = ">=2.13.3,<4.3.0"
@ -1406,14 +1406,14 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"]
[[package]]
name = "django"
version = "5.0.13"
version = "5.0.12"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
optional = false
python-versions = ">=3.10"
groups = ["main", "dev"]
files = [
{file = "Django-5.0.13-py3-none-any.whl", hash = "sha256:b983238dfa2eb2e6b27ebb815e14f0741cf186606eb7bcd857e740174017c50e"},
{file = "Django-5.0.13.tar.gz", hash = "sha256:f9d4b7b87a9dae248d5f20cec940cf7290e07d508d6d8432e3c2cabf09b3b0ff"},
{file = "Django-5.0.12-py3-none-any.whl", hash = "sha256:3566604af111f586a1c9d49cb14ba6c607a0ccbbf87f57d98872cd8aae7d48ad"},
{file = "Django-5.0.12.tar.gz", hash = "sha256:05097ea026cceb2db4db0655ecf77cc96b0753ac6a367280e458e603f6556f53"},
]
[package.dependencies]
@ -1611,7 +1611,7 @@ Django = ">=2.1,<5.1"
type = "git"
url = "https://github.com/rissson/django-tenants.git"
reference = "authentik-fixes"
resolved_reference = "156e53a6f5902d74b73dd9d0192fffaa2587a740"
resolved_reference = "a7f37c53f62f355a00142473ff1e3451bb794eca"
[[package]]
name = "djangorestframework"
@ -2033,14 +2033,14 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
[[package]]
name = "google-api-python-client"
version = "2.164.0"
version = "2.163.0"
description = "Google API Client Library for Python"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "google_api_python_client-2.164.0-py2.py3-none-any.whl", hash = "sha256:b2037c3d280793c8d5180b04317b16be4acd5f77af5dfa7213ace32d140a9ffe"},
{file = "google_api_python_client-2.164.0.tar.gz", hash = "sha256:116f5a05dfb95ed7f7ea0d0f561fc5464146709c583226cc814690f9bb221492"},
{file = "google_api_python_client-2.163.0-py2.py3-none-any.whl", hash = "sha256:080e8bc0669cb4c1fb8efb8da2f5b91a2625d8f0e7796cfad978f33f7016c6c4"},
{file = "google_api_python_client-2.163.0.tar.gz", hash = "sha256:88dee87553a2d82176e2224648bf89272d536c8f04dcdda37ef0a71473886dd7"},
]
[package.dependencies]
@ -2383,14 +2383,14 @@ files = [
[[package]]
name = "importlib-metadata"
version = "8.6.1"
version = "8.5.0"
description = "Read metadata from Python packages"
optional = false
python-versions = ">=3.9"
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
{file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"},
{file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"},
{file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"},
{file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"},
]
[package.dependencies]
@ -2402,7 +2402,7 @@ cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
enabler = ["pytest-enabler (>=2.2)"]
perf = ["ipython"]
test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
type = ["pytest-mypy"]
[[package]]
@ -2495,14 +2495,14 @@ files = [
[[package]]
name = "jsii"
version = "1.109.0"
version = "1.108.0"
description = "Python client for jsii runtime"
optional = false
python-versions = "~=3.9"
python-versions = "~=3.8"
groups = ["dev"]
files = [
{file = "jsii-1.109.0-py3-none-any.whl", hash = "sha256:100bb48c7f74b8e22b3182c5466db1c32565ddb681ed5e2bf556076a734d3f07"},
{file = "jsii-1.109.0.tar.gz", hash = "sha256:85e0deca8089e2918776541e986d5abab90a66d4330eedfc14e8a060dd507bad"},
{file = "jsii-1.108.0-py3-none-any.whl", hash = "sha256:d6c99671ab44520069ad6198e3b07379ae9c075bcb53b8a16455c1beb10288ea"},
{file = "jsii-1.108.0.tar.gz", hash = "sha256:f1053a414ac117c6ecae7208c5ca4cb6d10ca3420c69e30f8b9cca64cc37e61b"},
]
[package.dependencies]
@ -3265,14 +3265,14 @@ dev = ["bumpver", "isort", "mypy", "pylint", "pytest", "yapf"]
[[package]]
name = "msgraph-sdk"
version = "1.24.0"
version = "1.23.0"
description = "The Microsoft Graph Python SDK"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "msgraph_sdk-1.24.0-py3-none-any.whl", hash = "sha256:7cd2dd95c3e2b4d565fe11b019d26102723d252022b53dc854697378dc983538"},
{file = "msgraph_sdk-1.24.0.tar.gz", hash = "sha256:d401160cfdab239867faf3b7e6984632dd11524d893a0c816db2d5a64adda650"},
{file = "msgraph_sdk-1.23.0-py3-none-any.whl", hash = "sha256:58e0047b4ca59fd82022c02cd73fec0170a3d84f3b76721e3db2a0314df9a58a"},
{file = "msgraph_sdk-1.23.0.tar.gz", hash = "sha256:6dd1ba9a46f5f0ce8599fd9610133adbd9d1493941438b5d3632fce9e55ed607"},
]
[package.dependencies]
@ -3448,52 +3448,52 @@ resolved_reference = "20d69d9cc50a0fef31605b46f06da0c94f1ec3cf"
[[package]]
name = "opentelemetry-api"
version = "1.31.0"
version = "1.28.0"
description = "OpenTelemetry Python API"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "opentelemetry_api-1.31.0-py3-none-any.whl", hash = "sha256:145b72c6c16977c005c568ec32f4946054ab793d8474a17fd884b0397582c5f2"},
{file = "opentelemetry_api-1.31.0.tar.gz", hash = "sha256:d8da59e83e8e3993b4726e4c1023cd46f57c4d5a73142e239247e7d814309de1"},
{file = "opentelemetry_api-1.28.0-py3-none-any.whl", hash = "sha256:8457cd2c59ea1bd0988560f021656cecd254ad7ef6be4ba09dbefeca2409ce52"},
{file = "opentelemetry_api-1.28.0.tar.gz", hash = "sha256:578610bcb8aa5cdcb11169d136cc752958548fb6ccffb0969c1036b0ee9e5353"},
]
[package.dependencies]
deprecated = ">=1.2.6"
importlib-metadata = ">=6.0,<8.7.0"
importlib-metadata = ">=6.0,<=8.5.0"
[[package]]
name = "opentelemetry-sdk"
version = "1.31.0"
version = "1.28.0"
description = "OpenTelemetry Python SDK"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "opentelemetry_sdk-1.31.0-py3-none-any.whl", hash = "sha256:97c9a03865e69723725fb64fe04343a488c3e61e684eb804bd7d6da2215dfc60"},
{file = "opentelemetry_sdk-1.31.0.tar.gz", hash = "sha256:452d7d5b3c1db2e5e4cb64abede0ddd20690cb244a559c73a59652fdf6726070"},
{file = "opentelemetry_sdk-1.28.0-py3-none-any.whl", hash = "sha256:4b37da81d7fad67f6683c4420288c97f4ed0d988845d5886435f428ec4b8429a"},
{file = "opentelemetry_sdk-1.28.0.tar.gz", hash = "sha256:41d5420b2e3fb7716ff4981b510d551eff1fc60eb5a95cf7335b31166812a893"},
]
[package.dependencies]
opentelemetry-api = "1.31.0"
opentelemetry-semantic-conventions = "0.52b0"
opentelemetry-api = "1.28.0"
opentelemetry-semantic-conventions = "0.49b0"
typing-extensions = ">=3.7.4"
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.52b0"
version = "0.49b0"
description = "OpenTelemetry Semantic Conventions"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "opentelemetry_semantic_conventions-0.52b0-py3-none-any.whl", hash = "sha256:4d843652ae1f9f3c0d4d8df0bfef740627c90495ac043fc33f0a04bad3b606e2"},
{file = "opentelemetry_semantic_conventions-0.52b0.tar.gz", hash = "sha256:f8bc8873a69d0a2f45746c31980baad2bb10ccee16b1816497ccf99417770386"},
{file = "opentelemetry_semantic_conventions-0.49b0-py3-none-any.whl", hash = "sha256:0458117f6ead0b12e3221813e3e511d85698c31901cac84682052adb9c17c7cd"},
{file = "opentelemetry_semantic_conventions-0.49b0.tar.gz", hash = "sha256:dbc7b28339e5390b6b28e022835f9bac4e134a80ebf640848306d3c5192557e8"},
]
[package.dependencies]
deprecated = ">=1.2.6"
opentelemetry-api = "1.31.0"
opentelemetry-api = "1.28.0"
[[package]]
name = "orjson"
@ -3880,24 +3880,24 @@ files = [
[[package]]
name = "psycopg"
version = "3.2.6"
version = "3.2.5"
description = "PostgreSQL database adapter for Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "psycopg-3.2.6-py3-none-any.whl", hash = "sha256:f3ff5488525890abb0566c429146add66b329e20d6d4835662b920cbbf90ac58"},
{file = "psycopg-3.2.6.tar.gz", hash = "sha256:16fa094efa2698f260f2af74f3710f781e4a6f226efe9d1fd0c37f384639ed8a"},
{file = "psycopg-3.2.5-py3-none-any.whl", hash = "sha256:b782130983e5b3de30b4c529623d3687033b4dafa05bb661fc6bf45837ca5879"},
{file = "psycopg-3.2.5.tar.gz", hash = "sha256:f5f750611c67cb200e85b408882f29265c66d1de7f813add4f8125978bfd70e8"},
]
[package.dependencies]
psycopg-c = {version = "3.2.6", optional = true, markers = "implementation_name != \"pypy\" and extra == \"c\""}
psycopg-c = {version = "3.2.5", optional = true, markers = "implementation_name != \"pypy\" and extra == \"c\""}
typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""}
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
[package.extras]
binary = ["psycopg-binary (==3.2.6) ; implementation_name != \"pypy\""]
c = ["psycopg-c (==3.2.6) ; implementation_name != \"pypy\""]
binary = ["psycopg-binary (==3.2.5) ; implementation_name != \"pypy\""]
c = ["psycopg-c (==3.2.5) ; implementation_name != \"pypy\""]
dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "isort-psycopg", "isort[colors] (>=6.0)", "mypy (>=1.14)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "wheel (>=0.37)"]
docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"]
pool = ["psycopg-pool"]
@ -3905,14 +3905,14 @@ test = ["anyio (>=4.0)", "mypy (>=1.14)", "pproxy (>=2.7)", "pytest (>=6.2.5)",
[[package]]
name = "psycopg-c"
version = "3.2.6"
version = "3.2.5"
description = "PostgreSQL database adapter for Python -- C optimisation distribution"
optional = false
python-versions = ">=3.8"
groups = ["main"]
markers = "implementation_name != \"pypy\""
files = [
{file = "psycopg_c-3.2.6.tar.gz", hash = "sha256:b5fd4ce70f82766a122ca5076a36c4d5818eaa9df9bf76870bc83a064ffaed3a"},
{file = "psycopg_c-3.2.5.tar.gz", hash = "sha256:57ad4cfd28de278c424aaceb1f2ad5c7910466e315dfe84e403f3c7a0a2ce81b"},
]
[[package]]
@ -4185,19 +4185,18 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
[[package]]
name = "pyopenssl"
version = "25.0.0"
version = "24.3.0"
description = "Python wrapper module around the OpenSSL library"
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
files = [
{file = "pyOpenSSL-25.0.0-py3-none-any.whl", hash = "sha256:424c247065e46e76a37411b9ab1782541c23bb658bf003772c3405fbaa128e90"},
{file = "pyopenssl-25.0.0.tar.gz", hash = "sha256:cd2cef799efa3936bb08e8ccb9433a575722b9dd986023f1cabc4ae64e9dac16"},
{file = "pyOpenSSL-24.3.0-py3-none-any.whl", hash = "sha256:e474f5a473cd7f92221cc04976e48f4d11502804657a08a989fb3be5514c904a"},
{file = "pyopenssl-24.3.0.tar.gz", hash = "sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36"},
]
[package.dependencies]
cryptography = ">=41.0.5,<45"
typing-extensions = {version = ">=4.9", markers = "python_version < \"3.13\" and python_version >= \"3.8\""}
[package.extras]
docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx_rtd_theme"]
@ -4749,30 +4748,30 @@ pyasn1 = ">=0.1.3"
[[package]]
name = "ruff"
version = "0.9.10"
version = "0.9.9"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "ruff-0.9.10-py3-none-linux_armv6l.whl", hash = "sha256:eb4d25532cfd9fe461acc83498361ec2e2252795b4f40b17e80692814329e42d"},
{file = "ruff-0.9.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:188a6638dab1aa9bb6228a7302387b2c9954e455fb25d6b4470cb0641d16759d"},
{file = "ruff-0.9.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5284dcac6b9dbc2fcb71fdfc26a217b2ca4ede6ccd57476f52a587451ebe450d"},
{file = "ruff-0.9.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47678f39fa2a3da62724851107f438c8229a3470f533894b5568a39b40029c0c"},
{file = "ruff-0.9.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99713a6e2766b7a17147b309e8c915b32b07a25c9efd12ada79f217c9c778b3e"},
{file = "ruff-0.9.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524ee184d92f7c7304aa568e2db20f50c32d1d0caa235d8ddf10497566ea1a12"},
{file = "ruff-0.9.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df92aeac30af821f9acf819fc01b4afc3dfb829d2782884f8739fb52a8119a16"},
{file = "ruff-0.9.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de42e4edc296f520bb84954eb992a07a0ec5a02fecb834498415908469854a52"},
{file = "ruff-0.9.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d257f95b65806104b6b1ffca0ea53f4ef98454036df65b1eda3693534813ecd1"},
{file = "ruff-0.9.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60dec7201c0b10d6d11be00e8f2dbb6f40ef1828ee75ed739923799513db24c"},
{file = "ruff-0.9.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d838b60007da7a39c046fcdd317293d10b845001f38bcb55ba766c3875b01e43"},
{file = "ruff-0.9.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ccaf903108b899beb8e09a63ffae5869057ab649c1e9231c05ae354ebc62066c"},
{file = "ruff-0.9.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f9567d135265d46e59d62dc60c0bfad10e9a6822e231f5b24032dba5a55be6b5"},
{file = "ruff-0.9.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f202f0d93738c28a89f8ed9eaba01b7be339e5d8d642c994347eaa81c6d75b8"},
{file = "ruff-0.9.10-py3-none-win32.whl", hash = "sha256:bfb834e87c916521ce46b1788fbb8484966e5113c02df216680102e9eb960029"},
{file = "ruff-0.9.10-py3-none-win_amd64.whl", hash = "sha256:f2160eeef3031bf4b17df74e307d4c5fb689a6f3a26a2de3f7ef4044e3c484f1"},
{file = "ruff-0.9.10-py3-none-win_arm64.whl", hash = "sha256:5fd804c0327a5e5ea26615550e706942f348b197d5475ff34c19733aee4b2e69"},
{file = "ruff-0.9.10.tar.gz", hash = "sha256:9bacb735d7bada9cfb0f2c227d3658fc443d90a727b47f206fb33f52f3c0eac7"},
{file = "ruff-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:628abb5ea10345e53dff55b167595a159d3e174d6720bf19761f5e467e68d367"},
{file = "ruff-0.9.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cd1428e834b35d7493354723543b28cc11dc14d1ce19b685f6e68e07c05ec7"},
{file = "ruff-0.9.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ee162652869120ad260670706f3cd36cd3f32b0c651f02b6da142652c54941d"},
{file = "ruff-0.9.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa0f6b75082c9be1ec5a1db78c6d4b02e2375c3068438241dc19c7c306cc61a"},
{file = "ruff-0.9.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:584cc66e89fb5f80f84b05133dd677a17cdd86901d6479712c96597a3f28e7fe"},
{file = "ruff-0.9.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf3369325761a35aba75cd5c55ba1b5eb17d772f12ab168fbfac54be85cf18c"},
{file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3403a53a32a90ce929aa2f758542aca9234befa133e29f4933dcef28a24317be"},
{file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18454e7fa4e4d72cffe28a37cf6a73cb2594f81ec9f4eca31a0aaa9ccdfb1590"},
{file = "ruff-0.9.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fadfe2c88724c9617339f62319ed40dcdadadf2888d5afb88bf3adee7b35bfb"},
{file = "ruff-0.9.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df104d08c442a1aabcfd254279b8cc1e2cbf41a605aa3e26610ba1ec4acf0b0"},
{file = "ruff-0.9.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d7c62939daf5b2a15af48abbd23bea1efdd38c312d6e7c4cedf5a24e03207e17"},
{file = "ruff-0.9.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9494ba82a37a4b81b6a798076e4a3251c13243fc37967e998efe4cce58c8a8d1"},
{file = "ruff-0.9.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4efd7a96ed6d36ef011ae798bf794c5501a514be369296c672dab7921087fa57"},
{file = "ruff-0.9.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ab90a7944c5a1296f3ecb08d1cbf8c2da34c7e68114b1271a431a3ad30cb660e"},
{file = "ruff-0.9.9-py3-none-win32.whl", hash = "sha256:6b4c376d929c25ecd6d87e182a230fa4377b8e5125a4ff52d506ee8c087153c1"},
{file = "ruff-0.9.9-py3-none-win_amd64.whl", hash = "sha256:837982ea24091d4c1700ddb2f63b7070e5baec508e43b01de013dc7eff974ff1"},
{file = "ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf"},
{file = "ruff-0.9.9.tar.gz", hash = "sha256:0062ed13f22173e85f8f7056f9a24016e692efeea8704d1a5e8011b8aa850933"},
]
[[package]]
@ -5121,14 +5120,14 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0"
[[package]]
name = "structlog"
version = "25.2.0"
version = "25.1.0"
description = "Structured Logging for Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "structlog-25.2.0-py3-none-any.whl", hash = "sha256:0fecea2e345d5d491b72f3db2e5fcd6393abfc8cd06a4851f21fcd4d1a99f437"},
{file = "structlog-25.2.0.tar.gz", hash = "sha256:d9f9776944207d1035b8b26072b9b140c63702fd7aa57c2f85d28ab701bd8e92"},
{file = "structlog-25.1.0-py3-none-any.whl", hash = "sha256:843fe4f254540329f380812cbe612e1af5ec5b8172205ae634679cd35a6d6321"},
{file = "structlog-25.1.0.tar.gz", hash = "sha256:2ef2a572e0e27f09664965d31a576afe64e46ac6084ef5cec3c2b8cd6e4e3ad3"},
]
[package.extras]
@ -5229,14 +5228,14 @@ wsproto = ">=0.14"
[[package]]
name = "twilio"
version = "9.5.0"
version = "9.4.6"
description = "Twilio API client and TwiML generator"
optional = false
python-versions = ">=3.7.0"
groups = ["main"]
files = [
{file = "twilio-9.5.0-py2.py3-none-any.whl", hash = "sha256:e6d0ccf9162a83acfa6d21a02e90a22fdbc53f4269be3402ba579f13b2a259fc"},
{file = "twilio-9.5.0.tar.gz", hash = "sha256:633d213c21b394297a27a92f20498adb1c4cd2f6fc3f4e2bfcc7d787b29fc034"},
{file = "twilio-9.4.6-py2.py3-none-any.whl", hash = "sha256:6d7d677fa9ded4ee0c366ad0155a1e0af51e129109af603b6ec9cdc8826a5c37"},
{file = "twilio-9.4.6.tar.gz", hash = "sha256:ff33a6c3609f4a0769d02c4eb75f7ab55ff2ba962762b076cd39ef7da56fdaa4"},
]
[package.dependencies]
@ -5650,21 +5649,21 @@ files = [
[[package]]
name = "webauthn"
version = "2.5.2"
version = "2.5.1"
description = "Pythonic WebAuthn"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "webauthn-2.5.2-py3-none-any.whl", hash = "sha256:44246e496e617eb5e2f51165046b9f0197fcdf470f69cd6734061a27ba365f8e"},
{file = "webauthn-2.5.2.tar.gz", hash = "sha256:09c13dfc1c68c810f32fa4d89b1d37acb9f9ae9091c9d7019e313be4525a95ef"},
{file = "webauthn-2.5.1-py3-none-any.whl", hash = "sha256:86d1faa11ec26ebe49b9388d8c3d09bff4dca6c23d3c7e2dd066e99896d694f0"},
{file = "webauthn-2.5.1.tar.gz", hash = "sha256:f1b7447bae1056e110a9e71ff287f639d05d4d14589911d75fea255c3a03aff0"},
]
[package.dependencies]
asn1crypto = ">=1.5.1"
cbor2 = ">=5.6.5"
cryptography = ">=44.0.2"
pyOpenSSL = ">=25.0.0"
cryptography = ">=43.0.3"
pyOpenSSL = ">=24.2.1"
[[package]]
name = "websocket-client"

View File

@ -17,16 +17,11 @@ skip = [
"go.sum",
"locale",
"**/dist",
"**/storybook-static",
"**/web/src/locales",
"**/web/xliff",
"./web/storybook-static",
"./website/build",
"./gen-ts-api",
"./gen-py-api",
"./gen-go-api",
"*.api.mdx",
"./htmlcov",
]
dictionary = ".github/codespell-dictionary.txt,-"
ignore-words = ".github/codespell-words.txt"

View File

@ -35146,7 +35146,7 @@ paths:
- in: query
name: token_expiry
schema:
type: string
type: integer
- in: query
name: use_global_settings
schema:
@ -41582,12 +41582,6 @@ components:
- confidential
- public
type: string
CompatibilityModeEnum:
enum:
- default
- aws
- slack
type: string
Config:
type: object
description: Serialize authentik Config into DRF Object
@ -42780,8 +42774,10 @@ components:
format: email
maxLength: 254
token_expiry:
type: string
description: 'Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).'
type: integer
maximum: 2147483647
minimum: -2147483648
description: Time in minutes the token sent is valid.
subject:
type: string
template:
@ -42837,9 +42833,10 @@ components:
minLength: 1
maxLength: 254
token_expiry:
type: string
minLength: 1
description: 'Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).'
type: integer
maximum: 2147483647
minimum: -2147483648
description: Time in minutes the token sent is valid.
subject:
type: string
minLength: 1
@ -50392,9 +50389,10 @@ components:
minLength: 1
maxLength: 254
token_expiry:
type: string
minLength: 1
description: 'Time the token sent is valid (Format: hours=3,minutes=17,seconds=300).'
type: integer
maximum: 2147483647
minimum: -2147483648
description: Time in minutes the token sent is valid.
subject:
type: string
minLength: 1
@ -52447,11 +52445,6 @@ components:
type: string
minLength: 1
description: Authentication token
compatibility_mode:
allOf:
- $ref: '#/components/schemas/CompatibilityModeEnum'
title: SCIM Compatibility Mode
description: Alter authentik behavior for vendor-specific SCIM implementations.
exclude_users_service_account:
type: boolean
filter_group:
@ -55852,11 +55845,6 @@ components:
token:
type: string
description: Authentication token
compatibility_mode:
allOf:
- $ref: '#/components/schemas/CompatibilityModeEnum'
title: SCIM Compatibility Mode
description: Alter authentik behavior for vendor-specific SCIM implementations.
exclude_users_service_account:
type: boolean
filter_group:
@ -55947,11 +55935,6 @@ components:
type: string
minLength: 1
description: Authentication token
compatibility_mode:
allOf:
- $ref: '#/components/schemas/CompatibilityModeEnum'
title: SCIM Compatibility Mode
description: Alter authentik behavior for vendor-specific SCIM implementations.
exclude_users_service_account:
type: boolean
filter_group:

22
web/package-lock.json generated
View File

@ -23,7 +23,7 @@
"@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2025.2.1-1741798605",
"@goauthentik/api": "^2025.2.1-1740858273",
"@lit-labs/ssr": "^3.2.2",
"@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2",
@ -826,10 +826,9 @@
}
},
"node_modules/@babel/runtime-corejs3": {
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.10.tgz",
"integrity": "sha512-uITFQYO68pMEYR46AHgQoyBg7KPPJDAbGn4jUTIRgCFJIp88MIBUianVOplhZDEec07bp9zIyr4Kp0FCyQzmWg==",
"license": "MIT",
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.25.7.tgz",
"integrity": "sha512-gMmIEhg35sXk9Te5qbGp3W9YKrvLt3HV658/d3odWrHSqT0JeG5OzsJWFHRLiOohRyjRsJc/x03DhJm3i8VJxg==",
"dependencies": {
"core-js-pure": "^3.30.2",
"regenerator-runtime": "^0.14.0"
@ -1817,9 +1816,9 @@
}
},
"node_modules/@goauthentik/api": {
"version": "2025.2.1-1741798605",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.2.1-1741798605.tgz",
"integrity": "sha512-Go0Iij1q/imohOSqxj43pvju8D+OFH7iNBBg6FO1ytd9pcHi1QY7/jq1vy1HKWZw7oZ2fY6hZnSe+keRnBA0Fg=="
"version": "2025.2.1-1740858273",
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.2.1-1740858273.tgz",
"integrity": "sha512-dOY32RKJSy3fYteuL4h13NaRVznq0O9Z0IXdwUDwkCQ4GoBfqUaPIFb55tFvRNrdNZG0efor0PIGISxoHpRJwA=="
},
"node_modules/@goauthentik/web": {
"resolved": "",
@ -18235,10 +18234,9 @@
}
},
"node_modules/prismjs": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
"license": "MIT",
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
"engines": {
"node": ">=6"
}

View File

@ -11,7 +11,7 @@
"@floating-ui/dom": "^1.6.11",
"@formatjs/intl-listformat": "^7.5.7",
"@fortawesome/fontawesome-free": "^6.6.0",
"@goauthentik/api": "^2025.2.1-1741798605",
"@goauthentik/api": "^2025.2.1-1740858273",
"@lit-labs/ssr": "^3.2.2",
"@lit/context": "^1.1.2",
"@lit/localize": "^0.12.2",

View File

@ -28,7 +28,6 @@ import { when } from "lit/directives/when.js";
import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFDivider from "@patternfly/patternfly/components/Divider/divider.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFFlex from "@patternfly/patternfly/layouts/Flex/flex.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
@ -55,7 +54,6 @@ export class AdminOverviewPage extends AdminOverviewBase {
return [
PFBase,
PFGrid,
PFFlex,
PFPage,
PFContent,
PFDivider,
@ -69,6 +67,13 @@ export class AdminOverviewPage extends AdminOverviewBase {
.card-container {
max-height: 10em;
}
.ak-external-link {
display: inline-block;
margin-left: 0.175rem;
vertical-align: super;
line-height: normal;
font-size: var(--pf-global--icon--FontSize--sm);
}
`,
];
}
@ -94,34 +99,43 @@ export class AdminOverviewPage extends AdminOverviewBase {
return html`<ak-page-header description=${msg("General system status")} ?hasIcon=${false}>
<span slot="header"> ${msg(str`Welcome, ${name || ""}.`)} </span>
</ak-page-header>
<section class="pf-c-page__main-section">
<div class="pf-l-grid pf-m-gutter">
${this.renderCards()}
<div class="pf-l-grid__item pf-m-9-col pf-m-3-row">
<!-- row 1 -->
<div
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-6-col-on-2xl pf-l-grid pf-m-gutter"
>
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-4-col-on-2xl">
<ak-quick-actions-card .actions=${this.quickActions}>
</ak-quick-actions-card>
</div>
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-4-col-on-2xl">
<ak-aggregate-card
icon="pf-icon pf-icon-zone"
header=${msg("Outpost status")}
headerLink="#/outpost/outposts"
>
<ak-admin-status-chart-outpost></ak-admin-status-chart-outpost>
</ak-aggregate-card>
</div>
<div
class="pf-l-grid__item pf-m-12-col pf-m-12-col-on-xl pf-m-4-col-on-2xl"
>
<ak-aggregate-card icon="fa fa-sync-alt" header=${msg("Sync status")}>
<ak-admin-status-chart-sync></ak-admin-status-chart-sync>
</ak-aggregate-card>
</div>
<div class="pf-l-grid__item pf-m-12-col">
<hr class="pf-c-divider" />
</div>
${this.renderCards()}
</div>
<div class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl">
<ak-recent-events pageSize="6"></ak-recent-events>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl">
<ak-quick-actions-card .actions=${this.quickActions}>
</ak-quick-actions-card>
<div class="pf-l-grid__item pf-m-12-col">
<hr class="pf-c-divider" />
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl">
<ak-aggregate-card
icon="pf-icon pf-icon-zone"
header=${msg("Outpost status")}
headerLink="#/outpost/outposts"
>
<ak-admin-status-chart-outpost></ak-admin-status-chart-outpost>
</ak-aggregate-card>
</div>
<div class="pf-l-grid__item pf-m-6-col pf-m-3-col-on-md pf-m-3-col-on-xl">
<ak-aggregate-card icon="fa fa-sync-alt" header=${msg("Sync status")}>
<ak-admin-status-chart-sync></ak-admin-status-chart-sync>
</ak-aggregate-card>
</div>
<!-- row 3 -->
<div
class="pf-l-grid__item pf-m-12-col pf-m-6-col-on-xl pf-m-8-col-on-2xl big-graph-container"

View File

@ -1,15 +1,10 @@
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { PFSize } from "@goauthentik/common/enums.js";
import {
APIError,
parseAPIResponseError,
pluckErrorDetail,
} from "@goauthentik/common/errors/network";
import { AggregateCard } from "@goauthentik/elements/cards/AggregateCard";
import { msg } from "@lit/localize";
import { PropertyValues, TemplateResult, html, nothing } from "lit";
import { state } from "lit/decorators.js";
import { TemplateResult, html } from "lit";
import { until } from "lit/directives/until.js";
import { ResponseError } from "@goauthentik/api";
@ -18,143 +13,46 @@ export interface AdminStatus {
message?: TemplateResult;
}
/**
* Abstract base class for admin status cards with robust state management
*
* @template T - Type of the primary data value used in the card
*/
export abstract class AdminStatusCard<T> extends AggregateCard {
// Current data value state
@state()
value?: T;
// Current status state derived from value
@state()
protected status?: AdminStatus;
// Current error state if any request fails
@state()
protected error?: APIError;
// Abstract methods to be implemented by subclasses
abstract getPrimaryValue(): Promise<T>;
abstract getStatus(value: T): Promise<AdminStatus>;
value?: T;
constructor() {
super();
// Proper binding for event handler
this.fetchData = this.fetchData.bind(this);
// Register refresh event listener
this.addEventListener(EVENT_REFRESH, this.fetchData);
this.addEventListener(EVENT_REFRESH, () => {
this.requestUpdate();
});
}
// Lifecycle method: Called when component is added to DOM
connectedCallback(): void {
super.connectedCallback();
// Initial data fetch
this.fetchData();
}
/**
* Fetch primary data and handle errors
*/
private fetchData() {
this.getPrimaryValue()
.then((value: T) => {
this.value = value; // Triggers shouldUpdate
this.error = undefined;
})
.catch(async (error) => {
this.status = undefined;
this.error = await parseAPIResponseError(error);
});
}
/**
* Lit lifecycle method: Determine if component should update
*
* @param changed - Map of changed properties
* @returns boolean indicating if update should proceed
*/
shouldUpdate(changed: PropertyValues<this>) {
if (changed.has("value") && this.value !== undefined) {
// When value changes, fetch new status
this.getStatus(this.value)
.then((status) => {
this.status = status;
this.error = undefined;
})
.catch(async (error: ResponseError) => {
this.status = undefined;
this.error = await parseAPIResponseError(error);
});
// Prevent immediate re-render if only value changed
if (changed.size === 1) return false;
}
return true;
}
/**
* Render the primary value display
*
* @returns TemplateResult displaying the value
*/
protected renderValue(): TemplateResult {
renderValue(): TemplateResult {
return html`${this.value}`;
}
/**
* Render status state
*
* @param status - AdminStatus object containing icon and message
* @returns TemplateResult for status display
*/
private renderStatus(status: AdminStatus): TemplateResult {
return html`
<p><i class="${status.icon}"></i>&nbsp;${this.renderValue()}</p>
${status.message ? html`<p class="subtext">${status.message}</p>` : nothing}
`;
}
/**
* Render error state
*
* @param error - Error message to display
* @returns TemplateResult for error display
*/
private renderError(error: string): TemplateResult {
return html`
<p><i class="fa fa-times"></i>&nbsp;${msg("Failed to fetch")}</p>
<p class="subtext">${error}</p>
`;
}
/**
* Render loading state
*
* @returns TemplateResult for loading spinner
*/
private renderLoading(): TemplateResult {
return html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`;
}
/**
* Main render method that selects appropriate state display
*
* @returns TemplateResult for current component state
*/
renderInner(): TemplateResult {
return html`
<p class="center-value">
${
this.status
? this.renderStatus(this.status) // Status available
: this.error
? this.renderError(pluckErrorDetail(this.error)) // Error state
: this.renderLoading() // Loading state
}
</p>
`;
return html`<p class="center-value">
${until(
this.getPrimaryValue()
.then((v) => {
this.value = v;
return this.getStatus(v);
})
.then((status) => {
return html`<p><i class="${status.icon}"></i>&nbsp;${this.renderValue()}</p>
${status.message
? html`<p class="subtext">${status.message}</p>`
: html``}`;
})
.catch((exc: ResponseError) => {
return html` <p>
<i class="fa fa-times"></i>&nbsp;${exc.response.statusText}
</p>
<p class="subtext">${msg("Failed to fetch")}</p>`;
}),
html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`,
)}
</p>`;
}
}

View File

@ -1,4 +1,4 @@
import { EventUser, formatGeoEvent } from "@goauthentik/admin/events/utils";
import { EventGeo, EventUser } from "@goauthentik/admin/events/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EventWithContext } from "@goauthentik/common/events";
import { actionToLabel } from "@goauthentik/common/labels";
@ -10,7 +10,6 @@ import "@goauthentik/elements/buttons/ModalButton";
import "@goauthentik/elements/buttons/SpinnerButton";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
import { SlottedTemplateResult } from "@goauthentik/elements/types";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
@ -39,22 +38,6 @@ export class RecentEventsCard extends Table<Event> {
return super.styles.concat(
PFCard,
css`
.pf-c-table__sort.pf-m-selected {
background-color: var(--pf-global--BackgroundColor--dark-400);
border-block-end: var(--pf-global--BorderWidth--xl) solid var(--ak-accent);
.pf-c-table__button {
--pf-c-table__sort__button__text--Color: var(--ak-accent);
color: var(--pf-c-nav__link--m-current--Color);
.pf-c-table__text {
--pf-c-table__sort__button__text--Color: var(
--pf-c-nav__link--m-current--Color
);
}
}
}
.pf-c-card__title {
--pf-c-card__title--FontFamily: var(
--pf-global--FontFamily--heading--sans-serif
@ -62,47 +45,7 @@ export class RecentEventsCard extends Table<Event> {
--pf-c-card__title--FontSize: var(--pf-global--FontSize--md);
--pf-c-card__title--FontWeight: var(--pf-global--FontWeight--bold);
}
td[role="cell"] .ip-address {
max-width: 18ch;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
th[role="columnheader"]:nth-child(3) {
--pf-c-table--cell--MinWidth: fit-content;
--pf-c-table--cell--MaxWidth: none;
--pf-c-table--cell--Width: 1%;
--pf-c-table--cell--Overflow: visible;
--pf-c-table--cell--TextOverflow: clip;
--pf-c-table--cell--WhiteSpace: nowrap;
}
.group-header {
display: grid;
grid-template-columns: 1fr auto;
gap: var(--pf-global--spacer--sm);
font-weight: var(--pf-global--FontWeight--bold);
font-size: var(--pf-global--FontSize--md);
font-variant: all-petite-caps;
}
.pf-c-table thead:not(:first-child) {
background: hsl(0deg 0% 0% / 10%);
> tr {
border-block-end: 2px solid
var(
--pf-c-page__header-tools--c-button--m-selected--before--BackgroundColor
);
font-family: var(--pf-global--FontFamily--heading--sans-serif);
}
}
tbody * {
* {
word-break: break-all;
}
`,
@ -125,57 +68,20 @@ export class RecentEventsCard extends Table<Event> {
</div>`;
}
override groupBy(items: Event[]): [SlottedTemplateResult, Event[]][] {
const groupedByDay = new Map<string, Event[]>();
for (const item of items) {
const day = new Date(item.created);
day.setHours(0, 0, 0, 0);
const serializedDay = day.toISOString();
let dayEvents = groupedByDay.get(serializedDay);
if (!dayEvents) {
dayEvents = [];
groupedByDay.set(serializedDay, dayEvents);
}
dayEvents.push(item);
}
return Array.from(groupedByDay, ([serializedDay, events]) => {
const day = new Date(serializedDay);
return [
html` <div class="pf-c-content group-header">
<div>${getRelativeTime(day)}</div>
<small>${day.toLocaleDateString()}</small>
</div>`,
events,
];
});
}
row(item: EventWithContext): SlottedTemplateResult[] {
row(item: EventWithContext): TemplateResult[] {
return [
html`<div><a href="${`#/events/log/${item.pk}`}">${actionToLabel(item.action)}</a></div>
<small class="pf-m-monospace">${item.app}</small>`,
<small>${item.app}</small>`,
EventUser(item),
html`<time datetime="${item.created.toISOString()}" class="pf-c-content">
<div><small>${item.created.toLocaleTimeString()}</small></div>
</time>`,
html`<div class="ip-address pf-m-monospace">${item.clientIp || msg("-")}</div>
<small class="geographic-location">${formatGeoEvent(item)}</small>`,
html`<div>${getRelativeTime(item.created)}</div>
<small>${item.created.toLocaleString()}</small>`,
html` <div>${item.clientIp || msg("-")}</div>
<small>${EventGeo(item)}</small>`,
html`<span>${item.brand?.name || msg("-")}</span>`,
];
}
renderEmpty(inner?: SlottedTemplateResult): TemplateResult {
if (this.error) {
return super.renderEmpty(inner);
}
renderEmpty(): TemplateResult {
return super.renderEmpty(
html`<ak-empty-state header=${msg("No Events found.")}>
<div slot="body">${msg("No matching events could be found.")}</div>

View File

@ -30,13 +30,11 @@ export class OutpostStatusChart extends AKChart<SummarizedSyncStatus[]> {
const api = new OutpostsApi(DEFAULT_CONFIG);
const outposts = await api.outpostsInstancesList({});
const outpostStats: SummarizedSyncStatus[] = [];
await Promise.all(
outposts.results.map(async (element) => {
const health = await api.outpostsInstancesHealthList({
uuid: element.pk || "",
});
const singleStats: SummarizedSyncStatus = {
unsynced: 0,
healthy: 0,
@ -44,11 +42,9 @@ export class OutpostStatusChart extends AKChart<SummarizedSyncStatus[]> {
total: health.length,
label: element.name,
};
if (health.length === 0) {
singleStats.unsynced += 1;
}
health.forEach((h) => {
if (h.versionOutdated) {
singleStats.failed += 1;
@ -56,14 +52,11 @@ export class OutpostStatusChart extends AKChart<SummarizedSyncStatus[]> {
singleStats.healthy += 1;
}
});
outpostStats.push(singleStats);
}),
);
this.centerText = outposts.pagination.count.toString();
outpostStats.sort((a, b) => a.label.localeCompare(b.label));
return outpostStats;
}

View File

@ -14,9 +14,9 @@ import { property, query } from "lit/decorators.js";
import { ValidationError } from "@goauthentik/api";
import {
ApplicationTransactionValidationError,
type ApplicationWizardState,
type ApplicationWizardStateUpdate,
ExtendedValidationError,
} from "./types";
export class ApplicationWizardStep extends WizardStep {
@ -48,7 +48,7 @@ export class ApplicationWizardStep extends WizardStep {
}
protected removeErrors(
keyToDelete: keyof ApplicationTransactionValidationError,
keyToDelete: keyof ExtendedValidationError,
): ValidationError | undefined {
if (!this.wizard.errors) {
return undefined;

View File

@ -58,7 +58,7 @@ export class ApplicationWizardBindingsStep extends ApplicationWizardStep {
get bindingsAsColumns() {
return this.wizard.bindings.map((binding, index) => {
const { order, enabled, timeout } = binding;
const isSet = P.union(P.string.minLength(1), P.number);
const isSet = P.string.minLength(1);
const policy = match(binding)
.with({ policy: isSet }, (v) => msg(str`Policy ${v.policyObj?.name}`))
.with({ group: isSet }, (v) => msg(str`Group ${v.groupObj?.name}`))

View File

@ -1,7 +1,7 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title.js";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { parseAPIResponseError } from "@goauthentik/common/errors/network";
import { parseAPIError } from "@goauthentik/common/errors";
import { WizardNavigationEvent } from "@goauthentik/components/ak-wizard/events.js";
import { type WizardButton } from "@goauthentik/components/ak-wizard/types";
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
@ -33,7 +33,7 @@ import {
} from "@goauthentik/api";
import { ApplicationWizardStep } from "../ApplicationWizardStep.js";
import { ApplicationTransactionValidationError, OneOfProvider } from "../types.js";
import { ExtendedValidationError, OneOfProvider } from "../types.js";
import { providerRenderers } from "./SubmitStepOverviewRenderers.js";
const _submitStates = ["reviewing", "running", "submitted"] as const;
@ -131,36 +131,39 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
this.state = "running";
return new CoreApi(DEFAULT_CONFIG)
.coreTransactionalApplicationsUpdate({
transactionApplicationRequest: request,
})
.then((_response: TransactionApplicationResponse) => {
this.dispatchCustomEvent(EVENT_REFRESH);
this.state = "submitted";
})
return (
new CoreApi(DEFAULT_CONFIG)
.coreTransactionalApplicationsUpdate({
transactionApplicationRequest: request,
})
.then((_response: TransactionApplicationResponse) => {
this.dispatchCustomEvent(EVENT_REFRESH);
this.state = "submitted";
})
.catch(async (resolution) => {
const errors =
await parseAPIResponseError<ApplicationTransactionValidationError>(resolution);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch(async (resolution: any) => {
const errors = (await parseAPIError(
await resolution,
)) as ExtendedValidationError;
// THIS is a really gross special case; if the user is duplicating the name of an existing provider, the error appears on the `app` (!) error object.
// We have to move that to the `provider.name` error field so it shows up in the right place.
if (Array.isArray(errors?.app?.provider)) {
const providerError = errors.app.provider;
errors.provider = errors.provider ?? {};
errors.provider.name = providerError;
delete errors.app.provider;
if (Object.keys(errors.app).length === 0) {
delete errors.app;
// THIS is a really gross special case; if the user is duplicating the name of
// an existing provider, the error appears on the `app` (!) error object. We
// have to move that to the `provider.name` error field so it shows up in the
// right place.
if (Array.isArray(errors?.app?.provider)) {
const providerError = errors.app.provider;
errors.provider = errors.provider ?? {};
errors.provider.name = providerError;
delete errors.app.provider;
if (Object.keys(errors.app).length === 0) {
delete errors.app;
}
}
}
this.handleUpdate({ errors });
this.state = "reviewing";
});
this.handleUpdate({ errors });
this.state = "reviewing";
})
);
}
override handleButton(button: WizardButton) {
@ -229,7 +232,7 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
const navTo = (step: string) => () => this.dispatchEvent(new WizardNavigationEvent(step));
const errors = this.wizard.errors;
return html` <hr class="pf-c-divider" />
${match(errors as ApplicationTransactionValidationError)
${match(errors as ExtendedValidationError)
.with(
{ app: P.nonNullable },
() =>

View File

@ -9,7 +9,7 @@ import { customElement, state } from "lit/decorators.js";
import { OAuth2ProviderRequest, SourcesApi } from "@goauthentik/api";
import { type OAuth2Provider, type PaginatedOAuthSourceList } from "@goauthentik/api";
import { ApplicationTransactionValidationError } from "../../types.js";
import { ExtendedValidationError } from "../../types.js";
import { ApplicationWizardProviderForm } from "./ApplicationWizardProviderForm.js";
@customElement("ak-application-wizard-provider-for-oauth")
@ -34,7 +34,7 @@ export class ApplicationWizardOauth2ProviderForm extends ApplicationWizardProvid
});
}
renderForm(provider: OAuth2Provider, errors: ApplicationTransactionValidationError) {
renderForm(provider: OAuth2Provider, errors: ExtendedValidationError) {
const showClientSecretCallback = (show: boolean) => {
this.showClientSecret = show;
};

View File

@ -1,5 +1,3 @@
import { APIError } from "@goauthentik/common/errors/network";
import {
type ApplicationRequest,
type LDAPProviderRequest,
@ -27,31 +25,16 @@ export type OneOfProvider =
export type ValidationRecord = { [key: string]: string[] };
/**
* An error that occurs during the creation or modification of an application.
*
* @todo (Elf) Extend this type to include all possible errors that can occur during the creation or modification of an application.
*/
export interface ApplicationTransactionValidationError extends ValidationError {
// TODO: Elf, extend this type and apply it to every object in the wizard. Then run
// the type-checker again.
export type ExtendedValidationError = ValidationError & {
app?: ValidationRecord;
provider?: ValidationRecord;
bindings?: ValidationRecord;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
detail?: any;
}
/**
* Type-guard to determine if an API response is shaped like an {@linkcode ApplicationTransactionValidationError}.
*/
export function isApplicationTransactionValidationError(
error: APIError,
): error is ApplicationTransactionValidationError {
if ("app" in error) return true;
if ("provider" in error) return true;
if ("bindings" in error) return true;
return false;
}
};
// We use the PolicyBinding instead of the PolicyBindingRequest here, because that gives us a slot
// in which to preserve the retrieved policy, group, or user object from the SearchSelect used to
@ -66,7 +49,7 @@ export interface ApplicationWizardState {
proxyMode: ProxyMode;
bindings: PolicyBinding[];
currentBinding: number;
errors: ApplicationTransactionValidationError;
errors: ExtendedValidationError;
}
export interface ApplicationWizardStateUpdate {

View File

@ -1,5 +1,5 @@
import "@goauthentik/admin/events/EventVolumeChart";
import { EventUser, formatGeoEvent } from "@goauthentik/admin/events/utils";
import { EventGeo, EventUser } from "@goauthentik/admin/events/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EventWithContext } from "@goauthentik/common/events";
import { actionToLabel } from "@goauthentik/common/labels";
@ -80,7 +80,7 @@ export class EventListPage extends TablePage<Event> {
html`<div>${getRelativeTime(item.created)}</div>
<small>${item.created.toLocaleString()}</small>`,
html`<div>${item.clientIp || msg("-")}</div>
<small>${formatGeoEvent(item)}</small>`,
<small>${EventGeo(item)}</small>`,
html`<span>${item.brand?.name || msg("-")}</span>`,
html`<a href="#/events/log/${item.pk}">
<pf-tooltip position="top" content=${msg("Show details")}>

View File

@ -1,4 +1,4 @@
import { EventUser, formatGeoEvent } from "@goauthentik/admin/events/utils";
import { EventGeo, EventUser } from "@goauthentik/admin/events/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EventWithContext } from "@goauthentik/common/events";
import { actionToLabel } from "@goauthentik/common/labels";
@ -118,7 +118,7 @@ export class EventViewPage extends AKElement {
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<div>${this.event.clientIp || msg("-")}</div>
<small>${formatGeoEvent(this.event)}</small>
<small>${EventGeo(this.event)}</small>
</div>
</dd>
</div>

View File

@ -1,31 +1,27 @@
import { EventWithContext } from "@goauthentik/common/events";
import { truncate } from "@goauthentik/common/utils";
import { SlottedTemplateResult } from "@goauthentik/elements/types";
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
import { msg, str } from "@lit/localize";
import { html, nothing } from "lit";
import { TemplateResult, html } from "lit";
/**
* Given event with a geographical context, format it into a string for display.
*/
export function formatGeoEvent(event: EventWithContext): SlottedTemplateResult {
if (!event.context.geo) return nothing;
const { city, country, continent } = event.context.geo;
const parts = [city, country, continent].filter(Boolean);
return html`${parts.join(", ")}`;
export function EventGeo(event: EventWithContext): TemplateResult {
let geo: KeyUnknown | undefined = undefined;
if (Object.hasOwn(event.context, "geo")) {
geo = event.context.geo as KeyUnknown;
const parts = [geo.city, geo.country, geo.continent].filter(
(v) => v !== "" && v !== undefined,
);
return html`${parts.join(", ")}`;
}
return html``;
}
export function EventUser(
event: EventWithContext,
truncateUsername?: number,
): SlottedTemplateResult {
if (!event.user.username) return html`-`;
let body: SlottedTemplateResult = nothing;
export function EventUser(event: EventWithContext, truncateUsername?: number): TemplateResult {
if (!event.user.username) {
return html`-`;
}
let body = html``;
if (event.user.is_anonymous) {
body = html`<div>${msg("Anonymous user")}</div>`;
} else {
@ -37,14 +33,12 @@ export function EventUser(
>
</div>`;
}
if (event.user.on_behalf_of) {
return html`${body}<small>
body = html`${body}<small>
<a href="#/identity/users/${event.user.on_behalf_of.pk}"
>${msg(str`On behalf of ${event.user.on_behalf_of.username}`)}</a
>
</small>`;
}
return body;
}

View File

@ -1,5 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/sentry";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import "@goauthentik/components/ak-status-label";
import "@goauthentik/elements/events/LogViewer";
import { Form } from "@goauthentik/elements/forms/Form";

View File

@ -21,22 +21,12 @@ export class RelatedApplicationButton extends AKElement {
@property({ attribute: false })
provider?: Provider;
@property()
mode: "primary" | "backchannel" = "primary";
render(): TemplateResult {
if (this.mode === "primary" && this.provider?.assignedApplicationSlug) {
if (this.provider?.assignedApplicationSlug) {
return html`<a href="#/core/applications/${this.provider.assignedApplicationSlug}">
${this.provider.assignedApplicationName}
</a>`;
}
if (this.mode === "backchannel" && this.provider?.assignedBackchannelApplicationSlug) {
return html`<a
href="#/core/applications/${this.provider.assignedBackchannelApplicationSlug}"
>
${this.provider.assignedBackchannelApplicationName}
</a>`;
}
return html`<ak-forms-modal>
<span slot="submit"> ${msg("Create")} </span>
<span slot="header"> ${msg("Create Application")} </span>

View File

@ -1,6 +1,6 @@
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search-no-default";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/sentry";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { Form } from "@goauthentik/elements/forms/Form";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect";

View File

@ -11,7 +11,6 @@ import { html } from "lit";
import { ifDefined } from "lit/directives/if-defined.js";
import {
CompatibilityModeEnum,
CoreApi,
CoreGroupsListRequest,
Group,
@ -62,35 +61,6 @@ export function renderForm(provider?: Partial<SCIMProvider>, errors: ValidationE
)}
inputHint="code"
></ak-text-input>
<ak-radio-input
name="compatibilityMode"
label=${msg("Compatibility Mode")}
.value=${provider?.compatibilityMode}
required
.options=${[
{
label: msg("Default"),
value: CompatibilityModeEnum.Default,
default: true,
description: html`${msg("Default behavior.")}`,
},
{
label: msg("AWS"),
value: CompatibilityModeEnum.Aws,
description: html`${msg(
"Altered behavior for usage with Amazon Web Services.",
)}`,
},
{
label: msg("Slack"),
value: CompatibilityModeEnum.Slack,
description: html`${msg("Altered behavior for usage with Slack.")}`,
},
]}
help=${msg(
"Alter authentik's behavior for vendor-specific SCIM implementations.",
)}
></ak-radio-input>
<ak-form-element-horizontal name="dryRun">
<label class="pf-c-switch">
<input

View File

@ -174,7 +174,6 @@ export class SCIMProviderViewPage extends AKElement {
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
<ak-provider-related-application
mode="backchannel"
.provider=${this.provider}
></ak-provider-related-application>
</div>

View File

@ -57,13 +57,10 @@ export class SourceListPage extends TablePage<Source> {
}
renderToolbarSelected(): TemplateResult {
const disabled =
this.selectedElements.length < 1 ||
this.selectedElements.some((item) => item.component === "");
const nonBuiltInSources = this.selectedElements.filter((item) => item.component !== "");
const disabled = this.selectedElements.length < 1;
return html`<ak-forms-delete-bulk
objectLabel=${msg("Source(s)")}
.objects=${nonBuiltInSources}
.objects=${this.selectedElements}
.usedBy=${(item: Source) => {
return new SourcesApi(DEFAULT_CONFIG).sourcesAllUsedByList({
slug: item.slug,

View File

@ -3,7 +3,6 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/utils/TimeDeltaHelp";
import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit";
@ -203,20 +202,19 @@ export class EmailStageForm extends BaseStageForm<EmailStage> {
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Token expiration")}
label=${msg("Token expiry")}
?required=${true}
name="tokenExpiry"
>
<input
type="text"
value="${first(this.instance?.tokenExpiry, "minutes=30")}"
type="number"
value="${first(this.instance?.tokenExpiry, 30)}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${msg("Time the token sent is valid.")}
${msg("Time in minutes the token sent is valid.")}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Subject")}

View File

@ -1,9 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import {
containsNonFieldErrors,
parseAPIResponseError,
pluckErrorDetail,
} from "@goauthentik/common/errors/network";
import { parseAPIError } from "@goauthentik/common/errors";
import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror";
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
@ -21,7 +17,14 @@ import { map } from "lit/directives/map.js";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import { Prompt, PromptChallenge, PromptTypeEnum, StagesApi } from "@goauthentik/api";
import {
Prompt,
PromptChallenge,
PromptTypeEnum,
ResponseError,
StagesApi,
ValidationError,
} from "@goauthentik/api";
class PreviewStageHost implements StageHost {
challenge = undefined;
@ -75,22 +78,15 @@ export class PromptForm extends ModelForm<Prompt, string> {
return;
}
}
return new StagesApi(DEFAULT_CONFIG)
.stagesPromptPromptsPreviewCreate({
try {
this.preview = await new StagesApi(DEFAULT_CONFIG).stagesPromptPromptsPreviewCreate({
promptRequest: prompt,
})
.then((nextPreview) => {
this.preview = nextPreview;
this.previewError = undefined;
})
.catch(async (error) => {
const parsedError = await parseAPIResponseError(error);
this.previewError = containsNonFieldErrors(parsedError)
? error.nonFieldErrors
: [pluckErrorDetail(parsedError, msg("Failed to preview prompt"))];
});
this.previewError = undefined;
} catch (exc) {
const errorMessage = parseAPIError(exc as ResponseError);
this.previewError = (errorMessage as ValidationError).nonFieldErrors;
}
}
getSuccessMessage(): string {

View File

@ -1,6 +1,6 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { deviceTypeName } from "@goauthentik/common/labels";
import { SentryIgnoredError } from "@goauthentik/common/sentry";
import { getRelativeTime } from "@goauthentik/common/utils";
import "@goauthentik/elements/forms/DeleteBulkForm";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";

36
web/src/common/errors.ts Normal file
View File

@ -0,0 +1,36 @@
import {
GenericError,
GenericErrorFromJSON,
ResponseError,
ValidationError,
ValidationErrorFromJSON,
} from "@goauthentik/api";
export class SentryIgnoredError extends Error {}
export class NotFoundError extends Error {}
export class RequestError extends Error {}
export type APIErrorTypes = ValidationError | GenericError;
export const HTTP_BAD_REQUEST = 400;
export const HTTP_INTERNAL_SERVICE_ERROR = 500;
export async function parseAPIError(error: Error): Promise<APIErrorTypes> {
if (!(error instanceof ResponseError)) {
return error;
}
if (
error.response.status < HTTP_BAD_REQUEST ||
error.response.status >= HTTP_INTERNAL_SERVICE_ERROR
) {
return error;
}
const body = await error.response.json();
if (error.response.status === 400) {
return ValidationErrorFromJSON(body);
}
if (error.response.status === 403) {
return GenericErrorFromJSON(body);
}
return body;
}

View File

@ -1,194 +0,0 @@
import {
GenericError,
GenericErrorFromJSON,
ResponseError,
ValidationError,
ValidationErrorFromJSON,
} from "@goauthentik/api";
//#region HTTP
/**
* Common HTTP status names used in the API and their corresponding codes.
*/
export const HTTPStatusCode = {
BadRequest: 400,
Forbidden: 403,
InternalServiceError: 500,
} as const satisfies Record<string, number>;
export type HTTPStatusCode = (typeof HTTPStatusCode)[keyof typeof HTTPStatusCode];
export type HTTPErrorJSONTransformer<T = unknown> = (json: T) => APIError;
export const HTTPStatusCodeTransformer: Record<number, HTTPErrorJSONTransformer> = {
[HTTPStatusCode.BadRequest]: ValidationErrorFromJSON,
[HTTPStatusCode.Forbidden]: GenericErrorFromJSON,
} as const;
/**
* Type guard to check if a response contains a JSON body.
*
* This is useful to guard against parsing errors when attempting to read the response body.
*/
export function isJSONResponse(response: Response): boolean {
return Boolean(response.headers.get("content-type")?.includes("application/json"));
}
//#endregion
//#region API
/**
* An API response error, typically derived from a {@linkcode Response} body.
*
* @see {@linkcode parseAPIResponseError}
*/
export type APIError = ValidationError | GenericError;
/**
* Given an error-like object, attempts to normalize it into a {@linkcode GenericError}
* suitable for display to the user.
*/
export function createSyntheticGenericError(detail?: string): GenericError {
const syntheticGenericError: GenericError = {
detail: detail || ResponseErrorMessages[HTTPStatusCode.InternalServiceError].reason,
};
return syntheticGenericError;
}
/**
* An error that contains a native response object.
*
* @see {@linkcode isResponseErrorLike} to determine if an error contains a response object.
*/
export type APIErrorWithResponse = Pick<ResponseError, "response" | "message">;
/**
* Type guard to check if an error contains a HTTP {@linkcode Response} object.
*
* @see {@linkcode parseAPIError} to parse the response body into a {@linkcode APIError}.
*/
export function isResponseErrorLike(errorLike: unknown): errorLike is APIErrorWithResponse {
if (!errorLike || typeof errorLike !== "object") return false;
return "response" in errorLike && errorLike.response instanceof Response;
}
/**
* Type guard to check if an error contains non-field errors.
*
* This is a reasonable heuristic to determine if an error is a {@linkcode ValidationError}.
*
* @see {@linkcode parseAPIError} to parse the response body into a {@linkcode APIError}.
*/
export function containsNonFieldErrors(error: APIError): error is ValidationError {
return "non_field_errors" in error;
}
/**
* A descriptor to provide a human readable error message for a given HTTP status code.
*
* @see {@linkcode ResponseErrorMessages} for a list of fallback error messages.
*/
interface ResponseErrorDescriptor {
headline: string;
reason: string;
}
/**
* Fallback error messages for HTTP status codes used when a more specific error message is not available in the response.
*/
export const ResponseErrorMessages: Record<number, ResponseErrorDescriptor> = {
[HTTPStatusCode.BadRequest]: {
headline: "Bad request",
reason: "The server did not understand the request",
},
[HTTPStatusCode.InternalServiceError]: {
headline: "Internal server error",
reason: "An unexpected error occurred",
},
} as const;
/**
* Composes a human readable error message from a {@linkcode ResponseErrorDescriptor}.
*
* Note that this is kept separate from localization to lower the complexity of the error handling code.
*/
export function composeResponseErrorDescriptor(descriptor: ResponseErrorDescriptor): string {
return `${descriptor.headline}: ${descriptor.reason}`;
}
/**
* Attempts to pluck a human readable error message from a {@linkcode ValidationError}.
*/
export function pluckErrorDetail(validationError: ValidationError, fallback?: string): string;
/**
* Attempts to pluck a human readable error message from a {@linkcode GenericError}.
*/
export function pluckErrorDetail(genericError: GenericError, fallback?: string): string;
/**
* Attempts to pluck a human readable error message from an `Error` object.
*/
export function pluckErrorDetail(error: Error, fallback?: string): string;
/**
* Attempts to pluck a human readable error message from an error-like object.
*
* Prioritizes the `detail` key, then the `message` key.
*
*/
export function pluckErrorDetail(errorLike: unknown, fallback?: string): string;
export function pluckErrorDetail(errorLike: unknown, fallback?: string): string {
fallback ||= composeResponseErrorDescriptor(
ResponseErrorMessages[HTTPStatusCode.InternalServiceError],
);
if (!errorLike || typeof errorLike !== "object") {
return fallback;
}
if ("detail" in errorLike && typeof errorLike.detail === "string") {
return errorLike.detail;
}
if ("message" in errorLike && typeof errorLike.message === "string") {
return errorLike.message;
}
return fallback;
}
/**
* Given API error, parses the response body and transforms it into a {@linkcode APIError}.
*/
export async function parseAPIResponseError<T extends APIError = APIError>(
error: unknown,
): Promise<T> {
if (!isResponseErrorLike(error)) {
const message = error instanceof Error ? error.message : String(error);
return createSyntheticGenericError(message) as T;
}
const { response, message } = error;
if (!isJSONResponse(response)) {
return createSyntheticGenericError(message || response.statusText) as T;
}
return response
.json()
.then((body) => {
const transformer = HTTPStatusCodeTransformer[response.status];
const transformedBody = transformer ? transformer(body) : body;
return transformedBody as unknown as T;
})
.catch((transformerError) => {
console.error("Failed to parse response error body", transformerError);
return createSyntheticGenericError(message || response.statusText) as T;
});
}
//#endregion

View File

@ -8,10 +8,13 @@ export interface EventUser {
is_anonymous?: boolean;
}
export interface EventGeo {
city?: string;
country?: string;
continent?: string;
export interface EventContext {
[key: string]: EventContext | EventModel | string | number | string[];
}
export interface EventWithContext extends Event {
user: EventUser;
context: EventContext;
}
export interface EventModel {
@ -25,13 +28,3 @@ export interface EventRequest {
path: string;
method: string;
}
export interface EventContext {
[key: string]: EventContext | EventModel | EventGeo | string | number | string[] | undefined;
geo?: EventGeo;
}
export interface EventWithContext extends Event {
user: EventUser;
context: EventContext;
}

View File

@ -1,5 +1,5 @@
import { VERSION } from "@goauthentik/common/constants";
import { SentryIgnoredError } from "@goauthentik/common/sentry";
import { SentryIgnoredError } from "@goauthentik/common/errors";
export interface PlexPinResponse {
// Only has the fields we care about

View File

@ -1,5 +1,6 @@
import { config } from "@goauthentik/common/api/config";
import { VERSION } from "@goauthentik/common/constants";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { me } from "@goauthentik/common/users";
import {
ErrorEvent,
@ -15,85 +16,69 @@ import { CapabilitiesEnum, Config, ResponseError } from "@goauthentik/api";
export const TAG_SENTRY_COMPONENT = "authentik.component";
export const TAG_SENTRY_CAPABILITIES = "authentik.capabilities";
/**
* A generic error that can be thrown without triggering Sentry's reporting.
*/
export class SentryIgnoredError extends Error {}
/**
* Configure Sentry with the given configuration.
*
* @param canSendPII Whether the user can send personally identifiable information.
*/
export async function configureSentry(canSendPII = false): Promise<Config> {
export async function configureSentry(canDoPpi = false): Promise<Config> {
const cfg = await config();
if (!cfg.errorReporting.enabled) return cfg;
init({
dsn: cfg.errorReporting.sentryDsn,
ignoreErrors: [
/network/gi,
/fetch/gi,
/module/gi,
// Error on edge on ios,
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
/instantSearchSDKJSBridgeClearHighlight/gi,
// Seems to be an issue in Safari and Firefox
/MutationObserver.observe/gi,
/NS_ERROR_FAILURE/gi,
],
release: `authentik@${VERSION}`,
integrations: [
browserTracingIntegration({
shouldCreateSpanForRequest: (url: string) => {
return url.startsWith(window.location.host);
},
}),
],
tracesSampleRate: cfg.errorReporting.tracesSampleRate,
environment: cfg.errorReporting.environment,
beforeSend: (
event: ErrorEvent,
hint: EventHint,
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
if (!hint) {
if (cfg.errorReporting.enabled) {
init({
dsn: cfg.errorReporting.sentryDsn,
ignoreErrors: [
/network/gi,
/fetch/gi,
/module/gi,
// Error on edge on ios,
// https://stackoverflow.com/questions/69261499/what-is-instantsearchsdkjsbridgeclearhighlight
/instantSearchSDKJSBridgeClearHighlight/gi,
// Seems to be an issue in Safari and Firefox
/MutationObserver.observe/gi,
/NS_ERROR_FAILURE/gi,
],
release: `authentik@${VERSION}`,
integrations: [
browserTracingIntegration({
shouldCreateSpanForRequest: (url: string) => {
return url.startsWith(window.location.host);
},
}),
],
tracesSampleRate: cfg.errorReporting.tracesSampleRate,
environment: cfg.errorReporting.environment,
beforeSend: (
event: ErrorEvent,
hint: EventHint,
): ErrorEvent | PromiseLike<ErrorEvent | null> | null => {
if (!hint) {
return event;
}
if (hint.originalException instanceof SentryIgnoredError) {
return null;
}
if (
hint.originalException instanceof ResponseError ||
hint.originalException instanceof DOMException
) {
return null;
}
return event;
}
if (hint.originalException instanceof SentryIgnoredError) {
return null;
}
if (
hint.originalException instanceof ResponseError ||
hint.originalException instanceof DOMException
) {
return null;
}
return event;
},
});
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
if (window.location.pathname.includes("if/")) {
setTag(TAG_SENTRY_COMPONENT, `web/${currentInterface()}`);
}
if (cfg.capabilities.includes(CapabilitiesEnum.CanDebug)) {
const Spotlight = await import("@spotlightjs/spotlight");
Spotlight.init({ injectImmediately: true });
}
if (cfg.errorReporting.sendPii && canSendPII) {
await me().then((user) => {
setUser({ email: user.user.email });
console.debug("authentik/config: Sentry with PII enabled.");
},
});
} else {
console.debug("authentik/config: Sentry enabled.");
}
setTag(TAG_SENTRY_CAPABILITIES, cfg.capabilities.join(","));
if (window.location.pathname.includes("if/")) {
setTag(TAG_SENTRY_COMPONENT, `web/${currentInterface()}`);
}
if (cfg.capabilities.includes(CapabilitiesEnum.CanDebug)) {
const Spotlight = await import("@spotlightjs/spotlight");
Spotlight.init({ injectImmediately: true });
}
if (cfg.errorReporting.sendPii && canDoPpi) {
me().then((user) => {
setUser({ email: user.user.email });
console.debug("authentik/config: Sentry with PII enabled.");
});
} else {
console.debug("authentik/config: Sentry enabled.");
}
}
return cfg;
}

View File

@ -56,19 +56,6 @@ html > form > input {
vertical-align: middle;
}
.pf-c-card__title {
.pf-icon:first-child,
.fa:first-child {
margin-inline-end: var(--pf-global--spacer--sm);
}
}
a > .fas.fa-external-link-alt {
margin-inline-start: var(--pf-global--spacer--xs);
font-size: var(--pf-global--FontSize--sm);
transform: translateY(-0.1em);
}
.pf-c-form-control {
--pf-c-form-control--m-caps-lock--BackgroundUrl: url("data:image/svg+xml;charset=utf8,%3Csvg fill='%23aaabac' viewBox='0 0 56 56' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M 20.7812 37.6211 L 35.2421 37.6211 C 38.5233 37.6211 40.2577 35.6992 40.2577 32.6055 L 40.2577 28.4570 L 49.1404 28.4570 C 51.0859 28.4570 52.6329 27.3086 52.6329 25.5039 C 52.6329 24.4024 52.0703 23.5351 51.0158 22.6211 L 30.9062 4.8789 C 29.9452 4.0351 29.0546 3.4727 27.9999 3.4727 C 26.9687 3.4727 26.0780 4.0351 25.1171 4.8789 L 4.9843 22.6445 C 3.8828 23.6055 3.3671 24.4024 3.3671 25.5039 C 3.3671 27.3086 4.9140 28.4570 6.8828 28.4570 L 15.7421 28.4570 L 15.7421 32.6055 C 15.7421 35.6992 17.4999 37.6211 20.7812 37.6211 Z M 21.1562 34.0820 C 20.2655 34.0820 19.6562 33.4961 19.6562 32.6055 L 19.6562 25.7149 C 19.6562 25.1524 19.4452 24.9180 18.8828 24.9180 L 8.6640 24.9180 C 8.4999 24.9180 8.4296 24.8476 8.4296 24.7305 C 8.4296 24.6367 8.4530 24.5430 8.5702 24.4492 L 27.5077 7.9961 C 27.7187 7.8086 27.8359 7.7383 27.9999 7.7383 C 28.1640 7.7383 28.3046 7.8086 28.4921 7.9961 L 47.4532 24.4492 C 47.5703 24.5430 47.5939 24.6367 47.5939 24.7305 C 47.5939 24.8476 47.4998 24.9180 47.3356 24.9180 L 37.1406 24.9180 C 36.5780 24.9180 36.3671 25.1524 36.3671 25.7149 L 36.3671 32.6055 C 36.3671 33.4727 35.7109 34.0820 34.8671 34.0820 Z M 19.7733 52.5273 L 36.0624 52.5273 C 38.7577 52.5273 40.3046 51.0273 40.3046 48.3086 L 40.3046 44.9336 C 40.3046 42.2148 38.7577 40.6680 36.0624 40.6680 L 19.7733 40.6680 C 17.0546 40.6680 15.5077 42.2383 15.5077 44.9336 L 15.5077 48.3086 C 15.5077 51.0039 17.0546 52.5273 19.7733 52.5273 Z M 20.3124 49.2227 C 19.4921 49.2227 19.0468 48.8008 19.0468 47.9805 L 19.0468 45.2617 C 19.0468 44.4414 19.4921 43.9727 20.3124 43.9727 L 35.5233 43.9727 C 36.3202 43.9727 36.7655 44.4414 36.7655 45.2617 L 36.7655 47.9805 C 36.7655 48.8008 36.3202 49.2227 35.5233 49.2227 Z'/%3E%3C/svg%3E");
}

View File

@ -4,7 +4,6 @@
--pf-c-page__main-section--m-light--BackgroundColor: var(--ak-dark-background-darker);
--pf-global--link--Color: var(--ak-dark-foreground-link) !important;
--pf-global--BorderColor--100: var(--ak-dark-background-lighter) !important;
--pf-c-table--m-striped__tr--BackgroundColor: var(--pf-global--BackgroundColor--dark-300);
}
body {
background-color: var(--ak-dark-background) !important;

View File

@ -1,4 +1,4 @@
import { SentryIgnoredError } from "@goauthentik/common/sentry";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { CSSResult, css } from "lit";

View File

@ -1,4 +1,4 @@
import { EventUser, formatGeoEvent } from "@goauthentik/admin/events/utils";
import { EventGeo, EventUser } from "@goauthentik/admin/events/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EventWithContext } from "@goauthentik/common/events";
import { actionToLabel } from "@goauthentik/common/labels";
@ -76,7 +76,7 @@ export class ObjectChangelog extends Table<Event> {
<small>${item.created.toLocaleString()}</small>`,
html`<div>${item.clientIp || msg("-")}</div>
<small>${formatGeoEvent(item)}</small>`,
<small>${EventGeo(item)}</small>`,
];
}

View File

@ -1,5 +1,4 @@
import { AKElement } from "@goauthentik/elements/Base";
import { SlottedTemplateResult } from "@goauthentik/elements/types";
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators.js";
@ -97,30 +96,24 @@ export class AggregateCard extends AKElement implements IAggregateCard {
.pf-c-card__footer {
padding-bottom: 0;
}
.pf-c-card {
--pf-c-card__title--FontSize: var(--pf-global--FontSize--xs);
--pf-c-card--child--PaddingLeft: var(--pf-global--spacer--md);
--pf-c-card--child--PaddingRight: var(--pf-global--spacer--md);
}
`,
]);
}
renderInner(): SlottedTemplateResult {
renderInner(): TemplateResult {
return html`<slot></slot>`;
}
renderHeaderLink() {
if (!this.headerLink) return nothing;
return html`<a href="${this.headerLink}">
<i class="fa fa-link"></i>
</a>`;
renderHeaderLink(): TemplateResult {
return html`${this.headerLink
? html`<a href="${this.headerLink}">
<i class="fa fa-link"> </i>
</a>`
: ""}`;
}
renderHeader(): SlottedTemplateResult {
return this.header ? html`${this.header}` : nothing;
renderHeader(): TemplateResult {
return html`${this.header ? this.header : ""}`;
}
render(): TemplateResult {

View File

@ -1,9 +1,4 @@
import { EVENT_REFRESH, EVENT_THEME_CHANGE } from "@goauthentik/common/constants";
import {
APIError,
parseAPIResponseError,
pluckErrorDetail,
} from "@goauthentik/common/errors/network";
import { getRelativeTime } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/EmptyState";
@ -28,7 +23,7 @@ import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
import { property, state } from "lit/decorators.js";
import { UiThemeEnum } from "@goauthentik/api";
import { ResponseError, UiThemeEnum } from "@goauthentik/api";
Chart.register(Legend, Tooltip);
Chart.register(LineController, BarController, DoughnutController);
@ -72,7 +67,7 @@ export abstract class AKChart<T> extends AKElement {
chart?: Chart;
@state()
error?: APIError;
error?: ResponseError;
@property()
centerText?: string;
@ -84,9 +79,6 @@ export abstract class AKChart<T> extends AKElement {
css`
.container {
height: 100%;
width: 100%;
aspect-ratio: 1 / 1;
display: flex;
justify-content: center;
align-items: center;
@ -100,7 +92,6 @@ export abstract class AKChart<T> extends AKElement {
width: 100px;
height: 100px;
z-index: 1;
cursor: crosshair;
}
`,
];
@ -145,24 +136,19 @@ export abstract class AKChart<T> extends AKElement {
this.apiRequest()
.then((r) => {
const canvas = this.shadowRoot?.querySelector<HTMLCanvasElement>("canvas");
if (!canvas) {
console.warn("Failed to get canvas element");
return;
}
const ctx = canvas.getContext("2d");
if (!ctx) {
console.warn("failed to get 2d context");
return;
}
this.chart = this.configureChart(r, ctx);
})
.catch(async (error) => {
const parsedError = await parseAPIResponseError(error);
this.error = parsedError;
.catch((exc: ResponseError) => {
this.error = exc;
});
}
@ -228,7 +214,7 @@ export abstract class AKChart<T> extends AKElement {
${this.error
? html`
<ak-empty-state header="${msg("Failed to fetch data.")}" icon="fa-times">
<p slot="body">${pluckErrorDetail(this.error)}</p>
<p slot="body">${this.error.response.statusText}</p>
</ak-empty-state>
`
: html`${this.chart

View File

@ -1,5 +1,5 @@
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { parseAPIResponseError } from "@goauthentik/common/errors/network";
import { parseAPIError } from "@goauthentik/common/errors";
import { MessageLevel } from "@goauthentik/common/messages";
import { camelToSnake, convertToSlug, dateToUTC } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base";
@ -20,7 +20,13 @@ import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-gro
import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { instanceOfValidationError } from "@goauthentik/api";
import { ResponseError, ValidationError, instanceOfValidationError } from "@goauthentik/api";
export class APIError extends Error {
constructor(public response: ValidationError) {
super();
}
}
export interface KeyUnknown {
[key: string]: unknown;
@ -279,82 +285,73 @@ export abstract class Form<T> extends AKElement {
* field-levels errors to the fields, and send the rest of them to the Notifications.
*
*/
async submit(event: Event): Promise<unknown | undefined> {
event.preventDefault();
const data = this.serializeForm();
if (!data) return;
return this.send(data)
.then((response) => {
showMessage({
level: MessageLevel.success,
message: this.getSuccessMessage(),
});
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
return response;
})
.catch(async (error) => {
if (error instanceof PreventFormSubmit && error.element) {
error.element.errorMessages = [error.message];
error.element.invalid = true;
}
let errorMessage = error.response.statusText;
const parsedError = await parseAPIResponseError(error);
if (instanceOfValidationError(parsedError)) {
async submit(ev: Event): Promise<unknown | undefined> {
ev.preventDefault();
try {
const data = this.serializeForm();
if (!data) {
return;
}
const response = await this.send(data);
showMessage({
level: MessageLevel.success,
message: this.getSuccessMessage(),
});
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
return response;
} catch (ex) {
if (ex instanceof ResponseError) {
let errorMessage = ex.response.statusText;
const error = await parseAPIError(ex);
if (instanceOfValidationError(error)) {
// assign all input-related errors to their elements
const elements =
this.shadowRoot?.querySelectorAll<HorizontalFormElement>(
"ak-form-element-horizontal",
) || [];
elements.forEach((element) => {
element.requestUpdate();
const elementName = element.name;
if (!elementName) return;
const snakeProperty = camelToSnake(elementName);
if (snakeProperty in parsedError) {
element.errorMessages = parsedError[snakeProperty];
if (!elementName) {
return;
}
if (camelToSnake(elementName) in error) {
element.errorMessages = (error as ValidationError)[
camelToSnake(elementName)
];
element.invalid = true;
} else {
element.errorMessages = [];
element.invalid = false;
}
});
if (parsedError.nonFieldErrors) {
this.nonFieldErrors = parsedError.nonFieldErrors;
if ((error as ValidationError).nonFieldErrors) {
this.nonFieldErrors = (error as ValidationError).nonFieldErrors;
}
errorMessage = msg("Invalid update request.");
// Only change the message when we have `detail`.
// Everything else is handled in the form.
if ("detail" in parsedError) {
errorMessage = parsedError.detail;
if ("detail" in (error as ValidationError)) {
errorMessage = (error as ValidationError).detail;
}
}
showMessage({
message: errorMessage,
level: MessageLevel.error,
});
// Rethrow the error so the form doesn't close.
throw error;
});
}
if (ex instanceof PreventFormSubmit && ex.element) {
ex.element.errorMessages = [ex.message];
ex.element.invalid = true;
}
// rethrow the error so the form doesn't close
throw ex;
}
}
renderFormWrapper(): TemplateResult {

View File

@ -1,9 +1,5 @@
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import {
APIError,
parseAPIResponseError,
pluckErrorDetail,
} from "@goauthentik/common/errors/network";
import { APIErrorTypes, parseAPIError } from "@goauthentik/common/errors";
import { groupBy } from "@goauthentik/common/utils";
import { AkControlElement } from "@goauthentik/elements/AkControlElement.js";
import { PreventFormSubmit } from "@goauthentik/elements/forms/helpers";
@ -17,6 +13,8 @@ import { ifDefined } from "lit/directives/if-defined.js";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { ResponseError } from "@goauthentik/api";
import "./ak-search-select-loading-indicator.js";
import "./ak-search-select-view.js";
import { SearchSelectView } from "./ak-search-select-view.js";
@ -101,7 +99,7 @@ export class SearchSelectBase<T> extends AkControlElement<string> implements ISe
isFetchingData = false;
@state()
error?: APIError;
error?: APIErrorTypes;
public toForm(): string {
if (!this.objects) {
@ -130,26 +128,23 @@ export class SearchSelectBase<T> extends AkControlElement<string> implements ISe
}
this.isFetchingData = true;
this.dispatchEvent(new Event("loading"));
return this.fetchObjects(this.query)
.then((nextObjects) => {
nextObjects.forEach((obj) => {
if (this.selected && this.selected(obj, nextObjects || [])) {
.then((objects) => {
objects.forEach((obj) => {
if (this.selected && this.selected(obj, objects || [])) {
this.selectedObject = obj;
this.dispatchChangeEvent(this.selectedObject);
}
});
this.objects = nextObjects;
this.objects = objects;
this.isFetchingData = false;
})
.catch(async (error) => {
.catch((exc: ResponseError) => {
this.isFetchingData = false;
this.objects = undefined;
const parsedError = await parseAPIResponseError(error);
this.error = parsedError;
parseAPIError(exc).then((err) => {
this.error = err;
});
});
}
@ -238,9 +233,7 @@ export class SearchSelectBase<T> extends AkControlElement<string> implements ISe
public override render() {
if (this.error) {
return html`<em
>${msg("Failed to fetch objects: ")} ${pluckErrorDetail(this.error)}</em
>`;
return html`<em>${msg("Failed to fetch objects: ")} ${this.error.detail}</em>`;
}
// `this.objects` is both a container and a sigil; if it is in the `undefined` state, it's a

View File

@ -3,7 +3,7 @@ import {
EVENT_WS_MESSAGE,
WS_MSG_TYPE_MESSAGE,
} from "@goauthentik/common/constants";
import { SentryIgnoredError } from "@goauthentik/common/sentry";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { WSMessage } from "@goauthentik/common/ws";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/messages/Message";

View File

@ -1,9 +1,5 @@
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import {
APIError,
parseAPIResponseError,
pluckErrorDetail,
} from "@goauthentik/common/errors/network";
import { APIErrorTypes, parseAPIError } from "@goauthentik/common/errors";
import { uiConfig } from "@goauthentik/common/ui/config";
import { groupBy } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base";
@ -14,10 +10,9 @@ import "@goauthentik/elements/chips/ChipGroup";
import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch";
import "@goauthentik/elements/table/TablePagination";
import "@goauthentik/elements/table/TableSearch";
import { SlottedTemplateResult } from "@goauthentik/elements/types";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
import { CSSResult, TemplateResult, css, html } from "lit";
import { property, state } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { ifDefined } from "lit/directives/if-defined.js";
@ -31,7 +26,7 @@ import PFToolbar from "@patternfly/patternfly/components/Toolbar/toolbar.css";
import PFBullseye from "@patternfly/patternfly/layouts/Bullseye/bullseye.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { Pagination } from "@goauthentik/api";
import { Pagination, ResponseError } from "@goauthentik/api";
export interface TableLike {
order?: string;
@ -103,7 +98,7 @@ export interface PaginatedResponse<T> {
export abstract class Table<T> extends AKElement implements TableLike {
abstract apiEndpoint(): Promise<PaginatedResponse<T>>;
abstract columns(): TableColumn[];
abstract row(item: T): SlottedTemplateResult[];
abstract row(item: T): TemplateResult[];
private isLoading = false;
@ -111,12 +106,12 @@ export abstract class Table<T> extends AKElement implements TableLike {
return false;
}
renderExpanded(_item: T): SlottedTemplateResult {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
renderExpanded(item: T): TemplateResult {
if (this.expandable) {
throw new Error("Expandable is enabled but renderExpanded is not overridden!");
}
return nothing;
return html``;
}
@property({ attribute: false })
@ -125,11 +120,10 @@ export abstract class Table<T> extends AKElement implements TableLike {
@property({ type: Number })
page = getURLParam("tablePage", 1);
/**
* Set if your `selectedElements` use of the selection box is to enable bulk-delete,
* so that stale data is cleared out when the API returns a new list minus the deleted entries.
/** @prop
*
* @prop
* Set if your `selectedElements` use of the selection box is to enable bulk-delete, so that
* stale data is cleared out when the API returns a new list minus the deleted entries.
*/
@property({ attribute: "clear-on-refresh", type: Boolean, reflect: true })
clearOnRefresh = false;
@ -168,7 +162,7 @@ export abstract class Table<T> extends AKElement implements TableLike {
expandedElements: T[] = [];
@state()
error?: APIError;
error?: APIErrorTypes;
static get styles(): CSSResult[] {
return [
@ -193,12 +187,6 @@ export abstract class Table<T> extends AKElement implements TableLike {
.pf-c-toolbar__item .pf-c-input-group {
padding: 0 var(--pf-global--spacer--sm);
}
.pf-c-table {
--pf-c-table--m-striped__tr--BackgroundColor: var(
--pf-global--BackgroundColor--dark-300
);
}
`,
];
}
@ -225,74 +213,64 @@ export abstract class Table<T> extends AKElement implements TableLike {
};
}
public groupBy(items: T[]): [SlottedTemplateResult, T[]][] {
public groupBy(items: T[]): [string, T[]][] {
return groupBy(items, () => {
return "";
});
}
public async fetch(): Promise<void> {
if (this.isLoading) return;
if (this.isLoading) {
return;
}
this.isLoading = true;
return this.apiEndpoint()
.then((data) => {
this.data = data;
this.error = undefined;
this.page = this.data.pagination.current;
const newExpanded: T[] = [];
this.data.results.forEach((res) => {
const jsonRes = JSON.stringify(res);
// So because we're dealing with complex objects here, we can't use indexOf
// since it checks strict equality, and we also can't easily check in findIndex()
// Instead we default to comparing the JSON of both objects, which is quite slow
// Hence we check if the objects have `pk` attributes set (as most models do)
// and compare that instead, which will be much faster.
let comp = (item: T) => {
return JSON.stringify(item) === jsonRes;
try {
this.data = await this.apiEndpoint();
this.error = undefined;
this.page = this.data.pagination.current;
const newExpanded: T[] = [];
this.data.results.forEach((res) => {
const jsonRes = JSON.stringify(res);
// So because we're dealing with complex objects here, we can't use indexOf
// since it checks strict equality, and we also can't easily check in findIndex()
// Instead we default to comparing the JSON of both objects, which is quite slow
// Hence we check if the objects have `pk` attributes set (as most models do)
// and compare that instead, which will be much faster.
let comp = (item: T) => {
return JSON.stringify(item) === jsonRes;
};
if (Object.hasOwn(res as object, "pk")) {
comp = (item: T) => {
return (
(item as unknown as { pk: string | number }).pk ===
(res as unknown as { pk: string | number }).pk
);
};
if (Object.hasOwn(res as object, "pk")) {
comp = (item: T) => {
return (
(item as unknown as { pk: string | number }).pk ===
(res as unknown as { pk: string | number }).pk
);
};
}
const expandedIndex = this.expandedElements.findIndex(comp);
if (expandedIndex > -1) {
newExpanded.push(res);
}
});
this.expandedElements = newExpanded;
})
.catch(async (error) => {
this.error = await parseAPIResponseError(error);
})
.finally(() => {
this.isLoading = false;
this.requestUpdate();
}
const expandedIndex = this.expandedElements.findIndex(comp);
if (expandedIndex > -1) {
newExpanded.push(res);
}
});
this.isLoading = false;
this.expandedElements = newExpanded;
} catch (ex) {
this.isLoading = false;
this.error = await parseAPIError(ex as Error);
}
}
private renderLoading(): TemplateResult {
return html`<tr role="row">
<td role="cell" colspan="25">
<div class="pf-l-bullseye">
<ak-empty-state loading header=${msg("Loading")}></ak-empty-state>
<ak-empty-state loading header=${msg("Loading")}> </ak-empty-state>
</div>
</td>
</tr>`;
}
renderEmpty(inner?: SlottedTemplateResult): TemplateResult {
renderEmpty(inner?: TemplateResult): TemplateResult {
return html`<tbody role="rowgroup">
<tr role="row">
<td role="cell" colspan="8">
@ -307,16 +285,18 @@ export abstract class Table<T> extends AKElement implements TableLike {
</tbody>`;
}
renderObjectCreate(): SlottedTemplateResult {
return nothing;
renderObjectCreate(): TemplateResult {
return html``;
}
renderError(): SlottedTemplateResult {
if (!this.error) return nothing;
return html`<ak-empty-state header="${msg("Failed to fetch objects.")}" icon="fa-ban">
<div slot="body">${pluckErrorDetail(this.error)}</div>
</ak-empty-state>`;
renderError(): TemplateResult {
return this.error
? html`<ak-empty-state header="${msg("Failed to fetch objects.")}" icon="fa-times">
${this.error instanceof ResponseError
? html` <div slot="body">${this.error.message}</div> `
: html`<div slot="body">${this.error.detail}</div>`}
</ak-empty-state>`
: html``;
}
private renderRows(): TemplateResult[] | undefined {
@ -424,17 +404,15 @@ export abstract class Table<T> extends AKElement implements TableLike {
}
: itemSelectHandler}
>
${this.checkbox ? renderCheckbox() : nothing}
${this.expandable ? renderExpansion() : nothing}
${this.row(item).map((column, columnIndex) => {
return html`<td data-column-index="${columnIndex}" role="cell">
${column}
</td>`;
${this.checkbox ? renderCheckbox() : html``}
${this.expandable ? renderExpansion() : html``}
${this.row(item).map((col) => {
return html`<td role="cell">${col}</td>`;
})}
</tr>
<tr class="pf-c-table__expandable-row ${classMap(expandedClass)}" role="row">
<td></td>
${this.expandedElements.includes(item) ? this.renderExpanded(item) : nothing}
${this.expandedElements.includes(item) ? this.renderExpanded(item) : html``}
</tr>
</tbody>`;
});
@ -452,12 +430,12 @@ export abstract class Table<T> extends AKElement implements TableLike {
>`;
}
renderToolbarSelected(): SlottedTemplateResult {
return nothing;
renderToolbarSelected(): TemplateResult {
return html``;
}
renderToolbarAfter(): SlottedTemplateResult {
return nothing;
renderToolbarAfter(): TemplateResult {
return html``;
}
renderSearch(): TemplateResult {
@ -526,9 +504,9 @@ export abstract class Table<T> extends AKElement implements TableLike {
* chip-based subtable at the top that shows the list of selected entries. Long text result in
* ellipsized chips, which is sub-optimal.
*/
renderSelectedChip(_item: T): SlottedTemplateResult {
renderSelectedChip(_item: T): TemplateResult {
// Override this for chip-based displays
return nothing;
return html``;
}
get needChipGroup() {
@ -569,7 +547,7 @@ export abstract class Table<T> extends AKElement implements TableLike {
${this.renderToolbarContainer()}
<table class="pf-c-table pf-m-compact pf-m-grid-md pf-m-expandable">
<thead>
<tr role="row" class="pf-c-table__header-row">
<tr role="row">
${this.checkbox ? this.renderAllOnThisPageCheckbox() : html``}
${this.expandable ? html`<td role="cell"></td>` : html``}
${this.columns().map((col) => col.render(this))}

View File

@ -70,57 +70,52 @@ export class AuthenticatorValidateStageWebCode extends BaseDeviceStage<
return html`<ak-empty-state loading> </ak-empty-state>`;
}
return html`<div class="pf-c-login__main-body">
<form
class="pf-c-form"
@submit=${(e: Event) => {
this.submitForm(e);
}}
<form
class="pf-c-form"
@submit=${(e: Event) => {
this.submitForm(e);
}}
>
${this.renderUserInfo()}
<div class="icon-description">
<i class="fa ${this.deviceIcon()}" aria-hidden="true"></i>
<p>${this.deviceMessage()}</p>
</div>
<ak-form-element
label="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
? msg("Static token")
: msg("Authentication code")}"
required
class="pf-c-form__group"
.errors=${(this.challenge?.responseErrors || {})["code"]}
>
${this.renderUserInfo()}
<div class="icon-description">
<i class="fa ${this.deviceIcon()}" aria-hidden="true"></i>
<p>${this.deviceMessage()}</p>
</div>
<ak-form-element
label="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
? msg("Static token")
: msg("Authentication code")}"
<!-- @ts-ignore -->
<input
type="text"
name="code"
inputmode="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
? "text"
: "numeric"}"
pattern="${this.deviceChallenge?.deviceClass === DeviceClassesEnum.Static
? "[0-9a-zA-Z]*"
: "[0-9]*"}"
placeholder="${msg("Please enter your code")}"
autofocus=""
autocomplete="one-time-code"
class="pf-c-form-control"
value="${PasswordManagerPrefill.totp || ""}"
required
class="pf-c-form__group"
.errors=${(this.challenge?.responseErrors || {})["code"]}
>
<!-- @ts-ignore -->
<input
type="text"
name="code"
inputmode="${this.deviceChallenge?.deviceClass ===
DeviceClassesEnum.Static
? "text"
: "numeric"}"
pattern="${this.deviceChallenge?.deviceClass ===
DeviceClassesEnum.Static
? "[0-9a-zA-Z]*"
: "[0-9]*"}"
placeholder="${msg("Please enter your code")}"
autofocus=""
autocomplete="one-time-code"
class="pf-c-form-control"
value="${PasswordManagerPrefill.totp || ""}"
required
/>
</ak-form-element>
/>
</ak-form-element>
<div class="pf-c-form__group pf-m-action">
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
${msg("Continue")}
</button>
${this.renderReturnToDevicePicker()}
</div>
</form>
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links"></ul>
</footer>`;
<div class="pf-c-form__group pf-m-action">
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
${msg("Continue")}
</button>
${this.renderReturnToDevicePicker()}
</div>
</form>
</div>`;
}
}

View File

@ -165,21 +165,13 @@ class UserInterfacePresentation extends AKElement {
}
return html`<a
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
href="${globalAK().api.base}if/admin/"
slot="extra"
>
${msg("Admin interface")}
</a>
<a
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none-on-md pf-u-display-block"
href="${globalAK().api.base}if/admin/"
slot="extra"
>
${msg("Admin")}
</a>`;
class="pf-c-button pf-m-secondary pf-m-small pf-u-display-none pf-u-display-block-on-md"
href="${globalAK().api.base}if/admin/"
slot="extra"
>
${msg("Admin interface")}
</a>`;
}
render() {
// The `!` in the field definitions above only re-assure typescript and eslint that the
// values *should* be available, not that they *are*. Thus this contract check; it asserts

View File

@ -1,5 +1,5 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/sentry";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";

View File

@ -1,7 +1,7 @@
import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { SentryIgnoredError } from "@goauthentik/common/errors";
import { globalAK } from "@goauthentik/common/global";
import { deviceTypeName } from "@goauthentik/common/labels";
import { SentryIgnoredError } from "@goauthentik/common/sentry";
import { getRelativeTime } from "@goauthentik/common/utils";
import "@goauthentik/elements/buttons/Dropdown";
import "@goauthentik/elements/buttons/ModalButton";

View File

@ -4908,6 +4908,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>Wenn diese Option aktiviert ist, werden die globalen E-Mail Verbindungseinstellungen benutzt und die unten angegebenen Einstellungen ignoriert</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>Ablauf des Tokens</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>Zeit in Minuten wie lange der verschickte Token gültig ist</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9036,33 +9046,6 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -4001,6 +4001,14 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>When enabled, global Email connection settings will be used and connection settings below will be ignored.</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>Token expiry</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>Time in minutes the token sent is valid.</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
<target>Template</target>
@ -7565,33 +7573,6 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -4931,6 +4931,16 @@ no se aprueba cuando una o ambas de las opciones seleccionadas son iguales o sup
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>Cuando se habilita, se utilizará la configuración global de conexión de correo electrónico y se ignorarán las configuraciones de conexión que se indican a continuación</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>Caducidad del token</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>El tiempo en minutos que se envía el token es válido.</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9128,33 +9138,6 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -4999,6 +4999,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>Si activé, les paramètres globaux de connexion courriel seront utilisés et les paramètres de connexion ci-dessous seront ignorés.</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>Expiration du jeton</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>Temps en minutes durant lequel le jeton envoyé est valide.</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9611,63 +9621,21 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit>
<trans-unit id="s34661765d81e7340">
<source>Successfully cleared application cache</source>
<target>Cache d'application vidé avec succès</target>
</trans-unit>
<trans-unit id="s9377ae285bc0157c">
<source>Failed to delete application cache</source>
<target>Impossible de vider le cache d'application</target>
</trans-unit>
<trans-unit id="s754153d9e524ffce">
<source>Clear Application cache</source>
<target>Vider le cache d'application</target>
</trans-unit>
<trans-unit id="sea5092df2c72b064">
<source>Are you sure you want to clear the application cache? This will cause all policies to be re-evaluated on their next usage.</source>
<target>Êtes-vous sûr de vouloir vider le cache des applications ? Cela entraînera la réévaluation de toutes les politiques lors de leur prochaine utilisation.</target>
</trans-unit>
<trans-unit id="s1367dcd6e8749fcd">
<source>No name set</source>
<target>Aucun nom défini</target>
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
<target>Afficher les utilisateurs inactifs</target>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
<target>Erreur inconnue</target>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
<target>Temps durant lequel le jeton envoyé est valide.</target>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
<target>Mode de compatibilité</target>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
<target>Comportement par défaut.</target>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
<target>AWS</target>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
<target>Comportement spécifique pour utilisation avec Amazon Web Services.</target>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
<target>Slack</target>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
<target>Comportement spécifique pour utilisation avec Slack.</target>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
<target>Change le comportement d'authentik en fonction des spécificités d'implémentations des fournisseurs SCIM.</target>
</trans-unit>
</body>
</file>

View File

@ -4998,6 +4998,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>Se abilitato, verranno utilizzate le impostazioni di connessione e -mail globali e le impostazioni di connessione di seguito verranno ignorate.</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>Scadenza token</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>Il tempo in pochi minuti il token inviato è valido.</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9479,33 +9489,6 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -4945,6 +4945,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>활성화하면, 전역 이메일 연결 설정이 사용되며 아래의 연결 설정은 무시됩니다.</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>토큰 유효기간</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>전송한 토큰이 유효한 시간입니다.</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9035,33 +9045,6 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -4955,6 +4955,16 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>Indien ingeschakeld, worden de wereldwijde e-mail verbindingsinstellingen gebruikt en worden de onderstaande verbindingsinstellingen genegeerd.</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>Token-verval</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>Tijd in minuten dat het verzonden token geldig is.</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -8937,33 +8947,6 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -5001,6 +5001,16 @@ Można tu używać tylko zasad, ponieważ dostęp jest sprawdzany przed uwierzyt
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>Po włączeniu będą używane globalne ustawienia połączenia poczty e-mail, a poniższe ustawienia połączenia będą ignorowane.</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>Token wygasa</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>Czas w minutach, w którym wysłany token jest ważny.</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9365,33 +9375,6 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -4968,6 +4968,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>Ŵĥēń ēńàƀĺēď, ĝĺōƀàĺ Ēḿàĩĺ ćōńńēćţĩōń śēţţĩńĝś ŵĩĺĺ ƀē ũśēď àńď ćōńńēćţĩōń śēţţĩńĝś ƀēĺōŵ ŵĩĺĺ ƀē ĩĝńōŕēď.</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>Ţōķēń ēxƥĩŕŷ</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>Ţĩḿē ĩń ḿĩńũţēś ţĥē ţōķēń śēńţ ĩś vàĺĩď.</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9372,31 +9382,4 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body></file></xliff>

View File

@ -5000,6 +5000,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>Если эта функция включена, будут использоваться глобальные настройки подключения к электронной почте, а настройки подключения, указанные ниже, будут игнорироваться.</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>Срок действия токена</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>Время в минутах, в течение которого отправленный токен действителен.</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9398,33 +9408,6 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -4968,6 +4968,16 @@ Belirlenen seçeneklerden biri veya her ikisi de eşiğe eşit veya eşiğin üz
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>Etkinleştirildiğinde, genel E-posta bağlantısı ayarları kullanılır ve aşağıdaki bağlantı ayarları yoksayılır.</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>Belirteç son kullanma tarihi</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>Gönderilen belirtecin dakika cinsinden geçerlilik süresi.</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9428,33 +9438,6 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -3564,6 +3564,12 @@ doesn't pass when either or both of the selected options are equal or above the
<trans-unit id="sae1e1a59d22609c4">
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
</trans-unit>
@ -6170,33 +6176,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -1,4 +1,4 @@
<?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file target-language="zh-Hans" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="s4caed5b7a7e5d89b">
@ -596,9 +596,9 @@
</trans-unit>
<trans-unit id="saa0e2675da69651b">
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
<target>未找到 URL "
<x id="0" equiv-text="${this.url}"/>"。</target>
<source>The URL &quot;<x id="0" equiv-text="${this.url}"/>&quot; was not found.</source>
<target>未找到 URL &quot;
<x id="0" equiv-text="${this.url}"/>&quot;。</target>
</trans-unit>
<trans-unit id="s58cd9c2fe836d9c6">
@ -1715,8 +1715,8 @@
</trans-unit>
<trans-unit id="sa90b7809586c35ce">
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
<target>输入完整 URL、相对路径或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon &quot;fa-test&quot;.</source>
<target>输入完整 URL、相对路径或者使用 'fa://fa-test' 来使用 Font Awesome 图标 &quot;fa-test&quot;。</target>
</trans-unit>
<trans-unit id="s0410779cb47de312">
@ -2864,8 +2864,8 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s76768bebabb7d543">
<source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
<target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
<source>Field which contains members of a group. Note that if using the &quot;memberUid&quot; field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
<target>包含组成员的字段。请注意,如果使用 &quot;memberUid&quot; 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
</trans-unit>
<trans-unit id="s026555347e589f0e">
@ -3783,10 +3783,10 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="sa95a538bfbb86111">
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> &quot;<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</source>
<target>您确定要更新
<x id="0" equiv-text="${this.objectLabel}"/>"
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
<x id="0" equiv-text="${this.objectLabel}"/>&quot;
<x id="1" equiv-text="${this.obj?.name}"/>&quot; 吗?</target>
</trans-unit>
<trans-unit id="sc92d7cfb6ee1fec6">
@ -4857,7 +4857,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="sdf1d8edef27236f0">
<source>A "roaming" authenticator, like a YubiKey</source>
<source>A &quot;roaming&quot; authenticator, like a YubiKey</source>
<target>像 YubiKey 这样的“漫游”身份验证器</target>
</trans-unit>
@ -5000,6 +5000,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>启用后,将使用全局电子邮件连接设置,下面的连接设置将被忽略。</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>令牌过期</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>发出令牌的有效时间(单位为分钟)。</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -5216,7 +5226,7 @@ doesn't pass when either or both of the selected options are equal or above the
</trans-unit>
<trans-unit id="s1608b2f94fa0dbd4">
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
<source>If set to a duration above 0, the user will have the option to choose to &quot;stay signed in&quot;, which will extend their session by the time specified here.</source>
<target>如果设置时长大于 0用户可以选择“保持登录”选项这将使用户的会话延长此处设置的时间。</target>
</trans-unit>
@ -7507,7 +7517,7 @@ Bindings to groups/users are checked against the user of the event.</source>
<target>成功创建用户并添加到组 <x id="0" equiv-text="${this.group.name}"/></target>
</trans-unit>
<trans-unit id="s824e0943a7104668">
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
<source>This user will be added to the group &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot;.</source>
<target>此用户将会被添加到组 &amp;quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&amp;quot;。</target>
</trans-unit>
<trans-unit id="s62e7f6ed7d9cb3ca">
@ -8801,7 +8811,7 @@ Bindings to groups/users are checked against the user of the event.</source>
<target>同步组</target>
</trans-unit>
<trans-unit id="s2d5f69929bb7221d">
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<source><x id="0" equiv-text="${p.name}"/> (&quot;<x id="1" equiv-text="${p.fieldKey}"/>&quot;, of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/>&amp;quot;<x id="1" equiv-text="${p.fieldKey}"/>&amp;quot;,类型为 <x id="2" equiv-text="${p.type}"/></target>
</trans-unit>
<trans-unit id="s25bacc19d98b444e">
@ -9049,8 +9059,8 @@ Bindings to groups/users are checked against the user of the event.</source>
<target>授权流程成功后有效的重定向 URI。还可以在此处为隐式流程指定任何来源。</target>
</trans-unit>
<trans-unit id="s4c49d27de60a532b">
<source>To allow any redirect URI, set the mode to Regex and the value to ".*". Be aware of the possible security implications this can have.</source>
<target>要允许任何重定向 URI请设置模式为正则表达式并将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
<source>To allow any redirect URI, set the mode to Regex and the value to &quot;.*&quot;. Be aware of the possible security implications this can have.</source>
<target>要允许任何重定向 URI请设置模式为正则表达式并将此值设置为 &quot;.*&quot;。请注意这可能带来的安全影响。</target>
</trans-unit>
<trans-unit id="sa52bf79fe1ccb13e">
<source>Federated OIDC Sources</source>
@ -9633,43 +9643,7 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
<target>显示不活跃的用户</target>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
<target>未知错误</target>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
<target>发出令牌的有效时间。</target>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
<target>兼容模式</target>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
<target>默认行为。</target>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
<target>AWS</target>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
<target>更改行为以使用 Amazon Web 服务。</target>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
<target>Slack</target>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
<target>更改行为以使用 Slack。</target>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
<target>更改 authentik 的行为,以兼容特定厂商的 SCIM 实现。</target>
</trans-unit>
</body>
</file>
</xliff>
</xliff>

View File

@ -3791,6 +3791,14 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>启用后,将使用全局电子邮件连接设置,而下面的连接设置将被忽略。</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>令牌到期</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>发送的令牌的有效时间(以分钟为单位)。</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
<target>“模板”</target>
@ -7265,33 +7273,6 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -5000,6 +5000,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>启用后,将使用全局电子邮件连接设置,下面的连接设置将被忽略。</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>令牌过期</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>发出令牌的有效时间(单位为分钟)。</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9633,42 +9643,6 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
<target>显示不活跃的用户</target>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
<target>未知错误</target>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
<target>发出令牌的有效时间。</target>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
<target>兼容模式</target>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
<target>默认行为。</target>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
<target>AWS</target>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
<target>更改行为以使用 Amazon Web 服务。</target>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
<target>Slack</target>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
<target>更改行为以使用 Slack。</target>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
<target>更改 authentik 的行为,以兼容特定厂商的 SCIM 实现。</target>
</trans-unit>
</body>
</file>

View File

@ -4936,6 +4936,16 @@ doesn't pass when either or both of the selected options are equal or above the
<source>When enabled, global Email connection settings will be used and connection settings below will be ignored.</source>
<target>啟用時,將使用全域電子郵件連線設定,以下的連線設定將被忽略。</target>
</trans-unit>
<trans-unit id="sb1fe947f9ad27b9d">
<source>Token expiry</source>
<target>權杖有效期限</target>
</trans-unit>
<trans-unit id="s1c6ba8d100453392">
<source>Time in minutes the token sent is valid.</source>
<target>發送權杖的有效期限(分鐘為單位)。</target>
</trans-unit>
<trans-unit id="se47baf2fd16b9d2b">
<source>Template</source>
@ -9012,33 +9022,6 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s85a91d843c3eb4ad">
<source>Show inactive users</source>
</trans-unit>
<trans-unit id="s5787e20cab57b383">
<source>Unknown error</source>
</trans-unit>
<trans-unit id="sa3fc920064fc8380">
<source>Time the token sent is valid.</source>
</trans-unit>
<trans-unit id="s3483d693c72e412e">
<source>Compatibility Mode</source>
</trans-unit>
<trans-unit id="se7195530fa981896">
<source>Default behavior.</source>
</trans-unit>
<trans-unit id="sfa5ba219a0991116">
<source>AWS</source>
</trans-unit>
<trans-unit id="s44e80dc905ab2ecc">
<source>Altered behavior for usage with Amazon Web Services.</source>
</trans-unit>
<trans-unit id="s2f5e2bcd583fbc27">
<source>Slack</source>
</trans-unit>
<trans-unit id="s7f40ef46b7f213ea">
<source>Altered behavior for usage with Slack.</source>
</trans-unit>
<trans-unit id="s054cb3c0a84eefe5">
<source>Alter authentik's behavior for vendor-specific SCIM implementations.</source>
</trans-unit>
</body>
</file>

View File

@ -6,12 +6,12 @@ If you want to only make changes on the UI, you don't need a backend running fro
### Prerequisites
- Node.js (any recent version should work; we use 22.x to build)
- Node.js (any recent version should work; we use 20.x to build)
- Make (again, any recent version should work)
- Docker and Docker Compose
:::info
Depending on platform, some native dependencies might be required. On macOS, run `brew install node@22`, and for Docker `brew install --cask docker`
Depending on platform, some native dependencies might be required. On macOS, run `brew install node@20`, and for Docker `brew install --cask docker`
:::
### Instructions

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