Compare commits
144 Commits
sfe-packag
...
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 |
@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 2025.2.3
|
current_version = 2025.2.4
|
||||||
tag = True
|
tag = True
|
||||||
commit = 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*))?
|
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<rc_t>[a-zA-Z-]+)(?P<rc_n>[1-9]\\d*))?
|
||||||
|
|||||||
5
.github/workflows/api-ts-publish.yml
vendored
5
.github/workflows/api-ts-publish.yml
vendored
@ -36,6 +36,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'`
|
export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'`
|
||||||
npm i @goauthentik/api@$VERSION
|
npm i @goauthentik/api@$VERSION
|
||||||
|
- name: Upgrade /web/packages/sfe
|
||||||
|
working-directory: web/packages/sfe
|
||||||
|
run: |
|
||||||
|
export VERSION=`node -e 'console.log(require("../gen-ts-api/package.json").version)'`
|
||||||
|
npm i @goauthentik/api@$VERSION
|
||||||
- uses: peter-evans/create-pull-request@v7
|
- uses: peter-evans/create-pull-request@v7
|
||||||
id: cpr
|
id: cpr
|
||||||
with:
|
with:
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,6 +33,7 @@ eggs/
|
|||||||
lib64/
|
lib64/
|
||||||
parts/
|
parts/
|
||||||
dist/
|
dist/
|
||||||
|
out/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
|
|||||||
@ -30,6 +30,7 @@ WORKDIR /work/web
|
|||||||
|
|
||||||
RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \
|
RUN --mount=type=bind,target=/work/web/package.json,src=./web/package.json \
|
||||||
--mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \
|
--mount=type=bind,target=/work/web/package-lock.json,src=./web/package-lock.json \
|
||||||
|
--mount=type=bind,target=/work/web/packages/sfe/package.json,src=./web/packages/sfe/package.json \
|
||||||
--mount=type=bind,target=/work/web/scripts,src=./web/scripts \
|
--mount=type=bind,target=/work/web/scripts,src=./web/scripts \
|
||||||
--mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \
|
--mount=type=cache,id=npm-web,sharing=shared,target=/root/.npm \
|
||||||
npm ci --include=dev
|
npm ci --include=dev
|
||||||
@ -93,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"
|
/bin/sh -c "/usr/bin/entry.sh || echo 'Failed to get GeoIP database, disabling'; exit 0"
|
||||||
|
|
||||||
# Stage 5: Download uv
|
# Stage 5: Download uv
|
||||||
FROM ghcr.io/astral-sh/uv:0.6.12 AS uv
|
FROM ghcr.io/astral-sh/uv:0.6.14 AS uv
|
||||||
# Stage 6: Base python image
|
# Stage 6: Base python image
|
||||||
FROM ghcr.io/goauthentik/fips-python:3.12.9-slim-bookworm-fips AS python-base
|
FROM ghcr.io/goauthentik/fips-python:3.12.9-slim-bookworm-fips AS python-base
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
__version__ = "2025.2.3"
|
__version__ = "2025.2.4"
|
||||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -228,6 +228,7 @@ class UserSerializer(ModelSerializer):
|
|||||||
"name",
|
"name",
|
||||||
"is_active",
|
"is_active",
|
||||||
"last_login",
|
"last_login",
|
||||||
|
"date_joined",
|
||||||
"is_superuser",
|
"is_superuser",
|
||||||
"groups",
|
"groups",
|
||||||
"groups_obj",
|
"groups_obj",
|
||||||
@ -242,6 +243,7 @@ class UserSerializer(ModelSerializer):
|
|||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
"name": {"allow_blank": True},
|
"name": {"allow_blank": True},
|
||||||
|
"date_joined": {"read_only": True},
|
||||||
"password_change_date": {"read_only": True},
|
"password_change_date": {"read_only": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,6 @@ from authentik.flows.planner import (
|
|||||||
)
|
)
|
||||||
from authentik.flows.stage import StageView
|
from authentik.flows.stage import StageView
|
||||||
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET
|
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.lib.views import bad_request_message
|
||||||
from authentik.policies.denied import AccessDeniedResponse
|
from authentik.policies.denied import AccessDeniedResponse
|
||||||
from authentik.policies.utils import delete_none_values
|
from authentik.policies.utils import delete_none_values
|
||||||
@ -210,8 +209,6 @@ class SourceFlowManager:
|
|||||||
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
||||||
NEXT_ARG_NAME, "authentik_core:if-user"
|
NEXT_ARG_NAME, "authentik_core:if-user"
|
||||||
)
|
)
|
||||||
if not is_url_absolute(final_redirect):
|
|
||||||
final_redirect = "authentik_core:if-user"
|
|
||||||
flow_context.update(
|
flow_context.update(
|
||||||
{
|
{
|
||||||
# Since we authenticate the user by their token, they have no backend set
|
# Since we authenticate the user by their token, they have no backend set
|
||||||
|
|||||||
@ -49,6 +49,6 @@
|
|||||||
</main>
|
</main>
|
||||||
<span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span>
|
<span class="mt-3 mb-0 text-muted text-center">{% trans 'Powered by authentik' %}</span>
|
||||||
</div>
|
</div>
|
||||||
<script src="{% static 'dist/sfe/main.js' %}"></script>
|
<script src="{% static 'dist/sfe/index.js' %}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -28,8 +28,8 @@ def pytest_report_header(*_, **__):
|
|||||||
|
|
||||||
|
|
||||||
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
|
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
|
||||||
current_id = int(environ.get("CI_RUN_ID", 0)) - 1
|
current_id = int(environ.get("CI_RUN_ID", "0")) - 1
|
||||||
total_ids = int(environ.get("CI_TOTAL_RUNS", 0))
|
total_ids = int(environ.get("CI_TOTAL_RUNS", "0"))
|
||||||
|
|
||||||
if total_ids:
|
if total_ids:
|
||||||
num_tests = len(items)
|
num_tests = len(items)
|
||||||
|
|||||||
@ -99,6 +99,7 @@ class LDAPSourceSerializer(SourceSerializer):
|
|||||||
"sync_groups",
|
"sync_groups",
|
||||||
"sync_parent_group",
|
"sync_parent_group",
|
||||||
"connectivity",
|
"connectivity",
|
||||||
|
"lookup_groups_from_user",
|
||||||
]
|
]
|
||||||
extra_kwargs = {"bind_password": {"write_only": True}}
|
extra_kwargs = {"bind_password": {"write_only": True}}
|
||||||
|
|
||||||
@ -134,6 +135,7 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet):
|
|||||||
"sync_parent_group",
|
"sync_parent_group",
|
||||||
"user_property_mappings",
|
"user_property_mappings",
|
||||||
"group_property_mappings",
|
"group_property_mappings",
|
||||||
|
"lookup_groups_from_user",
|
||||||
]
|
]
|
||||||
search_fields = ["name", "slug"]
|
search_fields = ["name", "slug"]
|
||||||
ordering = ["name"]
|
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
|
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
|
@property
|
||||||
def component(self) -> str:
|
def component(self) -> str:
|
||||||
return "ak-source-ldap-form"
|
return "ak-source-ldap-form"
|
||||||
|
|||||||
@ -28,15 +28,17 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||||||
if not self._source.sync_groups:
|
if not self._source.sync_groups:
|
||||||
self.message("Group syncing is disabled for this Source")
|
self.message("Group syncing is disabled for this Source")
|
||||||
return iter(())
|
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(
|
return self.search_paginator(
|
||||||
search_base=self.base_dn_groups,
|
search_base=self.base_dn_groups,
|
||||||
search_filter=self._source.group_object_filter,
|
search_filter=self._source.group_object_filter,
|
||||||
search_scope=SUBTREE,
|
search_scope=SUBTREE,
|
||||||
attributes=[
|
attributes=attributes,
|
||||||
self._source.group_membership_field,
|
|
||||||
self._source.object_uniqueness_field,
|
|
||||||
LDAP_DISTINGUISHED_NAME,
|
|
||||||
],
|
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,9 +49,24 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||||||
return -1
|
return -1
|
||||||
membership_count = 0
|
membership_count = 0
|
||||||
for group in page_data:
|
for group in page_data:
|
||||||
if "attributes" not in group:
|
if self._source.lookup_groups_from_user:
|
||||||
continue
|
group_dn = group.get("dn", {})
|
||||||
members = group.get("attributes", {}).get(self._source.group_membership_field, [])
|
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)
|
ak_group = self.get_group(group)
|
||||||
if not ak_group:
|
if not ak_group:
|
||||||
continue
|
continue
|
||||||
@ -68,7 +85,7 @@ class MembershipLDAPSynchronizer(BaseLDAPSynchronizer):
|
|||||||
"ak_groups__in": [ak_group],
|
"ak_groups__in": [ak_group],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
).distinct()
|
||||||
membership_count += 1
|
membership_count += 1
|
||||||
membership_count += users.count()
|
membership_count += users.count()
|
||||||
ak_group.users.set(users)
|
ak_group.users.set(users)
|
||||||
|
|||||||
@ -96,6 +96,26 @@ def mock_freeipa_connection(password: str) -> Connection:
|
|||||||
"objectClass": "posixAccount",
|
"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
|
# Locked out user
|
||||||
connection.strategy.add_entry(
|
connection.strategy.add_entry(
|
||||||
"cn=user-nsaccountlock,ou=users,dc=goauthentik,dc=io",
|
"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.filter(username="user1_sn").exists())
|
||||||
self.assertFalse(User.objects.get(username="user-nsaccountlock").is_active)
|
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):
|
def test_sync_groups_ad(self):
|
||||||
"""Test group sync"""
|
"""Test group sync"""
|
||||||
self.source.user_property_mappings.set(
|
self.source.user_property_mappings.set(
|
||||||
|
|||||||
@ -33,7 +33,6 @@ from authentik.flows.planner import (
|
|||||||
)
|
)
|
||||||
from authentik.flows.stage import ChallengeStageView
|
from authentik.flows.stage import ChallengeStageView
|
||||||
from authentik.flows.views.executor import NEXT_ARG_NAME, SESSION_KEY_GET, SESSION_KEY_PLAN
|
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.lib.views import bad_request_message
|
||||||
from authentik.providers.saml.utils.encoding import nice64
|
from authentik.providers.saml.utils.encoding import nice64
|
||||||
from authentik.sources.saml.exceptions import MissingSAMLResponse, UnsupportedNameIDFormat
|
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(
|
final_redirect = self.request.session.get(SESSION_KEY_GET, {}).get(
|
||||||
NEXT_ARG_NAME, "authentik_core:if-user"
|
NEXT_ARG_NAME, "authentik_core:if-user"
|
||||||
)
|
)
|
||||||
if not is_url_absolute(final_redirect):
|
|
||||||
final_redirect = "authentik_core:if-user"
|
|
||||||
kwargs.update(
|
kwargs.update(
|
||||||
{
|
{
|
||||||
PLAN_CONTEXT_SSO: True,
|
PLAN_CONTEXT_SSO: True,
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema",
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
"$id": "https://goauthentik.io/blueprints/schema.json",
|
"$id": "https://goauthentik.io/blueprints/schema.json",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "authentik 2025.2.3 Blueprint schema",
|
"title": "authentik 2025.2.4 Blueprint schema",
|
||||||
"required": [
|
"required": [
|
||||||
"version",
|
"version",
|
||||||
"entries"
|
"entries"
|
||||||
@ -7885,6 +7885,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uuid",
|
"format": "uuid",
|
||||||
"title": "Sync parent group"
|
"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": []
|
"required": []
|
||||||
|
|||||||
@ -31,7 +31,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- redis:/data
|
- redis:/data
|
||||||
server:
|
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
|
restart: unless-stopped
|
||||||
command: server
|
command: server
|
||||||
environment:
|
environment:
|
||||||
@ -54,7 +54,7 @@ services:
|
|||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
worker:
|
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
|
restart: unless-stopped
|
||||||
command: worker
|
command: worker
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
14
go.mod
14
go.mod
@ -1,9 +1,10 @@
|
|||||||
module goauthentik.io
|
module goauthentik.io
|
||||||
|
|
||||||
go 1.24.0
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
beryju.io/ldap v0.1.0
|
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/getsentry/sentry-go v0.31.1
|
||||||
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
github.com/go-http-utils/etag v0.0.0-20161124023236-513ea8f21eb1
|
||||||
github.com/go-ldap/ldap/v3 v3.4.10
|
github.com/go-ldap/ldap/v3 v3.4.10
|
||||||
@ -19,17 +20,17 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484
|
||||||
github.com/pires/go-proxyproto v0.8.0
|
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/redis/go-redis/v9 v9.7.3
|
||||||
github.com/sethvargo/go-envconfig v1.1.1
|
github.com/sethvargo/go-envconfig v1.1.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/wwt/guac v1.3.2
|
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/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.28.0
|
golang.org/x/oauth2 v0.29.0
|
||||||
golang.org/x/sync v0.12.0
|
golang.org/x/sync v0.13.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
layeh.com/radius v0.0.0-20210819152912-ad72663a72ab
|
||||||
)
|
)
|
||||||
@ -59,7 +60,6 @@ require (
|
|||||||
github.com/go-openapi/validate v0.24.0 // indirect
|
github.com/go-openapi/validate v0.24.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/intern v1.0.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/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/oklog/ulid v1.3.1 // 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/crypto v0.36.0 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
golang.org/x/sys v0.31.0 // indirect
|
||||||
golang.org/x/text v0.23.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
|
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/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/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/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.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||||
github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
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/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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.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.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.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.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 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
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=
|
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.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/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/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.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
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/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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
|
||||||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
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.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 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
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.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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
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.2025024.1 h1:wYmpbNW1XptrjS5dlnZj8CrCs+JUGEVJYStrFdWL9aA=
|
||||||
goauthentik.io/api/v3 v3.2025023.2/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
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-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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/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-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-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.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.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.7.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.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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.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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
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.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
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 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
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())
|
return fmt.Sprintf("authentik@%s", FullVersion())
|
||||||
}
|
}
|
||||||
|
|
||||||
const VERSION = "2025.2.3"
|
const VERSION = "2025.2.4"
|
||||||
|
|||||||
@ -26,7 +26,7 @@ Parameters:
|
|||||||
Description: authentik Docker image
|
Description: authentik Docker image
|
||||||
AuthentikVersion:
|
AuthentikVersion:
|
||||||
Type: String
|
Type: String
|
||||||
Default: 2025.2.3
|
Default: 2025.2.4
|
||||||
Description: authentik Docker image tag
|
Description: authentik Docker image tag
|
||||||
AuthentikServerCPU:
|
AuthentikServerCPU:
|
||||||
Type: Number
|
Type: Number
|
||||||
|
|||||||
@ -6,23 +6,23 @@
|
|||||||
# Translators:
|
# Translators:
|
||||||
# Dario Rigolin, 2022
|
# Dario Rigolin, 2022
|
||||||
# aoor9, 2023
|
# aoor9, 2023
|
||||||
# Matteo Piccina <altermatte@gmail.com>, 2024
|
|
||||||
# Enrico Campani, 2024
|
# Enrico Campani, 2024
|
||||||
# Marco Vitale, 2024
|
# Marco Vitale, 2024
|
||||||
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2024
|
|
||||||
# Nicola Mersi, 2024
|
# Nicola Mersi, 2024
|
||||||
# tmassimi, 2024
|
# tmassimi, 2024
|
||||||
# Marc Schmitt, 2024
|
# Marc Schmitt, 2024
|
||||||
# albanobattistella <albanobattistella@gmail.com>, 2024
|
# albanobattistella <albanobattistella@gmail.com>, 2024
|
||||||
|
# Matteo Piccina <altermatte@gmail.com>, 2025
|
||||||
|
# Kowalski Dragon (kowalski7cc) <kowalski.7cc@gmail.com>, 2025
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \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"
|
"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"
|
"Language-Team: Italian (https://app.transifex.com/authentik/teams/119923/it/)\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\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"
|
msgid "Extra description not available"
|
||||||
msgstr "Descrizione extra non disponibile"
|
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
|
#: authentik/core/api/providers.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"When not set all providers are returned. When set to true, only backchannel "
|
"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"
|
msgid "Remove user from group"
|
||||||
msgstr "Rimuovi l'utente dal gruppo"
|
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
|
#: authentik/core/models.py
|
||||||
msgid "User's display name."
|
msgid "User's display name."
|
||||||
msgstr "Nome visualizzato dell'utente."
|
msgstr "Nome visualizzato dell'utente."
|
||||||
@ -260,11 +272,11 @@ msgstr "Applicazioni"
|
|||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Application Entitlement"
|
msgid "Application Entitlement"
|
||||||
msgstr ""
|
msgstr "Entitlement Applicazione"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Application Entitlements"
|
msgid "Application Entitlements"
|
||||||
msgstr ""
|
msgstr "Entitlements Applicazione"
|
||||||
|
|
||||||
#: authentik/core/models.py
|
#: authentik/core/models.py
|
||||||
msgid "Use the source-specific identifier"
|
msgid "Use the source-specific identifier"
|
||||||
@ -551,62 +563,6 @@ msgstr "Mappatura Microsoft Entra Provider"
|
|||||||
msgid "Microsoft Entra Provider Mappings"
|
msgid "Microsoft Entra Provider Mappings"
|
||||||
msgstr "Mappature Microsoft Entra Provider"
|
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/enterprise/providers/ssf/models.py
|
||||||
#: authentik/providers/oauth2/models.py
|
#: authentik/providers/oauth2/models.py
|
||||||
msgid "Signing Key"
|
msgid "Signing Key"
|
||||||
@ -614,39 +570,39 @@ msgstr "Chiave di firma"
|
|||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Key used to sign the SSF Events."
|
msgid "Key used to sign the SSF Events."
|
||||||
msgstr ""
|
msgstr "Chiave utilizzata per firmare gli eventi SSF."
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Shared Signals Framework Provider"
|
msgid "Shared Signals Framework Provider"
|
||||||
msgstr ""
|
msgstr "Fornitore Shared Signals Framework"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Shared Signals Framework Providers"
|
msgid "Shared Signals Framework Providers"
|
||||||
msgstr ""
|
msgstr "Fornitori Shared Signals Framework"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "Add stream to SSF provider"
|
msgid "Add stream to SSF provider"
|
||||||
msgstr ""
|
msgstr "Aggiungi Stream al provider SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream"
|
msgid "SSF Stream"
|
||||||
msgstr ""
|
msgstr "SSF Stream"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Streams"
|
msgid "SSF Streams"
|
||||||
msgstr ""
|
msgstr "SSF Streams"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream Event"
|
msgid "SSF Stream Event"
|
||||||
msgstr ""
|
msgstr "Evento di Stream SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/models.py
|
#: authentik/enterprise/providers/ssf/models.py
|
||||||
msgid "SSF Stream Events"
|
msgid "SSF Stream Events"
|
||||||
msgstr ""
|
msgstr "Eventi di Stream SSF"
|
||||||
|
|
||||||
#: authentik/enterprise/providers/ssf/tasks.py
|
#: authentik/enterprise/providers/ssf/tasks.py
|
||||||
msgid "Failed to send request"
|
msgid "Failed to send request"
|
||||||
msgstr ""
|
msgstr "Impossibile inviare la richiesta"
|
||||||
|
|
||||||
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
#: authentik/enterprise/stages/authenticator_endpoint_gdtc/models.py
|
||||||
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
msgid "Endpoint Authenticator Google Device Trust Connector Stage"
|
||||||
@ -712,9 +668,26 @@ msgid "Slack Webhook (Slack/Discord)"
|
|||||||
msgstr "Slack Webhook (Slack/Discord)"
|
msgstr "Slack Webhook (Slack/Discord)"
|
||||||
|
|
||||||
#: authentik/events/models.py
|
#: authentik/events/models.py
|
||||||
|
#: authentik/stages/authenticator_validate/models.py
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "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
|
#: authentik/events/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Only send notification once, for example when sending a webhook into a chat "
|
"Only send notification once, for example when sending a webhook into a chat "
|
||||||
@ -944,7 +917,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: authentik/flows/models.py
|
#: authentik/flows/models.py
|
||||||
msgid "Evaluate policies when the Stage is presented to the user."
|
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
|
#: authentik/flows/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -986,6 +959,14 @@ msgstr "Tokens del flusso"
|
|||||||
msgid "Invalid next URL"
|
msgid "Invalid next URL"
|
||||||
msgstr "URL successivo non valido"
|
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
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
msgid "Starting full provider sync"
|
msgid "Starting full provider sync"
|
||||||
msgstr "Avvio della sincronizzazione completa del provider"
|
msgstr "Avvio della sincronizzazione completa del provider"
|
||||||
@ -1000,6 +981,10 @@ msgstr "Sincronizzando pagina {page} degli utenti"
|
|||||||
msgid "Syncing page {page} of groups"
|
msgid "Syncing page {page} of groups"
|
||||||
msgstr "Sincronizzando pagina {page} dei gruppi"
|
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
|
#: authentik/lib/sync/outgoing/tasks.py
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Stopping sync due to error: {error}"
|
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."
|
msgid "Client IP is not in an allowed country."
|
||||||
msgstr "L'IP del client non si trova in un paese consentito."
|
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
|
#: authentik/policies/geoip/models.py
|
||||||
msgid "GeoIP Policy"
|
msgid "GeoIP Policy"
|
||||||
msgstr "Criterio GeoIP"
|
msgstr "Criterio GeoIP"
|
||||||
@ -1344,6 +1337,22 @@ msgstr "Punteggio di reputazione"
|
|||||||
msgid "Reputation Scores"
|
msgid "Reputation Scores"
|
||||||
msgstr "Punteggi di reputazione"
|
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
|
#: authentik/policies/templates/policies/denied.html
|
||||||
msgid "Permission denied"
|
msgid "Permission denied"
|
||||||
msgstr "Permesso negato"
|
msgstr "Permesso negato"
|
||||||
@ -1531,6 +1540,14 @@ msgstr "RS256 (Crittografia Asimmetrica)"
|
|||||||
msgid "ES256 (Asymmetric Encryption)"
|
msgid "ES256 (Asymmetric Encryption)"
|
||||||
msgstr "ES256 (Crittografia Asimmetrica)"
|
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
|
#: authentik/providers/oauth2/models.py
|
||||||
msgid "Scope used by the client"
|
msgid "Scope used by the client"
|
||||||
msgstr "Scope usato dall'utente"
|
msgstr "Scope usato dall'utente"
|
||||||
@ -1814,6 +1831,61 @@ msgstr "Provider Proxy"
|
|||||||
msgid "Proxy Providers"
|
msgid "Proxy Providers"
|
||||||
msgstr "Providers Proxy"
|
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
|
#: authentik/providers/radius/models.py
|
||||||
msgid "Shared secret between clients and server to hash packets."
|
msgid "Shared secret between clients and server to hash packets."
|
||||||
msgstr "Segreto condiviso tra client e server per hashare i pacchetti."
|
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, "
|
"Configura il modo in cui verrà creato il valore NameID. Se lasciato vuoto, "
|
||||||
"verrà considerato il NameIDPolicy della richiesta in arrivo"
|
"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
|
#: authentik/providers/saml/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Assertion valid not before current time + this value (Format: "
|
"Assertion valid not before current time + this value (Format: "
|
||||||
@ -2042,6 +2128,18 @@ msgstr "Provider SAML dai Metadati"
|
|||||||
msgid "SAML Providers from Metadata"
|
msgid "SAML Providers from Metadata"
|
||||||
msgstr "Providers SAML dai Metadati"
|
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
|
#: authentik/providers/scim/models.py
|
||||||
msgid "Base URL to SCIM requests, usually ends in /v2"
|
msgid "Base URL to SCIM requests, usually ends in /v2"
|
||||||
msgstr "URL di base per le richieste SCIM, di solito termina con /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"
|
msgid "Authentication token"
|
||||||
msgstr "Token di autenticazione"
|
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
|
#: authentik/providers/scim/models.py
|
||||||
msgid "SCIM Provider"
|
msgid "SCIM Provider"
|
||||||
msgstr "Privider SCIM"
|
msgstr "Privider SCIM"
|
||||||
@ -2125,7 +2233,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: authentik/sources/kerberos/models.py
|
#: authentik/sources/kerberos/models.py
|
||||||
msgid "KAdmin server type"
|
msgid "KAdmin server type"
|
||||||
msgstr ""
|
msgstr "Tipo server KAdmin"
|
||||||
|
|
||||||
#: authentik/sources/kerberos/models.py
|
#: authentik/sources/kerberos/models.py
|
||||||
msgid "Sync users from Kerberos into authentik"
|
msgid "Sync users from Kerberos into authentik"
|
||||||
@ -2730,6 +2838,117 @@ msgstr "Dispositivo Duo"
|
|||||||
msgid "Duo Devices"
|
msgid "Duo Devices"
|
||||||
msgstr "Dispositivi Duo"
|
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
|
#: authentik/stages/authenticator_sms/models.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"When enabled, the Phone number is only used during enrollment to verify the "
|
"When enabled, the Phone number is only used during enrollment to verify the "
|
||||||
@ -2768,11 +2987,6 @@ msgstr "Dispositivo SMS"
|
|||||||
msgid "SMS Devices"
|
msgid "SMS Devices"
|
||||||
msgstr "Dispositivi SMS"
|
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
|
#: authentik/stages/authenticator_sms/stage.py
|
||||||
msgid "Invalid phone number"
|
msgid "Invalid phone number"
|
||||||
msgstr "Numero di telefono non valido"
|
msgstr "Numero di telefono non valido"
|
||||||
@ -3013,23 +3227,10 @@ msgstr "Ripristino password"
|
|||||||
msgid "Account Confirmation"
|
msgid "Account Confirmation"
|
||||||
msgstr "Conferma dell'account"
|
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
|
#: authentik/stages/email/models.py
|
||||||
msgid "Activate users upon completion of stage."
|
msgid "Activate users upon completion of stage."
|
||||||
msgstr "Attiva gli utenti al completamento della fase."
|
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
|
#: authentik/stages/email/models.py
|
||||||
msgid "Email Stage"
|
msgid "Email Stage"
|
||||||
msgstr "Fase email"
|
msgstr "Fase email"
|
||||||
@ -3038,12 +3239,6 @@ msgstr "Fase email"
|
|||||||
msgid "Email Stages"
|
msgid "Email Stages"
|
||||||
msgstr "Fasi Email"
|
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
|
#: authentik/stages/email/stage.py
|
||||||
msgid "Successfully verified Email."
|
msgid "Successfully verified Email."
|
||||||
msgstr "Email verificato con successo."
|
msgstr "Email verificato con successo."
|
||||||
@ -3127,17 +3322,6 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Questa email è stata inviata dal trasporto delle notifiche %(name)s.\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
|
#: authentik/stages/email/templates/email/password_reset.html
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\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"
|
" 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
|
#: authentik/stages/email/templates/email/password_reset.txt
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
@ -3492,6 +3671,7 @@ msgstr ""
|
|||||||
#: authentik/stages/redirect/api.py
|
#: authentik/stages/redirect/api.py
|
||||||
msgid "Target Flow should be present when mode is Flow."
|
msgid "Target Flow should be present when mode is Flow."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"Il flusso target dovrebbe essere presente quando la modalità è Flusso."
|
||||||
|
|
||||||
#: authentik/stages/redirect/models.py
|
#: authentik/stages/redirect/models.py
|
||||||
msgid "Redirect Stage"
|
msgid "Redirect Stage"
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@goauthentik/authentik",
|
"name": "@goauthentik/authentik",
|
||||||
"version": "2025.2.3",
|
"version": "2025.2.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@goauthentik/authentik",
|
"name": "@goauthentik/authentik",
|
||||||
"version": "2025.2.3"
|
"version": "2025.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@goauthentik/authentik",
|
"name": "@goauthentik/authentik",
|
||||||
"version": "2025.2.3",
|
"version": "2025.2.4",
|
||||||
"private": true
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,23 +1,16 @@
|
|||||||
{
|
{
|
||||||
// TODO: Replace with @goauthentik/tsconfig after project compilation.
|
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"paths": {
|
|
||||||
"@goauthentik/web/authentication": ["../authentication/index.js"]
|
|
||||||
},
|
|
||||||
"alwaysStrict": true,
|
"alwaysStrict": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"rootDir": "../",
|
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"allowJs": true,
|
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
|
"esModuleInterop": false,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"emitDeclarationOnly": true,
|
"jsx": "react-jsx",
|
||||||
"esModuleInterop": true,
|
"lib": ["ESNext"],
|
||||||
"lib": ["DOM", "ES2015", "ES2017"],
|
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"newLine": "lf",
|
"newLine": "lf",
|
||||||
@ -32,15 +25,5 @@
|
|||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"useUnknownInCatchVariables": true
|
"useUnknownInCatchVariables": true
|
||||||
},
|
}
|
||||||
"exclude": [
|
|
||||||
// ---
|
|
||||||
"./out/**/*",
|
|
||||||
"./dist/**/*"
|
|
||||||
],
|
|
||||||
"include": [
|
|
||||||
// ---
|
|
||||||
"./**/*.js",
|
|
||||||
"../authentication/**/*.js"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "authentik"
|
name = "authentik"
|
||||||
version = "2025.2.3"
|
version = "2025.2.4"
|
||||||
description = ""
|
description = ""
|
||||||
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
authors = [{ name = "authentik Team", email = "hello@goauthentik.io" }]
|
||||||
requires-python = "==3.12.*"
|
requires-python = "==3.12.*"
|
||||||
|
|||||||
26
schema.yml
26
schema.yml
@ -1,7 +1,7 @@
|
|||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: authentik
|
title: authentik
|
||||||
version: 2025.2.3
|
version: 2025.2.4
|
||||||
description: Making authentication simple.
|
description: Making authentication simple.
|
||||||
contact:
|
contact:
|
||||||
email: hello@goauthentik.io
|
email: hello@goauthentik.io
|
||||||
@ -27649,6 +27649,10 @@ paths:
|
|||||||
format: uuid
|
format: uuid
|
||||||
explode: true
|
explode: true
|
||||||
style: form
|
style: form
|
||||||
|
- in: query
|
||||||
|
name: lookup_groups_from_user
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
- in: query
|
- in: query
|
||||||
name: name
|
name: name
|
||||||
schema:
|
schema:
|
||||||
@ -46345,6 +46349,11 @@ components:
|
|||||||
nullable: true
|
nullable: true
|
||||||
description: Get cached source connectivity
|
description: Get cached source connectivity
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
lookup_groups_from_user:
|
||||||
|
type: boolean
|
||||||
|
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:
|
required:
|
||||||
- base_dn
|
- base_dn
|
||||||
- component
|
- component
|
||||||
@ -46541,6 +46550,11 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
nullable: true
|
||||||
|
lookup_groups_from_user:
|
||||||
|
type: boolean
|
||||||
|
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:
|
required:
|
||||||
- base_dn
|
- base_dn
|
||||||
- name
|
- name
|
||||||
@ -51664,6 +51678,11 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
nullable: true
|
nullable: true
|
||||||
|
lookup_groups_from_user:
|
||||||
|
type: boolean
|
||||||
|
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
|
||||||
PatchedLicenseRequest:
|
PatchedLicenseRequest:
|
||||||
type: object
|
type: object
|
||||||
description: License Serializer
|
description: License Serializer
|
||||||
@ -58170,6 +58189,10 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
nullable: true
|
nullable: true
|
||||||
|
date_joined:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
readOnly: true
|
||||||
is_superuser:
|
is_superuser:
|
||||||
type: boolean
|
type: boolean
|
||||||
readOnly: true
|
readOnly: true
|
||||||
@ -58213,6 +58236,7 @@ components:
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
- avatar
|
- avatar
|
||||||
|
- date_joined
|
||||||
- groups_obj
|
- groups_obj
|
||||||
- is_superuser
|
- is_superuser
|
||||||
- name
|
- name
|
||||||
|
|||||||
@ -1,206 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file WebAuthn utilities.
|
|
||||||
*/
|
|
||||||
import { fromByteArray } from "base64-js";
|
|
||||||
|
|
||||||
//@ts-check
|
|
||||||
|
|
||||||
//#region Type Definitions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} Assertion
|
|
||||||
* @property {string} id
|
|
||||||
* @property {string} rawId
|
|
||||||
* @property {string} type
|
|
||||||
* @property {string} registrationClientExtensions
|
|
||||||
* @property {object} response
|
|
||||||
* @property {string} response.clientDataJSON
|
|
||||||
* @property {string} response.attestationObject
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} AuthAssertion
|
|
||||||
* @property {string} id
|
|
||||||
* @property {string} rawId
|
|
||||||
* @property {string} type
|
|
||||||
* @property {string} assertionClientExtensions
|
|
||||||
* @property {object} response
|
|
||||||
* @property {string} response.clientDataJSON
|
|
||||||
* @property {string} response.authenticatorData
|
|
||||||
* @property {string} response.signature
|
|
||||||
* @property {string | null} response.userHandle
|
|
||||||
*/
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Encoding/Decoding
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a byte array into a URL-safe base64 string.
|
|
||||||
*
|
|
||||||
* @param {Uint8Array} buffer
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function encodeBase64(buffer) {
|
|
||||||
return fromByteArray(buffer).replace(/\+/g, "-").replace(/\//g, "_").replace(/[=]/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a byte array into a base64 string without URL-safe encoding, i.e., with padding.
|
|
||||||
* @param {Uint8Array} buffer
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function encodeBase64Raw(buffer) {
|
|
||||||
return fromByteArray(buffer).replace(/\+/g, "-").replace(/\//g, "_");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes a base64 string into a byte array.
|
|
||||||
*
|
|
||||||
* @param {string} input
|
|
||||||
* @returns {Uint8Array}
|
|
||||||
*/
|
|
||||||
export function decodeBase64(input) {
|
|
||||||
return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) =>
|
|
||||||
c.charCodeAt(0),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region Utility Functions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the browser supports WebAuthn.
|
|
||||||
*
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export function isWebAuthnSupported() {
|
|
||||||
if ("credentials" in navigator) return true;
|
|
||||||
|
|
||||||
if (window.location.protocol === "http:" && window.location.hostname !== "localhost") {
|
|
||||||
console.warn("WebAuthn requires this page to be accessed via HTTPS.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.warn("WebAuthn not supported by browser.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts that the browser supports WebAuthn and that we're in a secure context.
|
|
||||||
*
|
|
||||||
* @throws {Error} If WebAuthn is not supported.
|
|
||||||
*/
|
|
||||||
export function assertWebAuthnSupport() {
|
|
||||||
// Is the navigator exposing the credentials API?
|
|
||||||
if ("credentials" in navigator) return;
|
|
||||||
|
|
||||||
if (window.location.protocol === "http:" && window.location.hostname !== "localhost") {
|
|
||||||
throw new Error("WebAuthn requires this page to be accessed via HTTPS.");
|
|
||||||
}
|
|
||||||
throw new Error("WebAuthn not supported by browser.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms items in the credentialCreateOptions generated on the server
|
|
||||||
* into byte arrays expected by the navigator.credentials.create() call
|
|
||||||
* @param {PublicKeyCredentialCreationOptions} credentialCreateOptions
|
|
||||||
* @param {string} userID
|
|
||||||
* @returns {PublicKeyCredentialCreationOptions}
|
|
||||||
*/
|
|
||||||
export function transformCredentialCreateOptions(credentialCreateOptions, userID) {
|
|
||||||
const user = credentialCreateOptions.user;
|
|
||||||
// Because json can't contain raw bytes, the server base64-encodes the User ID
|
|
||||||
// So to get the base64 encoded byte array, we first need to convert it to a regular
|
|
||||||
// string, then a byte array, re-encode it and wrap that in an array.
|
|
||||||
const stringId = decodeURIComponent(window.atob(userID));
|
|
||||||
|
|
||||||
user.id = decodeBase64(encodeBase64(decodeBase64(stringId)));
|
|
||||||
const challenge = decodeBase64(credentialCreateOptions.challenge.toString());
|
|
||||||
|
|
||||||
return {
|
|
||||||
...credentialCreateOptions,
|
|
||||||
challenge,
|
|
||||||
user,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms the binary data in the credential into base64 strings
|
|
||||||
* for posting to the server.
|
|
||||||
*
|
|
||||||
* @param {PublicKeyCredential} newAssertion
|
|
||||||
* @returns {Assertion}
|
|
||||||
*/
|
|
||||||
export function transformNewAssertionForServer(newAssertion) {
|
|
||||||
const response = /** @type {AuthenticatorAttestationResponse} */ (newAssertion.response);
|
|
||||||
|
|
||||||
const attObj = new Uint8Array(response.attestationObject);
|
|
||||||
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON);
|
|
||||||
const rawId = new Uint8Array(newAssertion.rawId);
|
|
||||||
|
|
||||||
const registrationClientExtensions = newAssertion.getClientExtensionResults();
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: newAssertion.id,
|
|
||||||
rawId: encodeBase64(rawId),
|
|
||||||
type: newAssertion.type,
|
|
||||||
registrationClientExtensions: JSON.stringify(registrationClientExtensions),
|
|
||||||
response: {
|
|
||||||
clientDataJSON: encodeBase64(clientDataJSON),
|
|
||||||
attestationObject: encodeBase64(attObj),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms the items in the credentialRequestOptions generated on the server
|
|
||||||
*
|
|
||||||
* @param {PublicKeyCredentialRequestOptions} credentialRequestOptions
|
|
||||||
* @returns {PublicKeyCredentialRequestOptions}
|
|
||||||
*/
|
|
||||||
export function transformCredentialRequestOptions(credentialRequestOptions) {
|
|
||||||
const challenge = decodeBase64(credentialRequestOptions.challenge.toString());
|
|
||||||
|
|
||||||
const allowCredentials = (credentialRequestOptions.allowCredentials || []).map(
|
|
||||||
(credentialDescriptor) => {
|
|
||||||
const id = decodeBase64(credentialDescriptor.id.toString());
|
|
||||||
return Object.assign({}, credentialDescriptor, { id });
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return Object.assign({}, credentialRequestOptions, {
|
|
||||||
challenge,
|
|
||||||
allowCredentials,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes the binary data in the assertion into strings for posting to the server.
|
|
||||||
* @param {PublicKeyCredential} newAssertion
|
|
||||||
* @returns {AuthAssertion}
|
|
||||||
*/
|
|
||||||
export function transformAssertionForServer(newAssertion) {
|
|
||||||
const response = /** @type {AuthenticatorAssertionResponse} */ (newAssertion.response);
|
|
||||||
|
|
||||||
const authData = new Uint8Array(response.authenticatorData);
|
|
||||||
const clientDataJSON = new Uint8Array(response.clientDataJSON);
|
|
||||||
const rawId = new Uint8Array(newAssertion.rawId);
|
|
||||||
const sig = new Uint8Array(response.signature);
|
|
||||||
const assertionClientExtensions = newAssertion.getClientExtensionResults();
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: newAssertion.id,
|
|
||||||
rawId: encodeBase64(rawId),
|
|
||||||
type: newAssertion.type,
|
|
||||||
assertionClientExtensions: JSON.stringify(assertionClientExtensions),
|
|
||||||
|
|
||||||
response: {
|
|
||||||
clientDataJSON: encodeBase64Raw(clientDataJSON),
|
|
||||||
signature: encodeBase64Raw(sig),
|
|
||||||
authenticatorData: encodeBase64Raw(authData),
|
|
||||||
userHandle: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -48,9 +48,6 @@ export default [
|
|||||||
"lit/no-template-bind": "error",
|
"lit/no-template-bind": "error",
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
"no-console": ["error", { allow: ["debug", "warn", "error"] }],
|
||||||
// TODO: TypeScript already handles this.
|
|
||||||
// Remove after project-wide ESLint config is properly set up.
|
|
||||||
"no-undef": "off",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/no-unused-vars": [
|
"@typescript-eslint/no-unused-vars": [
|
||||||
"error",
|
"error",
|
||||||
@ -74,18 +71,8 @@ export default [
|
|||||||
...globals.node,
|
...globals.node,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
files: [
|
files: ["scripts/**/*.mjs", "*.ts", "*.mjs"],
|
||||||
// TODO:Remove after project-wide ESLint config is properly set up.
|
|
||||||
"scripts/**/*.mjs",
|
|
||||||
"authentication/**/*.js",
|
|
||||||
"sfe/**/*.js",
|
|
||||||
"*.ts",
|
|
||||||
"*.mjs",
|
|
||||||
],
|
|
||||||
rules: {
|
rules: {
|
||||||
"no-undef": "off",
|
|
||||||
// TODO: TypeScript already handles this.
|
|
||||||
// Remove after project-wide ESLint config is properly set up.
|
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
// We WANT our scripts to output to the console!
|
// We WANT our scripts to output to the console!
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
|
|||||||
1922
web/package-lock.json
generated
1922
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@
|
|||||||
"@floating-ui/dom": "^1.6.11",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@formatjs/intl-listformat": "^7.5.7",
|
"@formatjs/intl-listformat": "^7.5.7",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@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-labs/ssr": "^3.2.2",
|
||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@lit/localize": "^0.12.2",
|
"@lit/localize": "^0.12.2",
|
||||||
@ -57,14 +57,9 @@
|
|||||||
"ts-pattern": "^5.4.0",
|
"ts-pattern": "^5.4.0",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.0.0",
|
||||||
"webcomponent-qr-code": "^1.2.0",
|
"webcomponent-qr-code": "^1.2.0",
|
||||||
"yaml": "^2.5.1",
|
"yaml": "^2.5.1"
|
||||||
"bootstrap": "^4.6.1",
|
|
||||||
"formdata-polyfill": "^4.0.10",
|
|
||||||
"jquery": "^3.7.1",
|
|
||||||
"weakmap-polyfill": "^2.0.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jquery": "^3.5.31",
|
|
||||||
"@eslint/js": "^9.11.1",
|
"@eslint/js": "^9.11.1",
|
||||||
"@hcaptcha/types": "^1.0.4",
|
"@hcaptcha/types": "^1.0.4",
|
||||||
"@lit/localize-tools": "^0.8.0",
|
"@lit/localize-tools": "^0.8.0",
|
||||||
@ -95,8 +90,6 @@
|
|||||||
"@wdio/spec-reporter": "^9.1.2",
|
"@wdio/spec-reporter": "^9.1.2",
|
||||||
"chromedriver": "^131.0.1",
|
"chromedriver": "^131.0.1",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"esbuild-plugin-copy": "^2.1.1",
|
|
||||||
"esbuild-plugin-es5": "^2.1.1",
|
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"esbuild-plugins-node-modules-polyfill": "^1.7.0",
|
"esbuild-plugins-node-modules-polyfill": "^1.7.0",
|
||||||
"eslint": "^9.11.1",
|
"eslint": "^9.11.1",
|
||||||
@ -168,12 +161,6 @@
|
|||||||
"watch": "run-s build-locales esbuild:watch"
|
"watch": "run-s build-locales esbuild:watch"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
|
||||||
"./package.json": "./package.json",
|
|
||||||
"./paths": "./paths.js",
|
|
||||||
"./authentication": "./authentication/index.js",
|
|
||||||
"./scripts/*": "./scripts/*.mjs"
|
|
||||||
},
|
|
||||||
"wireit": {
|
"wireit": {
|
||||||
"build": {
|
"build": {
|
||||||
"#comment": [
|
"#comment": [
|
||||||
@ -206,7 +193,8 @@
|
|||||||
"./dist/patternfly.min.css"
|
"./dist/patternfly.min.css"
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"build-locales"
|
"build-locales",
|
||||||
|
"./packages/sfe:build"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_RUNNER": {
|
"NODE_RUNNER": {
|
||||||
@ -216,7 +204,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"build:sfe": {
|
"build:sfe": {
|
||||||
"command": "node scripts/build-sfe.mjs"
|
"dependencies": [
|
||||||
|
"./packages/sfe:build"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"./packages/sfe/**/*.ts"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"build-proxy": {
|
"build-proxy": {
|
||||||
"command": "node scripts/build-web.mjs --proxy",
|
"command": "node scripts/build-web.mjs --proxy",
|
||||||
@ -249,6 +242,11 @@
|
|||||||
"lint:package"
|
"lint:package"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"format:packages": {
|
||||||
|
"dependencies": [
|
||||||
|
"./packages/sfe:prettier"
|
||||||
|
]
|
||||||
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
"command": "eslint --max-warnings 0 --fix",
|
"command": "eslint --max-warnings 0 --fix",
|
||||||
"env": {
|
"env": {
|
||||||
@ -276,6 +274,11 @@
|
|||||||
"shell": true,
|
"shell": true,
|
||||||
"command": "sh ./scripts/lint-lockfile.sh package-lock.json"
|
"command": "sh ./scripts/lint-lockfile.sh package-lock.json"
|
||||||
},
|
},
|
||||||
|
"lint:lockfiles": {
|
||||||
|
"dependencies": [
|
||||||
|
"./packages/sfe:lint:lockfile"
|
||||||
|
]
|
||||||
|
},
|
||||||
"lint:package": {
|
"lint:package": {
|
||||||
"command": "syncpack format -i ' '"
|
"command": "syncpack format -i ' '"
|
||||||
},
|
},
|
||||||
@ -311,7 +314,9 @@
|
|||||||
"lint:spelling",
|
"lint:spelling",
|
||||||
"lint:package",
|
"lint:package",
|
||||||
"lint:lockfile",
|
"lint:lockfile",
|
||||||
"lint:precommit"
|
"lint:lockfiles",
|
||||||
|
"lint:precommit",
|
||||||
|
"format:packages"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
|
|||||||
23
web/packages/sfe/.prettierrc.json
Normal file
23
web/packages/sfe/.prettierrc.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"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": ["@trivago/prettier-plugin-sort-imports"],
|
||||||
|
"importOrder": ["^(@?)lit(.*)$", "\\.css$", "^@goauthentik/api$", "^[./]"],
|
||||||
|
"importOrderSeparation": true,
|
||||||
|
"importOrderSortSpecifiers": true,
|
||||||
|
"importOrderParserPlugins": ["typescript", "classProperties", "decorators-legacy"]
|
||||||
|
}
|
||||||
18
web/packages/sfe/LICENSE.txt
Normal file
18
web/packages/sfe/LICENSE.txt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2024 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.
|
||||||
68
web/packages/sfe/package.json
Normal file
68
web/packages/sfe/package.json
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"name": "@goauthentik/web-sfe",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@goauthentik/api": "^2024.6.0-1719577139",
|
||||||
|
"base64-js": "^1.5.1",
|
||||||
|
"bootstrap": "^4.6.1",
|
||||||
|
"formdata-polyfill": "^4.0.10",
|
||||||
|
"jquery": "^3.7.1",
|
||||||
|
"weakmap-polyfill": "^2.0.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^28.0.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||||
|
"@rollup/plugin-swc": "^0.4.0",
|
||||||
|
"@swc/cli": "^0.4.0",
|
||||||
|
"@swc/core": "^1.7.28",
|
||||||
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
|
"@types/jquery": "^3.5.31",
|
||||||
|
"lockfile-lint": "^4.14.0",
|
||||||
|
"prettier": "^3.3.2",
|
||||||
|
"rollup": "^4.23.0",
|
||||||
|
"rollup-plugin-copy": "^3.5.0",
|
||||||
|
"wireit": "^0.14.9"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@swc/core": "^1.7.28",
|
||||||
|
"@swc/core-darwin-arm64": "^1.6.13",
|
||||||
|
"@swc/core-darwin-x64": "^1.6.13",
|
||||||
|
"@swc/core-linux-arm-gnueabihf": "^1.6.13",
|
||||||
|
"@swc/core-linux-arm64-gnu": "^1.6.13",
|
||||||
|
"@swc/core-linux-arm64-musl": "^1.6.13",
|
||||||
|
"@swc/core-linux-x64-gnu": "^1.6.13",
|
||||||
|
"@swc/core-linux-x64-musl": "^1.6.13",
|
||||||
|
"@swc/core-win32-arm64-msvc": "^1.6.13",
|
||||||
|
"@swc/core-win32-ia32-msvc": "^1.6.13",
|
||||||
|
"@swc/core-win32-x64-msvc": "^1.6.13"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "wireit",
|
||||||
|
"lint:lockfile": "wireit",
|
||||||
|
"prettier": "prettier --write ./src ./tsconfig.json ./rollup.config.js ./package.json",
|
||||||
|
"watch": "rollup -w -c rollup.config.js --bundleConfigAsCjs"
|
||||||
|
},
|
||||||
|
"wireit": {
|
||||||
|
"build:sfe": {
|
||||||
|
"command": "rollup -c rollup.config.js --bundleConfigAsCjs",
|
||||||
|
"files": [
|
||||||
|
"../../node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||||
|
"src/index.ts"
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
"./dist/sfe/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"command": "mkdir -p ../../dist/sfe && cp -r dist/sfe/* ../../dist/sfe",
|
||||||
|
"dependencies": [
|
||||||
|
"build:sfe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lint:lockfile": {
|
||||||
|
"command": "lockfile-lint --path package.json --type npm --allowed-hosts npm --validate-https"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
web/packages/sfe/rollup.config.js
Normal file
43
web/packages/sfe/rollup.config.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import commonjs from "@rollup/plugin-commonjs";
|
||||||
|
import resolve from "@rollup/plugin-node-resolve";
|
||||||
|
import swc from "@rollup/plugin-swc";
|
||||||
|
import copy from "rollup-plugin-copy";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: "src/index.ts",
|
||||||
|
output: {
|
||||||
|
dir: "./dist/sfe",
|
||||||
|
format: "cjs",
|
||||||
|
},
|
||||||
|
context: "window",
|
||||||
|
plugins: [
|
||||||
|
copy({
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
src: "../../node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||||
|
dest: "./dist/sfe",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
resolve({ browser: true }),
|
||||||
|
commonjs(),
|
||||||
|
swc({
|
||||||
|
swc: {
|
||||||
|
jsc: {
|
||||||
|
loose: false,
|
||||||
|
externalHelpers: false,
|
||||||
|
// Requires v1.2.50 or upper and requires target to be es2016 or upper.
|
||||||
|
keepClassNames: false,
|
||||||
|
},
|
||||||
|
minify: false,
|
||||||
|
env: {
|
||||||
|
targets: {
|
||||||
|
edge: "17",
|
||||||
|
ie: "11",
|
||||||
|
},
|
||||||
|
mode: "entry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
527
web/packages/sfe/src/index.ts
Normal file
527
web/packages/sfe/src/index.ts
Normal file
@ -0,0 +1,527 @@
|
|||||||
|
import { fromByteArray } from "base64-js";
|
||||||
|
import "formdata-polyfill";
|
||||||
|
import $ from "jquery";
|
||||||
|
import "weakmap-polyfill";
|
||||||
|
|
||||||
|
import {
|
||||||
|
type AuthenticatorValidationChallenge,
|
||||||
|
type AutosubmitChallenge,
|
||||||
|
type ChallengeTypes,
|
||||||
|
ChallengeTypesFromJSON,
|
||||||
|
type ContextualFlowInfo,
|
||||||
|
type DeviceChallenge,
|
||||||
|
type ErrorDetail,
|
||||||
|
type IdentificationChallenge,
|
||||||
|
type PasswordChallenge,
|
||||||
|
type RedirectChallenge,
|
||||||
|
} from "@goauthentik/api";
|
||||||
|
|
||||||
|
interface GlobalAuthentik {
|
||||||
|
brand: {
|
||||||
|
branding_logo: string;
|
||||||
|
};
|
||||||
|
api: {
|
||||||
|
base: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function ak(): GlobalAuthentik {
|
||||||
|
return (
|
||||||
|
window as unknown as {
|
||||||
|
authentik: GlobalAuthentik;
|
||||||
|
}
|
||||||
|
).authentik;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleFlowExecutor {
|
||||||
|
challenge?: ChallengeTypes;
|
||||||
|
flowSlug: string;
|
||||||
|
container: HTMLDivElement;
|
||||||
|
|
||||||
|
constructor(container: HTMLDivElement) {
|
||||||
|
this.flowSlug = window.location.pathname.split("/")[3];
|
||||||
|
this.container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
get apiURL() {
|
||||||
|
return `${ak().api.base}api/v3/flows/executor/${this.flowSlug}/?query=${encodeURIComponent(window.location.search.substring(1))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: this.apiURL,
|
||||||
|
success: (data) => {
|
||||||
|
this.challenge = ChallengeTypesFromJSON(data);
|
||||||
|
this.renderChallenge();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
submit(data: { [key: string]: unknown } | FormData) {
|
||||||
|
$("button[type=submit]").addClass("disabled")
|
||||||
|
.html(`<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
|
||||||
|
<span role="status">Loading...</span>`);
|
||||||
|
let finalData: { [key: string]: unknown } = {};
|
||||||
|
if (data instanceof FormData) {
|
||||||
|
finalData = {};
|
||||||
|
data.forEach((value, key) => {
|
||||||
|
finalData[key] = value;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
finalData = data;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: this.apiURL,
|
||||||
|
data: JSON.stringify(finalData),
|
||||||
|
success: (data) => {
|
||||||
|
this.challenge = ChallengeTypesFromJSON(data);
|
||||||
|
this.renderChallenge();
|
||||||
|
},
|
||||||
|
contentType: "application/json",
|
||||||
|
dataType: "json",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderChallenge() {
|
||||||
|
switch (this.challenge?.component) {
|
||||||
|
case "ak-stage-identification":
|
||||||
|
new IdentificationStage(this, this.challenge).render();
|
||||||
|
return;
|
||||||
|
case "ak-stage-password":
|
||||||
|
new PasswordStage(this, this.challenge).render();
|
||||||
|
return;
|
||||||
|
case "xak-flow-redirect":
|
||||||
|
new RedirectStage(this, this.challenge).render();
|
||||||
|
return;
|
||||||
|
case "ak-stage-autosubmit":
|
||||||
|
new AutosubmitStage(this, this.challenge).render();
|
||||||
|
return;
|
||||||
|
case "ak-stage-authenticator-validate":
|
||||||
|
new AuthenticatorValidateStage(this, this.challenge).render();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
this.container.innerText = "Unsupported stage: " + this.challenge?.component;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FlowInfoChallenge {
|
||||||
|
flowInfo?: ContextualFlowInfo;
|
||||||
|
responseErrors?: {
|
||||||
|
[key: string]: Array<ErrorDetail>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Stage<T extends FlowInfoChallenge> {
|
||||||
|
constructor(
|
||||||
|
public executor: SimpleFlowExecutor,
|
||||||
|
public challenge: T,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
error(fieldName: string) {
|
||||||
|
if (!this.challenge.responseErrors) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.challenge.responseErrors[fieldName] || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderInputError(fieldName: string) {
|
||||||
|
return `${this.error(fieldName)
|
||||||
|
.map((error) => {
|
||||||
|
return `<div class="invalid-feedback">
|
||||||
|
${error.string}
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
.join("")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNonFieldErrors() {
|
||||||
|
return `${this.error("non_field_errors")
|
||||||
|
.map((error) => {
|
||||||
|
return `<div class="alert alert-danger" role="alert">
|
||||||
|
${error.string}
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
.join("")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html(html: string) {
|
||||||
|
this.executor.container.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
throw new Error("Abstract method");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const IS_INVALID = "is-invalid";
|
||||||
|
|
||||||
|
class IdentificationStage extends Stage<IdentificationChallenge> {
|
||||||
|
render() {
|
||||||
|
this.html(`
|
||||||
|
<form id="ident-form">
|
||||||
|
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||||
|
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||||
|
${
|
||||||
|
this.challenge.applicationPre
|
||||||
|
? `<p>
|
||||||
|
Log in to continue to ${this.challenge.applicationPre}.
|
||||||
|
</p>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
<div class="form-label-group my-3 has-validation">
|
||||||
|
<input type="text" autofocus class="form-control" name="uid_field" placeholder="Email / Username">
|
||||||
|
</div>
|
||||||
|
${
|
||||||
|
this.challenge.passwordFields
|
||||||
|
? `<div class="form-label-group my-3 has-validation">
|
||||||
|
<input type="password" class="form-control ${this.error("password").length > 0 ? IS_INVALID : ""}" name="password" placeholder="Password">
|
||||||
|
${this.renderInputError("password")}
|
||||||
|
</div>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
${this.renderNonFieldErrors()}
|
||||||
|
<button class="btn btn-primary w-100 py-2" type="submit">${this.challenge.primaryAction}</button>
|
||||||
|
</form>`);
|
||||||
|
$("#ident-form input[name=uid_field]").trigger("focus");
|
||||||
|
$("#ident-form").on("submit", (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
const data = new FormData(ev.target as HTMLFormElement);
|
||||||
|
this.executor.submit(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasswordStage extends Stage<PasswordChallenge> {
|
||||||
|
render() {
|
||||||
|
this.html(`
|
||||||
|
<form id="password-form">
|
||||||
|
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||||
|
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||||
|
<div class="form-label-group my-3 has-validation">
|
||||||
|
<input type="password" autofocus class="form-control ${this.error("password").length > 0 ? IS_INVALID : ""}" name="password" placeholder="Password">
|
||||||
|
${this.renderInputError("password")}
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary w-100 py-2" type="submit">Continue</button>
|
||||||
|
</form>`);
|
||||||
|
$("#password-form input").trigger("focus");
|
||||||
|
$("#password-form").on("submit", (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
const data = new FormData(ev.target as HTMLFormElement);
|
||||||
|
this.executor.submit(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RedirectStage extends Stage<RedirectChallenge> {
|
||||||
|
render() {
|
||||||
|
window.location.assign(this.challenge.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AutosubmitStage extends Stage<AutosubmitChallenge> {
|
||||||
|
render() {
|
||||||
|
this.html(`
|
||||||
|
<form id="autosubmit-form" action="${this.challenge.url}" method="POST">
|
||||||
|
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||||
|
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||||
|
${Object.entries(this.challenge.attrs).map(([key, value]) => {
|
||||||
|
return `<input
|
||||||
|
type="hidden"
|
||||||
|
name="${key}"
|
||||||
|
value="${value}"
|
||||||
|
/>`;
|
||||||
|
})}
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<div class="spinner-border" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>`);
|
||||||
|
$("#autosubmit-form").submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Assertion {
|
||||||
|
id: string;
|
||||||
|
rawId: string;
|
||||||
|
type: string;
|
||||||
|
registrationClientExtensions: string;
|
||||||
|
response: {
|
||||||
|
clientDataJSON: string;
|
||||||
|
attestationObject: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthAssertion {
|
||||||
|
id: string;
|
||||||
|
rawId: string;
|
||||||
|
type: string;
|
||||||
|
assertionClientExtensions: string;
|
||||||
|
response: {
|
||||||
|
clientDataJSON: string;
|
||||||
|
authenticatorData: string;
|
||||||
|
signature: string;
|
||||||
|
userHandle: string | null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthenticatorValidateStage extends Stage<AuthenticatorValidationChallenge> {
|
||||||
|
deviceChallenge?: DeviceChallenge;
|
||||||
|
|
||||||
|
b64enc(buf: Uint8Array): string {
|
||||||
|
return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
b64RawEnc(buf: Uint8Array): string {
|
||||||
|
return fromByteArray(buf).replace(/\+/g, "-").replace(/\//g, "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
u8arr(input: string): Uint8Array {
|
||||||
|
return Uint8Array.from(atob(input.replace(/_/g, "/").replace(/-/g, "+")), (c) =>
|
||||||
|
c.charCodeAt(0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkWebAuthnSupport(): boolean {
|
||||||
|
if ("credentials" in navigator) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (window.location.protocol === "http:" && window.location.hostname !== "localhost") {
|
||||||
|
console.warn("WebAuthn requires this page to be accessed via HTTPS.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.warn("WebAuthn not supported by browser.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms items in the credentialCreateOptions generated on the server
|
||||||
|
* into byte arrays expected by the navigator.credentials.create() call
|
||||||
|
*/
|
||||||
|
transformCredentialCreateOptions(
|
||||||
|
credentialCreateOptions: PublicKeyCredentialCreationOptions,
|
||||||
|
userId: string,
|
||||||
|
): PublicKeyCredentialCreationOptions {
|
||||||
|
const user = credentialCreateOptions.user;
|
||||||
|
// Because json can't contain raw bytes, the server base64-encodes the User ID
|
||||||
|
// So to get the base64 encoded byte array, we first need to convert it to a regular
|
||||||
|
// string, then a byte array, re-encode it and wrap that in an array.
|
||||||
|
const stringId = decodeURIComponent(window.atob(userId));
|
||||||
|
user.id = this.u8arr(this.b64enc(this.u8arr(stringId)));
|
||||||
|
const challenge = this.u8arr(credentialCreateOptions.challenge.toString());
|
||||||
|
|
||||||
|
return Object.assign({}, credentialCreateOptions, {
|
||||||
|
challenge,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms the binary data in the credential into base64 strings
|
||||||
|
* for posting to the server.
|
||||||
|
* @param {PublicKeyCredential} newAssertion
|
||||||
|
*/
|
||||||
|
transformNewAssertionForServer(newAssertion: PublicKeyCredential): Assertion {
|
||||||
|
const attObj = new Uint8Array(
|
||||||
|
(newAssertion.response as AuthenticatorAttestationResponse).attestationObject,
|
||||||
|
);
|
||||||
|
const clientDataJSON = new Uint8Array(newAssertion.response.clientDataJSON);
|
||||||
|
const rawId = new Uint8Array(newAssertion.rawId);
|
||||||
|
|
||||||
|
const registrationClientExtensions = newAssertion.getClientExtensionResults();
|
||||||
|
return {
|
||||||
|
id: newAssertion.id,
|
||||||
|
rawId: this.b64enc(rawId),
|
||||||
|
type: newAssertion.type,
|
||||||
|
registrationClientExtensions: JSON.stringify(registrationClientExtensions),
|
||||||
|
response: {
|
||||||
|
clientDataJSON: this.b64enc(clientDataJSON),
|
||||||
|
attestationObject: this.b64enc(attObj),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
transformCredentialRequestOptions(
|
||||||
|
credentialRequestOptions: PublicKeyCredentialRequestOptions,
|
||||||
|
): PublicKeyCredentialRequestOptions {
|
||||||
|
const challenge = this.u8arr(credentialRequestOptions.challenge.toString());
|
||||||
|
|
||||||
|
const allowCredentials = (credentialRequestOptions.allowCredentials || []).map(
|
||||||
|
(credentialDescriptor) => {
|
||||||
|
const id = this.u8arr(credentialDescriptor.id.toString());
|
||||||
|
return Object.assign({}, credentialDescriptor, { id });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return Object.assign({}, credentialRequestOptions, {
|
||||||
|
challenge,
|
||||||
|
allowCredentials,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the binary data in the assertion into strings for posting to the server.
|
||||||
|
* @param {PublicKeyCredential} newAssertion
|
||||||
|
*/
|
||||||
|
transformAssertionForServer(newAssertion: PublicKeyCredential): AuthAssertion {
|
||||||
|
const response = newAssertion.response as AuthenticatorAssertionResponse;
|
||||||
|
const authData = new Uint8Array(response.authenticatorData);
|
||||||
|
const clientDataJSON = new Uint8Array(response.clientDataJSON);
|
||||||
|
const rawId = new Uint8Array(newAssertion.rawId);
|
||||||
|
const sig = new Uint8Array(response.signature);
|
||||||
|
const assertionClientExtensions = newAssertion.getClientExtensionResults();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: newAssertion.id,
|
||||||
|
rawId: this.b64enc(rawId),
|
||||||
|
type: newAssertion.type,
|
||||||
|
assertionClientExtensions: JSON.stringify(assertionClientExtensions),
|
||||||
|
|
||||||
|
response: {
|
||||||
|
clientDataJSON: this.b64RawEnc(clientDataJSON),
|
||||||
|
signature: this.b64RawEnc(sig),
|
||||||
|
authenticatorData: this.b64RawEnc(authData),
|
||||||
|
userHandle: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.deviceChallenge) {
|
||||||
|
return this.renderChallengePicker();
|
||||||
|
}
|
||||||
|
switch (this.deviceChallenge.deviceClass) {
|
||||||
|
case "static":
|
||||||
|
case "totp":
|
||||||
|
this.renderCodeInput();
|
||||||
|
break;
|
||||||
|
case "webauthn":
|
||||||
|
this.renderWebauthn();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderChallengePicker() {
|
||||||
|
const challenges = this.challenge.deviceChallenges.filter((challenge) =>
|
||||||
|
challenge.deviceClass === "webauthn" && !this.checkWebAuthnSupport()
|
||||||
|
? undefined
|
||||||
|
: challenge,
|
||||||
|
);
|
||||||
|
this.html(`<form id="picker-form">
|
||||||
|
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||||
|
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||||
|
${
|
||||||
|
challenges.length > 0
|
||||||
|
? "<p>Select an authentication method.</p>"
|
||||||
|
: `
|
||||||
|
<p>No compatible authentication method available</p>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
${challenges
|
||||||
|
.map((challenge) => {
|
||||||
|
let label = undefined;
|
||||||
|
switch (challenge.deviceClass) {
|
||||||
|
case "static":
|
||||||
|
label = "Recovery keys";
|
||||||
|
break;
|
||||||
|
case "totp":
|
||||||
|
label = "Traditional authenticator";
|
||||||
|
break;
|
||||||
|
case "webauthn":
|
||||||
|
label = "Security key";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!label) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return `<div class="form-label-group my-3 has-validation">
|
||||||
|
<button id="${challenge.deviceClass}-${challenge.deviceUid}" class="btn btn-secondary w-100 py-2" type="button">
|
||||||
|
${label}
|
||||||
|
</button>
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
.join("")}
|
||||||
|
</form>`);
|
||||||
|
this.challenge.deviceChallenges.forEach((challenge) => {
|
||||||
|
$(`#picker-form button#${challenge.deviceClass}-${challenge.deviceUid}`).on(
|
||||||
|
"click",
|
||||||
|
() => {
|
||||||
|
this.deviceChallenge = challenge;
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCodeInput() {
|
||||||
|
this.html(`
|
||||||
|
<form id="totp-form">
|
||||||
|
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||||
|
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||||
|
<div class="form-label-group my-3 has-validation">
|
||||||
|
<input type="text" autofocus class="form-control ${this.error("code").length > 0 ? IS_INVALID : ""}" name="code" placeholder="Please enter your code" autocomplete="one-time-code">
|
||||||
|
${this.renderInputError("code")}
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary w-100 py-2" type="submit">Continue</button>
|
||||||
|
</form>`);
|
||||||
|
$("#totp-form input").trigger("focus");
|
||||||
|
$("#totp-form").on("submit", (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
const data = new FormData(ev.target as HTMLFormElement);
|
||||||
|
this.executor.submit(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderWebauthn() {
|
||||||
|
this.html(`
|
||||||
|
<form id="totp-form">
|
||||||
|
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
||||||
|
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<div class="spinner-border" role="status">
|
||||||
|
<span class="sr-only">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`);
|
||||||
|
navigator.credentials
|
||||||
|
.get({
|
||||||
|
publicKey: this.transformCredentialRequestOptions(
|
||||||
|
this.deviceChallenge?.challenge as PublicKeyCredentialRequestOptions,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.then((assertion) => {
|
||||||
|
if (!assertion) {
|
||||||
|
throw new Error("No assertion");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// we now have an authentication assertion! encode the byte arrays contained
|
||||||
|
// in the assertion data as strings for posting to the server
|
||||||
|
const transformedAssertionForServer = this.transformAssertionForServer(
|
||||||
|
assertion as PublicKeyCredential,
|
||||||
|
);
|
||||||
|
|
||||||
|
// post the assertion to the server for verification.
|
||||||
|
this.executor.submit({
|
||||||
|
webauthn: transformedAssertionForServer,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Error when validating assertion on server: ${err}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn(error);
|
||||||
|
this.deviceChallenge = undefined;
|
||||||
|
this.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sfe = new SimpleFlowExecutor($("#flow-sfe-container")[0] as HTMLDivElement);
|
||||||
|
sfe.start();
|
||||||
7
web/packages/sfe/tsconfig.json
Normal file
7
web/packages/sfe/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["jquery"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["DOM", "ES2015", "ES2017"]
|
||||||
|
}
|
||||||
|
}
|
||||||
25
web/paths.js
25
web/paths.js
@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Path constants for the web package.
|
|
||||||
*/
|
|
||||||
import { dirname, resolve } from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {'@goauthentik/web'} WebPackageIdentifier
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The root of the web package.
|
|
||||||
*/
|
|
||||||
export const PackageRoot = /** @type {WebPackageIdentifier} */ (resolve(__dirname));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Path to the web package's distribution directory.
|
|
||||||
*
|
|
||||||
* This is where the built files are located after running the build process.
|
|
||||||
*/
|
|
||||||
export const DistDirectory = /** @type {`${WebPackageIdentifier}/dist`} */ (
|
|
||||||
resolve(__dirname, "dist")
|
|
||||||
);
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Build script for the simplified flow executor (SFE).
|
|
||||||
*/
|
|
||||||
import { DistDirectory, PackageRoot } from "@goauthentik/web/paths";
|
|
||||||
import esbuild from "esbuild";
|
|
||||||
import copy from "esbuild-plugin-copy";
|
|
||||||
import { es5Plugin } from "esbuild-plugin-es5";
|
|
||||||
import { createRequire } from "node:module";
|
|
||||||
import * as path from "node:path";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the Simplified Flow Executor bundle.
|
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
* The output directory and file names are referenced by the backend.
|
|
||||||
* @see {@link ../../authentik/flows/templates/if/flow-sfe.html}
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async function buildSFE() {
|
|
||||||
const require = createRequire(import.meta.url);
|
|
||||||
|
|
||||||
const sourceDirectory = path.join(PackageRoot, "sfe");
|
|
||||||
|
|
||||||
const entryPoint = path.join(sourceDirectory, "main.js");
|
|
||||||
const outDirectory = path.join(DistDirectory, "sfe");
|
|
||||||
|
|
||||||
const bootstrapCSSPath = require.resolve(
|
|
||||||
path.join("bootstrap", "dist", "css", "bootstrap.min.css"),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {esbuild.BuildOptions}
|
|
||||||
*/
|
|
||||||
const config = {
|
|
||||||
tsconfig: path.join(sourceDirectory, "tsconfig.json"),
|
|
||||||
entryPoints: [entryPoint],
|
|
||||||
minify: false,
|
|
||||||
bundle: true,
|
|
||||||
sourcemap: true,
|
|
||||||
treeShaking: true,
|
|
||||||
legalComments: "external",
|
|
||||||
platform: "browser",
|
|
||||||
format: "iife",
|
|
||||||
alias: {
|
|
||||||
"@swc/helpers": path.dirname(require.resolve("@swc/helpers/package.json")),
|
|
||||||
},
|
|
||||||
banner: {
|
|
||||||
js: [
|
|
||||||
// ---
|
|
||||||
"// Simplified Flow Executor (SFE)",
|
|
||||||
`// Bundled on ${new Date().toISOString()}`,
|
|
||||||
"// @ts-nocheck",
|
|
||||||
"",
|
|
||||||
].join("\n"),
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
copy({
|
|
||||||
assets: [
|
|
||||||
{
|
|
||||||
from: bootstrapCSSPath,
|
|
||||||
to: outDirectory,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
es5Plugin({
|
|
||||||
swc: {
|
|
||||||
jsc: {
|
|
||||||
loose: false,
|
|
||||||
externalHelpers: false,
|
|
||||||
keepClassNames: false,
|
|
||||||
},
|
|
||||||
minify: false,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
target: ["es5"],
|
|
||||||
outdir: outDirectory,
|
|
||||||
};
|
|
||||||
|
|
||||||
esbuild.build(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
buildSFE()
|
|
||||||
.then(() => {
|
|
||||||
console.log("Build complete");
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Build failed", error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import { DistDirectory, PackageRoot } from "@goauthentik/web/paths";
|
|
||||||
import { execFileSync } from "child_process";
|
import { execFileSync } from "child_process";
|
||||||
import { deepmerge } from "deepmerge-ts";
|
import { deepmerge } from "deepmerge-ts";
|
||||||
import esbuild from "esbuild";
|
import esbuild from "esbuild";
|
||||||
@ -171,7 +170,7 @@ function composeVersionID() {
|
|||||||
* @throws {Error} on build failure
|
* @throws {Error} on build failure
|
||||||
*/
|
*/
|
||||||
function createEntryPointOptions([source, dest], overrides = {}) {
|
function createEntryPointOptions([source, dest], overrides = {}) {
|
||||||
const outdir = path.join(DistDirectory, dest);
|
const outdir = path.join(__dirname, "..", "dist", dest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.BuildOptions}
|
* @type {esbuild.BuildOptions}
|
||||||
@ -234,7 +233,7 @@ async function doWatch() {
|
|||||||
buildObserverPlugin({
|
buildObserverPlugin({
|
||||||
serverURL,
|
serverURL,
|
||||||
logPrefix: entryPoint[1],
|
logPrefix: entryPoint[1],
|
||||||
relativeRoot: PackageRoot,
|
relativeRoot: path.join(__dirname, ".."),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
|
|||||||
@ -1,191 +0,0 @@
|
|||||||
/**
|
|
||||||
* @import { AuthenticatorValidationChallenge, DeviceChallenge } from "@goauthentik/api";
|
|
||||||
* @import { FlowExecutor } from './Stage.js';
|
|
||||||
*/
|
|
||||||
import {
|
|
||||||
isWebAuthnSupported,
|
|
||||||
transformAssertionForServer,
|
|
||||||
transformCredentialRequestOptions,
|
|
||||||
} from "@goauthentik/web/authentication";
|
|
||||||
import $ from "jquery";
|
|
||||||
|
|
||||||
import { Stage } from "./Stage.js";
|
|
||||||
import { ak } from "./utils.js";
|
|
||||||
|
|
||||||
//@ts-check
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {AuthenticatorValidationChallenge} T
|
|
||||||
* @extends {Stage<T>}
|
|
||||||
*/
|
|
||||||
export class AuthenticatorValidateStage extends Stage {
|
|
||||||
/**
|
|
||||||
* @param {FlowExecutor} executor - The executor for this stage
|
|
||||||
* @param {T} challenge - The challenge for this stage
|
|
||||||
*/
|
|
||||||
constructor(executor, challenge) {
|
|
||||||
super(executor, challenge);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {DeviceChallenge | null}
|
|
||||||
*/
|
|
||||||
this.deviceChallenge = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (!this.deviceChallenge) {
|
|
||||||
this.renderChallengePicker();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this.deviceChallenge.deviceClass) {
|
|
||||||
case "static":
|
|
||||||
case "totp":
|
|
||||||
this.renderCodeInput();
|
|
||||||
break;
|
|
||||||
case "webauthn":
|
|
||||||
this.renderWebauthn();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
renderChallengePicker() {
|
|
||||||
const challenges = this.challenge.deviceChallenges.filter((challenge) =>
|
|
||||||
challenge.deviceClass === "webauthn" && !isWebAuthnSupported() ? undefined : challenge,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.html(/* html */ `<form id="picker-form">
|
|
||||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
|
||||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
|
||||||
${
|
|
||||||
challenges.length > 0
|
|
||||||
? /* html */ `<p>Select an authentication method.</p>`
|
|
||||||
: /* html */ `<p>No compatible authentication method available</p>`
|
|
||||||
}
|
|
||||||
${challenges
|
|
||||||
.map((challenge) => {
|
|
||||||
let label = undefined;
|
|
||||||
|
|
||||||
switch (challenge.deviceClass) {
|
|
||||||
case "static":
|
|
||||||
label = "Recovery keys";
|
|
||||||
break;
|
|
||||||
case "totp":
|
|
||||||
label = "Traditional authenticator";
|
|
||||||
break;
|
|
||||||
case "webauthn":
|
|
||||||
label = "Security key";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!label) return "";
|
|
||||||
|
|
||||||
return /* html */ `<div class="form-label-group my-3 has-validation">
|
|
||||||
<button id="${challenge.deviceClass}-${challenge.deviceUid}" class="btn btn-secondary w-100 py-2" type="button">
|
|
||||||
${label}
|
|
||||||
</button>
|
|
||||||
</div>`;
|
|
||||||
})
|
|
||||||
.join("")}
|
|
||||||
</form>`);
|
|
||||||
|
|
||||||
this.challenge.deviceChallenges.forEach((challenge) => {
|
|
||||||
$(`#picker-form button#${challenge.deviceClass}-${challenge.deviceUid}`).on(
|
|
||||||
"click",
|
|
||||||
() => {
|
|
||||||
this.deviceChallenge = challenge;
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
renderCodeInput() {
|
|
||||||
this.html(/* html */ `
|
|
||||||
<form id="totp-form">
|
|
||||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
|
||||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
|
||||||
<div class="form-label-group my-3 has-validation">
|
|
||||||
<input type="text" autofocus class="form-control ${this.error("code").length > 0 ? "is-invalid" : ""}" name="code" placeholder="Please enter your code" autocomplete="one-time-code">
|
|
||||||
${this.renderInputError("code")}
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-primary w-100 py-2" type="submit">Continue</button>
|
|
||||||
</form>`);
|
|
||||||
|
|
||||||
$("#totp-form input").trigger("focus");
|
|
||||||
|
|
||||||
$("#totp-form").on("submit", (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
const target = /** @type {HTMLFormElement} */ (ev.target);
|
|
||||||
|
|
||||||
const data = new FormData(target);
|
|
||||||
this.executor.submit(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
renderWebauthn() {
|
|
||||||
this.html(/* html */ `
|
|
||||||
<form id="totp-form">
|
|
||||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
|
||||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
|
||||||
<div class="d-flex justify-content-center">
|
|
||||||
<div class="spinner-border" role="status">
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
`);
|
|
||||||
|
|
||||||
const challenge = /** @type {PublicKeyCredentialRequestOptions} */ (
|
|
||||||
this.deviceChallenge?.challenge
|
|
||||||
);
|
|
||||||
|
|
||||||
navigator.credentials
|
|
||||||
.get({
|
|
||||||
publicKey: transformCredentialRequestOptions(challenge),
|
|
||||||
})
|
|
||||||
.then((credential) => {
|
|
||||||
if (!credential) {
|
|
||||||
throw new Error("No assertion");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (credential.type !== "public-key") {
|
|
||||||
throw new Error("Invalid assertion type");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// We now have an authentication assertion!
|
|
||||||
// Encode the byte arrays contained in the assertion data as strings
|
|
||||||
// for posting to the server.
|
|
||||||
const transformedAssertionForServer = transformAssertionForServer(
|
|
||||||
/** @type {PublicKeyCredential} */ (credential),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Post the assertion to the server for verification.
|
|
||||||
this.executor.submit({
|
|
||||||
webauthn: transformedAssertionForServer,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(`Error when validating assertion on server: ${err}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.warn(error);
|
|
||||||
|
|
||||||
this.deviceChallenge = null;
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
/**
|
|
||||||
* @import { AutosubmitChallenge } from "@goauthentik/api";
|
|
||||||
*/
|
|
||||||
import $ from "jquery";
|
|
||||||
|
|
||||||
import { Stage } from "./Stage.js";
|
|
||||||
import { ak } from "./utils.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {AutosubmitChallenge} T
|
|
||||||
* @extends {Stage<T>}
|
|
||||||
*/
|
|
||||||
export class AutosubmitStage extends Stage {
|
|
||||||
render() {
|
|
||||||
this.html(/* html */ `
|
|
||||||
<form id="autosubmit-form" action="${this.challenge.url}" method="POST">
|
|
||||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
|
||||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
|
||||||
${Object.entries(this.challenge.attrs).map(([key, value]) => {
|
|
||||||
return /* html */ `<input
|
|
||||||
type="hidden"
|
|
||||||
name="${key}"
|
|
||||||
value="${value}"
|
|
||||||
/>`;
|
|
||||||
})}
|
|
||||||
<div class="d-flex justify-content-center">
|
|
||||||
<div class="spinner-border" role="status">
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>`);
|
|
||||||
|
|
||||||
$("#autosubmit-form").submit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
/**
|
|
||||||
* @import { IdentificationChallenge } from "@goauthentik/api";
|
|
||||||
*/
|
|
||||||
import $ from "jquery";
|
|
||||||
|
|
||||||
import { Stage } from "./Stage.js";
|
|
||||||
import { ak } from "./utils.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {IdentificationChallenge} T
|
|
||||||
* @extends {Stage<T>}
|
|
||||||
*/
|
|
||||||
export class IdentificationStage extends Stage {
|
|
||||||
render() {
|
|
||||||
this.html(/* html */ `
|
|
||||||
<form id="ident-form">
|
|
||||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
|
||||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
|
||||||
${
|
|
||||||
this.challenge.applicationPre
|
|
||||||
? /* html */ `<p>
|
|
||||||
Log in to continue to ${this.challenge.applicationPre}.
|
|
||||||
</p>`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
<div class="form-label-group my-3 has-validation">
|
|
||||||
<input type="text" autofocus class="form-control" name="uid_field" placeholder="Email / Username">
|
|
||||||
</div>
|
|
||||||
${
|
|
||||||
this.challenge.passwordFields
|
|
||||||
? /* html */ `<div class="form-label-group my-3 has-validation">
|
|
||||||
<input type="password" class="form-control ${this.error("password").length > 0 ? "is-invalid" : ""}" name="password" placeholder="Password">
|
|
||||||
${this.renderInputError("password")}
|
|
||||||
</div>`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
${this.renderNonFieldErrors()}
|
|
||||||
<button class="btn btn-primary w-100 py-2" type="submit">${this.challenge.primaryAction}</button>
|
|
||||||
</form>`);
|
|
||||||
|
|
||||||
$("#ident-form input[name=uid_field]").trigger("focus");
|
|
||||||
$("#ident-form").on("submit", (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
const target = /** @type {HTMLFormElement} */ (ev.target);
|
|
||||||
|
|
||||||
const data = new FormData(target);
|
|
||||||
this.executor.submit(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* @import { PasswordChallenge } from "@goauthentik/api";
|
|
||||||
*/
|
|
||||||
import $ from "jquery";
|
|
||||||
|
|
||||||
import { Stage } from "./Stage.js";
|
|
||||||
import { ak } from "./utils.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {PasswordChallenge} T
|
|
||||||
* @extends {Stage<T>}
|
|
||||||
*/
|
|
||||||
export class PasswordStage extends Stage {
|
|
||||||
render() {
|
|
||||||
this.html(/* html */ `
|
|
||||||
<form id="password-form">
|
|
||||||
<img class="mb-4 brand-icon" src="${ak().brand.branding_logo}" alt="">
|
|
||||||
<h1 class="h3 mb-3 fw-normal text-center">${this.challenge?.flowInfo?.title}</h1>
|
|
||||||
<div class="form-label-group my-3 has-validation">
|
|
||||||
<input type="password" autofocus class="form-control ${this.error("password").length > 0 ? "is-invalid" : ""}" name="password" placeholder="Password">
|
|
||||||
${this.renderInputError("password")}
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-primary w-100 py-2" type="submit">Continue</button>
|
|
||||||
</form>`);
|
|
||||||
|
|
||||||
$("#password-form input").trigger("focus");
|
|
||||||
|
|
||||||
$("#password-form").on("submit", (ev) => {
|
|
||||||
ev.preventDefault();
|
|
||||||
|
|
||||||
const target = /** @type {HTMLFormElement} */ (ev.target);
|
|
||||||
|
|
||||||
const data = new FormData(target);
|
|
||||||
this.executor.submit(data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
/**
|
|
||||||
* @import { RedirectChallenge } from "@goauthentik/api";
|
|
||||||
*/
|
|
||||||
import { Stage } from "./Stage.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template {RedirectChallenge} T
|
|
||||||
* @extends {Stage<T>}
|
|
||||||
*/
|
|
||||||
export class RedirectStage extends Stage {
|
|
||||||
render() {
|
|
||||||
window.location.assign(this.challenge.to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
/**
|
|
||||||
* @import { ChallengeTypes } from "@goauthentik/api";
|
|
||||||
* @import { FlowExecutor } from './Stage.js';
|
|
||||||
*/
|
|
||||||
import $ from "jquery";
|
|
||||||
|
|
||||||
import { ChallengeTypesFromJSON } from "@goauthentik/api";
|
|
||||||
|
|
||||||
import { AuthenticatorValidateStage } from "./AuthenticatorValidateStage.js";
|
|
||||||
import { AutosubmitStage } from "./AutosubmitStage.js";
|
|
||||||
import { IdentificationStage } from "./IdentificationStage.js";
|
|
||||||
import { PasswordStage } from "./PasswordStage.js";
|
|
||||||
import { RedirectStage } from "./RedirectStage.js";
|
|
||||||
import { ak } from "./utils.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple Flow Executor lifecycle.
|
|
||||||
*
|
|
||||||
* @implements {FlowExecutor}
|
|
||||||
*/
|
|
||||||
export class SimpleFlowExecutor {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {HTMLDivElement} container
|
|
||||||
*/
|
|
||||||
constructor(container) {
|
|
||||||
/**
|
|
||||||
* @type {ChallengeTypes | null} The current challenge.
|
|
||||||
*/
|
|
||||||
this.challenge = null;
|
|
||||||
/**
|
|
||||||
* @type {string} The flow slug.
|
|
||||||
*/
|
|
||||||
this.flowSlug = window.location.pathname.split("/")[3] || "";
|
|
||||||
/**
|
|
||||||
* @type {HTMLDivElement} The container element for the flow executor.
|
|
||||||
*/
|
|
||||||
this.container = container;
|
|
||||||
}
|
|
||||||
|
|
||||||
get apiURL() {
|
|
||||||
return `${ak().api.base}api/v3/flows/executor/${this.flowSlug}/?query=${encodeURIComponent(window.location.search.substring(1))}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
|
||||||
$.ajax({
|
|
||||||
type: "GET",
|
|
||||||
url: this.apiURL,
|
|
||||||
success: (data) => {
|
|
||||||
this.challenge = ChallengeTypesFromJSON(data);
|
|
||||||
|
|
||||||
this.renderChallenge();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submits the form data.
|
|
||||||
* @param {Record<string, unknown> | FormData} payload
|
|
||||||
*/
|
|
||||||
submit(payload) {
|
|
||||||
$("button[type=submit]").addClass("disabled")
|
|
||||||
.html(`<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
|
|
||||||
<span role="status">Loading...</span>`);
|
|
||||||
/**
|
|
||||||
* @type {Record<string, unknown>}
|
|
||||||
*/
|
|
||||||
let finalData;
|
|
||||||
|
|
||||||
if (payload instanceof FormData) {
|
|
||||||
finalData = {};
|
|
||||||
|
|
||||||
payload.forEach((value, key) => {
|
|
||||||
finalData[key] = value;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
finalData = payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: "POST",
|
|
||||||
url: this.apiURL,
|
|
||||||
data: JSON.stringify(finalData),
|
|
||||||
success: (data) => {
|
|
||||||
this.challenge = ChallengeTypesFromJSON(data);
|
|
||||||
this.renderChallenge();
|
|
||||||
},
|
|
||||||
contentType: "application/json",
|
|
||||||
dataType: "json",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
renderChallenge() {
|
|
||||||
switch (this.challenge?.component) {
|
|
||||||
case "ak-stage-identification":
|
|
||||||
return new IdentificationStage(this, this.challenge).render();
|
|
||||||
case "ak-stage-password":
|
|
||||||
return new PasswordStage(this, this.challenge).render();
|
|
||||||
case "xak-flow-redirect":
|
|
||||||
return new RedirectStage(this, this.challenge).render();
|
|
||||||
case "ak-stage-autosubmit":
|
|
||||||
return new AutosubmitStage(this, this.challenge).render();
|
|
||||||
case "ak-stage-authenticator-validate":
|
|
||||||
return new AuthenticatorValidateStage(this, this.challenge).render();
|
|
||||||
default:
|
|
||||||
this.container.innerText = `Unsupported stage: ${this.challenge?.component}`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
/**
|
|
||||||
* @import { ContextualFlowInfo, ErrorDetail } from "@goauthentik/api";
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {object} FlowInfoChallenge
|
|
||||||
* @property {ContextualFlowInfo} [flowInfo]
|
|
||||||
* @property {Record<string, Array<ErrorDetail>>} [responseErrors]
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
export class FlowExecutor {
|
|
||||||
constructor() {
|
|
||||||
/**
|
|
||||||
* The DOM container element.
|
|
||||||
*
|
|
||||||
* @type {HTMLElement}
|
|
||||||
* @abstract
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
||||||
this.container;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Submits the form data.
|
|
||||||
*
|
|
||||||
* @param {Record<string, unknown> | FormData} data The data to submit.
|
|
||||||
* @abstract
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
submit(data) {
|
|
||||||
throw new Error(`Method 'submit' not implemented in ${this.constructor.name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a stage in a flow
|
|
||||||
* @template {FlowInfoChallenge} T
|
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
export class Stage {
|
|
||||||
/**
|
|
||||||
* @param {FlowExecutor} executor - The executor for this stage
|
|
||||||
* @param {T} challenge - The challenge for this stage
|
|
||||||
*/
|
|
||||||
constructor(executor, challenge) {
|
|
||||||
/** @type {FlowExecutor} */
|
|
||||||
this.executor = executor;
|
|
||||||
|
|
||||||
/** @type {T} */
|
|
||||||
this.challenge = challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @protected
|
|
||||||
* @param {string} fieldName
|
|
||||||
*/
|
|
||||||
error(fieldName) {
|
|
||||||
if (!this.challenge.responseErrors) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return this.challenge.responseErrors[fieldName] || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @protected
|
|
||||||
* @param {string} fieldName
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
renderInputError(fieldName) {
|
|
||||||
return `${this.error(fieldName)
|
|
||||||
.map((error) => {
|
|
||||||
return /* html */ `<div class="invalid-feedback">
|
|
||||||
${error.string}
|
|
||||||
</div>`;
|
|
||||||
})
|
|
||||||
.join("")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @protected
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
renderNonFieldErrors() {
|
|
||||||
return `${this.error("non_field_errors")
|
|
||||||
.map((error) => {
|
|
||||||
return /* html */ `<div class="alert alert-danger" role="alert">
|
|
||||||
${error.string}
|
|
||||||
</div>`;
|
|
||||||
})
|
|
||||||
.join("")}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @protected
|
|
||||||
* @param {string} innerHTML
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
html(innerHTML) {
|
|
||||||
this.executor.container.innerHTML = innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the stage (must be implemented by subclasses)
|
|
||||||
*
|
|
||||||
* @abstract
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
throw new Error("Abstract method");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Simplified Flow Executor (SFE) library module.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from "./Stage.js";
|
|
||||||
export * from "./SimpleFlowExecutor.js";
|
|
||||||
export * from "./AuthenticatorValidateStage.js";
|
|
||||||
export * from "./AutosubmitStage.js";
|
|
||||||
export * from "./IdentificationStage.js";
|
|
||||||
export * from "./PasswordStage.js";
|
|
||||||
export * from "./RedirectStage.js";
|
|
||||||
export * from "./utils.js";
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* @typedef {object} GlobalAuthentik
|
|
||||||
* @property {object} brand
|
|
||||||
* @property {string} brand.branding_logo
|
|
||||||
* @property {object} api
|
|
||||||
* @property {string} api.base
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the global authentik object from the window.
|
|
||||||
* @throws {Error} If the object not found
|
|
||||||
* @returns {GlobalAuthentik}
|
|
||||||
*/
|
|
||||||
export function ak() {
|
|
||||||
if (!("authentik" in window)) {
|
|
||||||
throw new Error("No authentik object found in window");
|
|
||||||
}
|
|
||||||
|
|
||||||
return /** @type {GlobalAuthentik} */ (window.authentik);
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file Simplified Flow Executor (SFE) entry point.
|
|
||||||
*/
|
|
||||||
import "formdata-polyfill";
|
|
||||||
import $ from "jquery";
|
|
||||||
|
|
||||||
import { SimpleFlowExecutor } from "./lib/index.js";
|
|
||||||
|
|
||||||
const flowContainer = /** @type {HTMLDivElement} */ ($("#flow-sfe-container")[0]);
|
|
||||||
|
|
||||||
if (!flowContainer) {
|
|
||||||
throw new Error("No flow container element found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const sfe = new SimpleFlowExecutor(flowContainer);
|
|
||||||
|
|
||||||
sfe.start();
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { VERSION } from "@goauthentik/common/constants";
|
import { VERSION } from "@goauthentik/common/constants";
|
||||||
import { globalAK } from "@goauthentik/common/global";
|
import { BrandConfig, ServerConfig } from "@goauthentik/common/global";
|
||||||
import "@goauthentik/elements/EmptyState";
|
import "@goauthentik/elements/EmptyState";
|
||||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||||
@ -33,7 +33,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
|||||||
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
const status = await new AdminApi(DEFAULT_CONFIG).adminSystemRetrieve();
|
||||||
const version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
|
const version = await new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
|
||||||
let build: string | TemplateResult = msg("Release");
|
let build: string | TemplateResult = msg("Release");
|
||||||
if (globalAK().config.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
if (ServerConfig.capabilities.includes(CapabilitiesEnum.CanDebug)) {
|
||||||
build = msg("Development");
|
build = msg("Development");
|
||||||
} else if (version.buildHash !== "") {
|
} else if (version.buildHash !== "") {
|
||||||
build = html`<a
|
build = html`<a
|
||||||
@ -58,7 +58,7 @@ export class AboutModal extends WithLicenseSummary(WithBrandConfig(ModalButton))
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderModal() {
|
renderModal() {
|
||||||
let product = globalAK().brand.brandingTitle || DefaultBrand.brandingTitle;
|
let product = BrandConfig.brandingTitle || DefaultBrand.brandingTitle;
|
||||||
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
|
if (this.licenseSummary.status != LicenseSummaryStatusEnum.Unlicensed) {
|
||||||
product += ` ${msg("Enterprise")}`;
|
product += ` ${msg("Enterprise")}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { EventGeo, EventUser } from "@goauthentik/admin/events/utils";
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EventWithContext } from "@goauthentik/common/events";
|
import { EventWithContext } from "@goauthentik/common/events";
|
||||||
import { actionToLabel } from "@goauthentik/common/labels";
|
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/components/ak-event-info";
|
||||||
import "@goauthentik/elements/Tabs";
|
import "@goauthentik/elements/Tabs";
|
||||||
import "@goauthentik/elements/buttons/Dropdown";
|
import "@goauthentik/elements/buttons/Dropdown";
|
||||||
@ -74,7 +74,7 @@ export class RecentEventsCard extends Table<Event> {
|
|||||||
html`<div><a href="${`#/events/log/${item.pk}`}">${actionToLabel(item.action)}</a></div>
|
html`<div><a href="${`#/events/log/${item.pk}`}">${actionToLabel(item.action)}</a></div>
|
||||||
<small>${item.app}</small>`,
|
<small>${item.app}</small>`,
|
||||||
EventUser(item),
|
EventUser(item),
|
||||||
html`<div>${getRelativeTime(item.created)}</div>
|
html`<div>${formatElapsedTime(item.created)}</div>
|
||||||
<small>${item.created.toLocaleString()}</small>`,
|
<small>${item.created.toLocaleString()}</small>`,
|
||||||
html` <div>${item.clientIp || msg("-")}</div>
|
html` <div>${item.clientIp || msg("-")}</div>
|
||||||
<small>${EventGeo(item)}</small>`,
|
<small>${EventGeo(item)}</small>`,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import "@goauthentik/admin/blueprints/BlueprintForm";
|
|||||||
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
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/components/ak-status-label";
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
@ -141,7 +141,7 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
|
|||||||
html`<div>${item.name}</div>
|
html`<div>${item.name}</div>
|
||||||
${description ? html`<small>${description}</small>` : html``}`,
|
${description ? html`<small>${description}</small>` : html``}`,
|
||||||
html`${BlueprintStatus(item)}`,
|
html`${BlueprintStatus(item)}`,
|
||||||
html`<div>${getRelativeTime(item.lastApplied)}</div>
|
html`<div>${formatElapsedTime(item.lastApplied)}</div>
|
||||||
<small>${item.lastApplied.toLocaleString()}</small>`,
|
<small>${item.lastApplied.toLocaleString()}</small>`,
|
||||||
html`<ak-status-label ?good=${item.enabled}></ak-status-label>`,
|
html`<ak-status-label ?good=${item.enabled}></ak-status-label>`,
|
||||||
html`<ak-forms-modal>
|
html`<ak-forms-modal>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import "@goauthentik/admin/enterprise/EnterpriseLicenseForm";
|
|||||||
import "@goauthentik/admin/enterprise/EnterpriseStatusCard";
|
import "@goauthentik/admin/enterprise/EnterpriseStatusCard";
|
||||||
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
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 { PFColor } from "@goauthentik/elements/Label";
|
||||||
import "@goauthentik/elements/Spinner";
|
import "@goauthentik/elements/Spinner";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
@ -186,7 +186,7 @@ export class EnterpriseLicenseListPage extends TablePage<License> {
|
|||||||
>
|
>
|
||||||
${this.summary &&
|
${this.summary &&
|
||||||
this.summary?.status !== LicenseSummaryStatusEnum.Unlicensed
|
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>`
|
<small>${this.summary.latestValid.toLocaleString()}</small>`
|
||||||
: "-"}
|
: "-"}
|
||||||
</ak-aggregate-card>
|
</ak-aggregate-card>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { EventGeo, EventUser } from "@goauthentik/admin/events/utils";
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EventWithContext } from "@goauthentik/common/events";
|
import { EventWithContext } from "@goauthentik/common/events";
|
||||||
import { actionToLabel } from "@goauthentik/common/labels";
|
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/components/ak-event-info";
|
||||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||||
import { TableColumn } from "@goauthentik/elements/table/Table";
|
import { TableColumn } from "@goauthentik/elements/table/Table";
|
||||||
@ -78,7 +78,7 @@ export class EventListPage extends TablePage<Event> {
|
|||||||
html`<div>${actionToLabel(item.action)}</div>
|
html`<div>${actionToLabel(item.action)}</div>
|
||||||
<small>${item.app}</small>`,
|
<small>${item.app}</small>`,
|
||||||
EventUser(item),
|
EventUser(item),
|
||||||
html`<div>${getRelativeTime(item.created)}</div>
|
html`<div>${formatElapsedTime(item.created)}</div>
|
||||||
<small>${item.created.toLocaleString()}</small>`,
|
<small>${item.created.toLocaleString()}</small>`,
|
||||||
html`<div>${item.clientIp || msg("-")}</div>
|
html`<div>${item.clientIp || msg("-")}</div>
|
||||||
<small>${EventGeo(item)}</small>`,
|
<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 { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EventWithContext } from "@goauthentik/common/events";
|
import { EventWithContext } from "@goauthentik/common/events";
|
||||||
import { actionToLabel } from "@goauthentik/common/labels";
|
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/components/ak-event-info";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import "@goauthentik/elements/PageHeader";
|
import "@goauthentik/elements/PageHeader";
|
||||||
@ -104,7 +104,7 @@ export class EventViewPage extends AKElement {
|
|||||||
</dt>
|
</dt>
|
||||||
<dd class="pf-c-description-list__description">
|
<dd class="pf-c-description-list__description">
|
||||||
<div class="pf-c-description-list__text">
|
<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>
|
<small>${this.event.created.toLocaleString()}</small>
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||||
import "@goauthentik/components/ak-status-label";
|
import "@goauthentik/components/ak-status-label";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||||
@ -105,7 +105,7 @@ export class MemberSelectTable extends TableModal<User> {
|
|||||||
<small>${item.name}</small>`,
|
<small>${item.name}</small>`,
|
||||||
html` <ak-status-label type="warning" ?good=${item.isActive}></ak-status-label>`,
|
html` <ak-status-label type="warning" ?good=${item.isActive}></ak-status-label>`,
|
||||||
html`${item.lastLogin
|
html`${item.lastLogin
|
||||||
? html`<div>${getRelativeTime(item.lastLogin)}</div>
|
? html`<div>${formatElapsedTime(item.lastLogin)}</div>
|
||||||
<small>${item.lastLogin.toLocaleString()}</small>`
|
<small>${item.lastLogin.toLocaleString()}</small>`
|
||||||
: msg("-")}`,
|
: msg("-")}`,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -8,8 +8,8 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
|||||||
import { PFSize } from "@goauthentik/common/enums.js";
|
import { PFSize } from "@goauthentik/common/enums.js";
|
||||||
import { parseAPIResponseError, pluckErrorDetail } from "@goauthentik/common/errors/network";
|
import { parseAPIResponseError, pluckErrorDetail } from "@goauthentik/common/errors/network";
|
||||||
import { MessageLevel } from "@goauthentik/common/messages";
|
import { MessageLevel } from "@goauthentik/common/messages";
|
||||||
|
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||||
import { me } from "@goauthentik/common/users";
|
import { me } from "@goauthentik/common/users";
|
||||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
|
||||||
import "@goauthentik/components/ak-status-label";
|
import "@goauthentik/components/ak-status-label";
|
||||||
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
import { WithBrandConfig } from "@goauthentik/elements/Interface/brandProvider";
|
||||||
import {
|
import {
|
||||||
@ -194,7 +194,7 @@ export class RelatedUserList extends WithBrandConfig(WithCapabilitiesConfig(Tabl
|
|||||||
</a>`,
|
</a>`,
|
||||||
html`<ak-status-label ?good=${item.isActive}></ak-status-label>`,
|
html`<ak-status-label ?good=${item.isActive}></ak-status-label>`,
|
||||||
html`${item.lastLogin
|
html`${item.lastLogin
|
||||||
? html`<div>${getRelativeTime(item.lastLogin)}</div>
|
? html`<div>${formatElapsedTime(item.lastLogin)}</div>
|
||||||
<small>${item.lastLogin.toLocaleString()}</small>`
|
<small>${item.lastLogin.toLocaleString()}</small>`
|
||||||
: msg("-")}`,
|
: msg("-")}`,
|
||||||
html`<ak-forms-modal>
|
html`<ak-forms-modal>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import { PFColor } from "@goauthentik/elements/Label";
|
import { PFColor } from "@goauthentik/elements/Label";
|
||||||
import "@goauthentik/elements/Spinner";
|
import "@goauthentik/elements/Spinner";
|
||||||
@ -51,7 +51,7 @@ export class OutpostHealthElement extends AKElement {
|
|||||||
<div class="pf-c-description-list__text">
|
<div class="pf-c-description-list__text">
|
||||||
<ak-label color=${PFColor.Green} ?compact=${true}>
|
<ak-label color=${PFColor.Green} ?compact=${true}>
|
||||||
${msg(
|
${msg(
|
||||||
str`${getRelativeTime(this.outpostHealth.lastSeen)} (${this.outpostHealth.lastSeen?.toLocaleTimeString()})`,
|
str`${formatElapsedTime(this.outpostHealth.lastSeen)} (${this.outpostHealth.lastSeen?.toLocaleTimeString()})`,
|
||||||
)}
|
)}
|
||||||
</ak-label>
|
</ak-label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||||
import { AKElement } from "@goauthentik/elements/Base";
|
import { AKElement } from "@goauthentik/elements/Base";
|
||||||
import { PFColor } from "@goauthentik/elements/Label";
|
import { PFColor } from "@goauthentik/elements/Label";
|
||||||
import "@goauthentik/elements/Spinner";
|
import "@goauthentik/elements/Spinner";
|
||||||
@ -69,7 +69,7 @@ export class OutpostHealthSimpleElement extends AKElement {
|
|||||||
const lastSeen = this.outpostHealths[0].lastSeen;
|
const lastSeen = this.outpostHealths[0].lastSeen;
|
||||||
return html`<ak-label color=${PFColor.Green}>
|
return html`<ak-label color=${PFColor.Green}>
|
||||||
${msg(
|
${msg(
|
||||||
str`Last seen: ${getRelativeTime(lastSeen)} (${lastSeen.toLocaleTimeString()})`,
|
str`Last seen: ${formatElapsedTime(lastSeen)} (${lastSeen.toLocaleTimeString()})`,
|
||||||
)}</ak-label
|
)}</ak-label
|
||||||
>`;
|
>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
import "@goauthentik/admin/rbac/ObjectPermissionModal";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||||
import "@goauthentik/elements/buttons/ModalButton";
|
import "@goauthentik/elements/buttons/ModalButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||||
@ -89,7 +89,7 @@ export class ReputationListPage extends TablePage<Reputation> {
|
|||||||
: html``}
|
: html``}
|
||||||
${item.ip}`,
|
${item.ip}`,
|
||||||
html`${item.score}`,
|
html`${item.score}`,
|
||||||
html`<div>${getRelativeTime(item.updated)}</div>
|
html`<div>${formatElapsedTime(item.updated)}</div>
|
||||||
<small>${item.updated.toLocaleString()}</small>`,
|
<small>${item.updated.toLocaleString()}</small>`,
|
||||||
html`
|
html`
|
||||||
<ak-rbac-object-permission-modal
|
<ak-rbac-object-permission-modal
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import {
|
|||||||
GroupMatchingModeToLabel,
|
GroupMatchingModeToLabel,
|
||||||
UserMatchingModeToLabel,
|
UserMatchingModeToLabel,
|
||||||
} from "@goauthentik/admin/sources/oauth/utils";
|
} from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import { ServerConfig } from "@goauthentik/common/global";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/components/ak-switch-input";
|
import "@goauthentik/components/ak-switch-input";
|
||||||
import "@goauthentik/components/ak-text-input";
|
import "@goauthentik/components/ak-text-input";
|
||||||
@ -61,8 +62,7 @@ export class KerberosSourceForm extends WithCapabilitiesConfig(BaseSourceForm<Ke
|
|||||||
kerberosSourceRequest: data as unknown as KerberosSourceRequest,
|
kerberosSourceRequest: data as unknown as KerberosSourceRequest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const c = await config();
|
if (ServerConfig.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||||
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
|
||||||
const icon = this.getFormFiles()["icon"];
|
const icon = this.getFormFiles()["icon"];
|
||||||
if (icon || this.clearIcon) {
|
if (icon || this.clearIcon) {
|
||||||
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||||
|
|||||||
@ -412,7 +412,29 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
|
|||||||
/>
|
/>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
${msg(
|
${msg(
|
||||||
"Field which contains members of a group. Note that if using the \"memberUid\" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'",
|
"Field which contains members of a group. Note that if using the \"memberUid\" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'. When selecting 'Lookup using a user attribute', this should be a user attribute, otherwise a group attribute.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ak-form-element-horizontal>
|
||||||
|
<ak-form-element-horizontal name="lookupGroupsFromUser">
|
||||||
|
<label class="pf-c-switch">
|
||||||
|
<input
|
||||||
|
class="pf-c-switch__input"
|
||||||
|
type="checkbox"
|
||||||
|
?checked=${first(this.instance?.lookupGroupsFromUser, false)}
|
||||||
|
/>
|
||||||
|
<span class="pf-c-switch__toggle">
|
||||||
|
<span class="pf-c-switch__toggle-icon">
|
||||||
|
<i class="fas fa-check" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="pf-c-switch__label"
|
||||||
|
>${msg("Lookup using user attribute")}</span
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
<p class="pf-c-form__helper-text">
|
||||||
|
${msg(
|
||||||
|
"Field which contains DNs of groups the user is a member of. This field is used to lookup groups from users, e.g. 'memberOf'. To lookup nested groups in an Active Directory environment use 'memberOf:1.2.840.113556.1.4.1941:'.",
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
|||||||
@ -5,7 +5,8 @@ import {
|
|||||||
GroupMatchingModeToLabel,
|
GroupMatchingModeToLabel,
|
||||||
UserMatchingModeToLabel,
|
UserMatchingModeToLabel,
|
||||||
} from "@goauthentik/admin/sources/oauth/utils";
|
} from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import { ServerConfig } from "@goauthentik/common/global";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||||
@ -71,8 +72,7 @@ export class OAuthSourceForm extends WithCapabilitiesConfig(BaseSourceForm<OAuth
|
|||||||
oAuthSourceRequest: data as unknown as OAuthSourceRequest,
|
oAuthSourceRequest: data as unknown as OAuthSourceRequest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const c = await config();
|
if (ServerConfig.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||||
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
|
||||||
const icon = this.getFormFiles()["icon"];
|
const icon = this.getFormFiles()["icon"];
|
||||||
if (icon || this.clearIcon) {
|
if (icon || this.clearIcon) {
|
||||||
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import {
|
|||||||
GroupMatchingModeToLabel,
|
GroupMatchingModeToLabel,
|
||||||
UserMatchingModeToLabel,
|
UserMatchingModeToLabel,
|
||||||
} from "@goauthentik/admin/sources/oauth/utils";
|
} from "@goauthentik/admin/sources/oauth/utils";
|
||||||
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import { ServerConfig } from "@goauthentik/common/global";
|
||||||
import { first } from "@goauthentik/common/utils";
|
import { first } from "@goauthentik/common/utils";
|
||||||
import {
|
import {
|
||||||
CapabilitiesEnum,
|
CapabilitiesEnum,
|
||||||
@ -62,8 +63,7 @@ export class SAMLSourceForm extends WithCapabilitiesConfig(BaseSourceForm<SAMLSo
|
|||||||
sAMLSourceRequest: data,
|
sAMLSourceRequest: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const c = await config();
|
if (ServerConfig.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
||||||
if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
|
|
||||||
const icon = this.getFormFiles()["icon"];
|
const icon = this.getFormFiles()["icon"];
|
||||||
if (icon || this.clearIcon) {
|
if (icon || this.clearIcon) {
|
||||||
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
await new SourcesApi(DEFAULT_CONFIG).sourcesAllSetIconCreate({
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { dateTimeLocal, first } from "@goauthentik/common/utils";
|
import { dateTimeLocal } from "@goauthentik/common/temporal";
|
||||||
|
import { first } from "@goauthentik/common/utils";
|
||||||
import "@goauthentik/elements/CodeMirror";
|
import "@goauthentik/elements/CodeMirror";
|
||||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
import { formatElapsedTime } from "@goauthentik/common/temporal";
|
||||||
import { PFColor } from "@goauthentik/elements/Label";
|
import { PFColor } from "@goauthentik/elements/Label";
|
||||||
import "@goauthentik/elements/buttons/ActionButton";
|
import "@goauthentik/elements/buttons/ActionButton";
|
||||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||||
@ -100,7 +100,7 @@ export class SystemTaskListPage extends TablePage<SystemTask> {
|
|||||||
item.expires || new Date()
|
item.expires || new Date()
|
||||||
).toLocaleString()}
|
).toLocaleString()}
|
||||||
>
|
>
|
||||||
${getRelativeTime(item.expires || new Date())}
|
${formatElapsedTime(item.expires || new Date())}
|
||||||
</pf-tooltip>
|
</pf-tooltip>
|
||||||
`
|
`
|
||||||
: msg("-")}
|
: msg("-")}
|
||||||
@ -128,7 +128,7 @@ export class SystemTaskListPage extends TablePage<SystemTask> {
|
|||||||
return [
|
return [
|
||||||
html`<pre>${item.name}${item.uid ? `:${item.uid}` : ""}</pre>`,
|
html`<pre>${item.name}${item.uid ? `:${item.uid}` : ""}</pre>`,
|
||||||
html`${item.description}`,
|
html`${item.description}`,
|
||||||
html`<div>${getRelativeTime(item.finishTimestamp)}</div>
|
html`<div>${formatElapsedTime(item.finishTimestamp)}</div>
|
||||||
<small>${item.finishTimestamp.toLocaleString()}</small>`,
|
<small>${item.finishTimestamp.toLocaleString()}</small>`,
|
||||||
this.taskStatus(item),
|
this.taskStatus(item),
|
||||||
html`<ak-action-button
|
html`<ak-action-button
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user