Compare commits
161 Commits
router-tid
...
static-con
Author | SHA1 | Date | |
---|---|---|---|
86c1d60093 | |||
e5e53f034e | |||
71b87127d1 | |||
d5d67fe22d | |||
5d2685341d | |||
f1ac4ff9c9 | |||
79f4c66286 | |||
1f82094c0b | |||
35440acba3 | |||
eca9901704 | |||
6ddd5a3d5f | |||
5664e62eca | |||
1403f17d62 | |||
1ac8989e81 | |||
b0a1db77e3 | |||
46da4cb59e | |||
154df5cdf7 | |||
5b889456f6 | |||
3eaed82c48 | |||
feaf9d8bc9 | |||
2899668ae2 | |||
4c25e1bb24 | |||
464ff3f5b1 | |||
22eb5f56f1 | |||
7e48e87f49 | |||
8ce12f7850 | |||
2514baabeb | |||
945930a507 | |||
537a80ad97 | |||
5c993e23fe | |||
eb2db18494 | |||
12a46a8426 | |||
4a1213310a | |||
84c2097148 | |||
c05dedc573 | |||
18c197e75b | |||
0c26a0bce2 | |||
5fd6a4cead | |||
51fb1bd8e7 | |||
4a30f87a42 | |||
8e6b6ede30 | |||
af30c2a68e | |||
9b65627a3e | |||
4bad91c901 | |||
f3c479d077 | |||
b024df9903 | |||
f6a6458088 | |||
f0dc0e8900 | |||
79e89b0376 | |||
4cc7d91379 | |||
245909e31a | |||
997a1ddb3d | |||
42335a60bf | |||
fc539332e1 | |||
d9efb02078 | |||
6212250e19 | |||
c18beefc8f | |||
f23da6e402 | |||
e934b246c8 | |||
ead684a410 | |||
d782aadab7 | |||
4ac6f83aea | |||
6281d36a69 | |||
8129ad4ec0 | |||
24eea415b2 | |||
a615ce8e95 | |||
5b275cf7fb | |||
d6e91c119f | |||
7841e47e74 | |||
ad2a4bea3e | |||
a554c085c1 | |||
ff0d978754 | |||
de48e62819 | |||
e50e995d2f | |||
3bf4156cb3 | |||
89990facf5 | |||
48545950ed | |||
0544aa5fae | |||
5d69455b87 | |||
3d291cf4da | |||
44d7c42dc7 | |||
4ea4e925e3 | |||
169172c85f | |||
adea637fa4 | |||
0231277d9c | |||
45643ed1f6 | |||
3823d56dbd | |||
43cfd59ac0 | |||
c8555bbf59 | |||
a4251a3410 | |||
50985f9b0b | |||
9ec24528d4 | |||
5eac38c0cc | |||
010df0c31c | |||
7ba858eff3 | |||
817d2d5ff8 | |||
70e34e03b4 | |||
d61f9f6d57 | |||
bdf81706b8 | |||
7b56602fc9 | |||
7c6e25a996 | |||
0eeaeaf1ff | |||
9ce4337b11 | |||
c6a3c7371c | |||
42a7cf10f2 | |||
bb4f7b1193 | |||
3eecfb835b | |||
92ab856bd3 | |||
178549a756 | |||
67d178aa11 | |||
ef53abace9 | |||
5effb3a0f6 | |||
3a37916a8f | |||
428d5ac9cf | |||
7b4037fdda | |||
2c7bbcc27b | |||
19fb24de99 | |||
2709702896 | |||
7d0d5a7dc2 | |||
6a04a2ca69 | |||
ea561c9da6 | |||
9b9c55f17c | |||
bd5e78bd44 | |||
ab98028022 | |||
813ff64ba1 | |||
c99e742214 | |||
dac6ad3cd6 | |||
e4d2a53ccc | |||
3b6775fd9c | |||
5882e0b2cb | |||
65f0b471d8 | |||
7d054db1a5 | |||
cb75ba2e5e | |||
36cecc1391 | |||
81b91d8777 | |||
41dc23b3c2 | |||
370eff1494 | |||
0ff8def03b | |||
b01cafd9fe | |||
90aa8abb80 | |||
fd21aae4f9 | |||
360223a2ff | |||
0e83de2697 | |||
a23bac9d9b | |||
220378b3f2 | |||
363d655378 | |||
e93b2a1a75 | |||
76665cf65e | |||
3ad7f4dc24 | |||
c5045e8792 | |||
a8c9b3a8ba | |||
148506639a | |||
53814d9919 | |||
08b04c32f5 | |||
1c1d97339d | |||
cafa9c1737 | |||
5f64347ba1 | |||
45ef54480a | |||
a3dc8af4c6 | |||
36933a0aca | |||
8f689890df |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2025.2.3
|
||||
current_version = 2025.2.4
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,6 +33,7 @@ eggs/
|
||||
lib64/
|
||||
parts/
|
||||
dist/
|
||||
out/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
|
@ -94,7 +94,7 @@ RUN --mount=type=secret,id=GEOIPUPDATE_ACCOUNT_ID \
|
||||
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||
|
||||
# Stage 5: Download uv
|
||||
FROM ghcr.io/astral-sh/uv:0.6.11 AS uv
|
||||
FROM ghcr.io/astral-sh/uv:0.6.14 AS uv
|
||||
# Stage 6: Base python image
|
||||
FROM ghcr.io/goauthentik/fips-python:3.12.9-slim-bookworm-fips AS python-base
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from os import environ
|
||||
|
||||
__version__ = "2025.2.3"
|
||||
__version__ = "2025.2.4"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
||||
|
||||
|
@ -179,10 +179,13 @@ class UserSourceConnectionSerializer(SourceSerializer):
|
||||
"user",
|
||||
"source",
|
||||
"source_obj",
|
||||
"identifier",
|
||||
"created",
|
||||
"last_updated",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"created": {"read_only": True},
|
||||
"last_updated": {"read_only": True},
|
||||
}
|
||||
|
||||
|
||||
@ -199,7 +202,7 @@ class UserSourceConnectionViewSet(
|
||||
queryset = UserSourceConnection.objects.all()
|
||||
serializer_class = UserSourceConnectionSerializer
|
||||
filterset_fields = ["user", "source__slug"]
|
||||
search_fields = ["source__slug"]
|
||||
search_fields = ["user__username", "source__slug", "identifier"]
|
||||
ordering = ["source__slug", "pk"]
|
||||
owner_field = "user"
|
||||
|
||||
@ -218,9 +221,11 @@ class GroupSourceConnectionSerializer(SourceSerializer):
|
||||
"source_obj",
|
||||
"identifier",
|
||||
"created",
|
||||
"last_updated",
|
||||
]
|
||||
extra_kwargs = {
|
||||
"created": {"read_only": True},
|
||||
"last_updated": {"read_only": True},
|
||||
}
|
||||
|
||||
|
||||
@ -237,6 +242,5 @@ class GroupSourceConnectionViewSet(
|
||||
queryset = GroupSourceConnection.objects.all()
|
||||
serializer_class = GroupSourceConnectionSerializer
|
||||
filterset_fields = ["group", "source__slug"]
|
||||
search_fields = ["source__slug"]
|
||||
search_fields = ["group__name", "source__slug", "identifier"]
|
||||
ordering = ["source__slug", "pk"]
|
||||
owner_field = "user"
|
||||
|
@ -228,6 +228,7 @@ class UserSerializer(ModelSerializer):
|
||||
"name",
|
||||
"is_active",
|
||||
"last_login",
|
||||
"date_joined",
|
||||
"is_superuser",
|
||||
"groups",
|
||||
"groups_obj",
|
||||
@ -242,6 +243,7 @@ class UserSerializer(ModelSerializer):
|
||||
]
|
||||
extra_kwargs = {
|
||||
"name": {"allow_blank": True},
|
||||
"date_joined": {"read_only": True},
|
||||
"password_change_date": {"read_only": True},
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.0.13 on 2025-04-07 14:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0043_alter_group_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="usersourceconnection",
|
||||
name="new_identifier",
|
||||
field=models.TextField(default=""),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -0,0 +1,30 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_core", "0044_usersourceconnection_new_identifier"),
|
||||
("authentik_sources_kerberos", "0003_migrate_userkerberossourceconnection_identifier"),
|
||||
("authentik_sources_oauth", "0009_migrate_useroauthsourceconnection_identifier"),
|
||||
("authentik_sources_plex", "0005_migrate_userplexsourceconnection_identifier"),
|
||||
("authentik_sources_saml", "0019_migrate_usersamlsourceconnection_identifier"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name="usersourceconnection",
|
||||
old_name="new_identifier",
|
||||
new_name="identifier",
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="usersourceconnection",
|
||||
index=models.Index(fields=["identifier"], name="authentik_c_identif_59226f_idx"),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="usersourceconnection",
|
||||
index=models.Index(
|
||||
fields=["source", "identifier"], name="authentik_c_source__649e04_idx"
|
||||
),
|
||||
),
|
||||
]
|
@ -824,6 +824,7 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
source = models.ForeignKey(Source, on_delete=models.CASCADE)
|
||||
identifier = models.TextField()
|
||||
|
||||
objects = InheritanceManager()
|
||||
|
||||
@ -837,6 +838,10 @@ class UserSourceConnection(SerializerModel, CreatedUpdatedModel):
|
||||
|
||||
class Meta:
|
||||
unique_together = (("user", "source"),)
|
||||
indexes = (
|
||||
models.Index(fields=("identifier",)),
|
||||
models.Index(fields=("source", "identifier")),
|
||||
)
|
||||
|
||||
|
||||
class GroupSourceConnection(SerializerModel, CreatedUpdatedModel):
|
||||
|
@ -36,7 +36,6 @@ from authentik.flows.planner import (
|
||||
)
|
||||
from authentik.flows.stage import StageView
|
||||
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET
|
||||
from authentik.lib.utils.urls import is_url_absolute
|
||||
from authentik.lib.views import bad_request_message
|
||||
from authentik.policies.denied import AccessDeniedResponse
|
||||
from authentik.policies.utils import delete_none_values
|
||||
@ -210,8 +209,6 @@ class SourceFlowManager:
|
||||
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
||||
NEXT_ARG_NAME, "authentik_core:if-user"
|
||||
)
|
||||
if not is_url_absolute(final_redirect):
|
||||
final_redirect = "authentik_core:if-user"
|
||||
flow_context.update(
|
||||
{
|
||||
# Since we authenticate the user by their token, they have no backend set
|
||||
|
@ -13,7 +13,11 @@ from authentik.core.api.devices import AdminDeviceViewSet, DeviceViewSet
|
||||
from authentik.core.api.groups import GroupViewSet
|
||||
from authentik.core.api.property_mappings import PropertyMappingViewSet
|
||||
from authentik.core.api.providers import ProviderViewSet
|
||||
from authentik.core.api.sources import SourceViewSet, UserSourceConnectionViewSet
|
||||
from authentik.core.api.sources import (
|
||||
GroupSourceConnectionViewSet,
|
||||
SourceViewSet,
|
||||
UserSourceConnectionViewSet,
|
||||
)
|
||||
from authentik.core.api.tokens import TokenViewSet
|
||||
from authentik.core.api.transactional_applications import TransactionalApplicationView
|
||||
from authentik.core.api.users import UserViewSet
|
||||
@ -81,6 +85,7 @@ api_urlpatterns = [
|
||||
("core/tokens", TokenViewSet),
|
||||
("sources/all", SourceViewSet),
|
||||
("sources/user_connections/all", UserSourceConnectionViewSet),
|
||||
("sources/group_connections/all", GroupSourceConnectionViewSet),
|
||||
("providers/all", ProviderViewSet),
|
||||
("propertymappings/all", PropertyMappingViewSet),
|
||||
("authenticators/all", DeviceViewSet, "device"),
|
||||
|
@ -28,8 +28,8 @@ def pytest_report_header(*_, **__):
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
|
||||
current_id = int(environ.get("CI_RUN_ID", 0)) - 1
|
||||
total_ids = int(environ.get("CI_TOTAL_RUNS", 0))
|
||||
current_id = int(environ.get("CI_RUN_ID", "0")) - 1
|
||||
total_ids = int(environ.get("CI_TOTAL_RUNS", "0"))
|
||||
|
||||
if total_ids:
|
||||
num_tests = len(items)
|
||||
|
@ -1,13 +1,11 @@
|
||||
"""Kerberos Source Serializer"""
|
||||
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.sources import (
|
||||
GroupSourceConnectionSerializer,
|
||||
GroupSourceConnectionViewSet,
|
||||
UserSourceConnectionSerializer,
|
||||
UserSourceConnectionViewSet,
|
||||
)
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.sources.kerberos.models import (
|
||||
GroupKerberosSourceConnection,
|
||||
UserKerberosSourceConnection,
|
||||
@ -15,33 +13,20 @@ from authentik.sources.kerberos.models import (
|
||||
|
||||
|
||||
class UserKerberosSourceConnectionSerializer(UserSourceConnectionSerializer):
|
||||
"""Kerberos Source Serializer"""
|
||||
|
||||
class Meta:
|
||||
class Meta(UserSourceConnectionSerializer.Meta):
|
||||
model = UserKerberosSourceConnection
|
||||
fields = UserSourceConnectionSerializer.Meta.fields + ["identifier"]
|
||||
|
||||
|
||||
class UserKerberosSourceConnectionViewSet(UsedByMixin, ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
class UserKerberosSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
||||
queryset = UserKerberosSourceConnection.objects.all()
|
||||
serializer_class = UserKerberosSourceConnectionSerializer
|
||||
filterset_fields = ["source__slug"]
|
||||
search_fields = ["source__slug"]
|
||||
ordering = ["source__slug"]
|
||||
owner_field = "user"
|
||||
|
||||
|
||||
class GroupKerberosSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
||||
"""OAuth Group-Source connection Serializer"""
|
||||
|
||||
class Meta(GroupSourceConnectionSerializer.Meta):
|
||||
model = GroupKerberosSourceConnection
|
||||
|
||||
|
||||
class GroupKerberosSourceConnectionViewSet(GroupSourceConnectionViewSet):
|
||||
"""Group-source connection Viewset"""
|
||||
|
||||
class GroupKerberosSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
|
||||
queryset = GroupKerberosSourceConnection.objects.all()
|
||||
serializer_class = GroupKerberosSourceConnectionSerializer
|
||||
|
@ -0,0 +1,28 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_identifier(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
UserKerberosSourceConnection = apps.get_model(
|
||||
"authentik_sources_kerberos", "UserKerberosSourceConnection"
|
||||
)
|
||||
|
||||
for connection in UserKerberosSourceConnection.objects.using(db_alias).all():
|
||||
connection.new_identifier = connection.identifier
|
||||
connection.save(using=db_alias)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_kerberos", "0002_kerberossource_kadmin_type"),
|
||||
("authentik_core", "0044_usersourceconnection_new_identifier"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(code=migrate_identifier, reverse_code=migrations.RunPython.noop),
|
||||
migrations.RemoveField(
|
||||
model_name="userkerberossourceconnection",
|
||||
name="identifier",
|
||||
),
|
||||
]
|
@ -372,8 +372,6 @@ class KerberosSourcePropertyMapping(PropertyMapping):
|
||||
class UserKerberosSourceConnection(UserSourceConnection):
|
||||
"""Connection to configured Kerberos Sources."""
|
||||
|
||||
identifier = models.TextField()
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[Serializer]:
|
||||
from authentik.sources.kerberos.api.source_connection import (
|
||||
|
@ -99,6 +99,7 @@ class LDAPSourceSerializer(SourceSerializer):
|
||||
"sync_groups",
|
||||
"sync_parent_group",
|
||||
"connectivity",
|
||||
"lookup_groups_from_user",
|
||||
]
|
||||
extra_kwargs = {"bind_password": {"write_only": True}}
|
||||
|
||||
@ -134,6 +135,7 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
||||
"sync_parent_group",
|
||||
"user_property_mappings",
|
||||
"group_property_mappings",
|
||||
"lookup_groups_from_user",
|
||||
]
|
||||
search_fields = ["name", "slug"]
|
||||
ordering = ["name"]
|
||||
|
@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.0.13 on 2025-03-26 17:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"authentik_sources_ldap",
|
||||
"0006_rename_ldappropertymapping_ldapsourcepropertymapping_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="ldapsource",
|
||||
name="lookup_groups_from_user",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="Lookup group membership based on a user attribute instead of a group attribute. This allows nested group resolution on systems like FreeIPA and Active Directory",
|
||||
),
|
||||
),
|
||||
]
|
@ -123,6 +123,14 @@ class LDAPSource(Source):
|
||||
Group, blank=True, null=True, default=None, on_delete=models.SET_DEFAULT
|
||||
)
|
||||
|
||||
lookup_groups_from_user = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_(
|
||||
"Lookup group membership based on a user attribute instead of a group attribute. "
|
||||
"This allows nested group resolution on systems like FreeIPA and Active Directory"
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
def component(self) -> str:
|
||||
return "ak-source-ldap-form"
|
||||
|
@ -28,15 +28,17 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
if not self._source.sync_groups:
|
||||
self.message("Group syncing is disabled for this Source")
|
||||
return iter(())
|
||||
|
||||
# If we are looking up groups from users, we don't need to fetch the group membership field
|
||||
attributes = [self._source.object_uniqueness_field, LDAP_DISTINGUISHED_NAME]
|
||||
if not self._source.lookup_groups_from_user:
|
||||
attributes.append(self._source.group_membership_field)
|
||||
|
||||
return self.search_paginator(
|
||||
search_base=self.base_dn_groups,
|
||||
search_filter=self._source.group_object_filter,
|
||||
search_scope=SUBTREE,
|
||||
attributes=[
|
||||
self._source.group_membership_field,
|
||||
self._source.object_uniqueness_field,
|
||||
LDAP_DISTINGUISHED_NAME,
|
||||
],
|
||||
attributes=attributes,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@ -47,9 +49,24 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
return -1
|
||||
membership_count = 0
|
||||
for group in page_data:
|
||||
if "attributes" not in group:
|
||||
continue
|
||||
members = group.get("attributes", {}).get(self._source.group_membership_field, [])
|
||||
if self._source.lookup_groups_from_user:
|
||||
group_dn = group.get("dn", {})
|
||||
group_filter = f"({self._source.group_membership_field}={group_dn})"
|
||||
group_members = self._source.connection().extend.standard.paged_search(
|
||||
search_base=self.base_dn_users,
|
||||
search_filter=group_filter,
|
||||
search_scope=SUBTREE,
|
||||
attributes=[self._source.object_uniqueness_field],
|
||||
)
|
||||
members = []
|
||||
for group_member in group_members:
|
||||
group_member_dn = group_member.get("dn", {})
|
||||
members.append(group_member_dn)
|
||||
else:
|
||||
if "attributes" not in group:
|
||||
continue
|
||||
members = group.get("attributes", {}).get(self._source.group_membership_field, [])
|
||||
|
||||
ak_group = self.get_group(group)
|
||||
if not ak_group:
|
||||
continue
|
||||
@ -68,7 +85,7 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
||||
"ak_groups__in": [ak_group],
|
||||
}
|
||||
)
|
||||
)
|
||||
).distinct()
|
||||
membership_count += 1
|
||||
membership_count += users.count()
|
||||
ak_group.users.set(users)
|
||||
|
@ -96,6 +96,26 @@ def mock_freeipa_connection(password: str) -> Connection:
|
||||
"objectClass": "posixAccount",
|
||||
},
|
||||
)
|
||||
# User with groups in memberOf attribute
|
||||
connection.strategy.add_entry(
|
||||
"cn=user4,ou=users,dc=goauthentik,dc=io",
|
||||
{
|
||||
"name": "user4_sn",
|
||||
"uid": "user4_sn",
|
||||
"objectClass": "person",
|
||||
"memberOf": [
|
||||
"cn=reverse-lookup-group,ou=groups,dc=goauthentik,dc=io",
|
||||
],
|
||||
},
|
||||
)
|
||||
connection.strategy.add_entry(
|
||||
"cn=reverse-lookup-group,ou=groups,dc=goauthentik,dc=io",
|
||||
{
|
||||
"cn": "reverse-lookup-group",
|
||||
"uid": "reverse-lookup-group",
|
||||
"objectClass": "groupOfNames",
|
||||
},
|
||||
)
|
||||
# Locked out user
|
||||
connection.strategy.add_entry(
|
||||
"cn=user-nsaccountlock,ou=users,dc=goauthentik,dc=io",
|
||||
|
@ -162,6 +162,43 @@ class LDAPSyncTests(TestCase):
|
||||
self.assertFalse(User.objects.filter(username="user1_sn").exists())
|
||||
self.assertFalse(User.objects.get(username="user-nsaccountlock").is_active)
|
||||
|
||||
def test_sync_groups_freeipa_memberOf(self):
|
||||
"""Test group sync when membership is derived from memberOf user attribute"""
|
||||
self.source.object_uniqueness_field = "uid"
|
||||
self.source.group_object_filter = "(objectClass=groupOfNames)"
|
||||
self.source.lookup_groups_from_user = True
|
||||
self.source.group_membership_field = "memberOf"
|
||||
self.source.user_property_mappings.set(
|
||||
LDAPSourcePropertyMapping.objects.filter(
|
||||
Q(managed__startswith="goauthentik.io/sources/ldap/default")
|
||||
| Q(managed__startswith="goauthentik.io/sources/ldap/openldap")
|
||||
)
|
||||
)
|
||||
self.source.group_property_mappings.set(
|
||||
LDAPSourcePropertyMapping.objects.filter(
|
||||
managed="goauthentik.io/sources/ldap/openldap-cn"
|
||||
)
|
||||
)
|
||||
connection = MagicMock(return_value=mock_freeipa_connection(LDAP_PASSWORD))
|
||||
with patch("authentik.sources.ldap.models.LDAPSource.connection", connection):
|
||||
user_sync = UserLDAPSynchronizer(self.source)
|
||||
user_sync.sync_full()
|
||||
group_sync = GroupLDAPSynchronizer(self.source)
|
||||
group_sync.sync_full()
|
||||
membership_sync = MembershipLDAPSynchronizer(self.source)
|
||||
membership_sync.sync_full()
|
||||
|
||||
self.assertTrue(
|
||||
User.objects.filter(username="user4_sn").exists(), "User does not exist"
|
||||
)
|
||||
# Test if membership mapping based on memberOf works.
|
||||
memberof_group = Group.objects.filter(name="reverse-lookup-group")
|
||||
self.assertTrue(memberof_group.exists(), "Group does not exist")
|
||||
self.assertTrue(
|
||||
memberof_group.first().users.filter(username="user4_sn").exists(),
|
||||
"User not a member of the group",
|
||||
)
|
||||
|
||||
def test_sync_groups_ad(self):
|
||||
"""Test group sync"""
|
||||
self.source.user_property_mappings.set(
|
||||
|
@ -1,5 +1,3 @@
|
||||
"""OAuth Source Serializer"""
|
||||
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.sources import (
|
||||
@ -12,11 +10,9 @@ from authentik.sources.oauth.models import GroupOAuthSourceConnection, UserOAuth
|
||||
|
||||
|
||||
class UserOAuthSourceConnectionSerializer(UserSourceConnectionSerializer):
|
||||
"""OAuth Source Serializer"""
|
||||
|
||||
class Meta(UserSourceConnectionSerializer.Meta):
|
||||
model = UserOAuthSourceConnection
|
||||
fields = UserSourceConnectionSerializer.Meta.fields + ["identifier", "access_token"]
|
||||
fields = UserSourceConnectionSerializer.Meta.fields + ["access_token"]
|
||||
extra_kwargs = {
|
||||
**UserSourceConnectionSerializer.Meta.extra_kwargs,
|
||||
"access_token": {"write_only": True},
|
||||
@ -24,21 +20,15 @@ class UserOAuthSourceConnectionSerializer(UserSourceConnectionSerializer):
|
||||
|
||||
|
||||
class UserOAuthSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = UserOAuthSourceConnection.objects.all()
|
||||
serializer_class = UserOAuthSourceConnectionSerializer
|
||||
|
||||
|
||||
class GroupOAuthSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
||||
"""OAuth Group-Source connection Serializer"""
|
||||
|
||||
class Meta(GroupSourceConnectionSerializer.Meta):
|
||||
model = GroupOAuthSourceConnection
|
||||
|
||||
|
||||
class GroupOAuthSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
|
||||
"""Group-source connection Viewset"""
|
||||
|
||||
queryset = GroupOAuthSourceConnection.objects.all()
|
||||
serializer_class = GroupOAuthSourceConnectionSerializer
|
||||
|
@ -0,0 +1,28 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_identifier(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
UserOAuthSourceConnection = apps.get_model(
|
||||
"authentik_sources_oauth", "UserOAuthSourceConnection"
|
||||
)
|
||||
|
||||
for connection in UserOAuthSourceConnection.objects.using(db_alias).all():
|
||||
connection.new_identifier = connection.identifier
|
||||
connection.save(using=db_alias)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_oauth", "0008_groupoauthsourceconnection_and_more"),
|
||||
("authentik_core", "0044_usersourceconnection_new_identifier"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(code=migrate_identifier, reverse_code=migrations.RunPython.noop),
|
||||
migrations.RemoveField(
|
||||
model_name="useroauthsourceconnection",
|
||||
name="identifier",
|
||||
),
|
||||
]
|
@ -286,7 +286,6 @@ class OAuthSourcePropertyMapping(PropertyMapping):
|
||||
class UserOAuthSourceConnection(UserSourceConnection):
|
||||
"""Authorized remote OAuth provider."""
|
||||
|
||||
identifier = models.CharField(max_length=255)
|
||||
access_token = models.TextField(blank=True, null=True, default=None)
|
||||
|
||||
@property
|
||||
|
@ -1,5 +1,3 @@
|
||||
"""Plex Source connection Serializer"""
|
||||
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.sources import (
|
||||
@ -12,14 +10,9 @@ from authentik.sources.plex.models import GroupPlexSourceConnection, UserPlexSou
|
||||
|
||||
|
||||
class UserPlexSourceConnectionSerializer(UserSourceConnectionSerializer):
|
||||
"""Plex Source connection Serializer"""
|
||||
|
||||
class Meta(UserSourceConnectionSerializer.Meta):
|
||||
model = UserPlexSourceConnection
|
||||
fields = UserSourceConnectionSerializer.Meta.fields + [
|
||||
"identifier",
|
||||
"plex_token",
|
||||
]
|
||||
fields = UserSourceConnectionSerializer.Meta.fields + ["plex_token"]
|
||||
extra_kwargs = {
|
||||
**UserSourceConnectionSerializer.Meta.extra_kwargs,
|
||||
"plex_token": {"write_only": True},
|
||||
@ -27,21 +20,15 @@ class UserPlexSourceConnectionSerializer(UserSourceConnectionSerializer):
|
||||
|
||||
|
||||
class UserPlexSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
||||
"""Plex Source connection Serializer"""
|
||||
|
||||
queryset = UserPlexSourceConnection.objects.all()
|
||||
serializer_class = UserPlexSourceConnectionSerializer
|
||||
|
||||
|
||||
class GroupPlexSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
||||
"""Plex Group-Source connection Serializer"""
|
||||
|
||||
class Meta(GroupSourceConnectionSerializer.Meta):
|
||||
model = GroupPlexSourceConnection
|
||||
|
||||
|
||||
class GroupPlexSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
|
||||
"""Group-source connection Viewset"""
|
||||
|
||||
queryset = GroupPlexSourceConnection.objects.all()
|
||||
serializer_class = GroupPlexSourceConnectionSerializer
|
||||
|
@ -0,0 +1,29 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_identifier(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
UserPlexSourceConnection = apps.get_model("authentik_sources_plex", "UserPlexSourceConnection")
|
||||
|
||||
for connection in UserPlexSourceConnection.objects.using(db_alias).all():
|
||||
connection.new_identifier = connection.identifier
|
||||
connection.save(using=db_alias)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"authentik_sources_plex",
|
||||
"0004_groupplexsourceconnection_plexsourcepropertymapping_and_more",
|
||||
),
|
||||
("authentik_core", "0044_usersourceconnection_new_identifier"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(code=migrate_identifier, reverse_code=migrations.RunPython.noop),
|
||||
migrations.RemoveField(
|
||||
model_name="userplexsourceconnection",
|
||||
name="identifier",
|
||||
),
|
||||
]
|
@ -141,7 +141,6 @@ class UserPlexSourceConnection(UserSourceConnection):
|
||||
"""Connect user and plex source"""
|
||||
|
||||
plex_token = models.TextField()
|
||||
identifier = models.TextField()
|
||||
|
||||
@property
|
||||
def serializer(self) -> type[Serializer]:
|
||||
|
@ -1,5 +1,3 @@
|
||||
"""SAML Source Serializer"""
|
||||
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from authentik.core.api.sources import (
|
||||
@ -12,29 +10,20 @@ from authentik.sources.saml.models import GroupSAMLSourceConnection, UserSAMLSou
|
||||
|
||||
|
||||
class UserSAMLSourceConnectionSerializer(UserSourceConnectionSerializer):
|
||||
"""SAML Source Serializer"""
|
||||
|
||||
class Meta(UserSourceConnectionSerializer.Meta):
|
||||
model = UserSAMLSourceConnection
|
||||
fields = UserSourceConnectionSerializer.Meta.fields + ["identifier"]
|
||||
|
||||
|
||||
class UserSAMLSourceConnectionViewSet(UserSourceConnectionViewSet, ModelViewSet):
|
||||
"""Source Viewset"""
|
||||
|
||||
queryset = UserSAMLSourceConnection.objects.all()
|
||||
serializer_class = UserSAMLSourceConnectionSerializer
|
||||
|
||||
|
||||
class GroupSAMLSourceConnectionSerializer(GroupSourceConnectionSerializer):
|
||||
"""OAuth Group-Source connection Serializer"""
|
||||
|
||||
class Meta(GroupSourceConnectionSerializer.Meta):
|
||||
model = GroupSAMLSourceConnection
|
||||
|
||||
|
||||
class GroupSAMLSourceConnectionViewSet(GroupSourceConnectionViewSet):
|
||||
"""Group-source connection Viewset"""
|
||||
|
||||
class GroupSAMLSourceConnectionViewSet(GroupSourceConnectionViewSet, ModelViewSet):
|
||||
queryset = GroupSAMLSourceConnection.objects.all()
|
||||
serializer_class = GroupSAMLSourceConnectionSerializer
|
||||
|
@ -0,0 +1,26 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_identifier(apps, schema_editor):
|
||||
db_alias = schema_editor.connection.alias
|
||||
UserSAMLSourceConnection = apps.get_model("authentik_sources_saml", "UserSAMLSourceConnection")
|
||||
|
||||
for connection in UserSAMLSourceConnection.objects.using(db_alias).all():
|
||||
connection.new_identifier = connection.identifier
|
||||
connection.save(using=db_alias)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_saml", "0018_alter_samlsource_slo_url_alter_samlsource_sso_url"),
|
||||
("authentik_core", "0044_usersourceconnection_new_identifier"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(code=migrate_identifier, reverse_code=migrations.RunPython.noop),
|
||||
migrations.RemoveField(
|
||||
model_name="usersamlsourceconnection",
|
||||
name="identifier",
|
||||
),
|
||||
]
|
@ -318,8 +318,6 @@ class SAMLSourcePropertyMapping(PropertyMapping):
|
||||
class UserSAMLSourceConnection(UserSourceConnection):
|
||||
"""Connection to configured SAML Sources."""
|
||||
|
||||
identifier = models.TextField()
|
||||
|
||||
@property
|
||||
def serializer(self) -> Serializer:
|
||||
from authentik.sources.saml.api.source_connection import UserSAMLSourceConnectionSerializer
|
||||
|
@ -33,7 +33,6 @@ from authentik.flows.planner import (
|
||||
)
|
||||
from authentik.flows.stage import ChallengeStageView
|
||||
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
|
||||
from authentik.lib.utils.urls import is_url_absolute
|
||||
from authentik.lib.views import bad_request_message
|
||||
from authentik.providers.saml.utils.encoding import nice64
|
||||
from authentik.sources.saml.exceptions import MissingSAMLResponse, UnsupportedNameIDFormat
|
||||
@ -74,8 +73,6 @@ class InitiateView(View):
|
||||
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
||||
NEXT_ARG_NAME, "authentik_core:if-user"
|
||||
)
|
||||
if not is_url_absolute(final_redirect):
|
||||
final_redirect = "authentik_core:if-user"
|
||||
kwargs.update(
|
||||
{
|
||||
PLAN_CONTEXT_SSO: True,
|
||||
|
@ -104,6 +104,13 @@ def send_mail(
|
||||
# can't be converted to json)
|
||||
message_object.attach(logo_data())
|
||||
|
||||
if (
|
||||
message_object.to
|
||||
and isinstance(message_object.to[0], str)
|
||||
and "=?utf-8?" in message_object.to[0]
|
||||
):
|
||||
message_object.to = [message_object.to[0].split("<")[-1].replace(">", "")]
|
||||
|
||||
LOGGER.debug("Sending mail", to=message_object.to)
|
||||
backend.send_messages([message_object])
|
||||
Event.new(
|
||||
|
@ -97,6 +97,37 @@ class TestEmailStageSending(FlowTestCase):
|
||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
||||
self.assertEqual(mail.outbox[0].to, [f"Test User Many Words <{long_user.email}>"])
|
||||
|
||||
def test_utf8_name(self):
|
||||
"""Test with pending user"""
|
||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||
utf8_user = create_test_user()
|
||||
utf8_user.name = "Cirilo ЉМНЊ el cirilico И̂ӢЙӤ "
|
||||
utf8_user.email = "cyrillic@authentik.local"
|
||||
utf8_user.save()
|
||||
plan.context[PLAN_CONTEXT_PENDING_USER] = utf8_user
|
||||
session = self.client.session
|
||||
session[SESSION_KEY_PLAN] = plan
|
||||
session.save()
|
||||
Event.objects.filter(action=EventAction.EMAIL_SENT).delete()
|
||||
|
||||
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
|
||||
with patch(
|
||||
"authentik.stages.email.models.EmailStage.backend_class",
|
||||
PropertyMock(return_value=EmailBackend),
|
||||
):
|
||||
response = self.client.post(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertStageResponse(
|
||||
response,
|
||||
self.flow,
|
||||
response_errors={
|
||||
"non_field_errors": [{"string": "email-sent", "code": "email-sent"}]
|
||||
},
|
||||
)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, "authentik")
|
||||
self.assertEqual(mail.outbox[0].to, [f"{utf8_user.email}"])
|
||||
|
||||
def test_pending_fake_user(self):
|
||||
"""Test with pending (fake) user"""
|
||||
self.flow.designation = FlowDesignation.RECOVERY
|
||||
|
@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||
"type": "object",
|
||||
"title": "authentik 2025.2.3 Blueprint schema",
|
||||
"title": "authentik 2025.2.4 Blueprint schema",
|
||||
"required": [
|
||||
"version",
|
||||
"entries"
|
||||
@ -7885,6 +7885,11 @@
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Sync parent group"
|
||||
},
|
||||
"lookup_groups_from_user": {
|
||||
"type": "boolean",
|
||||
"title": "Lookup groups from user",
|
||||
"description": "Lookup group membership based on a user attribute instead of a group attribute. This allows nested group resolution on systems like FreeIPA and Active Directory"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
@ -8231,7 +8236,6 @@
|
||||
},
|
||||
"identifier": {
|
||||
"type": "string",
|
||||
"maxLength": 255,
|
||||
"minLength": 1,
|
||||
"title": "Identifier"
|
||||
},
|
||||
|
@ -31,7 +31,7 @@ services:
|
||||
volumes:
|
||||
- redis:/data
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.3}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.4}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -54,7 +54,7 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.3}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.2.4}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
|
14
go.mod
14
go.mod
@ -1,9 +1,10 @@
|
||||
module goauthentik.io
|
||||
|
||||
go 1.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.14.1
|
||||
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
|
||||
@ -19,17 +20,17 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||
github.com/pires/go-proxyproto v0.8.0
|
||||
github.com/prometheus/client_golang v1.21.1
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/redis/go-redis/v9 v9.7.3
|
||||
github.com/sethvargo/go-envconfig v1.1.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
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.2025023.2
|
||||
goauthentik.io/api/v3 v3.2025024.1
|
||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||
golang.org/x/oauth2 v0.28.0
|
||||
golang.org/x/sync v0.12.0
|
||||
golang.org/x/oauth2 v0.29.0
|
||||
golang.org/x/sync v0.13.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
||||
)
|
||||
@ -59,7 +60,6 @@ require (
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
@ -76,6 +76,6 @@ require (
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
google.golang.org/protobuf v1.36.1 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
31
go.sum
31
go.sum
@ -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.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||
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=
|
||||
@ -148,8 +148,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
@ -207,8 +208,8 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@ -239,8 +240,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
@ -299,8 +300,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.2025023.2 h1:4XHlnykN5jQH78liQ4cp2Jf8eigvQImIJp+A+bsq1nA=
|
||||
goauthentik.io/api/v3 v3.2025023.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
goauthentik.io/api/v3 v3.2025024.1 h1:wYmpbNW1XptrjS5dlnZj8CrCs+JUGEVJYStrFdWL9aA=
|
||||
goauthentik.io/api/v3 v3.2025024.1/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -395,8 +396,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -411,8 +412,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -599,8 +600,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
@ -29,4 +29,4 @@ func UserAgent() string {
|
||||
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||
}
|
||||
|
||||
const VERSION = "2025.2.3"
|
||||
const VERSION = "2025.2.4"
|
||||
|
8
lifecycle/aws/package-lock.json
generated
8
lifecycle/aws/package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1006.0",
|
||||
"aws-cdk": "^2.1007.0",
|
||||
"cross-env": "^7.0.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -17,9 +17,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/aws-cdk": {
|
||||
"version": "2.1006.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1006.0.tgz",
|
||||
"integrity": "sha512-6qYnCt4mBN+3i/5F+FC2yMETkDHY/IL7gt3EuqKVPcaAO4jU7oXfVSlR60CYRkZWL4fnAurUV14RkJuJyVG/IA==",
|
||||
"version": "2.1007.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1007.0.tgz",
|
||||
"integrity": "sha512-/UOYOTGWUm+pP9qxg03tID5tL6euC+pb+xo0RBue+xhnUWwj/Bbsw6DbqbpOPMrNzTUxmM723/uMEQmM6S26dw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
@ -10,7 +10,7 @@
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aws-cdk": "^2.1006.0",
|
||||
"aws-cdk": "^2.1007.0",
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ Parameters:
|
||||
Description: authentik Docker image
|
||||
AuthentikVersion:
|
||||
Type: String
|
||||
Default: 2025.2.3
|
||||
Default: 2025.2.4
|
||||
Description: authentik Docker image tag
|
||||
AuthentikServerCPU:
|
||||
Type: Number
|
||||
|
@ -6,23 +6,23 @@
|
||||
# Translators:
|
||||
# Dario Rigolin, 2022
|
||||
# aoor9, 2023
|
||||
# Matteo Piccina <altermatte@gmail.com>, 2024
|
||||
# Enrico Campani, 2024
|
||||
# Marco Vitale, 2024
|
||||
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2024
|
||||
# Nicola Mersi, 2024
|
||||
# tmassimi, 2024
|
||||
# Marc Schmitt, 2024
|
||||
# albanobattistella <albanobattistella@gmail.com>, 2024
|
||||
# Matteo Piccina <altermatte@gmail.com>, 2025
|
||||
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-14 14:49+0000\n"
|
||||
"POT-Creation-Date: 2025-03-31 00:10+0000\n"
|
||||
"PO-Revision-Date: 2022-09-26 16:47+0000\n"
|
||||
"Last-Translator: albanobattistella <albanobattistella@gmail.com>, 2024\n"
|
||||
"Last-Translator: Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025\n"
|
||||
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -130,6 +130,10 @@ msgstr "L'utente non ha accesso all'applicazione."
|
||||
msgid "Extra description not available"
|
||||
msgstr "Descrizione extra non disponibile"
|
||||
|
||||
#: authentik/core/api/groups.py
|
||||
msgid "Cannot set group as parent of itself."
|
||||
msgstr "Impossibile impostare il gruppo come padre di se stesso."
|
||||
|
||||
#: authentik/core/api/providers.py
|
||||
msgid ""
|
||||
"When not set all providers are returned. When set to true, only backchannel "
|
||||
@ -177,6 +181,14 @@ msgstr "Aggiungi utente al gruppo"
|
||||
msgid "Remove user from group"
|
||||
msgstr "Rimuovi l'utente dal gruppo"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Enable superuser status"
|
||||
msgstr "Abilita stato di superutente"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Disable superuser status"
|
||||
msgstr "Disabilita stato di superutente"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "User's display name."
|
||||
msgstr "Nome visualizzato dell'utente."
|
||||
@ -260,11 +272,11 @@ msgstr "Applicazioni"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlement"
|
||||
msgstr ""
|
||||
msgstr "Entitlement Applicazione"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Application Entitlements"
|
||||
msgstr ""
|
||||
msgstr "Entitlements Applicazione"
|
||||
|
||||
#: authentik/core/models.py
|
||||
msgid "Use the source-specific identifier"
|
||||
@ -551,62 +563,6 @@ msgstr "Mappatura Microsoft Entra Provider"
|
||||
msgid "Microsoft Entra Provider Mappings"
|
||||
msgstr "Mappature Microsoft Entra Provider"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
#: authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr ""
|
||||
"Determina quanto può durare una sessione. Se impostato a 0, la sessione "
|
||||
"durerà fino alla chiusura del browser. (Formato: "
|
||||
"hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||
msgstr ""
|
||||
"Se impostato su vero, i token di connessione verranno eliminati alla "
|
||||
"disconnessione."
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider"
|
||||
msgstr "Fornitore di controllo dell'accesso remoto"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Providers"
|
||||
msgstr "Fornitori di controllo dell'accesso remoto"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Endpoint"
|
||||
msgstr "Endpoint RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Endpoints"
|
||||
msgstr "Endpoints RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mapping"
|
||||
msgstr "Mappatura delle proprietà del provider RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mappings"
|
||||
msgstr "Mappature proprietà del provider RAC"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Connection token"
|
||||
msgstr "RAC Connection token"
|
||||
|
||||
#: authentik/enterprise/providers/rac/models.py
|
||||
msgid "RAC Connection tokens"
|
||||
msgstr "RAC Connection tokens"
|
||||
|
||||
#: authentik/enterprise/providers/rac/views.py
|
||||
msgid "Maximum connection limit reached."
|
||||
msgstr "Limite massimo di connessioni raggiunto."
|
||||
|
||||
#: authentik/enterprise/providers/rac/views.py
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr "(Sei già connesso in un'altra scheda/finestra)"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Signing Key"
|
||||
@ -614,39 +570,39 @@ msgstr "Chiave di firma"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "Key used to sign the SSF Events."
|
||||
msgstr ""
|
||||
msgstr "Chiave utilizzata per firmare gli eventi SSF."
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "Shared Signals Framework Provider"
|
||||
msgstr ""
|
||||
msgstr "Fornitore Shared Signals Framework"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "Shared Signals Framework Providers"
|
||||
msgstr ""
|
||||
msgstr "Fornitori Shared Signals Framework"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "Add stream to SSF provider"
|
||||
msgstr ""
|
||||
msgstr "Aggiungi Stream al provider SSF"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "SSF Stream"
|
||||
msgstr ""
|
||||
msgstr "SSF Stream"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "SSF Streams"
|
||||
msgstr ""
|
||||
msgstr "SSF Streams"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "SSF Stream Event"
|
||||
msgstr ""
|
||||
msgstr "Evento di Stream SSF"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/models.py
|
||||
msgid "SSF Stream Events"
|
||||
msgstr ""
|
||||
msgstr "Eventi di Stream SSF"
|
||||
|
||||
#: authentik/enterprise/providers/ssf/tasks.py
|
||||
msgid "Failed to send request"
|
||||
msgstr ""
|
||||
msgstr "Impossibile inviare la richiesta"
|
||||
|
||||
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||
@ -712,9 +668,26 @@ msgid "Slack Webhook (Slack/Discord)"
|
||||
msgstr "Slack Webhook (Slack/Discord)"
|
||||
|
||||
#: authentik/events/models.py
|
||||
#: authentik/stages/authenticator_validate/models.py
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
"Customize the body of the request. Mapping should return data that is JSON-"
|
||||
"serializable."
|
||||
msgstr ""
|
||||
"Personalizza il corpo della richiesta. Il mapping dovrebbe restituire dati "
|
||||
"serializzabili in JSON."
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
"Configure additional headers to be sent. Mapping should return a dictionary "
|
||||
"of key-value pairs"
|
||||
msgstr ""
|
||||
"Configurare le intestazioni aggiuntive da inviare. Il mapping dovrebbe "
|
||||
"restituire un dizionario di coppie chiave-valore."
|
||||
|
||||
#: authentik/events/models.py
|
||||
msgid ""
|
||||
"Only send notification once, for example when sending a webhook into a chat "
|
||||
@ -944,7 +917,7 @@ msgstr ""
|
||||
|
||||
#: authentik/flows/models.py
|
||||
msgid "Evaluate policies when the Stage is presented to the user."
|
||||
msgstr ""
|
||||
msgstr "Valutare i criteri quando la fase viene presentata all'utente."
|
||||
|
||||
#: authentik/flows/models.py
|
||||
msgid ""
|
||||
@ -986,6 +959,14 @@ msgstr "Tokens del flusso"
|
||||
msgid "Invalid next URL"
|
||||
msgstr "URL successivo non valido"
|
||||
|
||||
#: authentik/lib/sync/outgoing/models.py
|
||||
msgid ""
|
||||
"When enabled, provider will not modify or create objects in the remote "
|
||||
"system."
|
||||
msgstr ""
|
||||
"Quando abilitato, il provider non modificherà o creerà oggetti nel sistema "
|
||||
"remoto."
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
msgid "Starting full provider sync"
|
||||
msgstr "Avvio della sincronizzazione completa del provider"
|
||||
@ -1000,6 +981,10 @@ msgstr "Sincronizzando pagina {page} degli utenti"
|
||||
msgid "Syncing page {page} of groups"
|
||||
msgstr "Sincronizzando pagina {page} dei gruppi"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
msgid "Dropping mutating request due to dry run"
|
||||
msgstr "Richiesta di mutazione ignorata a causa della prova di funzionamento"
|
||||
|
||||
#: authentik/lib/sync/outgoing/tasks.py
|
||||
#, python-brace-format
|
||||
msgid "Stopping sync due to error: {error}"
|
||||
@ -1212,6 +1197,14 @@ msgstr "GeoIP: indirizzo IP del client non trovato nel database della città."
|
||||
msgid "Client IP is not in an allowed country."
|
||||
msgstr "L'IP del client non si trova in un paese consentito."
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance from previous authentication is larger than threshold."
|
||||
msgstr "La distanza dall'autenticazione precedente è maggiore della soglia."
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "Distance is further than possible."
|
||||
msgstr "La distanza è maggiore del possibile."
|
||||
|
||||
#: authentik/policies/geoip/models.py
|
||||
msgid "GeoIP Policy"
|
||||
msgstr "Criterio GeoIP"
|
||||
@ -1344,6 +1337,22 @@ msgstr "Punteggio di reputazione"
|
||||
msgid "Reputation Scores"
|
||||
msgstr "Punteggi di reputazione"
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Waiting for authentication..."
|
||||
msgstr "In attesa di autenticazione..."
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid ""
|
||||
"You're already authenticating in another tab. This page will refresh once "
|
||||
"authentication is completed."
|
||||
msgstr ""
|
||||
"Ti stai già autenticando in un'altra scheda. Questa pagina si aggiornerà una"
|
||||
" volta completata l'autenticazione."
|
||||
|
||||
#: authentik/policies/templates/policies/buffer.html
|
||||
msgid "Authenticate in this tab"
|
||||
msgstr "Autenticati in questa scheda"
|
||||
|
||||
#: authentik/policies/templates/policies/denied.html
|
||||
msgid "Permission denied"
|
||||
msgstr "Permesso negato"
|
||||
@ -1531,6 +1540,14 @@ msgstr "RS256 (Crittografia Asimmetrica)"
|
||||
msgid "ES256 (Asymmetric Encryption)"
|
||||
msgstr "ES256 (Crittografia Asimmetrica)"
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "ES384 (Asymmetric Encryption)"
|
||||
msgstr "ES384 (Crittografia Asimmetrica)"
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "ES512 (Asymmetric Encryption)"
|
||||
msgstr "ES512 (Crittografia Asimmetrica)"
|
||||
|
||||
#: authentik/providers/oauth2/models.py
|
||||
msgid "Scope used by the client"
|
||||
msgstr "Scope usato dall'utente"
|
||||
@ -1814,6 +1831,61 @@ msgstr "Provider Proxy"
|
||||
msgid "Proxy Providers"
|
||||
msgstr "Providers Proxy"
|
||||
|
||||
#: authentik/providers/rac/models.py authentik/stages/user_login/models.py
|
||||
msgid ""
|
||||
"Determines how long a session lasts. Default of 0 means that the sessions "
|
||||
"lasts until the browser is closed. (Format: hours=-1;minutes=-2;seconds=-3)"
|
||||
msgstr ""
|
||||
"Determina quanto può durare una sessione. Se impostato a 0, la sessione "
|
||||
"durerà fino alla chiusura del browser. (Formato: "
|
||||
"hours=-1;minutes=-2;seconds=-3)"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "When set to true, connection tokens will be deleted upon disconnect."
|
||||
msgstr ""
|
||||
"Se impostato su vero, i token di connessione verranno eliminati alla "
|
||||
"disconnessione."
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider"
|
||||
msgstr "Fornitore di controllo dell'accesso remoto"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Providers"
|
||||
msgstr "Fornitori di controllo dell'accesso remoto"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Endpoint"
|
||||
msgstr "Endpoint RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Endpoints"
|
||||
msgstr "Endpoints RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mapping"
|
||||
msgstr "Mappatura delle proprietà del provider RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Provider Property Mappings"
|
||||
msgstr "Mappature proprietà del provider RAC"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Connection token"
|
||||
msgstr "RAC Connection token"
|
||||
|
||||
#: authentik/providers/rac/models.py
|
||||
msgid "RAC Connection tokens"
|
||||
msgstr "RAC Connection tokens"
|
||||
|
||||
#: authentik/providers/rac/views.py
|
||||
msgid "Maximum connection limit reached."
|
||||
msgstr "Limite massimo di connessioni raggiunto."
|
||||
|
||||
#: authentik/providers/rac/views.py
|
||||
msgid "(You are already connected in another tab/window)"
|
||||
msgstr "(Sei già connesso in un'altra scheda/finestra)"
|
||||
|
||||
#: authentik/providers/radius/models.py
|
||||
msgid "Shared secret between clients and server to hash packets."
|
||||
msgstr "Segreto condiviso tra client e server per hashare i pacchetti."
|
||||
@ -1901,6 +1973,20 @@ msgstr ""
|
||||
"Configura il modo in cui verrà creato il valore NameID. Se lasciato vuoto, "
|
||||
"verrà considerato il NameIDPolicy della richiesta in arrivo"
|
||||
|
||||
#: authentik/providers/saml/models.py
|
||||
msgid "AuthnContextClassRef Property Mapping"
|
||||
msgstr "Mapping delle proprietà AuthnContextClassRef"
|
||||
|
||||
#: authentik/providers/saml/models.py
|
||||
msgid ""
|
||||
"Configure how the AuthnContextClassRef value will be created. When left "
|
||||
"empty, the AuthnContextClassRef will be set based on which authentication "
|
||||
"methods the user used to authenticate."
|
||||
msgstr ""
|
||||
"Configura come verrà creato il valore AuthnContextClassRef. Se lasciato "
|
||||
"vuoto, AuthnContextClassRef verrà impostato in base ai metodi di "
|
||||
"autenticazione utilizzati dall'utente."
|
||||
|
||||
#: authentik/providers/saml/models.py
|
||||
msgid ""
|
||||
"Assertion valid not before current time + this value (Format: "
|
||||
@ -2042,6 +2128,18 @@ msgstr "Provider SAML dai Metadati"
|
||||
msgid "SAML Providers from Metadata"
|
||||
msgstr "Providers SAML dai Metadati"
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "Default"
|
||||
msgstr "Predefinito"
|
||||
|
||||
#: 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 di base per le richieste SCIM, di solito termina con /v2"
|
||||
@ -2050,6 +2148,16 @@ msgstr "URL di base per le richieste SCIM, di solito termina con /v2"
|
||||
msgid "Authentication token"
|
||||
msgstr "Token di autenticazione"
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "SCIM Compatibility Mode"
|
||||
msgstr "SCIM Modalità di Compatibilità"
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "Alter authentik behavior for vendor-specific SCIM implementations."
|
||||
msgstr ""
|
||||
"Modifica il comportamento di autenticazione per le implementazioni SCIM "
|
||||
"specifiche del fornitore."
|
||||
|
||||
#: authentik/providers/scim/models.py
|
||||
msgid "SCIM Provider"
|
||||
msgstr "Privider SCIM"
|
||||
@ -2125,7 +2233,7 @@ msgstr ""
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid "KAdmin server type"
|
||||
msgstr ""
|
||||
msgstr "Tipo server KAdmin"
|
||||
|
||||
#: authentik/sources/kerberos/models.py
|
||||
msgid "Sync users from Kerberos into authentik"
|
||||
@ -2730,6 +2838,117 @@ msgstr "Dispositivo Duo"
|
||||
msgid "Duo Devices"
|
||||
msgstr "Dispositivi Duo"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email OTP"
|
||||
msgstr "Email OTP"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"When enabled, global Email connection settings will be used and connection "
|
||||
"settings below will be ignored."
|
||||
msgstr ""
|
||||
"Se abilitato, verranno utilizzate le impostazioni di connessione e-mail "
|
||||
"globali e le impostazioni di connessione riportate di seguito verranno "
|
||||
"ignorate."
|
||||
|
||||
#: 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 ""
|
||||
"Tempo di validità del token inviato (formato: "
|
||||
"hours=3,minutes=17,seconds=300)."
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stage"
|
||||
msgstr "Fase di configurazione dell'autenticatore email"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Authenticator Setup Stages"
|
||||
msgstr "Fasi di configurazione dell'autenticatore email"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Exception occurred while rendering E-mail template"
|
||||
msgstr ""
|
||||
"Eccezione verificatasi durante la visualizzazione del modello di posta "
|
||||
"elettronica"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Device"
|
||||
msgstr "Dispositivo email"
|
||||
|
||||
#: authentik/stages/authenticator_email/models.py
|
||||
msgid "Email Devices"
|
||||
msgstr "Dispositivi email"
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
#: authentik/stages/authenticator_totp/stage.py
|
||||
msgid "Code does not match"
|
||||
msgstr "Il codice non corrisponde"
|
||||
|
||||
#: authentik/stages/authenticator_email/stage.py
|
||||
msgid "Invalid email"
|
||||
msgstr "Email non valida"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Hi %(username)s,\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Ciao %(username)s,\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
msgid ""
|
||||
"\n"
|
||||
" Email MFA code.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Codice MFA via e-mail.\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" If you did not request this code, please ignore this email. The code above is valid for %(expires)s.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Se non hai richiesto questo codice, ignora questa email. Il codice sopra riportato è valido per %(expires)s.\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
#, python-format
|
||||
msgid "Hi %(username)s,"
|
||||
msgstr "Ciao %(username)s,"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
msgid ""
|
||||
"\n"
|
||||
"Email MFA code\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Codice e-mail MFA\n"
|
||||
|
||||
#: authentik/stages/authenticator_email/templates/email/email_otp.txt
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
"If you did not request this code, please ignore this email. The code above is valid for %(expires)s.\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Se non hai richiesto questo codice, ignora questa email. Il codice sopra riportato è valido per %(expires)s.\n"
|
||||
|
||||
#: authentik/stages/authenticator_sms/models.py
|
||||
msgid ""
|
||||
"When enabled, the Phone number is only used during enrollment to verify the "
|
||||
@ -2768,11 +2987,6 @@ msgstr "Dispositivo SMS"
|
||||
msgid "SMS Devices"
|
||||
msgstr "Dispositivi SMS"
|
||||
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
#: authentik/stages/authenticator_totp/stage.py
|
||||
msgid "Code does not match"
|
||||
msgstr "Il codice non corrisponde"
|
||||
|
||||
#: authentik/stages/authenticator_sms/stage.py
|
||||
msgid "Invalid phone number"
|
||||
msgstr "Numero di telefono non valido"
|
||||
@ -3013,23 +3227,10 @@ msgstr "Ripristino password"
|
||||
msgid "Account Confirmation"
|
||||
msgstr "Conferma dell'account"
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid ""
|
||||
"When enabled, global Email connection settings will be used and connection "
|
||||
"settings below will be ignored."
|
||||
msgstr ""
|
||||
"Se abilitato, verranno utilizzate le impostazioni di connessione e-mail "
|
||||
"globali e le impostazioni di connessione riportate di seguito verranno "
|
||||
"ignorate."
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid "Activate users upon completion of stage."
|
||||
msgstr "Attiva gli utenti al completamento della fase."
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid "Time in minutes the token sent is valid."
|
||||
msgstr "Tempo in minuti in cui il token inviato è valido."
|
||||
|
||||
#: authentik/stages/email/models.py
|
||||
msgid "Email Stage"
|
||||
msgstr "Fase email"
|
||||
@ -3038,12 +3239,6 @@ msgstr "Fase email"
|
||||
msgid "Email Stages"
|
||||
msgstr "Fasi Email"
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Exception occurred while rendering E-mail template"
|
||||
msgstr ""
|
||||
"Eccezione verificatasi durante la visualizzazione del modello di posta "
|
||||
"elettronica"
|
||||
|
||||
#: authentik/stages/email/stage.py
|
||||
msgid "Successfully verified Email."
|
||||
msgstr "Email verificato con successo."
|
||||
@ -3127,17 +3322,6 @@ msgstr ""
|
||||
"\n"
|
||||
"Questa email è stata inviata dal trasporto delle notifiche %(name)s.\n"
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" Hi %(username)s,\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Ciao %(username)s,\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.html
|
||||
msgid ""
|
||||
"\n"
|
||||
@ -3158,11 +3342,6 @@ msgstr ""
|
||||
" Se non hai richiesto una modifica della password, ignora questa e-mail. Il link sopra è valido per %(expires)s.\n"
|
||||
" "
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
#, python-format
|
||||
msgid "Hi %(username)s,"
|
||||
msgstr "Ciao %(username)s,"
|
||||
|
||||
#: authentik/stages/email/templates/email/password_reset.txt
|
||||
msgid ""
|
||||
"\n"
|
||||
@ -3492,6 +3671,7 @@ msgstr ""
|
||||
#: authentik/stages/redirect/api.py
|
||||
msgid "Target Flow should be present when mode is Flow."
|
||||
msgstr ""
|
||||
"Il flusso target dovrebbe essere presente quando la modalità è Flusso."
|
||||
|
||||
#: authentik/stages/redirect/models.py
|
||||
msgid "Redirect Stage"
|
||||
|
Binary file not shown.
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@goauthentik/authentik",
|
||||
"version": "2025.2.3",
|
||||
"version": "2025.2.4",
|
||||
"private": true
|
||||
}
|
||||
|
11
packages/eslint-config/@types/eslint-plugin-react-hooks.d.ts
vendored
Normal file
11
packages/eslint-config/@types/eslint-plugin-react-hooks.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @file TypeScript type definitions for eslint-plugin-react-hooks
|
||||
*/
|
||||
declare module "eslint-plugin-react-hooks" {
|
||||
import { ESLint } from "eslint";
|
||||
// We have to do this because ESLint aliases the namespace and class simultaneously.
|
||||
type PluginInstance = ESLint.Plugin;
|
||||
const Plugin: PluginInstance;
|
||||
|
||||
export default Plugin;
|
||||
}
|
11
packages/eslint-config/@types/eslint-plugin-react.d.ts
vendored
Normal file
11
packages/eslint-config/@types/eslint-plugin-react.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* @file TypeScript type definitions for eslint-plugin-react
|
||||
*/
|
||||
declare module "eslint-plugin-react" {
|
||||
import { ESLint } from "eslint";
|
||||
// We have to do this because ESLint aliases the namespace and class simultaneously.
|
||||
type PluginInstance = ESLint.Plugin;
|
||||
const Plugin: PluginInstance;
|
||||
|
||||
export default Plugin;
|
||||
}
|
18
packages/eslint-config/LICENSE.txt
Normal file
18
packages/eslint-config/LICENSE.txt
Normal file
@ -0,0 +1,18 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Authentik Security, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
5
packages/eslint-config/README.md
Normal file
5
packages/eslint-config/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# `@goauthentik/eslint-config`
|
||||
|
||||
This package contains the ESLint configuration used by authentik.
|
||||
While it is possible to use this configuration outside of our projects,
|
||||
you may find that it is not as useful as other popular configurations.
|
72
packages/eslint-config/index.js
Normal file
72
packages/eslint-config/index.js
Normal file
@ -0,0 +1,72 @@
|
||||
import eslint from "@eslint/js";
|
||||
import { javaScriptConfig } from "@goauthentik/eslint-config/javascript-config";
|
||||
import { reactConfig } from "@goauthentik/eslint-config/react-config";
|
||||
import { typescriptConfig } from "@goauthentik/eslint-config/typescript-config";
|
||||
import * as litconf from "eslint-plugin-lit";
|
||||
import * as wcconf from "eslint-plugin-wc";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @typedef ESLintPackageConfigOptions Options for creating package ESLint configuration.
|
||||
* @property {string[]} [ignorePatterns] Override ignore patterns for ESLint.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {string[]} Default Ignore patterns for ESLint.
|
||||
*/
|
||||
export const DefaultIgnorePatterns = [
|
||||
// ---
|
||||
"**/*.md",
|
||||
"**/out",
|
||||
"**/dist",
|
||||
"**/.wireit",
|
||||
"website/build/**",
|
||||
"website/.docusaurus/**",
|
||||
"**/node_modules",
|
||||
"**/coverage",
|
||||
"**/storybook-static",
|
||||
"**/locale-codes.ts",
|
||||
"**/src/locales",
|
||||
"**/gen-ts-api",
|
||||
];
|
||||
|
||||
/**
|
||||
* Given a preferred package name, creates a ESLint configuration object.
|
||||
*
|
||||
* @param {ESLintPackageConfigOptions} options The preferred package configuration options.
|
||||
*
|
||||
* @returns The ESLint configuration object.
|
||||
*/
|
||||
export function createESLintPackageConfig({ ignorePatterns = DefaultIgnorePatterns } = {}) {
|
||||
return tseslint.config(
|
||||
{
|
||||
ignores: ignorePatterns,
|
||||
},
|
||||
|
||||
eslint.configs.recommended,
|
||||
javaScriptConfig,
|
||||
|
||||
wcconf.configs["flat/recommended"],
|
||||
litconf.configs["flat/recommended"],
|
||||
|
||||
...tseslint.configs.recommended,
|
||||
|
||||
...typescriptConfig,
|
||||
|
||||
...reactConfig,
|
||||
|
||||
{
|
||||
rules: {
|
||||
"no-console": "off",
|
||||
},
|
||||
files: [
|
||||
// ---
|
||||
"**/scripts/**/*",
|
||||
"**/test/**/*",
|
||||
"**/tests/**/*",
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
143
packages/eslint-config/javascript-config.js
Normal file
143
packages/eslint-config/javascript-config.js
Normal file
@ -0,0 +1,143 @@
|
||||
// @ts-check
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
const MAX_DEPTH = 4;
|
||||
const MAX_NESTED_CALLBACKS = 4;
|
||||
const MAX_PARAMS = 5;
|
||||
|
||||
/**
|
||||
* ESLint configuration for JavaScript authentik projects.
|
||||
*/
|
||||
export const javaScriptConfig = tseslint.config({
|
||||
rules: {
|
||||
// TODO: Clean up before enabling.
|
||||
"accessor-pairs": "off",
|
||||
"array-callback-return": "error",
|
||||
"block-scoped-var": "error",
|
||||
"consistent-return": ["error", { treatUndefinedAsUnspecified: false }],
|
||||
"consistent-this": ["error", "that"],
|
||||
"curly": "off",
|
||||
"dot-notation": [
|
||||
"error",
|
||||
{
|
||||
allowKeywords: true,
|
||||
},
|
||||
],
|
||||
"eqeqeq": "error",
|
||||
"func-names": ["error", "as-needed"],
|
||||
"guard-for-in": "error",
|
||||
"max-depth": ["error", MAX_DEPTH],
|
||||
"max-nested-callbacks": ["error", MAX_NESTED_CALLBACKS],
|
||||
"max-params": ["error", MAX_PARAMS],
|
||||
// TODO: Clean up before enabling.
|
||||
// "new-cap": "error",
|
||||
"no-alert": "error",
|
||||
"no-array-constructor": "error",
|
||||
"no-bitwise": [
|
||||
"error",
|
||||
{
|
||||
allow: ["~"],
|
||||
int32Hint: true,
|
||||
},
|
||||
],
|
||||
"no-caller": "error",
|
||||
"no-case-declarations": "error",
|
||||
"no-class-assign": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-constant-condition": "error",
|
||||
"no-control-regex": "error",
|
||||
"no-debugger": "error",
|
||||
"no-delete-var": "error",
|
||||
"no-div-regex": "error",
|
||||
"no-dupe-args": "error",
|
||||
"no-dupe-keys": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-else-return": "error",
|
||||
"no-empty": "error",
|
||||
"no-empty-character-class": "error",
|
||||
"no-empty-function": ["error", { allow: ["constructors"] }],
|
||||
"no-labels": "error",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
"no-ex-assign": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-fallthrough": "error",
|
||||
"no-func-assign": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-implicit-coercion": "error",
|
||||
"no-implicit-globals": "error",
|
||||
"no-inner-declarations": ["error", "functions"],
|
||||
"no-invalid-regexp": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-iterator": "error",
|
||||
"no-label-var": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-multi-str": "error",
|
||||
// TODO: Clean up before enabling.
|
||||
"no-negated-condition": "off",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-obj-calls": "error",
|
||||
"no-octal": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": ["error", { props: false }],
|
||||
"no-proto": "error",
|
||||
"no-redeclare": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-restricted-syntax": ["error", "WithStatement"],
|
||||
"no-script-url": "error",
|
||||
"no-self-assign": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
// TODO: Clean up before enabling.
|
||||
// "no-shadow": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-sparse-arrays": "error",
|
||||
"no-this-before-super": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-trailing-spaces": "off", // Handled by Prettier.
|
||||
"no-undef": "off",
|
||||
"no-undef-init": "off",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-unused-labels": "error",
|
||||
"no-use-before-define": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-var": "error",
|
||||
"no-void": "error",
|
||||
"no-with": "error",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "error",
|
||||
"radix": "error",
|
||||
"require-yield": "error",
|
||||
"strict": ["error", "global"],
|
||||
"use-isnan": "error",
|
||||
"valid-typeof": "error",
|
||||
"vars-on-top": "error",
|
||||
"yoda": ["error", "never"],
|
||||
|
||||
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||
// SonarJS is not yet compatible with ESLint 9. Commenting these out
|
||||
// until it is.
|
||||
// "sonarjs/cognitive-complexity": ["off", MAX_COGNITIVE_COMPLEXITY],
|
||||
// "sonarjs/no-duplicate-string": "off",
|
||||
// "sonarjs/no-nested-template-literals": "off",
|
||||
},
|
||||
});
|
||||
|
||||
export default javaScriptConfig;
|
53
packages/eslint-config/package.json
Normal file
53
packages/eslint-config/package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "@goauthentik/eslint-config",
|
||||
"version": "1.0.0",
|
||||
"description": "authentik's ESLint config",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./out/index.d.ts"
|
||||
},
|
||||
"./react-config": {
|
||||
"import": "./react-config.js",
|
||||
"types": "./out/react-config.d.ts"
|
||||
},
|
||||
"./javascript-config": {
|
||||
"import": "./javascript-config.js",
|
||||
"types": "./out/javascript-config.d.ts"
|
||||
},
|
||||
"./typescript-config": {
|
||||
"import": "./typescript-config.js",
|
||||
"types": "./out/typescript-config.d.ts"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-hooks": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@goauthentik/tsconfig": "1.0.0",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.29.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.29.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"react": "^18.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
},
|
||||
"types": "./out/index.d.ts",
|
||||
"prettier": "@goauthentik/prettier-config",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
34
packages/eslint-config/react-config.js
vendored
Normal file
34
packages/eslint-config/react-config.js
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
import reactPlugin from "eslint-plugin-react";
|
||||
import hooksPlugin from "eslint-plugin-react-hooks";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
/**
|
||||
* ESLint configuration for React authentik projects.
|
||||
*/
|
||||
export const reactConfig = tseslint.config({
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
|
||||
plugins: {
|
||||
"react": reactPlugin,
|
||||
"react-hooks": hooksPlugin,
|
||||
},
|
||||
|
||||
rules: {
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
|
||||
"react/jsx-uses-react": 0,
|
||||
|
||||
"react/display-name": "off",
|
||||
"react/jsx-curly-brace-presence": "error",
|
||||
"react/jsx-no-leaked-render": "error",
|
||||
"react/prop-types": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
},
|
||||
});
|
||||
|
||||
export default reactConfig;
|
8
packages/eslint-config/tsconfig.json
Normal file
8
packages/eslint-config/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@goauthentik/tsconfig",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"checkJs": true,
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
35
packages/eslint-config/typescript-config.js
Normal file
35
packages/eslint-config/typescript-config.js
Normal file
@ -0,0 +1,35 @@
|
||||
// @ts-check
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
/**
|
||||
* ESLint configuration for TypeScript authentik projects.
|
||||
*/
|
||||
export const typescriptConfig = tseslint.config({
|
||||
rules: {
|
||||
"@typescript-eslint/ban-ts-comment": [
|
||||
"error",
|
||||
{
|
||||
"ts-expect-error": "allow-with-description",
|
||||
"ts-ignore": true,
|
||||
"ts-nocheck": "allow-with-description",
|
||||
"ts-check": false,
|
||||
"minimumDescriptionLength": 5,
|
||||
},
|
||||
],
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": "error",
|
||||
"no-invalid-this": "off",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-namespace": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
caughtErrorsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export default typescriptConfig;
|
18
packages/monorepo/LICENSE.txt
Normal file
18
packages/monorepo/LICENSE.txt
Normal file
@ -0,0 +1,18 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Authentik Security, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
5
packages/monorepo/README.md
Normal file
5
packages/monorepo/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# `@goauthentik/monorepo`
|
||||
|
||||
This package contains utility scripts common to all TypeScript and JavaScript packages in the
|
||||
`@goauthentik` monorepo.
|
||||
|
17
packages/monorepo/constants.js
Normal file
17
packages/monorepo/constants.js
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @file Constants for JavaScript and TypeScript files.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* The current Node.js environment, defaulting to "development" when not set.
|
||||
*
|
||||
* Note, this should only be used during the build process.
|
||||
*
|
||||
* If you need to check the environment at runtime, use `process.env.NODE_ENV` to
|
||||
* ensure that module tree-shaking works correctly.
|
||||
*
|
||||
*/
|
||||
export const NodeEnvironment = /** @type {'development' | 'production'} */ (
|
||||
process.env.NODE_ENV || "development"
|
||||
);
|
4
packages/monorepo/index.js
Normal file
4
packages/monorepo/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./paths.js";
|
||||
export * from "./constants.js";
|
||||
export * from "./version.js";
|
||||
export * from "./scripting.js";
|
19
packages/monorepo/package.json
Normal file
19
packages/monorepo/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@goauthentik/monorepo",
|
||||
"version": "1.0.0",
|
||||
"description": "Utilities for the authentik monorepo.",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./out/index.d.ts"
|
||||
}
|
||||
},
|
||||
"types": "./out/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
}
|
||||
}
|
30
packages/monorepo/paths.js
Normal file
30
packages/monorepo/paths.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { createRequire } from "node:module";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/**
|
||||
* @typedef {'~authentik'} MonoRepoRoot
|
||||
*/
|
||||
|
||||
/**
|
||||
* The root of the authentik monorepo.
|
||||
*/
|
||||
export const MonoRepoRoot = /** @type {MonoRepoRoot} */ (resolve(__dirname, "..", ".."));
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
/**
|
||||
* Resolve a package name to its location in the monorepo to the single node_modules directory.
|
||||
* @param {string} packageName
|
||||
* @returns {string} The resolved path to the package.
|
||||
* @throws {Error} If the package cannot be resolved.
|
||||
*/
|
||||
export function resolvePackage(packageName) {
|
||||
const packageJSONPath = require.resolve(join(packageName, "package.json"), {
|
||||
paths: [MonoRepoRoot],
|
||||
});
|
||||
|
||||
return dirname(packageJSONPath);
|
||||
}
|
40
packages/monorepo/scripting.js
Normal file
40
packages/monorepo/scripting.js
Normal file
@ -0,0 +1,40 @@
|
||||
import { createRequire } from "node:module";
|
||||
import * as path from "node:path";
|
||||
import * as process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/**
|
||||
* Predicate to determine if a module was run directly, i.e. not imported.
|
||||
*
|
||||
* @param {ImportMeta} meta The `import.meta` object of the module.
|
||||
*
|
||||
* @return {boolean} Whether the module was run directly.
|
||||
*/
|
||||
export function isMain(meta) {
|
||||
// Are we not in a module context?
|
||||
if (!meta) return false;
|
||||
|
||||
const relativeScriptPath = process.argv[1];
|
||||
|
||||
if (!relativeScriptPath) return false;
|
||||
|
||||
const require = createRequire(meta.url);
|
||||
const absoluteScriptPath = require.resolve(relativeScriptPath);
|
||||
|
||||
const modulePath = fileURLToPath(meta.url);
|
||||
|
||||
const scriptExtension = path.extname(absoluteScriptPath);
|
||||
|
||||
if (scriptExtension) {
|
||||
return modulePath === absoluteScriptPath;
|
||||
}
|
||||
|
||||
const moduleExtension = path.extname(modulePath);
|
||||
|
||||
if (moduleExtension) {
|
||||
return absoluteScriptPath === modulePath.slice(0, -moduleExtension.length);
|
||||
}
|
||||
|
||||
// If both are without extension, compare them directly.
|
||||
return modulePath === absoluteScriptPath;
|
||||
}
|
0
packages/monorepo/scripts.js
Normal file
0
packages/monorepo/scripts.js
Normal file
9
packages/monorepo/tsconfig.json
Normal file
9
packages/monorepo/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@goauthentik/tsconfig",
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"checkJs": true,
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
45
packages/monorepo/version.js
Normal file
45
packages/monorepo/version.js
Normal file
@ -0,0 +1,45 @@
|
||||
import { execSync } from "node:child_process";
|
||||
|
||||
import PackageJSON from "../../package.json" with { type: "json" };
|
||||
import { MonoRepoRoot } from "./paths.js";
|
||||
|
||||
/**
|
||||
* The current version of authentik in SemVer format.
|
||||
*
|
||||
*/
|
||||
export const AuthentikVersion = /**@type {`${number}.${number}.${number}`} */ (PackageJSON.version);
|
||||
|
||||
/**
|
||||
* Reads the last commit hash from the current git repository.
|
||||
*/
|
||||
export function readGitBuildHash() {
|
||||
try {
|
||||
const commit = execSync("git rev-parse HEAD", {
|
||||
encoding: "utf8",
|
||||
cwd: MonoRepoRoot,
|
||||
})
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
return commit;
|
||||
} catch (_error) {
|
||||
console.debug("Git commit could not be read.");
|
||||
}
|
||||
|
||||
return process.env.GIT_BUILD_HASH || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the build identifier for the current environment.
|
||||
*
|
||||
* This must match the behavior defined in authentik's server-side `get_full_version` function.
|
||||
*
|
||||
* @see {@link "authentik\_\_init\_\_.py"}
|
||||
*/
|
||||
export function readBuildIdentifier() {
|
||||
const { GIT_BUILD_HASH } = process.env;
|
||||
|
||||
if (!GIT_BUILD_HASH) return AuthentikVersion;
|
||||
|
||||
return [AuthentikVersion, GIT_BUILD_HASH].join("+");
|
||||
}
|
18
packages/prettier-config/LICENSE.txt
Normal file
18
packages/prettier-config/LICENSE.txt
Normal file
@ -0,0 +1,18 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Authentik Security, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
5
packages/prettier-config/README.md
Normal file
5
packages/prettier-config/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# `@goauthentik/prettier-config`
|
||||
|
||||
This package contains the Prettier configuration used by authentik.
|
||||
While it is possible to use this configuration outside of our projects,
|
||||
you may find that it is not as useful as other popular configurations.
|
80
packages/prettier-config/config.js
Normal file
80
packages/prettier-config/config.js
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @file Prettier configuration for authentik.
|
||||
*
|
||||
* @import { Config as PrettierConfig } from "prettier";
|
||||
* @import { PluginConfig as SortPluginConfig } from "@trivago/prettier-plugin-sort-imports";
|
||||
*
|
||||
* @typedef {object} PackageJSONPluginConfig
|
||||
* @property {string[]} [packageSortOrder] Custom ordering array.
|
||||
*/
|
||||
|
||||
/**
|
||||
* authentik Prettier configuration.
|
||||
*
|
||||
* @type {PrettierConfig & SortPluginConfig & PackageJSONPluginConfig}
|
||||
* @internal
|
||||
*/
|
||||
export const AuthentikPrettierConfig = {
|
||||
arrowParens: "always",
|
||||
bracketSpacing: true,
|
||||
embeddedLanguageFormatting: "auto",
|
||||
htmlWhitespaceSensitivity: "css",
|
||||
insertPragma: false,
|
||||
jsxSingleQuote: false,
|
||||
printWidth: 100,
|
||||
proseWrap: "preserve",
|
||||
quoteProps: "consistent",
|
||||
requirePragma: false,
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
tabWidth: 4,
|
||||
trailingComma: "all",
|
||||
useTabs: false,
|
||||
vueIndentScriptAndStyle: false,
|
||||
plugins: ["prettier-plugin-packagejson", "@trivago/prettier-plugin-sort-imports"],
|
||||
importOrder: ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
|
||||
importOrderSeparation: true,
|
||||
importOrderSortSpecifiers: true,
|
||||
importOrderParserPlugins: ["typescript", "jsx", "classProperties", "decorators-legacy"],
|
||||
overrides: [
|
||||
{
|
||||
files: "schemas/**/*.json",
|
||||
options: {
|
||||
tabWidth: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: "tsconfig.json",
|
||||
options: {
|
||||
trailingComma: "none",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: "package.json",
|
||||
options: {
|
||||
packageSortOrder: [
|
||||
// ---
|
||||
"name",
|
||||
"version",
|
||||
"description",
|
||||
"license",
|
||||
"private",
|
||||
"author",
|
||||
"authors",
|
||||
"scripts",
|
||||
"main",
|
||||
"type",
|
||||
"exports",
|
||||
"imports",
|
||||
"dependencies",
|
||||
"devDependencies",
|
||||
"peerDependencies",
|
||||
"optionalDependencies",
|
||||
"wireit",
|
||||
"resolutions",
|
||||
"engines",
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
20
packages/prettier-config/format.js
Normal file
20
packages/prettier-config/format.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { format } from "prettier";
|
||||
|
||||
import { AuthentikPrettierConfig } from "./config.js";
|
||||
|
||||
/**
|
||||
* Format using Prettier.
|
||||
*
|
||||
* Defaults to using the TypeScript parser.
|
||||
*
|
||||
* @category Formatting
|
||||
* @param {string} fileContents The contents of the file to format.
|
||||
*
|
||||
* @returns {Promise<string>} The formatted file contents.
|
||||
*/
|
||||
export function formatWithPrettier(fileContents) {
|
||||
return format(fileContents, {
|
||||
...AuthentikPrettierConfig,
|
||||
parser: "typescript",
|
||||
});
|
||||
}
|
6
packages/prettier-config/index.js
Normal file
6
packages/prettier-config/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { AuthentikPrettierConfig } from "./config.js";
|
||||
|
||||
export * from "./config.js";
|
||||
export * from "./format.js";
|
||||
|
||||
export default AuthentikPrettierConfig;
|
27
packages/prettier-config/package.json
Normal file
27
packages/prettier-config/package.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@goauthentik/prettier-config",
|
||||
"version": "1.0.0",
|
||||
"description": "authentik's Prettier config",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": "./index.js",
|
||||
"types": "./out/index.d.ts"
|
||||
}
|
||||
},
|
||||
"types": "./out/index.d.ts",
|
||||
"peerDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-packagejson": "^2.5.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
8
packages/prettier-config/tsconfig.json
Normal file
8
packages/prettier-config/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@goauthentik/tsconfig",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"checkJs": true,
|
||||
"emitDeclarationOnly": true
|
||||
}
|
||||
}
|
18
packages/tsconfig/LICENSE.txt
Normal file
18
packages/tsconfig/LICENSE.txt
Normal file
@ -0,0 +1,18 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Authentik Security, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
6
packages/tsconfig/README.md
Normal file
6
packages/tsconfig/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# `@goauthentik/tsconfig`
|
||||
|
||||
This package contains the TypeScript configuration used by authentik TypeScript projects.
|
||||
|
||||
While it is possible to use this configuration outside of our projects,
|
||||
you may find that it is not as useful as other popular configurations.
|
18
packages/tsconfig/package.json
Normal file
18
packages/tsconfig/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@goauthentik/tsconfig",
|
||||
"version": "1.0.0",
|
||||
"description": "authentik's s base TypeScript configuration.",
|
||||
"keywords": [
|
||||
"tsconfig",
|
||||
"typescript"
|
||||
],
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "tsconfig.json",
|
||||
"engines": {
|
||||
"node": ">=20.11"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
29
packages/tsconfig/tsconfig.json
Normal file
29
packages/tsconfig/tsconfig.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"alwaysStrict": true,
|
||||
"baseUrl": ".",
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": false,
|
||||
"isolatedModules": true,
|
||||
"incremental": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["ESNext"],
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"newLine": "lf",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": false,
|
||||
"outDir": "${configDir}/out",
|
||||
"pretty": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"target": "ESNext",
|
||||
"useUnknownInCatchVariables": true
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "authentik"
|
||||
version = "2025.2.3"
|
||||
version = "2025.2.4"
|
||||
description = ""
|
||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||
requires-python = "==3.12.*"
|
||||
@ -52,7 +52,7 @@ dependencies = [
|
||||
"pydantic-scim",
|
||||
"pyjwt",
|
||||
"pyrad",
|
||||
"python-kadmin-rs ==0.5.3",
|
||||
"python-kadmin-rs ==0.6.0",
|
||||
"pyyaml",
|
||||
"requests-oauthlib",
|
||||
"scim2-filter-parser",
|
||||
|
571
schema.yml
571
schema.yml
File diff suppressed because it is too large
Load Diff
8
web/package-lock.json
generated
8
web/package-lock.json
generated
@ -24,7 +24,7 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.5.7",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@goauthentik/api": "^2025.2.3-1743464496",
|
||||
"@goauthentik/api": "^2025.2.4-1744139776",
|
||||
"@lit-labs/ssr": "^3.2.2",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
@ -1835,9 +1835,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@goauthentik/api": {
|
||||
"version": "2025.2.3-1743464496",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.2.3-1743464496.tgz",
|
||||
"integrity": "sha512-35+SqFNoBZ+WNpyG2Xv/VKYKIIxjwRmIbgX5WZSpc9IlJVv7yyckUYvLpU2F0hZVUMDnxAUE5bsiNn7K4EQslw=="
|
||||
"version": "2025.2.4-1744139776",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.2.4-1744139776.tgz",
|
||||
"integrity": "sha512-4CLjeW19Zc5/WhxOX/welwAI+YvyKp8cR4uBqLCScvyy/fTwB8dH2NnRj4eDv5U+OQpAwdKXZCBY16fWGg0/kg=="
|
||||
},
|
||||
"node_modules/@goauthentik/web": {
|
||||
"resolved": "",
|
||||
|
@ -12,7 +12,7 @@
|
||||
"@floating-ui/dom": "^1.6.11",
|
||||
"@formatjs/intl-listformat": "^7.5.7",
|
||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||
"@goauthentik/api": "^2025.2.3-1743464496",
|
||||
"@goauthentik/api": "^2025.2.4-1744139776",
|
||||
"@lit-labs/ssr": "^3.2.2",
|
||||
"@lit/context": "^1.1.2",
|
||||
"@lit/localize": "^0.12.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { VERSION } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { BrandConfig, ServerConfig } from "@goauthentik/common/global";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
@ -19,7 +19,7 @@ import { AdminApi, CapabilitiesEnum, LicenseSummaryStatusEnum } from "@goauthent
|
||||
@customElement("ak-about-modal")
|
||||
export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton)) {
|
||||
static get styles() {
|
||||
return super.styles.concat(
|
||||
return ModalButton.styles.concat(
|
||||
PFAbout,
|
||||
css`
|
||||
.pf-c-about-modal-box__hero {
|
||||
@ -33,7 +33,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
||||
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
||||
const version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
|
||||
let build: string | TemplateResult = msg("Release");
|
||||
if (globalAK().config.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
if (ServerConfig.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||
build = msg("Development");
|
||||
} else if (version.buildHash !== "") {
|
||||
build = html`<a
|
||||
@ -58,7 +58,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
||||
}
|
||||
|
||||
renderModal() {
|
||||
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle;
|
||||
let product = BrandConfig.brandingTitle || DefaultBrand.brandingTitle;
|
||||
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
|
||||
product += ` ${msg("Enterprise")}`;
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ import "@goauthentik/elements/messages/MessageContainer";
|
||||
import "@goauthentik/elements/messages/MessageContainer";
|
||||
import "@goauthentik/elements/notifications/APIDrawer";
|
||||
import "@goauthentik/elements/notifications/NotificationDrawer";
|
||||
import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch";
|
||||
import "@goauthentik/elements/router/RouterOutlet";
|
||||
import { getRouteParameter, patchRouteParams } from "@goauthentik/elements/router/utils";
|
||||
import "@goauthentik/elements/sidebar/Sidebar";
|
||||
import "@goauthentik/elements/sidebar/SidebarItem";
|
||||
|
||||
@ -37,10 +37,10 @@ import "./AdminSidebar";
|
||||
@customElement("ak-interface-admin")
|
||||
export class AdminInterface extends AuthenticatedInterface {
|
||||
@property({ type: Boolean })
|
||||
notificationDrawerOpen = getRouteParameter("notificationDrawerOpen", false);
|
||||
notificationDrawerOpen = getURLParam("notificationDrawerOpen", false);
|
||||
|
||||
@property({ type: Boolean })
|
||||
apiDrawerOpen = getRouteParameter("apiDrawerOpen", false);
|
||||
apiDrawerOpen = getURLParam("apiDrawerOpen", false);
|
||||
|
||||
ws: WebsocketClient;
|
||||
|
||||
@ -93,14 +93,14 @@ export class AdminInterface extends AuthenticatedInterface {
|
||||
|
||||
window.addEventListener(EVENT_NOTIFICATION_DRAWER_TOGGLE, () => {
|
||||
this.notificationDrawerOpen = !this.notificationDrawerOpen;
|
||||
patchRouteParams({
|
||||
updateURLParams({
|
||||
notificationDrawerOpen: this.notificationDrawerOpen,
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener(EVENT_API_DRAWER_TOGGLE, () => {
|
||||
this.apiDrawerOpen = !this.apiDrawerOpen;
|
||||
patchRouteParams({
|
||||
updateURLParams({
|
||||
apiDrawerOpen: this.apiDrawerOpen,
|
||||
});
|
||||
});
|
||||
@ -123,7 +123,7 @@ export class AdminInterface extends AuthenticatedInterface {
|
||||
super.connectedCallback();
|
||||
|
||||
if (process.env.NODE_ENV === "development" && process.env.WATCHER_URL) {
|
||||
const { ESBuildObserver } = await import("src/development/build-observer");
|
||||
const { ESBuildObserver } = await import("@goauthentik/common/client");
|
||||
|
||||
new ESBuildObserver(process.env.WATCHER_URL);
|
||||
}
|
||||
@ -158,7 +158,7 @@ export class AdminInterface extends AuthenticatedInterface {
|
||||
class="pf-c-page__main"
|
||||
tabindex="-1"
|
||||
id="main-content"
|
||||
defaultURL="/administration/overview"
|
||||
defaultUrl="/administration/overview"
|
||||
.routes=${ROUTES}
|
||||
>
|
||||
</ak-router-outlet>
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
WithCapabilitiesConfig,
|
||||
} from "@goauthentik/elements/Interface/capabilitiesProvider";
|
||||
import { WithVersion } from "@goauthentik/elements/Interface/versionProvider";
|
||||
import { ID_PATTERN, SLUG_PATTERN, UUID_PATTERN } from "@goauthentik/elements/router";
|
||||
import { ID_REGEX, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
|
||||
import { getRootStyle } from "@goauthentik/elements/utils/getRootStyle";
|
||||
import { spread } from "@open-wc/lit-helpers";
|
||||
|
||||
@ -95,127 +95,62 @@ export class AkAdminSidebar extends WithCapabilitiesConfig(WithVersion(AKElement
|
||||
}
|
||||
|
||||
renderSidebarItems(): TemplateResult {
|
||||
// The second attribute type is of string[] to help with the 'activeWhen' control, which was
|
||||
// commonplace and singular enough to merit its own handler.
|
||||
type SidebarEntry = [
|
||||
/**
|
||||
* The pathname to match against. If null, this is a parent item.
|
||||
*/
|
||||
pathname: string | null,
|
||||
/**
|
||||
* The label to display in the sidebar.
|
||||
*/
|
||||
path: string | null,
|
||||
label: string,
|
||||
/**
|
||||
* The attributes to apply to the sidebar item. This is a map of attribute name to value.
|
||||
*
|
||||
* The second attribute type is of string[] to help with the 'activeWhen' control,
|
||||
* which was commonplace and singular enough to merit its own handler.
|
||||
*/
|
||||
attributes?: Record<string, unknown> | string[] | null,
|
||||
/**
|
||||
* The children of this sidebar item. This is a recursive structure.
|
||||
*/
|
||||
attributes?: Record<string, any> | string[] | null, // eslint-disable-line
|
||||
children?: SidebarEntry[],
|
||||
];
|
||||
|
||||
// prettier-ignore
|
||||
const sidebarContent: SidebarEntry[] = [
|
||||
// ---
|
||||
[
|
||||
null,
|
||||
msg("Dashboards"),
|
||||
{ "?expanded": true },
|
||||
[
|
||||
["/administration/overview", msg("Overview")],
|
||||
["/administration/dashboard/users", msg("User Statistics")],
|
||||
["/administration/system-tasks", msg("System Tasks")],
|
||||
],
|
||||
],
|
||||
[
|
||||
null,
|
||||
msg("Applications"),
|
||||
null,
|
||||
[
|
||||
[
|
||||
"/core/applications",
|
||||
msg("Applications"),
|
||||
[`/core/applications/:slug(${SLUG_PATTERN})`],
|
||||
],
|
||||
["/core/providers", msg("Providers"), [`/core/providers/:id(${ID_PATTERN})`]],
|
||||
["/outpost/outposts", msg("Outposts")],
|
||||
],
|
||||
],
|
||||
[
|
||||
null,
|
||||
msg("Events"),
|
||||
null,
|
||||
[
|
||||
["/events/log", msg("Logs"), [`/events/log/:id(${UUID_PATTERN})`]],
|
||||
["/events/rules", msg("Notification Rules")],
|
||||
["/events/transports", msg("Notification Transports")],
|
||||
],
|
||||
],
|
||||
[
|
||||
null,
|
||||
msg("Customization"),
|
||||
null,
|
||||
[
|
||||
["/policy/policies", msg("Policies")],
|
||||
["/core/property-mappings", msg("Property Mappings")],
|
||||
["/blueprints/instances", msg("Blueprints")],
|
||||
["/policy/reputation", msg("Reputation scores")],
|
||||
],
|
||||
],
|
||||
[
|
||||
null,
|
||||
msg("Flows and Stages"),
|
||||
null,
|
||||
[
|
||||
["/flow/flows", msg("Flows"), [`/flow/flows/:slug(${SLUG_PATTERN})`]],
|
||||
["/flow/stages", msg("Stages")],
|
||||
["/flow/stages/prompts", msg("Prompts")],
|
||||
],
|
||||
],
|
||||
[
|
||||
null,
|
||||
msg("Directory"),
|
||||
null,
|
||||
[
|
||||
["/identity/users", msg("Users"), [`/identity/users/:id(${ID_PATTERN})`]],
|
||||
["/identity/groups", msg("Groups"), [`/identity/groups/:id(${UUID_PATTERN})`]],
|
||||
["/identity/roles", msg("Roles"), [`/identity/roles/:id(${UUID_PATTERN})`]],
|
||||
[
|
||||
"/core/sources",
|
||||
msg("Federation and Social login"),
|
||||
[`/core/sources/:slug(${SLUG_PATTERN})`],
|
||||
],
|
||||
["/core/tokens", msg("Tokens and App passwords")],
|
||||
["/flow/stages/invitations", msg("Invitations")],
|
||||
],
|
||||
],
|
||||
[
|
||||
null,
|
||||
msg("System"),
|
||||
null,
|
||||
[
|
||||
["/core/brands", msg("Brands")],
|
||||
["/crypto/certificates", msg("Certificates")],
|
||||
["/outpost/integrations", msg("Outpost Integrations")],
|
||||
["/admin/settings", msg("Settings")],
|
||||
],
|
||||
],
|
||||
[null, msg("Dashboards"), { "?expanded": true }, [
|
||||
["/administration/overview", msg("Overview")],
|
||||
["/administration/dashboard/users", msg("User Statistics")],
|
||||
["/administration/system-tasks", msg("System Tasks")]]],
|
||||
[null, msg("Applications"), null, [
|
||||
["/core/applications", msg("Applications"), [`^/core/applications/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/providers", msg("Providers"), [`^/core/providers/(?<id>${ID_REGEX})$`]],
|
||||
["/outpost/outposts", msg("Outposts")]]],
|
||||
[null, msg("Events"), null, [
|
||||
["/events/log", msg("Logs"), [`^/events/log/(?<id>${UUID_REGEX})$`]],
|
||||
["/events/rules", msg("Notification Rules")],
|
||||
["/events/transports", msg("Notification Transports")]]],
|
||||
[null, msg("Customization"), null, [
|
||||
["/policy/policies", msg("Policies")],
|
||||
["/core/property-mappings", msg("Property Mappings")],
|
||||
["/blueprints/instances", msg("Blueprints")],
|
||||
["/policy/reputation", msg("Reputation scores")]]],
|
||||
[null, msg("Flows and Stages"), null, [
|
||||
["/flow/flows", msg("Flows"), [`^/flow/flows/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/flow/stages", msg("Stages")],
|
||||
["/flow/stages/prompts", msg("Prompts")]]],
|
||||
[null, msg("Directory"), null, [
|
||||
["/identity/users", msg("Users"), [`^/identity/users/(?<id>${ID_REGEX})$`]],
|
||||
["/identity/groups", msg("Groups"), [`^/identity/groups/(?<id>${UUID_REGEX})$`]],
|
||||
["/identity/roles", msg("Roles"), [`^/identity/roles/(?<id>${UUID_REGEX})$`]],
|
||||
["/core/sources", msg("Federation and Social login"), [`^/core/sources/(?<slug>${SLUG_REGEX})$`]],
|
||||
["/core/tokens", msg("Tokens and App passwords")],
|
||||
["/flow/stages/invitations", msg("Invitations")]]],
|
||||
[null, msg("System"), null, [
|
||||
["/core/brands", msg("Brands")],
|
||||
["/crypto/certificates", msg("Certificates")],
|
||||
["/outpost/integrations", msg("Outpost Integrations")],
|
||||
["/admin/settings", msg("Settings")]]],
|
||||
];
|
||||
|
||||
// Typescript requires the type here to correctly type the recursive path
|
||||
type SidebarRenderer = (_: SidebarEntry) => TemplateResult;
|
||||
|
||||
const renderOneSidebarItem: SidebarRenderer = ([pathname, label, attributes, children]) => {
|
||||
const renderOneSidebarItem: SidebarRenderer = ([path, label, attributes, children]) => {
|
||||
const properties = Array.isArray(attributes)
|
||||
? { ".activeWhen": attributes }
|
||||
: (attributes ?? {});
|
||||
|
||||
if (pathname) {
|
||||
properties.pathname = pathname;
|
||||
if (path) {
|
||||
properties.path = path;
|
||||
}
|
||||
|
||||
return html`<ak-sidebar-item ${spread(properties)}>
|
||||
${label ? html`<span slot="label">${label}</span>` : nothing}
|
||||
${map(children, renderOneSidebarItem)}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { parseAPIResponseError, pluckErrorDetail } from "@goauthentik/common/errors/network";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/PageHeader";
|
||||
@ -54,10 +55,12 @@ export class DebugPage extends AKElement {
|
||||
message: "Success",
|
||||
});
|
||||
})
|
||||
.catch((exc) => {
|
||||
.catch(async (error) => {
|
||||
const parsedError = await parseAPIResponseError(error);
|
||||
|
||||
showMessage({
|
||||
level: MessageLevel.error,
|
||||
message: exc,
|
||||
message: pluckErrorDetail(parsedError),
|
||||
});
|
||||
});
|
||||
}}
|
||||
|
@ -1,210 +1,155 @@
|
||||
import "@goauthentik/admin/admin-overview/AdminOverviewPage";
|
||||
import { Route } from "@goauthentik/elements/router/Route";
|
||||
import { ID_PATTERN, SLUG_PATTERN, UUID_PATTERN } from "@goauthentik/elements/router/constants";
|
||||
import { ID_REGEX, Route, SLUG_REGEX, UUID_REGEX } from "@goauthentik/elements/router/Route";
|
||||
|
||||
import { html } from "lit";
|
||||
|
||||
interface IDParameters {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface SlugParameters {
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface UUIDParameters {
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export const ROUTES = [
|
||||
export const ROUTES: Route[] = [
|
||||
// Prevent infinite Shell loops
|
||||
Route.redirect("^/$", "/administration/overview"),
|
||||
Route.redirect("^#.*", "/administration/overview"),
|
||||
Route.redirect("^/library$", "/if/user/", true),
|
||||
new Route(new RegExp("^/$")).redirect("/administration/overview"),
|
||||
new Route(new RegExp("^#.*")).redirect("/administration/overview"),
|
||||
new Route(new RegExp("^/library$")).redirect("/if/user/", true),
|
||||
// statically imported since this is the default route
|
||||
new Route("/administration/overview", () => {
|
||||
new Route(new RegExp("^/administration/overview$"), async () => {
|
||||
return html`<ak-admin-overview></ak-admin-overview>`;
|
||||
}),
|
||||
new Route("/administration/dashboard/users", async () => {
|
||||
new Route(new RegExp("^/administration/dashboard/users$"), async () => {
|
||||
await import("@goauthentik/admin/admin-overview/DashboardUserPage");
|
||||
|
||||
return html`<ak-admin-dashboard-users></ak-admin-dashboard-users>`;
|
||||
}),
|
||||
new Route("/administration/system-tasks", async () => {
|
||||
new Route(new RegExp("^/administration/system-tasks$"), async () => {
|
||||
await import("@goauthentik/admin/system-tasks/SystemTaskListPage");
|
||||
|
||||
return html`<ak-system-task-list></ak-system-task-list>`;
|
||||
}),
|
||||
new Route("/core/providers", async () => {
|
||||
new Route(new RegExp("^/core/providers$"), async () => {
|
||||
await import("@goauthentik/admin/providers/ProviderListPage");
|
||||
|
||||
return html`<ak-provider-list></ak-provider-list>`;
|
||||
}),
|
||||
new Route<IDParameters>(
|
||||
new URLPattern({
|
||||
pathname: `/core/providers/:id(${ID_PATTERN})`,
|
||||
}),
|
||||
async (params) => {
|
||||
await import("@goauthentik/admin/providers/ProviderViewPage");
|
||||
|
||||
return html`<ak-provider-view
|
||||
.providerID=${parseInt(params.id, 10)}
|
||||
></ak-provider-view>`;
|
||||
},
|
||||
),
|
||||
new Route("/core/applications", async () => {
|
||||
new Route(new RegExp(`^/core/providers/(?<id>${ID_REGEX})$`), async (args) => {
|
||||
await import("@goauthentik/admin/providers/ProviderViewPage");
|
||||
return html`<ak-provider-view .providerID=${parseInt(args.id, 10)}></ak-provider-view>`;
|
||||
}),
|
||||
new Route(new RegExp("^/core/applications$"), async () => {
|
||||
await import("@goauthentik/admin/applications/ApplicationListPage");
|
||||
|
||||
return html`<ak-application-list></ak-application-list>`;
|
||||
}),
|
||||
new Route(`/core/applications/:slug(${SLUG_PATTERN})`, async ({ slug }) => {
|
||||
new Route(new RegExp(`^/core/applications/(?<slug>${SLUG_REGEX})$`), async (args) => {
|
||||
await import("@goauthentik/admin/applications/ApplicationViewPage");
|
||||
|
||||
return html`<ak-application-view .applicationSlug=${slug}></ak-application-view>`;
|
||||
return html`<ak-application-view .applicationSlug=${args.slug}></ak-application-view>`;
|
||||
}),
|
||||
new Route("/core/sources", async () => {
|
||||
new Route(new RegExp("^/core/sources$"), async () => {
|
||||
await import("@goauthentik/admin/sources/SourceListPage");
|
||||
|
||||
return html`<ak-source-list></ak-source-list>`;
|
||||
}),
|
||||
new Route(`/core/sources/:slug(${SLUG_PATTERN})`, async ({ slug }) => {
|
||||
new Route(new RegExp(`^/core/sources/(?<slug>${SLUG_REGEX})$`), async (args) => {
|
||||
await import("@goauthentik/admin/sources/SourceViewPage");
|
||||
|
||||
return html`<ak-source-view .sourceSlug=${slug}></ak-source-view>`;
|
||||
return html`<ak-source-view .sourceSlug=${args.slug}></ak-source-view>`;
|
||||
}),
|
||||
new Route("/core/property-mappings", async () => {
|
||||
new Route(new RegExp("^/core/property-mappings$"), async () => {
|
||||
await import("@goauthentik/admin/property-mappings/PropertyMappingListPage");
|
||||
|
||||
return html`<ak-property-mapping-list></ak-property-mapping-list>`;
|
||||
}),
|
||||
new Route("/core/tokens", async () => {
|
||||
new Route(new RegExp("^/core/tokens$"), async () => {
|
||||
await import("@goauthentik/admin/tokens/TokenListPage");
|
||||
|
||||
return html`<ak-token-list></ak-token-list>`;
|
||||
}),
|
||||
new Route("/core/brands", async () => {
|
||||
new Route(new RegExp("^/core/brands"), async () => {
|
||||
await import("@goauthentik/admin/brands/BrandListPage");
|
||||
|
||||
return html`<ak-brand-list></ak-brand-list>`;
|
||||
}),
|
||||
new Route("/policy/policies", async () => {
|
||||
new Route(new RegExp("^/policy/policies$"), async () => {
|
||||
await import("@goauthentik/admin/policies/PolicyListPage");
|
||||
|
||||
return html`<ak-policy-list></ak-policy-list>`;
|
||||
}),
|
||||
new Route("/policy/reputation", async () => {
|
||||
new Route(new RegExp("^/policy/reputation$"), async () => {
|
||||
await import("@goauthentik/admin/policies/reputation/ReputationListPage");
|
||||
|
||||
return html`<ak-policy-reputation-list></ak-policy-reputation-list>`;
|
||||
}),
|
||||
new Route("/identity/groups", async () => {
|
||||
new Route(new RegExp("^/identity/groups$"), async () => {
|
||||
await import("@goauthentik/admin/groups/GroupListPage");
|
||||
|
||||
return html`<ak-group-list></ak-group-list>`;
|
||||
}),
|
||||
new Route<UUIDParameters>(`/identity/groups/:uuid(${UUID_PATTERN})`, async ({ uuid }) => {
|
||||
new Route(new RegExp(`^/identity/groups/(?<uuid>${UUID_REGEX})$`), async (args) => {
|
||||
await import("@goauthentik/admin/groups/GroupViewPage");
|
||||
|
||||
return html`<ak-group-view .groupId=${uuid}></ak-group-view>`;
|
||||
return html`<ak-group-view .groupId=${args.uuid}></ak-group-view>`;
|
||||
}),
|
||||
new Route("/identity/users", async () => {
|
||||
new Route(new RegExp("^/identity/users$"), async () => {
|
||||
await import("@goauthentik/admin/users/UserListPage");
|
||||
|
||||
return html`<ak-user-list></ak-user-list>`;
|
||||
}),
|
||||
new Route<IDParameters>(`/identity/users/:id(${ID_PATTERN})`, async ({ id }) => {
|
||||
new Route(new RegExp(`^/identity/users/(?<id>${ID_REGEX})$`), async (args) => {
|
||||
await import("@goauthentik/admin/users/UserViewPage");
|
||||
|
||||
return html`<ak-user-view .userId=${parseInt(id, 10)}></ak-user-view>`;
|
||||
return html`<ak-user-view .userId=${parseInt(args.id, 10)}></ak-user-view>`;
|
||||
}),
|
||||
new Route("/identity/roles", async () => {
|
||||
new Route(new RegExp("^/identity/roles$"), async () => {
|
||||
await import("@goauthentik/admin/roles/RoleListPage");
|
||||
|
||||
return html`<ak-role-list></ak-role-list>`;
|
||||
}),
|
||||
new Route<IDParameters>(`/identity/roles/:id(${UUID_PATTERN})`, async ({ id }) => {
|
||||
new Route(new RegExp(`^/identity/roles/(?<id>${UUID_REGEX})$`), async (args) => {
|
||||
await import("@goauthentik/admin/roles/RoleViewPage");
|
||||
|
||||
return html`<ak-role-view roleId=${id}></ak-role-view>`;
|
||||
return html`<ak-role-view roleId=${args.id}></ak-role-view>`;
|
||||
}),
|
||||
new Route("/flow/stages/invitations", async () => {
|
||||
new Route(new RegExp("^/flow/stages/invitations$"), async () => {
|
||||
await import("@goauthentik/admin/stages/invitation/InvitationListPage");
|
||||
|
||||
return html`<ak-stage-invitation-list></ak-stage-invitation-list>`;
|
||||
}),
|
||||
new Route("/flow/stages/prompts", async () => {
|
||||
new Route(new RegExp("^/flow/stages/prompts$"), async () => {
|
||||
await import("@goauthentik/admin/stages/prompt/PromptListPage");
|
||||
|
||||
return html`<ak-stage-prompt-list></ak-stage-prompt-list>`;
|
||||
}),
|
||||
new Route("/flow/stages", async () => {
|
||||
new Route(new RegExp("^/flow/stages$"), async () => {
|
||||
await import("@goauthentik/admin/stages/StageListPage");
|
||||
|
||||
return html`<ak-stage-list></ak-stage-list>`;
|
||||
}),
|
||||
new Route("/flow/flows", async () => {
|
||||
new Route(new RegExp("^/flow/flows$"), async () => {
|
||||
await import("@goauthentik/admin/flows/FlowListPage");
|
||||
|
||||
return html`<ak-flow-list></ak-flow-list>`;
|
||||
}),
|
||||
new Route<SlugParameters>(`/flow/flows/:slug(${SLUG_PATTERN})`, async ({ slug }) => {
|
||||
new Route(new RegExp(`^/flow/flows/(?<slug>${SLUG_REGEX})$`), async (args) => {
|
||||
await import("@goauthentik/admin/flows/FlowViewPage");
|
||||
|
||||
return html`<ak-flow-view .flowSlug=${slug}></ak-flow-view>`;
|
||||
return html`<ak-flow-view .flowSlug=${args.slug}></ak-flow-view>`;
|
||||
}),
|
||||
new Route("/events/log", async () => {
|
||||
new Route(new RegExp("^/events/log$"), async () => {
|
||||
await import("@goauthentik/admin/events/EventListPage");
|
||||
|
||||
return html`<ak-event-list></ak-event-list>`;
|
||||
}),
|
||||
new Route<IDParameters>(`/events/log/:id(${UUID_PATTERN})`, async ({ id }) => {
|
||||
new Route(new RegExp(`^/events/log/(?<id>${UUID_REGEX})$`), async (args) => {
|
||||
await import("@goauthentik/admin/events/EventViewPage");
|
||||
|
||||
return html`<ak-event-view .eventID=${id}></ak-event-view>`;
|
||||
return html`<ak-event-view .eventID=${args.id}></ak-event-view>`;
|
||||
}),
|
||||
new Route("/events/transports", async () => {
|
||||
new Route(new RegExp("^/events/transports$"), async () => {
|
||||
await import("@goauthentik/admin/events/TransportListPage");
|
||||
|
||||
return html`<ak-event-transport-list></ak-event-transport-list>`;
|
||||
}),
|
||||
new Route("/events/rules", async () => {
|
||||
new Route(new RegExp("^/events/rules$"), async () => {
|
||||
await import("@goauthentik/admin/events/RuleListPage");
|
||||
|
||||
return html`<ak-event-rule-list></ak-event-rule-list>`;
|
||||
}),
|
||||
new Route("/outpost/outposts", async () => {
|
||||
new Route(new RegExp("^/outpost/outposts$"), async () => {
|
||||
await import("@goauthentik/admin/outposts/OutpostListPage");
|
||||
|
||||
return html`<ak-outpost-list></ak-outpost-list>`;
|
||||
}),
|
||||
new Route("/outpost/integrations", async () => {
|
||||
new Route(new RegExp("^/outpost/integrations$"), async () => {
|
||||
await import("@goauthentik/admin/outposts/ServiceConnectionListPage");
|
||||
|
||||
return html`<ak-outpost-service-connection-list></ak-outpost-service-connection-list>`;
|
||||
}),
|
||||
new Route("/crypto/certificates", async () => {
|
||||
new Route(new RegExp("^/crypto/certificates$"), async () => {
|
||||
await import("@goauthentik/admin/crypto/CertificateKeyPairListPage");
|
||||
|
||||
return html`<ak-crypto-certificate-list></ak-crypto-certificate-list>`;
|
||||
}),
|
||||
new Route("/admin/settings", async () => {
|
||||
new Route(new RegExp("^/admin/settings$"), async () => {
|
||||
await import("@goauthentik/admin/admin-settings/AdminSettingsPage");
|
||||
|
||||
return html`<ak-admin-settings></ak-admin-settings>`;
|
||||
}),
|
||||
new Route("/blueprints/instances", async () => {
|
||||
new Route(new RegExp("^/blueprints/instances$"), async () => {
|
||||
await import("@goauthentik/admin/blueprints/BlueprintListPage");
|
||||
|
||||
return html`<ak-blueprint-list></ak-blueprint-list>`;
|
||||
}),
|
||||
new Route("/debug", async () => {
|
||||
new Route(new RegExp("^/debug$"), async () => {
|
||||
await import("@goauthentik/admin/DebugPage");
|
||||
|
||||
return html`<ak-admin-debug-page></ak-admin-debug-page>`;
|
||||
}),
|
||||
new Route("/enterprise/licenses", async () => {
|
||||
new Route(new RegExp("^/enterprise/licenses$"), async () => {
|
||||
await import("@goauthentik/admin/enterprise/EnterpriseLicenseListPage");
|
||||
|
||||
return html`<ak-enterprise-license-list></ak-enterprise-license-list>`;
|
||||
}),
|
||||
] satisfies Route<never>[];
|
||||
];
|
||||
|
@ -16,7 +16,7 @@ import "@goauthentik/elements/PageHeader";
|
||||
import "@goauthentik/elements/cards/AggregatePromiseCard";
|
||||
import "@goauthentik/elements/cards/QuickActionsCard.js";
|
||||
import type { QuickAction } from "@goauthentik/elements/cards/QuickActionsCard.js";
|
||||
import { formatRouteHash } from "@goauthentik/elements/router";
|
||||
import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
@ -79,13 +79,10 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
||||
}
|
||||
|
||||
quickActions: QuickAction[] = [
|
||||
[
|
||||
msg("Create a new application"),
|
||||
formatRouteHash("/core/applications", { createForm: true }),
|
||||
],
|
||||
[msg("Check the logs"), formatRouteHash("/events/log")],
|
||||
[msg("Create a new application"), paramURL("/core/applications", { createForm: true })],
|
||||
[msg("Check the logs"), paramURL("/events/log")],
|
||||
[msg("Explore integrations"), "https://goauthentik.io/integrations/", true],
|
||||
[msg("Manage users"), formatRouteHash("/identity/users")],
|
||||
[msg("Manage users"), paramURL("/identity/users")],
|
||||
[msg("Check the release notes"), `https://goauthentik.io/docs/releases/${RELEASE}`, true],
|
||||
];
|
||||
|
||||
@ -198,13 +195,10 @@ export class AdminOverviewPage extends AdminOverviewBase {
|
||||
const release = `${versionFamily()}#fixed-in-${VERSION.replaceAll(".", "")}`;
|
||||
|
||||
const quickActions: [string, string][] = [
|
||||
[
|
||||
msg("Create a new application"),
|
||||
formatRouteHash("/core/applications", { createForm: true }),
|
||||
],
|
||||
[msg("Check the logs"), formatRouteHash("/events/log")],
|
||||
[msg("Create a new application"), paramURL("/core/applications", { createForm: true })],
|
||||
[msg("Check the logs"), paramURL("/events/log")],
|
||||
[msg("Explore integrations"), "https://goauthentik.io/integrations/"],
|
||||
[msg("Manage users"), formatRouteHash("/identity/users")],
|
||||
[msg("Manage users"), paramURL("/identity/users")],
|
||||
[msg("Check the release notes"), `https://goauthentik.io/docs/releases/${release}`],
|
||||
];
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
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 { ResponseError } from "@goauthentik/api";
|
||||
|
||||
export interface AdminStatus {
|
||||
icon: string;
|
||||
message?: TemplateResult;
|
||||
@ -29,7 +32,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
|
||||
// Current error state if any request fails
|
||||
@state()
|
||||
protected error?: string;
|
||||
protected error?: APIError;
|
||||
|
||||
// Abstract methods to be implemented by subclasses
|
||||
abstract getPrimaryValue(): Promise<T>;
|
||||
@ -59,9 +62,9 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
this.value = value; // Triggers shouldUpdate
|
||||
this.error = undefined;
|
||||
})
|
||||
.catch((err: ResponseError) => {
|
||||
.catch(async (error: unknown) => {
|
||||
this.status = undefined;
|
||||
this.error = err?.response?.statusText ?? msg("Unknown error");
|
||||
this.error = await parseAPIResponseError(error);
|
||||
});
|
||||
}
|
||||
|
||||
@ -79,9 +82,9 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
this.status = status;
|
||||
this.error = undefined;
|
||||
})
|
||||
.catch((err: ResponseError) => {
|
||||
.catch(async (error: unknown) => {
|
||||
this.status = undefined;
|
||||
this.error = err?.response?.statusText ?? msg("Unknown error");
|
||||
this.error = await parseAPIResponseError(error);
|
||||
});
|
||||
|
||||
// Prevent immediate re-render if only value changed
|
||||
@ -120,8 +123,8 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
*/
|
||||
private renderError(error: string): TemplateResult {
|
||||
return html`
|
||||
<p><i class="fa fa-times"></i> ${error}</p>
|
||||
<p class="subtext">${msg("Failed to fetch")}</p>
|
||||
<p><i class="fa fa-times"></i> ${msg("Failed to fetch")}</p>
|
||||
<p class="subtext">${error}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -146,7 +149,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
|
||||
this.status
|
||||
? this.renderStatus(this.status) // Status available
|
||||
: this.error
|
||||
? this.renderError(this.error) // Error state
|
||||
? this.renderError(pluckErrorDetail(this.error)) // Error state
|
||||
: this.renderLoading() // Loading state
|
||||
}
|
||||
</p>
|
||||
|
@ -2,7 +2,7 @@ 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";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||
import "@goauthentik/components/ak-event-info";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
import "@goauthentik/elements/buttons/Dropdown";
|
||||
@ -10,6 +10,7 @@ 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";
|
||||
@ -68,12 +69,12 @@ export class RecentEventsCard extends Table<Event> {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
row(item: EventWithContext): TemplateResult[] {
|
||||
row(item: EventWithContext): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`<div><a href="${`#/events/log/${item.pk}`}">${actionToLabel(item.action)}</a></div>
|
||||
<small>${item.app}</small>`,
|
||||
EventUser(item),
|
||||
html`<div>${getRelativeTime(item.created)}</div>
|
||||
html`<div>${formatElapsedTime(item.created)}</div>
|
||||
<small>${item.created.toLocaleString()}</small>`,
|
||||
html` <div>${item.clientIp || msg("-")}</div>
|
||||
<small>${EventGeo(item)}</small>`,
|
||||
@ -81,7 +82,11 @@ export class RecentEventsCard extends Table<Event> {
|
||||
];
|
||||
}
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
renderEmpty(inner?: SlottedTemplateResult): TemplateResult {
|
||||
if (this.error) {
|
||||
return super.renderEmpty(inner);
|
||||
}
|
||||
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state header=${msg("No Events found.")}>
|
||||
<div slot="body">${msg("No matching events could be found.")}</div>
|
||||
|
@ -7,7 +7,7 @@ import "@goauthentik/elements/ak-mdx";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
import { getRouteParameter } from "@goauthentik/elements/router/utils";
|
||||
import { getURLParam } from "@goauthentik/elements/router/RouteMatch";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { TableColumn } from "@goauthentik/elements/table/Table";
|
||||
import { TablePage } from "@goauthentik/elements/table/TablePage";
|
||||
@ -71,7 +71,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(PFCard, applicationListStyle);
|
||||
return TablePage.styles.concat(PFCard, applicationListStyle);
|
||||
}
|
||||
|
||||
columns(): TableColumn[] {
|
||||
@ -156,7 +156,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html` <ak-application-wizard .open=${getRouteParameter("createWizard", false)}>
|
||||
return html` <ak-application-wizard .open=${getURLParam("createWizard", false)}>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="pf-c-button pf-m-primary"
|
||||
@ -165,7 +165,7 @@ export class ApplicationListPage extends WithBrandConfig(TablePage<Application>)
|
||||
${msg("Create with Provider")}
|
||||
</button>
|
||||
</ak-application-wizard>
|
||||
<ak-forms-modal .open=${getRouteParameter("createForm", false)}>
|
||||
<ak-forms-modal .open=${getURLParam("createForm", false)}>
|
||||
<span slot="submit"> ${msg("Create")} </span>
|
||||
<span slot="header"> ${msg("Create Application")} </span>
|
||||
<ak-application-form slot="form"> </ak-application-form>
|
||||
|
@ -8,7 +8,7 @@ import "@goauthentik/components/ak-hint/ak-hint-body";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/Label";
|
||||
import "@goauthentik/elements/buttons/ActionButton/ak-action-button";
|
||||
import { getRouteParameter } from "@goauthentik/elements/router/utils";
|
||||
import { getURLParam } from "@goauthentik/elements/router/RouteMatch";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { css, html } from "lit";
|
||||
@ -110,7 +110,7 @@ export class AkApplicationWizardHint extends AKElement implements ShowHintContro
|
||||
the same time with our new Application Wizard.
|
||||
<!-- <a href="(link to docs)">Learn more about the wizard here.</a> -->
|
||||
</p>
|
||||
<ak-application-wizard .open=${getRouteParameter("createWizard", false)}>
|
||||
<ak-application-wizard .open=${getURLParam("createWizard", false)}>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="pf-c-button pf-m-primary"
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { styles } from "@goauthentik/admin/applications/wizard/ApplicationWizardFormStepStyles.css.js";
|
||||
import { WizardStep } from "@goauthentik/components/ak-wizard/WizardStep.js";
|
||||
import {
|
||||
NavigationUpdate,
|
||||
NavigationEventInit,
|
||||
WizardNavigationEvent,
|
||||
WizardUpdateEvent,
|
||||
} from "@goauthentik/components/ak-wizard/events";
|
||||
@ -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 ExtendedValidationError,
|
||||
keyToDelete: keyof ApplicationTransactionValidationError,
|
||||
): ValidationError | undefined {
|
||||
if (!this.wizard.errors) {
|
||||
return undefined;
|
||||
@ -71,7 +71,7 @@ export class ApplicationWizardStep extends WizardStep {
|
||||
public handleUpdate(
|
||||
update?: ApplicationWizardStateUpdate,
|
||||
destination?: string,
|
||||
enable?: NavigationUpdate,
|
||||
enable?: NavigationEventInit,
|
||||
) {
|
||||
// Inform ApplicationWizard of content state
|
||||
if (update) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { policyOptions } from "@goauthentik/admin/applications/PolicyOptions.js";
|
||||
import { ApplicationWizardStep } from "@goauthentik/admin/applications/wizard/ApplicationWizardStep.js";
|
||||
import "@goauthentik/admin/applications/wizard/ak-wizard-title.js";
|
||||
import { isSlug } from "@goauthentik/common/utils.js";
|
||||
import { camelToSnake } from "@goauthentik/common/utils.js";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-slug-input";
|
||||
@ -10,7 +11,6 @@ import { type NavigableButton, type WizardButton } from "@goauthentik/components
|
||||
import { type KeyUnknown } from "@goauthentik/elements/forms/Form";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { isSlug } from "@goauthentik/elements/router";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html } from "lit";
|
||||
|
@ -1,9 +1,10 @@
|
||||
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 { parseAPIError } from "@goauthentik/common/errors";
|
||||
import { parseAPIResponseError } from "@goauthentik/common/errors/network";
|
||||
import { WizardNavigationEvent } from "@goauthentik/components/ak-wizard/events.js";
|
||||
import { type WizardButton } from "@goauthentik/components/ak-wizard/types";
|
||||
import { showAPIErrorMessage } from "@goauthentik/elements/messages/MessageContainer";
|
||||
import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||
import { P, match } from "ts-pattern";
|
||||
|
||||
@ -30,10 +31,11 @@ import {
|
||||
type TransactionApplicationRequest,
|
||||
type TransactionApplicationResponse,
|
||||
type TransactionPolicyBindingRequest,
|
||||
instanceOfValidationError,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import { ApplicationWizardStep } from "../ApplicationWizardStep.js";
|
||||
import { ExtendedValidationError, OneOfProvider } from "../types.js";
|
||||
import { OneOfProvider, isApplicationTransactionValidationError } from "../types.js";
|
||||
import { providerRenderers } from "./SubmitStepOverviewRenderers.js";
|
||||
|
||||
const _submitStates = ["reviewing", "running", "submitted"] as const;
|
||||
@ -131,39 +133,46 @@ 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";
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.catch(async (resolution: any) => {
|
||||
const errors = (await parseAPIError(
|
||||
await resolution,
|
||||
)) as ExtendedValidationError;
|
||||
.catch(async (error) => {
|
||||
const parsedError = await parseAPIResponseError(error);
|
||||
|
||||
// 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;
|
||||
if (!instanceOfValidationError(parsedError)) {
|
||||
showAPIErrorMessage(parsedError);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isApplicationTransactionValidationError(parsedError)) {
|
||||
// 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(parsedError.app?.provider)) {
|
||||
const providerError = parsedError.app.provider;
|
||||
|
||||
parsedError.provider = {
|
||||
...parsedError.provider,
|
||||
name: providerError,
|
||||
};
|
||||
|
||||
delete parsedError.app.provider;
|
||||
|
||||
if (Object.keys(parsedError.app).length === 0) {
|
||||
delete parsedError.app;
|
||||
}
|
||||
}
|
||||
this.handleUpdate({ errors });
|
||||
this.state = "reviewing";
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
this.handleUpdate({ errors: parsedError });
|
||||
this.state = "reviewing";
|
||||
});
|
||||
}
|
||||
|
||||
override handleButton(button: WizardButton) {
|
||||
@ -225,22 +234,20 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
||||
}
|
||||
|
||||
renderError() {
|
||||
if (Object.keys(this.wizard.errors).length === 0) {
|
||||
return nothing;
|
||||
}
|
||||
const { errors } = this.wizard;
|
||||
|
||||
if (Object.keys(errors).length === 0) return nothing;
|
||||
|
||||
const navTo = (step: string) => () => this.dispatchEvent(new WizardNavigationEvent(step));
|
||||
const errors = this.wizard.errors;
|
||||
return html` <hr class="pf-c-divider" />
|
||||
${match(errors as ExtendedValidationError)
|
||||
${match(errors)
|
||||
.with(
|
||||
{ app: P.nonNullable },
|
||||
() =>
|
||||
html`<p>${msg("There was an error in the application.")}</p>
|
||||
<p>
|
||||
<a @click=${navTo("application")}
|
||||
>${msg("Review the application.")}</a
|
||||
>
|
||||
<a @click=${WizardNavigationEvent.toListener(this, "application")}>
|
||||
${msg("Review the application.")}
|
||||
</a>
|
||||
</p>`,
|
||||
)
|
||||
.with(
|
||||
@ -248,13 +255,20 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
||||
() =>
|
||||
html`<p>${msg("There was an error in the provider.")}</p>
|
||||
<p>
|
||||
<a @click=${navTo("provider")}>${msg("Review the provider.")}</a>
|
||||
<a @click=${WizardNavigationEvent.toListener(this, "provider")}
|
||||
>${msg("Review the provider.")}</a
|
||||
>
|
||||
</p>`,
|
||||
)
|
||||
.with(
|
||||
{ detail: P.nonNullable },
|
||||
() =>
|
||||
`<p>${msg("There was an error. Please go back and review the application.")}: ${errors.detail}</p>`,
|
||||
html`<p>
|
||||
${msg(
|
||||
"There was an error. Please go back and review the application.",
|
||||
)}:
|
||||
${errors.detail}
|
||||
</p>`,
|
||||
)
|
||||
.with(
|
||||
{
|
||||
@ -264,7 +278,7 @@ export class ApplicationWizardSubmitStep extends CustomEmitterElement(Applicatio
|
||||
html`<p>${msg("There was an error:")}:</p>
|
||||
<ul>
|
||||
${(errors.nonFieldErrors ?? []).map(
|
||||
(e: string) => html`<li>${e}</li>`,
|
||||
(reason) => html`<li>${reason}</li>`,
|
||||
)}
|
||||
</ul>
|
||||
<p>${msg("Please go back and review the application.")}</p>`,
|
||||
|
@ -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 { ExtendedValidationError } from "../../types.js";
|
||||
import { ApplicationTransactionValidationError } 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: ExtendedValidationError) {
|
||||
renderForm(provider: OAuth2Provider, errors: ApplicationTransactionValidationError) {
|
||||
const showClientSecretCallback = (show: boolean) => {
|
||||
this.showClientSecret = show;
|
||||
};
|
||||
|
@ -25,16 +25,30 @@ export type OneOfProvider =
|
||||
|
||||
export type ValidationRecord = { [key: string]: string[] };
|
||||
|
||||
// TODO: Elf, extend this type and apply it to every object in the wizard. Then run
|
||||
// the type-checker again.
|
||||
|
||||
export type ExtendedValidationError = ValidationError & {
|
||||
/**
|
||||
* 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 {
|
||||
app?: ValidationRecord;
|
||||
provider?: ValidationRecord;
|
||||
bindings?: ValidationRecord;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
detail?: any;
|
||||
};
|
||||
detail?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-guard to determine if an API response is shaped like an {@linkcode ApplicationTransactionValidationError}.
|
||||
*/
|
||||
export function isApplicationTransactionValidationError(
|
||||
error: ValidationError,
|
||||
): 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
|
||||
@ -49,7 +63,7 @@ export interface ApplicationWizardState {
|
||||
proxyMode: ProxyMode;
|
||||
bindings: PolicyBinding[];
|
||||
currentBinding: number;
|
||||
errors: ExtendedValidationError;
|
||||
errors: ValidationError | ApplicationTransactionValidationError;
|
||||
}
|
||||
|
||||
export interface ApplicationWizardStateUpdate {
|
||||
|
@ -2,7 +2,7 @@ import "@goauthentik/admin/blueprints/BlueprintForm";
|
||||
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||
import "@goauthentik/components/ak-status-label";
|
||||
import "@goauthentik/elements/buttons/ActionButton";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
@ -141,7 +141,7 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
||||
html`<div>${item.name}</div>
|
||||
${description ? html`<small>${description}</small>` : html``}`,
|
||||
html`${BlueprintStatus(item)}`,
|
||||
html`<div>${getRelativeTime(item.lastApplied)}</div>
|
||||
html`<div>${formatElapsedTime(item.lastApplied)}</div>
|
||||
<small>${item.lastApplied.toLocaleString()}</small>`,
|
||||
html`<ak-status-label ?good=${item.enabled}></ak-status-label>`,
|
||||
html`<ak-forms-modal>
|
||||
|
@ -2,7 +2,7 @@ import "@goauthentik/admin/enterprise/EnterpriseLicenseForm";
|
||||
import "@goauthentik/admin/enterprise/EnterpriseStatusCard";
|
||||
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||
import { PFColor } from "@goauthentik/elements/Label";
|
||||
import "@goauthentik/elements/Spinner";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
@ -186,7 +186,7 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
||||
>
|
||||
${this.summary &&
|
||||
this.summary?.status !== LicenseSummaryStatusEnum.Unlicensed
|
||||
? html`<div>${getRelativeTime(this.summary.latestValid)}</div>
|
||||
? html`<div>${formatElapsedTime(this.summary.latestValid)}</div>
|
||||
<small>${this.summary.latestValid.toLocaleString()}</small>`
|
||||
: "-"}
|
||||
</ak-aggregate-card>
|
||||
|
@ -3,11 +3,12 @@ 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";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||
import "@goauthentik/components/ak-event-info";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { TableColumn } from "@goauthentik/elements/table/Table";
|
||||
import { TablePage } from "@goauthentik/elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "@goauthentik/elements/types";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
@ -72,12 +73,12 @@ export class EventListPage extends TablePage<Event> {
|
||||
`;
|
||||
}
|
||||
|
||||
row(item: EventWithContext): TemplateResult[] {
|
||||
row(item: EventWithContext): SlottedTemplateResult[] {
|
||||
return [
|
||||
html`<div>${actionToLabel(item.action)}</div>
|
||||
<small>${item.app}</small>`,
|
||||
EventUser(item),
|
||||
html`<div>${getRelativeTime(item.created)}</div>
|
||||
html`<div>${formatElapsedTime(item.created)}</div>
|
||||
<small>${item.created.toLocaleString()}</small>`,
|
||||
html`<div>${item.clientIp || msg("-")}</div>
|
||||
<small>${EventGeo(item)}</small>`,
|
||||
|
@ -2,7 +2,7 @@ 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";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||
import "@goauthentik/components/ak-event-info";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/PageHeader";
|
||||
@ -104,7 +104,7 @@ export class EventViewPage extends AKElement {
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<div>${getRelativeTime(this.event.created)}</div>
|
||||
<div>${formatElapsedTime(this.event.created)}</div>
|
||||
<small>${this.event.created.toLocaleString()}</small>
|
||||
</div>
|
||||
</dd>
|
||||
|
@ -1,27 +1,31 @@
|
||||
import { EventWithContext } from "@goauthentik/common/events";
|
||||
import { truncate } from "@goauthentik/common/utils";
|
||||
import { KeyUnknown } from "@goauthentik/elements/forms/Form";
|
||||
import { SlottedTemplateResult } from "@goauthentik/elements/types";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { html, nothing } from "lit";
|
||||
|
||||
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``;
|
||||
/**
|
||||
* Given event with a geographical context, format it into a string for display.
|
||||
*/
|
||||
export function EventGeo(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 EventUser(event: EventWithContext, truncateUsername?: number): TemplateResult {
|
||||
if (!event.user.username) {
|
||||
return html`-`;
|
||||
}
|
||||
let body = html``;
|
||||
export function EventUser(
|
||||
event: EventWithContext,
|
||||
truncateUsername?: number,
|
||||
): SlottedTemplateResult {
|
||||
if (!event.user.username) return html`-`;
|
||||
|
||||
let body: SlottedTemplateResult = nothing;
|
||||
|
||||
if (event.user.is_anonymous) {
|
||||
body = html`<div>${msg("Anonymous user")}</div>`;
|
||||
} else {
|
||||
@ -33,12 +37,14 @@ export function EventUser(event: EventWithContext, truncateUsername?: number): T
|
||||
>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (event.user.on_behalf_of) {
|
||||
body = html`${body}<small>
|
||||
return 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;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user