Merge branch 'main' into celery-2-dramatiq
This commit is contained in:
4
Makefile
4
Makefile
@ -86,6 +86,10 @@ dev-create-db:
|
|||||||
|
|
||||||
dev-reset: dev-drop-db dev-create-db migrate ## Drop and restore the Authentik PostgreSQL instance to a "fresh install" state.
|
dev-reset: dev-drop-db dev-create-db migrate ## Drop and restore the Authentik PostgreSQL instance to a "fresh install" state.
|
||||||
|
|
||||||
|
update-test-mmdb: ## Update test GeoIP and ASN Databases
|
||||||
|
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-ASN-Test.mmdb -o ${PWD}/tests/GeoLite2-ASN-Test.mmdb
|
||||||
|
curl -L https://raw.githubusercontent.com/maxmind/MaxMind-DB/refs/heads/main/test-data/GeoLite2-City-Test.mmdb -o ${PWD}/tests/GeoLite2-City-Test.mmdb
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
## API Schema
|
## API Schema
|
||||||
#########################
|
#########################
|
||||||
|
@ -13,7 +13,6 @@ class Command(TenantCommand):
|
|||||||
parser.add_argument("usernames", nargs="*", type=str)
|
parser.add_argument("usernames", nargs="*", type=str)
|
||||||
|
|
||||||
def handle_per_tenant(self, **options):
|
def handle_per_tenant(self, **options):
|
||||||
print(options)
|
|
||||||
new_type = UserTypes(options["type"])
|
new_type = UserTypes(options["type"])
|
||||||
qs = (
|
qs = (
|
||||||
User.objects.exclude_anonymous()
|
User.objects.exclude_anonymous()
|
||||||
|
@ -97,6 +97,7 @@ class SourceStageFinal(StageView):
|
|||||||
token: FlowToken = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN)
|
token: FlowToken = self.request.session.get(SESSION_KEY_OVERRIDE_FLOW_TOKEN)
|
||||||
self.logger.info("Replacing source flow with overridden flow", flow=token.flow.slug)
|
self.logger.info("Replacing source flow with overridden flow", flow=token.flow.slug)
|
||||||
plan = token.plan
|
plan = token.plan
|
||||||
|
plan.context.update(self.executor.plan.context)
|
||||||
plan.context[PLAN_CONTEXT_IS_RESTORED] = token
|
plan.context[PLAN_CONTEXT_IS_RESTORED] = token
|
||||||
response = plan.to_redirect(self.request, token.flow)
|
response = plan.to_redirect(self.request, token.flow)
|
||||||
token.delete()
|
token.delete()
|
||||||
|
@ -90,14 +90,17 @@ class TestSourceStage(FlowTestCase):
|
|||||||
plan: FlowPlan = session[SESSION_KEY_PLAN]
|
plan: FlowPlan = session[SESSION_KEY_PLAN]
|
||||||
plan.insert_stage(in_memory_stage(SourceStageFinal), index=0)
|
plan.insert_stage(in_memory_stage(SourceStageFinal), index=0)
|
||||||
plan.context[PLAN_CONTEXT_IS_RESTORED] = flow_token
|
plan.context[PLAN_CONTEXT_IS_RESTORED] = flow_token
|
||||||
|
plan.context["foo"] = "bar"
|
||||||
session[SESSION_KEY_PLAN] = plan
|
session[SESSION_KEY_PLAN] = plan
|
||||||
session.save()
|
session.save()
|
||||||
|
|
||||||
# Pretend we've just returned from the source
|
# Pretend we've just returned from the source
|
||||||
response = self.client.get(
|
with self.assertFlowFinishes() as ff:
|
||||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), follow=True
|
response = self.client.get(
|
||||||
)
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}), follow=True
|
||||||
self.assertEqual(response.status_code, 200)
|
)
|
||||||
self.assertStageRedirects(
|
self.assertEqual(response.status_code, 200)
|
||||||
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
|
self.assertStageRedirects(
|
||||||
)
|
response, reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
|
||||||
|
)
|
||||||
|
self.assertEqual(ff().context["foo"], "bar")
|
||||||
|
@ -15,13 +15,13 @@ class MMDBContextProcessor(EventContextProcessor):
|
|||||||
self.reader: Reader | None = None
|
self.reader: Reader | None = None
|
||||||
self._last_mtime: float = 0.0
|
self._last_mtime: float = 0.0
|
||||||
self.logger = get_logger()
|
self.logger = get_logger()
|
||||||
self.open()
|
self.load()
|
||||||
|
|
||||||
def path(self) -> str | None:
|
def path(self) -> str | None:
|
||||||
"""Get the path to the MMDB file to load"""
|
"""Get the path to the MMDB file to load"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def open(self):
|
def load(self):
|
||||||
"""Get GeoIP Reader, if configured, otherwise none"""
|
"""Get GeoIP Reader, if configured, otherwise none"""
|
||||||
path = self.path()
|
path = self.path()
|
||||||
if path == "" or not path:
|
if path == "" or not path:
|
||||||
@ -44,7 +44,7 @@ class MMDBContextProcessor(EventContextProcessor):
|
|||||||
diff = self._last_mtime < mtime
|
diff = self._last_mtime < mtime
|
||||||
if diff > 0:
|
if diff > 0:
|
||||||
self.logger.info("Found new MMDB Database, reopening", diff=diff, path=path)
|
self.logger.info("Found new MMDB Database, reopening", diff=diff, path=path)
|
||||||
self.open()
|
self.load()
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
self.logger.warning("Failed to check MMDB age", exc=exc)
|
self.logger.warning("Failed to check MMDB age", exc=exc)
|
||||||
|
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from authentik.events.context_processors.base import get_context_processors
|
||||||
from authentik.events.context_processors.geoip import GeoIPContextProcessor
|
from authentik.events.context_processors.geoip import GeoIPContextProcessor
|
||||||
|
from authentik.events.models import Event, EventAction
|
||||||
|
|
||||||
|
|
||||||
class TestGeoIP(TestCase):
|
class TestGeoIP(TestCase):
|
||||||
@ -13,8 +15,7 @@ class TestGeoIP(TestCase):
|
|||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
"""Test simple city wrapper"""
|
"""Test simple city wrapper"""
|
||||||
# IPs from
|
# IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json
|
||||||
# https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.reader.city_dict("2.125.160.216"),
|
self.reader.city_dict("2.125.160.216"),
|
||||||
{
|
{
|
||||||
@ -25,3 +26,12 @@ class TestGeoIP(TestCase):
|
|||||||
"long": -1.25,
|
"long": -1.25,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_special_chars(self):
|
||||||
|
"""Test city name with special characters"""
|
||||||
|
# IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json
|
||||||
|
event = Event.new(EventAction.LOGIN)
|
||||||
|
event.client_ip = "89.160.20.112"
|
||||||
|
for processor in get_context_processors():
|
||||||
|
processor.enrich_event(event)
|
||||||
|
event.save()
|
||||||
|
@ -1,13 +1,49 @@
|
|||||||
"""authentik database backend"""
|
"""authentik database backend"""
|
||||||
|
|
||||||
|
from django.core.checks import Warning
|
||||||
|
from django.db.backends.base.validation import BaseDatabaseValidation
|
||||||
from django_tenants.postgresql_backend.base import DatabaseWrapper as BaseDatabaseWrapper
|
from django_tenants.postgresql_backend.base import DatabaseWrapper as BaseDatabaseWrapper
|
||||||
|
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseValidation(BaseDatabaseValidation):
|
||||||
|
|
||||||
|
def check(self, **kwargs):
|
||||||
|
return self._check_encoding()
|
||||||
|
|
||||||
|
def _check_encoding(self):
|
||||||
|
"""Throw a warning when the server_encoding is not UTF-8 or
|
||||||
|
server_encoding and client_encoding are mismatched"""
|
||||||
|
messages = []
|
||||||
|
with self.connection.cursor() as cursor:
|
||||||
|
cursor.execute("SHOW server_encoding;")
|
||||||
|
server_encoding = cursor.fetchone()[0]
|
||||||
|
cursor.execute("SHOW client_encoding;")
|
||||||
|
client_encoding = cursor.fetchone()[0]
|
||||||
|
if server_encoding != client_encoding:
|
||||||
|
messages.append(
|
||||||
|
Warning(
|
||||||
|
"PostgreSQL Server and Client encoding are mismatched: Server: "
|
||||||
|
f"{server_encoding}, Client: {client_encoding}",
|
||||||
|
id="ak.db.W001",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if server_encoding != "UTF8":
|
||||||
|
messages.append(
|
||||||
|
Warning(
|
||||||
|
f"PostgreSQL Server encoding is not UTF8: {server_encoding}",
|
||||||
|
id="ak.db.W002",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return messages
|
||||||
|
|
||||||
|
|
||||||
class DatabaseWrapper(BaseDatabaseWrapper):
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
"""database backend which supports rotating credentials"""
|
"""database backend which supports rotating credentials"""
|
||||||
|
|
||||||
|
validation_class = DatabaseValidation
|
||||||
|
|
||||||
def get_connection_params(self):
|
def get_connection_params(self):
|
||||||
"""Refresh DB credentials before getting connection params"""
|
"""Refresh DB credentials before getting connection params"""
|
||||||
conn_params = super().get_connection_params()
|
conn_params = super().get_connection_params()
|
||||||
|
@ -12,6 +12,8 @@ from django.test.runner import DiscoverRunner
|
|||||||
from django.test.testcases import apps
|
from django.test.testcases import apps
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
|
from authentik.events.context_processors.asn import ASN_CONTEXT_PROCESSOR
|
||||||
|
from authentik.events.context_processors.geoip import GEOIP_CONTEXT_PROCESSOR
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG
|
||||||
from authentik.lib.sentry import sentry_init
|
from authentik.lib.sentry import sentry_init
|
||||||
from authentik.root.signals import post_startup, pre_startup, startup
|
from authentik.root.signals import post_startup, pre_startup, startup
|
||||||
@ -76,6 +78,9 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
|
|||||||
for key, value in test_config.items():
|
for key, value in test_config.items():
|
||||||
CONFIG.set(key, value)
|
CONFIG.set(key, value)
|
||||||
|
|
||||||
|
ASN_CONTEXT_PROCESSOR.load()
|
||||||
|
GEOIP_CONTEXT_PROCESSOR.load()
|
||||||
|
|
||||||
sentry_init()
|
sentry_init()
|
||||||
self.logger.debug("Test environment configured")
|
self.logger.debug("Test environment configured")
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""Validation stage challenge checking"""
|
"""Validation stage challenge checking"""
|
||||||
|
|
||||||
from json import loads
|
from json import loads
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
@ -36,10 +37,12 @@ from authentik.stages.authenticator_email.models import EmailDevice
|
|||||||
from authentik.stages.authenticator_sms.models import SMSDevice
|
from authentik.stages.authenticator_sms.models import SMSDevice
|
||||||
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
|
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
|
||||||
from authentik.stages.authenticator_webauthn.models import UserVerification, WebAuthnDevice
|
from authentik.stages.authenticator_webauthn.models import UserVerification, WebAuthnDevice
|
||||||
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
|
from authentik.stages.authenticator_webauthn.stage import PLAN_CONTEXT_WEBAUTHN_CHALLENGE
|
||||||
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
|
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from authentik.stages.authenticator_validate.stage import AuthenticatorValidateStageView
|
||||||
|
|
||||||
|
|
||||||
class DeviceChallenge(PassiveSerializer):
|
class DeviceChallenge(PassiveSerializer):
|
||||||
@ -52,11 +55,11 @@ class DeviceChallenge(PassiveSerializer):
|
|||||||
|
|
||||||
|
|
||||||
def get_challenge_for_device(
|
def get_challenge_for_device(
|
||||||
request: HttpRequest, stage: AuthenticatorValidateStage, device: Device
|
stage_view: "AuthenticatorValidateStageView", stage: AuthenticatorValidateStage, device: Device
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Generate challenge for a single device"""
|
"""Generate challenge for a single device"""
|
||||||
if isinstance(device, WebAuthnDevice):
|
if isinstance(device, WebAuthnDevice):
|
||||||
return get_webauthn_challenge(request, stage, device)
|
return get_webauthn_challenge(stage_view, stage, device)
|
||||||
if isinstance(device, EmailDevice):
|
if isinstance(device, EmailDevice):
|
||||||
return {"email": mask_email(device.email)}
|
return {"email": mask_email(device.email)}
|
||||||
# Code-based challenges have no hints
|
# Code-based challenges have no hints
|
||||||
@ -64,26 +67,30 @@ def get_challenge_for_device(
|
|||||||
|
|
||||||
|
|
||||||
def get_webauthn_challenge_without_user(
|
def get_webauthn_challenge_without_user(
|
||||||
request: HttpRequest, stage: AuthenticatorValidateStage
|
stage_view: "AuthenticatorValidateStageView", stage: AuthenticatorValidateStage
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Same as `get_webauthn_challenge`, but allows any client device. We can then later check
|
"""Same as `get_webauthn_challenge`, but allows any client device. We can then later check
|
||||||
who the device belongs to."""
|
who the device belongs to."""
|
||||||
request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE, None)
|
stage_view.executor.plan.context.pop(PLAN_CONTEXT_WEBAUTHN_CHALLENGE, None)
|
||||||
authentication_options = generate_authentication_options(
|
authentication_options = generate_authentication_options(
|
||||||
rp_id=get_rp_id(request),
|
rp_id=get_rp_id(stage_view.request),
|
||||||
allow_credentials=[],
|
allow_credentials=[],
|
||||||
user_verification=UserVerificationRequirement(stage.webauthn_user_verification),
|
user_verification=UserVerificationRequirement(stage.webauthn_user_verification),
|
||||||
)
|
)
|
||||||
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = authentication_options.challenge
|
stage_view.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = (
|
||||||
|
authentication_options.challenge
|
||||||
|
)
|
||||||
|
|
||||||
return loads(options_to_json(authentication_options))
|
return loads(options_to_json(authentication_options))
|
||||||
|
|
||||||
|
|
||||||
def get_webauthn_challenge(
|
def get_webauthn_challenge(
|
||||||
request: HttpRequest, stage: AuthenticatorValidateStage, device: WebAuthnDevice | None = None
|
stage_view: "AuthenticatorValidateStageView",
|
||||||
|
stage: AuthenticatorValidateStage,
|
||||||
|
device: WebAuthnDevice | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Send the client a challenge that we'll check later"""
|
"""Send the client a challenge that we'll check later"""
|
||||||
request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE, None)
|
stage_view.executor.plan.context.pop(PLAN_CONTEXT_WEBAUTHN_CHALLENGE, None)
|
||||||
|
|
||||||
allowed_credentials = []
|
allowed_credentials = []
|
||||||
|
|
||||||
@ -94,12 +101,14 @@ def get_webauthn_challenge(
|
|||||||
allowed_credentials.append(user_device.descriptor)
|
allowed_credentials.append(user_device.descriptor)
|
||||||
|
|
||||||
authentication_options = generate_authentication_options(
|
authentication_options = generate_authentication_options(
|
||||||
rp_id=get_rp_id(request),
|
rp_id=get_rp_id(stage_view.request),
|
||||||
allow_credentials=allowed_credentials,
|
allow_credentials=allowed_credentials,
|
||||||
user_verification=UserVerificationRequirement(stage.webauthn_user_verification),
|
user_verification=UserVerificationRequirement(stage.webauthn_user_verification),
|
||||||
)
|
)
|
||||||
|
|
||||||
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = authentication_options.challenge
|
stage_view.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = (
|
||||||
|
authentication_options.challenge
|
||||||
|
)
|
||||||
|
|
||||||
return loads(options_to_json(authentication_options))
|
return loads(options_to_json(authentication_options))
|
||||||
|
|
||||||
@ -146,7 +155,7 @@ def validate_challenge_code(code: str, stage_view: StageView, user: User) -> Dev
|
|||||||
def validate_challenge_webauthn(data: dict, stage_view: StageView, user: User) -> Device:
|
def validate_challenge_webauthn(data: dict, stage_view: StageView, user: User) -> Device:
|
||||||
"""Validate WebAuthn Challenge"""
|
"""Validate WebAuthn Challenge"""
|
||||||
request = stage_view.request
|
request = stage_view.request
|
||||||
challenge = request.session.get(SESSION_KEY_WEBAUTHN_CHALLENGE)
|
challenge = stage_view.executor.plan.context.get(PLAN_CONTEXT_WEBAUTHN_CHALLENGE)
|
||||||
stage: AuthenticatorValidateStage = stage_view.executor.current_stage
|
stage: AuthenticatorValidateStage = stage_view.executor.current_stage
|
||||||
try:
|
try:
|
||||||
credential = parse_authentication_credential_json(data)
|
credential = parse_authentication_credential_json(data)
|
||||||
|
@ -224,7 +224,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||||||
data={
|
data={
|
||||||
"device_class": device_class,
|
"device_class": device_class,
|
||||||
"device_uid": device.pk,
|
"device_uid": device.pk,
|
||||||
"challenge": get_challenge_for_device(self.request, stage, device),
|
"challenge": get_challenge_for_device(self, stage, device),
|
||||||
"last_used": device.last_used,
|
"last_used": device.last_used,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -243,7 +243,7 @@ class AuthenticatorValidateStageView(ChallengeStageView):
|
|||||||
"device_class": DeviceClasses.WEBAUTHN,
|
"device_class": DeviceClasses.WEBAUTHN,
|
||||||
"device_uid": -1,
|
"device_uid": -1,
|
||||||
"challenge": get_webauthn_challenge_without_user(
|
"challenge": get_webauthn_challenge_without_user(
|
||||||
self.request,
|
self,
|
||||||
self.executor.current_stage,
|
self.executor.current_stage,
|
||||||
),
|
),
|
||||||
"last_used": None,
|
"last_used": None,
|
||||||
|
@ -31,7 +31,7 @@ from authentik.stages.authenticator_webauthn.models import (
|
|||||||
WebAuthnDevice,
|
WebAuthnDevice,
|
||||||
WebAuthnDeviceType,
|
WebAuthnDeviceType,
|
||||||
)
|
)
|
||||||
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
|
from authentik.stages.authenticator_webauthn.stage import PLAN_CONTEXT_WEBAUTHN_CHALLENGE
|
||||||
from authentik.stages.authenticator_webauthn.tasks import webauthn_mds_import
|
from authentik.stages.authenticator_webauthn.tasks import webauthn_mds_import
|
||||||
from authentik.stages.identification.models import IdentificationStage, UserFields
|
from authentik.stages.identification.models import IdentificationStage, UserFields
|
||||||
from authentik.stages.user_login.models import UserLoginStage
|
from authentik.stages.user_login.models import UserLoginStage
|
||||||
@ -103,7 +103,11 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
device_classes=[DeviceClasses.WEBAUTHN],
|
device_classes=[DeviceClasses.WEBAUTHN],
|
||||||
webauthn_user_verification=UserVerification.PREFERRED,
|
webauthn_user_verification=UserVerification.PREFERRED,
|
||||||
)
|
)
|
||||||
challenge = get_challenge_for_device(request, stage, webauthn_device)
|
plan = FlowPlan("")
|
||||||
|
stage_view = AuthenticatorValidateStageView(
|
||||||
|
FlowExecutorView(flow=None, current_stage=stage, plan=plan), request=request
|
||||||
|
)
|
||||||
|
challenge = get_challenge_for_device(stage_view, stage, webauthn_device)
|
||||||
del challenge["challenge"]
|
del challenge["challenge"]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
challenge,
|
challenge,
|
||||||
@ -122,7 +126,9 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
|
|
||||||
with self.assertRaises(ValidationError):
|
with self.assertRaises(ValidationError):
|
||||||
validate_challenge_webauthn(
|
validate_challenge_webauthn(
|
||||||
{}, StageView(FlowExecutorView(current_stage=stage), request=request), self.user
|
{},
|
||||||
|
StageView(FlowExecutorView(current_stage=stage, plan=plan), request=request),
|
||||||
|
self.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_device_challenge_webauthn_restricted(self):
|
def test_device_challenge_webauthn_restricted(self):
|
||||||
@ -193,22 +199,35 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
sign_count=0,
|
sign_count=0,
|
||||||
rp_id=generate_id(),
|
rp_id=generate_id(),
|
||||||
)
|
)
|
||||||
challenge = get_challenge_for_device(request, stage, webauthn_device)
|
plan = FlowPlan("")
|
||||||
webauthn_challenge = request.session[SESSION_KEY_WEBAUTHN_CHALLENGE]
|
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||||
|
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
||||||
|
)
|
||||||
|
stage_view = AuthenticatorValidateStageView(
|
||||||
|
FlowExecutorView(flow=None, current_stage=stage, plan=plan), request=request
|
||||||
|
)
|
||||||
|
challenge = get_challenge_for_device(stage_view, stage, webauthn_device)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
challenge,
|
challenge["allowCredentials"],
|
||||||
{
|
[
|
||||||
"allowCredentials": [
|
{
|
||||||
{
|
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
||||||
"id": "QKZ97ASJAOIDyipAs6mKUxDUZgDrWrbAsUb5leL7-oU",
|
"type": "public-key",
|
||||||
"type": "public-key",
|
}
|
||||||
}
|
],
|
||||||
],
|
)
|
||||||
"challenge": bytes_to_base64url(webauthn_challenge),
|
self.assertIsNotNone(challenge["challenge"])
|
||||||
"rpId": "testserver",
|
self.assertEqual(
|
||||||
"timeout": 60000,
|
challenge["rpId"],
|
||||||
"userVerification": "preferred",
|
"testserver",
|
||||||
},
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
challenge["timeout"],
|
||||||
|
60000,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
challenge["userVerification"],
|
||||||
|
"preferred",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_challenge_userless(self):
|
def test_get_challenge_userless(self):
|
||||||
@ -228,18 +247,16 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
sign_count=0,
|
sign_count=0,
|
||||||
rp_id=generate_id(),
|
rp_id=generate_id(),
|
||||||
)
|
)
|
||||||
challenge = get_webauthn_challenge_without_user(request, stage)
|
plan = FlowPlan("")
|
||||||
webauthn_challenge = request.session[SESSION_KEY_WEBAUTHN_CHALLENGE]
|
stage_view = AuthenticatorValidateStageView(
|
||||||
self.assertEqual(
|
FlowExecutorView(flow=None, current_stage=stage, plan=plan), request=request
|
||||||
challenge,
|
|
||||||
{
|
|
||||||
"allowCredentials": [],
|
|
||||||
"challenge": bytes_to_base64url(webauthn_challenge),
|
|
||||||
"rpId": "testserver",
|
|
||||||
"timeout": 60000,
|
|
||||||
"userVerification": "preferred",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
challenge = get_webauthn_challenge_without_user(stage_view, stage)
|
||||||
|
self.assertEqual(challenge["allowCredentials"], [])
|
||||||
|
self.assertIsNotNone(challenge["challenge"])
|
||||||
|
self.assertEqual(challenge["rpId"], "testserver")
|
||||||
|
self.assertEqual(challenge["timeout"], 60000)
|
||||||
|
self.assertEqual(challenge["userVerification"], "preferred")
|
||||||
|
|
||||||
def test_validate_challenge_unrestricted(self):
|
def test_validate_challenge_unrestricted(self):
|
||||||
"""Test webauthn authentication (unrestricted webauthn device)"""
|
"""Test webauthn authentication (unrestricted webauthn device)"""
|
||||||
@ -275,10 +292,10 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
"last_used": None,
|
"last_used": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
session[SESSION_KEY_PLAN] = plan
|
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
|
||||||
"aCC6ak_DP45xMH1qyxzUM5iC2xc4QthQb09v7m4qDBmY8FvWvhxFzSuFlDYQmclrh5fWS5q0TPxgJGF4vimcFQ"
|
"aCC6ak_DP45xMH1qyxzUM5iC2xc4QthQb09v7m4qDBmY8FvWvhxFzSuFlDYQmclrh5fWS5q0TPxgJGF4vimcFQ"
|
||||||
)
|
)
|
||||||
|
session[SESSION_KEY_PLAN] = plan
|
||||||
session.save()
|
session.save()
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -352,10 +369,10 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
"last_used": None,
|
"last_used": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
session[SESSION_KEY_PLAN] = plan
|
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
|
||||||
"aCC6ak_DP45xMH1qyxzUM5iC2xc4QthQb09v7m4qDBmY8FvWvhxFzSuFlDYQmclrh5fWS5q0TPxgJGF4vimcFQ"
|
"aCC6ak_DP45xMH1qyxzUM5iC2xc4QthQb09v7m4qDBmY8FvWvhxFzSuFlDYQmclrh5fWS5q0TPxgJGF4vimcFQ"
|
||||||
)
|
)
|
||||||
|
session[SESSION_KEY_PLAN] = plan
|
||||||
session.save()
|
session.save()
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -433,10 +450,10 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
"last_used": None,
|
"last_used": None,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
session[SESSION_KEY_PLAN] = plan
|
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
|
||||||
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
||||||
)
|
)
|
||||||
|
session[SESSION_KEY_PLAN] = plan
|
||||||
session.save()
|
session.save()
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -496,17 +513,14 @@ class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
|
|||||||
not_configured_action=NotConfiguredAction.CONFIGURE,
|
not_configured_action=NotConfiguredAction.CONFIGURE,
|
||||||
device_classes=[DeviceClasses.WEBAUTHN],
|
device_classes=[DeviceClasses.WEBAUTHN],
|
||||||
)
|
)
|
||||||
stage_view = AuthenticatorValidateStageView(
|
plan = FlowPlan(flow.pk.hex)
|
||||||
FlowExecutorView(flow=flow, current_stage=stage), request=request
|
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
||||||
)
|
|
||||||
request = get_request("/")
|
|
||||||
request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = base64url_to_bytes(
|
|
||||||
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
"g98I51mQvZXo5lxLfhrD2zfolhZbLRyCgqkkYap1jwSaJ13BguoJWCF9_Lg3AgO4Wh-Bqa556JE20oKsYbl6RA"
|
||||||
)
|
)
|
||||||
request.session.save()
|
request = get_request("/")
|
||||||
|
|
||||||
stage_view = AuthenticatorValidateStageView(
|
stage_view = AuthenticatorValidateStageView(
|
||||||
FlowExecutorView(flow=flow, current_stage=stage), request=request
|
FlowExecutorView(flow=flow, current_stage=stage, plan=plan), request=request
|
||||||
)
|
)
|
||||||
request.META["SERVER_NAME"] = "localhost"
|
request.META["SERVER_NAME"] = "localhost"
|
||||||
request.META["SERVER_PORT"] = "9000"
|
request.META["SERVER_PORT"] = "9000"
|
||||||
|
@ -25,6 +25,7 @@ class AuthenticatorWebAuthnStageSerializer(StageSerializer):
|
|||||||
"resident_key_requirement",
|
"resident_key_requirement",
|
||||||
"device_type_restrictions",
|
"device_type_restrictions",
|
||||||
"device_type_restrictions_obj",
|
"device_type_restrictions_obj",
|
||||||
|
"max_attempts",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.1.11 on 2025-06-13 22:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
(
|
||||||
|
"authentik_stages_authenticator_webauthn",
|
||||||
|
"0012_webauthndevice_created_webauthndevice_last_updated_and_more",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="authenticatorwebauthnstage",
|
||||||
|
name="max_attempts",
|
||||||
|
field=models.PositiveIntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
@ -84,6 +84,8 @@ class AuthenticatorWebAuthnStage(ConfigurableStage, FriendlyNamedStage, Stage):
|
|||||||
|
|
||||||
device_type_restrictions = models.ManyToManyField("WebAuthnDeviceType", blank=True)
|
device_type_restrictions = models.ManyToManyField("WebAuthnDeviceType", blank=True)
|
||||||
|
|
||||||
|
max_attempts = models.PositiveIntegerField(default=0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serializer(self) -> type[BaseSerializer]:
|
def serializer(self) -> type[BaseSerializer]:
|
||||||
from authentik.stages.authenticator_webauthn.api.stages import (
|
from authentik.stages.authenticator_webauthn.api.stages import (
|
||||||
|
@ -5,12 +5,13 @@ from uuid import UUID
|
|||||||
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.http.request import QueryDict
|
from django.http.request import QueryDict
|
||||||
|
from django.utils.translation import gettext as __
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework.fields import CharField
|
from rest_framework.fields import CharField
|
||||||
from rest_framework.serializers import ValidationError
|
from rest_framework.serializers import ValidationError
|
||||||
from webauthn import options_to_json
|
from webauthn import options_to_json
|
||||||
from webauthn.helpers.bytes_to_base64url import bytes_to_base64url
|
from webauthn.helpers.bytes_to_base64url import bytes_to_base64url
|
||||||
from webauthn.helpers.exceptions import InvalidRegistrationResponse
|
from webauthn.helpers.exceptions import WebAuthnException
|
||||||
from webauthn.helpers.structs import (
|
from webauthn.helpers.structs import (
|
||||||
AttestationConveyancePreference,
|
AttestationConveyancePreference,
|
||||||
AuthenticatorAttachment,
|
AuthenticatorAttachment,
|
||||||
@ -41,7 +42,8 @@ from authentik.stages.authenticator_webauthn.models import (
|
|||||||
)
|
)
|
||||||
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
|
from authentik.stages.authenticator_webauthn.utils import get_origin, get_rp_id
|
||||||
|
|
||||||
SESSION_KEY_WEBAUTHN_CHALLENGE = "authentik/stages/authenticator_webauthn/challenge"
|
PLAN_CONTEXT_WEBAUTHN_CHALLENGE = "goauthentik.io/stages/authenticator_webauthn/challenge"
|
||||||
|
PLAN_CONTEXT_WEBAUTHN_ATTEMPT = "goauthentik.io/stages/authenticator_webauthn/attempt"
|
||||||
|
|
||||||
|
|
||||||
class AuthenticatorWebAuthnChallenge(WithUserInfoChallenge):
|
class AuthenticatorWebAuthnChallenge(WithUserInfoChallenge):
|
||||||
@ -62,7 +64,7 @@ class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
|
|||||||
|
|
||||||
def validate_response(self, response: dict) -> dict:
|
def validate_response(self, response: dict) -> dict:
|
||||||
"""Validate webauthn challenge response"""
|
"""Validate webauthn challenge response"""
|
||||||
challenge = self.request.session[SESSION_KEY_WEBAUTHN_CHALLENGE]
|
challenge = self.stage.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
registration: VerifiedRegistration = verify_registration_response(
|
registration: VerifiedRegistration = verify_registration_response(
|
||||||
@ -71,7 +73,7 @@ class AuthenticatorWebAuthnChallengeResponse(ChallengeResponse):
|
|||||||
expected_rp_id=get_rp_id(self.request),
|
expected_rp_id=get_rp_id(self.request),
|
||||||
expected_origin=get_origin(self.request),
|
expected_origin=get_origin(self.request),
|
||||||
)
|
)
|
||||||
except InvalidRegistrationResponse as exc:
|
except WebAuthnException as exc:
|
||||||
self.stage.logger.warning("registration failed", exc=exc)
|
self.stage.logger.warning("registration failed", exc=exc)
|
||||||
raise ValidationError(f"Registration failed. Error: {exc}") from None
|
raise ValidationError(f"Registration failed. Error: {exc}") from None
|
||||||
|
|
||||||
@ -114,9 +116,10 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
|||||||
response_class = AuthenticatorWebAuthnChallengeResponse
|
response_class = AuthenticatorWebAuthnChallengeResponse
|
||||||
|
|
||||||
def get_challenge(self, *args, **kwargs) -> Challenge:
|
def get_challenge(self, *args, **kwargs) -> Challenge:
|
||||||
# clear session variables prior to starting a new registration
|
|
||||||
self.request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE, None)
|
|
||||||
stage: AuthenticatorWebAuthnStage = self.executor.current_stage
|
stage: AuthenticatorWebAuthnStage = self.executor.current_stage
|
||||||
|
self.executor.plan.context.setdefault(PLAN_CONTEXT_WEBAUTHN_ATTEMPT, 0)
|
||||||
|
# clear flow variables prior to starting a new registration
|
||||||
|
self.executor.plan.context.pop(PLAN_CONTEXT_WEBAUTHN_CHALLENGE, None)
|
||||||
user = self.get_pending_user()
|
user = self.get_pending_user()
|
||||||
|
|
||||||
# library accepts none so we store null in the database, but if there is a value
|
# library accepts none so we store null in the database, but if there is a value
|
||||||
@ -139,8 +142,7 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
|||||||
attestation=AttestationConveyancePreference.DIRECT,
|
attestation=AttestationConveyancePreference.DIRECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.request.session[SESSION_KEY_WEBAUTHN_CHALLENGE] = registration_options.challenge
|
self.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = registration_options.challenge
|
||||||
self.request.session.save()
|
|
||||||
return AuthenticatorWebAuthnChallenge(
|
return AuthenticatorWebAuthnChallenge(
|
||||||
data={
|
data={
|
||||||
"registration": loads(options_to_json(registration_options)),
|
"registration": loads(options_to_json(registration_options)),
|
||||||
@ -153,6 +155,24 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
|||||||
response.user = self.get_pending_user()
|
response.user = self.get_pending_user()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def challenge_invalid(self, response):
|
||||||
|
stage: AuthenticatorWebAuthnStage = self.executor.current_stage
|
||||||
|
self.executor.plan.context.setdefault(PLAN_CONTEXT_WEBAUTHN_ATTEMPT, 0)
|
||||||
|
self.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_ATTEMPT] += 1
|
||||||
|
if (
|
||||||
|
stage.max_attempts > 0
|
||||||
|
and self.executor.plan.context[PLAN_CONTEXT_WEBAUTHN_ATTEMPT] >= stage.max_attempts
|
||||||
|
):
|
||||||
|
return self.executor.stage_invalid(
|
||||||
|
__(
|
||||||
|
"Exceeded maximum attempts. "
|
||||||
|
"Contact your {brand} administrator for help.".format(
|
||||||
|
brand=self.request.brand.branding_title
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return super().challenge_invalid(response)
|
||||||
|
|
||||||
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
|
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
|
||||||
# Webauthn Challenge has already been validated
|
# Webauthn Challenge has already been validated
|
||||||
webauthn_credential: VerifiedRegistration = response.validated_data["response"]
|
webauthn_credential: VerifiedRegistration = response.validated_data["response"]
|
||||||
@ -179,6 +199,3 @@ class AuthenticatorWebAuthnStageView(ChallengeStageView):
|
|||||||
else:
|
else:
|
||||||
return self.executor.stage_invalid("Device with Credential ID already exists.")
|
return self.executor.stage_invalid("Device with Credential ID already exists.")
|
||||||
return self.executor.stage_ok()
|
return self.executor.stage_ok()
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
self.request.session.pop(SESSION_KEY_WEBAUTHN_CHALLENGE, None)
|
|
||||||
|
@ -18,7 +18,7 @@ from authentik.stages.authenticator_webauthn.models import (
|
|||||||
WebAuthnDevice,
|
WebAuthnDevice,
|
||||||
WebAuthnDeviceType,
|
WebAuthnDeviceType,
|
||||||
)
|
)
|
||||||
from authentik.stages.authenticator_webauthn.stage import SESSION_KEY_WEBAUTHN_CHALLENGE
|
from authentik.stages.authenticator_webauthn.stage import PLAN_CONTEXT_WEBAUTHN_CHALLENGE
|
||||||
from authentik.stages.authenticator_webauthn.tasks import webauthn_mds_import
|
from authentik.stages.authenticator_webauthn.tasks import webauthn_mds_import
|
||||||
|
|
||||||
|
|
||||||
@ -57,6 +57,9 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
|||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plan: FlowPlan = self.client.session[SESSION_KEY_PLAN]
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
session = self.client.session
|
session = self.client.session
|
||||||
self.assertStageResponse(
|
self.assertStageResponse(
|
||||||
@ -70,7 +73,7 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
|||||||
"name": self.user.username,
|
"name": self.user.username,
|
||||||
"displayName": self.user.name,
|
"displayName": self.user.name,
|
||||||
},
|
},
|
||||||
"challenge": bytes_to_base64url(session[SESSION_KEY_WEBAUTHN_CHALLENGE]),
|
"challenge": bytes_to_base64url(plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE]),
|
||||||
"pubKeyCredParams": [
|
"pubKeyCredParams": [
|
||||||
{"type": "public-key", "alg": -7},
|
{"type": "public-key", "alg": -7},
|
||||||
{"type": "public-key", "alg": -8},
|
{"type": "public-key", "alg": -8},
|
||||||
@ -97,11 +100,11 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
|||||||
"""Test registration"""
|
"""Test registration"""
|
||||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||||
session = self.client.session
|
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = b64decode(
|
||||||
session[SESSION_KEY_PLAN] = plan
|
|
||||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = b64decode(
|
|
||||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||||
)
|
)
|
||||||
|
session = self.client.session
|
||||||
|
session[SESSION_KEY_PLAN] = plan
|
||||||
session.save()
|
session.save()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||||
@ -146,11 +149,11 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
|||||||
|
|
||||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||||
session = self.client.session
|
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = b64decode(
|
||||||
session[SESSION_KEY_PLAN] = plan
|
|
||||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = b64decode(
|
|
||||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||||
)
|
)
|
||||||
|
session = self.client.session
|
||||||
|
session[SESSION_KEY_PLAN] = plan
|
||||||
session.save()
|
session.save()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||||
@ -209,11 +212,11 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
|||||||
|
|
||||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||||
session = self.client.session
|
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = b64decode(
|
||||||
session[SESSION_KEY_PLAN] = plan
|
|
||||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = b64decode(
|
|
||||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||||
)
|
)
|
||||||
|
session = self.client.session
|
||||||
|
session[SESSION_KEY_PLAN] = plan
|
||||||
session.save()
|
session.save()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||||
@ -259,11 +262,11 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
|||||||
|
|
||||||
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||||
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||||
session = self.client.session
|
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = b64decode(
|
||||||
session[SESSION_KEY_PLAN] = plan
|
|
||||||
session[SESSION_KEY_WEBAUTHN_CHALLENGE] = b64decode(
|
|
||||||
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||||
)
|
)
|
||||||
|
session = self.client.session
|
||||||
|
session[SESSION_KEY_PLAN] = plan
|
||||||
session.save()
|
session.save()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||||
@ -298,3 +301,109 @@ class TestAuthenticatorWebAuthnStage(FlowTestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
||||||
self.assertTrue(WebAuthnDevice.objects.filter(user=self.user).exists())
|
self.assertTrue(WebAuthnDevice.objects.filter(user=self.user).exists())
|
||||||
|
|
||||||
|
def test_register_max_retries(self):
|
||||||
|
"""Test registration (exceeding max retries)"""
|
||||||
|
self.stage.max_attempts = 2
|
||||||
|
self.stage.save()
|
||||||
|
|
||||||
|
plan = FlowPlan(flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()])
|
||||||
|
plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
|
||||||
|
plan.context[PLAN_CONTEXT_WEBAUTHN_CHALLENGE] = b64decode(
|
||||||
|
b"03Xodi54gKsfnP5I9VFfhaGXVVE2NUyZpBBXns/JI+x6V9RY2Tw2QmxRJkhh7174EkRazUntIwjMVY9bFG60Lw=="
|
||||||
|
)
|
||||||
|
session = self.client.session
|
||||||
|
session[SESSION_KEY_PLAN] = plan
|
||||||
|
session.save()
|
||||||
|
|
||||||
|
# first failed request
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||||
|
data={
|
||||||
|
"component": "ak-stage-authenticator-webauthn",
|
||||||
|
"response": {
|
||||||
|
"id": "kqnmrVLnDG-OwsSNHkihYZaNz5s",
|
||||||
|
"rawId": "kqnmrVLnDG-OwsSNHkihYZaNz5s",
|
||||||
|
"type": "public-key",
|
||||||
|
"registrationClientExtensions": "{}",
|
||||||
|
"response": {
|
||||||
|
"clientDataJSON": (
|
||||||
|
"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmd"
|
||||||
|
"lIjoiMDNYb2RpNTRnS3NmblA1STlWRmZoYUdYVlZFMk5VeV"
|
||||||
|
"pwQkJYbnNfSkkteDZWOVJZMlR3MlFteFJKa2hoNzE3NEVrU"
|
||||||
|
"mF6VW50SXdqTVZZOWJGRzYwTHciLCJvcmlnaW4iOiJodHRw"
|
||||||
|
"Oi8vbG9jYWxob3N0OjkwMDAiLCJjcm9zc09yaWdpbiI6ZmF"
|
||||||
|
),
|
||||||
|
"attestationObject": (
|
||||||
|
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYSZYN5Yg"
|
||||||
|
"OjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAPv8MA"
|
||||||
|
"cVTk7MjAtuAgVX170AFJKp5q1S5wxvjsLEjR5IoWGWjc-bp"
|
||||||
|
"QECAyYgASFYIKtcZHPumH37XHs0IM1v3pUBRIqHVV_SE-Lq"
|
||||||
|
"2zpJAOVXIlgg74Fg_WdB0kuLYqCKbxogkEPaVtR_iR3IyQFIJAXBzds"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SERVER_NAME="localhost",
|
||||||
|
SERVER_PORT="9000",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertStageResponse(
|
||||||
|
response,
|
||||||
|
flow=self.flow,
|
||||||
|
component="ak-stage-authenticator-webauthn",
|
||||||
|
response_errors={
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"string": (
|
||||||
|
"Registration failed. Error: Unable to decode "
|
||||||
|
"client_data_json bytes as JSON"
|
||||||
|
),
|
||||||
|
"code": "invalid",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertFalse(WebAuthnDevice.objects.filter(user=self.user).exists())
|
||||||
|
|
||||||
|
# Second failed request
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
|
||||||
|
data={
|
||||||
|
"component": "ak-stage-authenticator-webauthn",
|
||||||
|
"response": {
|
||||||
|
"id": "kqnmrVLnDG-OwsSNHkihYZaNz5s",
|
||||||
|
"rawId": "kqnmrVLnDG-OwsSNHkihYZaNz5s",
|
||||||
|
"type": "public-key",
|
||||||
|
"registrationClientExtensions": "{}",
|
||||||
|
"response": {
|
||||||
|
"clientDataJSON": (
|
||||||
|
"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmd"
|
||||||
|
"lIjoiMDNYb2RpNTRnS3NmblA1STlWRmZoYUdYVlZFMk5VeV"
|
||||||
|
"pwQkJYbnNfSkkteDZWOVJZMlR3MlFteFJKa2hoNzE3NEVrU"
|
||||||
|
"mF6VW50SXdqTVZZOWJGRzYwTHciLCJvcmlnaW4iOiJodHRw"
|
||||||
|
"Oi8vbG9jYWxob3N0OjkwMDAiLCJjcm9zc09yaWdpbiI6ZmF"
|
||||||
|
),
|
||||||
|
"attestationObject": (
|
||||||
|
"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViYSZYN5Yg"
|
||||||
|
"OjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAPv8MA"
|
||||||
|
"cVTk7MjAtuAgVX170AFJKp5q1S5wxvjsLEjR5IoWGWjc-bp"
|
||||||
|
"QECAyYgASFYIKtcZHPumH37XHs0IM1v3pUBRIqHVV_SE-Lq"
|
||||||
|
"2zpJAOVXIlgg74Fg_WdB0kuLYqCKbxogkEPaVtR_iR3IyQFIJAXBzds"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SERVER_NAME="localhost",
|
||||||
|
SERVER_PORT="9000",
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertStageResponse(
|
||||||
|
response,
|
||||||
|
flow=self.flow,
|
||||||
|
component="ak-stage-access-denied",
|
||||||
|
error_message=(
|
||||||
|
"Exceeded maximum attempts. Contact your authentik administrator for help."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
self.assertFalse(WebAuthnDevice.objects.filter(user=self.user).exists())
|
||||||
|
@ -101,9 +101,9 @@ class BoundSessionMiddleware(SessionMiddleware):
|
|||||||
SESSION_KEY_BINDING_GEO, GeoIPBinding.NO_BINDING
|
SESSION_KEY_BINDING_GEO, GeoIPBinding.NO_BINDING
|
||||||
)
|
)
|
||||||
if configured_binding_net != NetworkBinding.NO_BINDING:
|
if configured_binding_net != NetworkBinding.NO_BINDING:
|
||||||
self.recheck_session_net(configured_binding_net, last_ip, new_ip)
|
BoundSessionMiddleware.recheck_session_net(configured_binding_net, last_ip, new_ip)
|
||||||
if configured_binding_geo != GeoIPBinding.NO_BINDING:
|
if configured_binding_geo != GeoIPBinding.NO_BINDING:
|
||||||
self.recheck_session_geo(configured_binding_geo, last_ip, new_ip)
|
BoundSessionMiddleware.recheck_session_geo(configured_binding_geo, last_ip, new_ip)
|
||||||
# If we got to this point without any error being raised, we need to
|
# If we got to this point without any error being raised, we need to
|
||||||
# update the last saved IP to the current one
|
# update the last saved IP to the current one
|
||||||
if SESSION_KEY_BINDING_NET in request.session or SESSION_KEY_BINDING_GEO in request.session:
|
if SESSION_KEY_BINDING_NET in request.session or SESSION_KEY_BINDING_GEO in request.session:
|
||||||
@ -111,7 +111,8 @@ class BoundSessionMiddleware(SessionMiddleware):
|
|||||||
# (== basically requires the user to be logged in)
|
# (== basically requires the user to be logged in)
|
||||||
request.session[request.session.model.Keys.LAST_IP] = new_ip
|
request.session[request.session.model.Keys.LAST_IP] = new_ip
|
||||||
|
|
||||||
def recheck_session_net(self, binding: NetworkBinding, last_ip: str, new_ip: str):
|
@staticmethod
|
||||||
|
def recheck_session_net(binding: NetworkBinding, last_ip: str, new_ip: str):
|
||||||
"""Check network/ASN binding"""
|
"""Check network/ASN binding"""
|
||||||
last_asn = ASN_CONTEXT_PROCESSOR.asn(last_ip)
|
last_asn = ASN_CONTEXT_PROCESSOR.asn(last_ip)
|
||||||
new_asn = ASN_CONTEXT_PROCESSOR.asn(new_ip)
|
new_asn = ASN_CONTEXT_PROCESSOR.asn(new_ip)
|
||||||
@ -158,7 +159,8 @@ class BoundSessionMiddleware(SessionMiddleware):
|
|||||||
new_ip,
|
new_ip,
|
||||||
)
|
)
|
||||||
|
|
||||||
def recheck_session_geo(self, binding: GeoIPBinding, last_ip: str, new_ip: str):
|
@staticmethod
|
||||||
|
def recheck_session_geo(binding: GeoIPBinding, last_ip: str, new_ip: str):
|
||||||
"""Check GeoIP binding"""
|
"""Check GeoIP binding"""
|
||||||
last_geo = GEOIP_CONTEXT_PROCESSOR.city(last_ip)
|
last_geo = GEOIP_CONTEXT_PROCESSOR.city(last_ip)
|
||||||
new_geo = GEOIP_CONTEXT_PROCESSOR.city(new_ip)
|
new_geo = GEOIP_CONTEXT_PROCESSOR.city(new_ip)
|
||||||
@ -179,8 +181,8 @@ class BoundSessionMiddleware(SessionMiddleware):
|
|||||||
if last_geo.continent != new_geo.continent:
|
if last_geo.continent != new_geo.continent:
|
||||||
raise SessionBindingBroken(
|
raise SessionBindingBroken(
|
||||||
"geoip.continent",
|
"geoip.continent",
|
||||||
last_geo.continent,
|
last_geo.continent.to_dict(),
|
||||||
new_geo.continent,
|
new_geo.continent.to_dict(),
|
||||||
last_ip,
|
last_ip,
|
||||||
new_ip,
|
new_ip,
|
||||||
)
|
)
|
||||||
@ -192,8 +194,8 @@ class BoundSessionMiddleware(SessionMiddleware):
|
|||||||
if last_geo.country != new_geo.country:
|
if last_geo.country != new_geo.country:
|
||||||
raise SessionBindingBroken(
|
raise SessionBindingBroken(
|
||||||
"geoip.country",
|
"geoip.country",
|
||||||
last_geo.country,
|
last_geo.country.to_dict(),
|
||||||
new_geo.country,
|
new_geo.country.to_dict(),
|
||||||
last_ip,
|
last_ip,
|
||||||
new_ip,
|
new_ip,
|
||||||
)
|
)
|
||||||
@ -202,8 +204,8 @@ class BoundSessionMiddleware(SessionMiddleware):
|
|||||||
if last_geo.city != new_geo.city:
|
if last_geo.city != new_geo.city:
|
||||||
raise SessionBindingBroken(
|
raise SessionBindingBroken(
|
||||||
"geoip.city",
|
"geoip.city",
|
||||||
last_geo.city,
|
last_geo.city.to_dict(),
|
||||||
new_geo.city,
|
new_geo.city.to_dict(),
|
||||||
last_ip,
|
last_ip,
|
||||||
new_ip,
|
new_ip,
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from django.http import HttpRequest
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
@ -17,7 +18,12 @@ from authentik.flows.views.executor import SESSION_KEY_PLAN
|
|||||||
from authentik.lib.generators import generate_id
|
from authentik.lib.generators import generate_id
|
||||||
from authentik.lib.utils.time import timedelta_from_string
|
from authentik.lib.utils.time import timedelta_from_string
|
||||||
from authentik.root.middleware import ClientIPMiddleware
|
from authentik.root.middleware import ClientIPMiddleware
|
||||||
from authentik.stages.user_login.models import UserLoginStage
|
from authentik.stages.user_login.middleware import (
|
||||||
|
BoundSessionMiddleware,
|
||||||
|
SessionBindingBroken,
|
||||||
|
logout_extra,
|
||||||
|
)
|
||||||
|
from authentik.stages.user_login.models import GeoIPBinding, NetworkBinding, UserLoginStage
|
||||||
|
|
||||||
|
|
||||||
class TestUserLoginStage(FlowTestCase):
|
class TestUserLoginStage(FlowTestCase):
|
||||||
@ -192,3 +198,52 @@ class TestUserLoginStage(FlowTestCase):
|
|||||||
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
|
||||||
response = self.client.get(reverse("authentik_api:application-list"))
|
response = self.client.get(reverse("authentik_api:application-list"))
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
def test_binding_net_break_log(self):
|
||||||
|
"""Test logout_extra with exception"""
|
||||||
|
# IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-ASN-Test.json
|
||||||
|
for args, expect in [
|
||||||
|
[[NetworkBinding.BIND_ASN, "8.8.8.8", "8.8.8.8"], ["network.missing"]],
|
||||||
|
[[NetworkBinding.BIND_ASN, "1.0.0.1", "1.128.0.1"], ["network.asn"]],
|
||||||
|
[
|
||||||
|
[NetworkBinding.BIND_ASN_NETWORK, "12.81.96.1", "12.81.128.1"],
|
||||||
|
["network.asn_network"],
|
||||||
|
],
|
||||||
|
[[NetworkBinding.BIND_ASN_NETWORK_IP, "1.0.0.1", "1.0.0.2"], ["network.ip"]],
|
||||||
|
]:
|
||||||
|
with self.subTest(args[0]):
|
||||||
|
with self.assertRaises(SessionBindingBroken) as cm:
|
||||||
|
BoundSessionMiddleware.recheck_session_net(*args)
|
||||||
|
self.assertEqual(cm.exception.reason, expect[0])
|
||||||
|
# Ensure the request can be logged without throwing errors
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
request = HttpRequest()
|
||||||
|
request.session = self.client.session
|
||||||
|
request.user = self.user
|
||||||
|
logout_extra(request, cm.exception)
|
||||||
|
|
||||||
|
def test_binding_geo_break_log(self):
|
||||||
|
"""Test logout_extra with exception"""
|
||||||
|
# IPs from https://github.com/maxmind/MaxMind-DB/blob/main/source-data/GeoLite2-City-Test.json
|
||||||
|
for args, expect in [
|
||||||
|
[[GeoIPBinding.BIND_CONTINENT, "8.8.8.8", "8.8.8.8"], ["geoip.missing"]],
|
||||||
|
[[GeoIPBinding.BIND_CONTINENT, "2.125.160.216", "67.43.156.1"], ["geoip.continent"]],
|
||||||
|
[
|
||||||
|
[GeoIPBinding.BIND_CONTINENT_COUNTRY, "81.2.69.142", "89.160.20.112"],
|
||||||
|
["geoip.country"],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[GeoIPBinding.BIND_CONTINENT_COUNTRY_CITY, "2.125.160.216", "81.2.69.142"],
|
||||||
|
["geoip.city"],
|
||||||
|
],
|
||||||
|
]:
|
||||||
|
with self.subTest(args[0]):
|
||||||
|
with self.assertRaises(SessionBindingBroken) as cm:
|
||||||
|
BoundSessionMiddleware.recheck_session_geo(*args)
|
||||||
|
self.assertEqual(cm.exception.reason, expect[0])
|
||||||
|
# Ensure the request can be logged without throwing errors
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
request = HttpRequest()
|
||||||
|
request.session = self.client.session
|
||||||
|
request.user = self.user
|
||||||
|
logout_extra(request, cm.exception)
|
||||||
|
@ -16,6 +16,7 @@ def check_embedded_outpost_disabled(app_configs, **kwargs):
|
|||||||
"Embedded outpost must be disabled when tenants API is enabled.",
|
"Embedded outpost must be disabled when tenants API is enabled.",
|
||||||
hint="Disable embedded outpost by setting outposts.disable_embedded_outpost to "
|
hint="Disable embedded outpost by setting outposts.disable_embedded_outpost to "
|
||||||
"True, or disable the tenants API by setting tenants.enabled to False",
|
"True, or disable the tenants API by setting tenants.enabled to False",
|
||||||
|
id="ak.tenants.E001",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
return []
|
return []
|
||||||
|
@ -13359,6 +13359,12 @@
|
|||||||
"format": "uuid"
|
"format": "uuid"
|
||||||
},
|
},
|
||||||
"title": "Device type restrictions"
|
"title": "Device type restrictions"
|
||||||
|
},
|
||||||
|
"max_attempts": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 2147483647,
|
||||||
|
"title": "Max attempts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
2
go.mod
2
go.mod
@ -29,7 +29,7 @@ require (
|
|||||||
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.2025062.3
|
goauthentik.io/api/v3 v3.2025062.4
|
||||||
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
golang.org/x/exp v0.0.0-20230210204819-062eb4c674ab
|
||||||
golang.org/x/oauth2 v0.30.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/sync v0.15.0
|
golang.org/x/sync v0.15.0
|
||||||
|
4
go.sum
4
go.sum
@ -298,8 +298,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.2025062.3 h1:syBOKigaHyX/8Rwmh9kOSF+TzsxOzmP5i7rsFwbemzA=
|
goauthentik.io/api/v3 v3.2025062.4 h1:HuyL12kKserXT2w+wCDUYNRSeyCCGX81wU9SRCPuxDo=
|
||||||
goauthentik.io/api/v3 v3.2025062.3/go.mod h1:zz+mEZg8rY/7eEjkMGWJ2DnGqk+zqxuybGCGrR2O4Kw=
|
goauthentik.io/api/v3 v3.2025062.4/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=
|
||||||
|
@ -10,7 +10,7 @@ from typing import Any
|
|||||||
from psycopg import Connection, Cursor, connect
|
from psycopg import Connection, Cursor, connect
|
||||||
from structlog.stdlib import get_logger
|
from structlog.stdlib import get_logger
|
||||||
|
|
||||||
from authentik.lib.config import CONFIG
|
from authentik.lib.config import CONFIG, django_db_config
|
||||||
|
|
||||||
LOGGER = get_logger()
|
LOGGER = get_logger()
|
||||||
ADV_LOCK_UID = 1000
|
ADV_LOCK_UID = 1000
|
||||||
@ -115,9 +115,13 @@ def run_migrations():
|
|||||||
execute_from_command_line(["", "migrate_schemas"])
|
execute_from_command_line(["", "migrate_schemas"])
|
||||||
if CONFIG.get_bool("tenants.enabled", False):
|
if CONFIG.get_bool("tenants.enabled", False):
|
||||||
execute_from_command_line(["", "migrate_schemas", "--schema", "template", "--tenant"])
|
execute_from_command_line(["", "migrate_schemas", "--schema", "template", "--tenant"])
|
||||||
execute_from_command_line(
|
# Run django system checks for all databases
|
||||||
["", "check"] + ([] if CONFIG.get_bool("debug") else ["--deploy"])
|
check_args = ["", "check"]
|
||||||
)
|
for label in django_db_config(CONFIG).keys():
|
||||||
|
check_args.append(f"--database={label}")
|
||||||
|
if not CONFIG.get_bool("DEBUG"):
|
||||||
|
check_args.append("--deploy")
|
||||||
|
execute_from_command_line(check_args)
|
||||||
finally:
|
finally:
|
||||||
release_lock(curr)
|
release_lock(curr)
|
||||||
curr.close()
|
curr.close()
|
||||||
|
96
packages/eslint-config/package-lock.json
generated
96
packages/eslint-config/package-lock.json
generated
@ -920,17 +920,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/array-includes": {
|
"node_modules/array-includes": {
|
||||||
"version": "3.1.8",
|
"version": "3.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
|
||||||
"integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
|
"integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.7",
|
"call-bind": "^1.0.8",
|
||||||
|
"call-bound": "^1.0.4",
|
||||||
"define-properties": "^1.2.1",
|
"define-properties": "^1.2.1",
|
||||||
"es-abstract": "^1.23.2",
|
"es-abstract": "^1.24.0",
|
||||||
"es-object-atoms": "^1.0.0",
|
"es-object-atoms": "^1.1.1",
|
||||||
"get-intrinsic": "^1.2.4",
|
"get-intrinsic": "^1.3.0",
|
||||||
"is-string": "^1.0.7"
|
"is-string": "^1.1.1",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -1376,27 +1378,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-abstract": {
|
"node_modules/es-abstract": {
|
||||||
"version": "1.23.9",
|
"version": "1.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
|
||||||
"integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
|
"integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"array-buffer-byte-length": "^1.0.2",
|
"array-buffer-byte-length": "^1.0.2",
|
||||||
"arraybuffer.prototype.slice": "^1.0.4",
|
"arraybuffer.prototype.slice": "^1.0.4",
|
||||||
"available-typed-arrays": "^1.0.7",
|
"available-typed-arrays": "^1.0.7",
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.4",
|
||||||
"data-view-buffer": "^1.0.2",
|
"data-view-buffer": "^1.0.2",
|
||||||
"data-view-byte-length": "^1.0.2",
|
"data-view-byte-length": "^1.0.2",
|
||||||
"data-view-byte-offset": "^1.0.1",
|
"data-view-byte-offset": "^1.0.1",
|
||||||
"es-define-property": "^1.0.1",
|
"es-define-property": "^1.0.1",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"es-object-atoms": "^1.0.0",
|
"es-object-atoms": "^1.1.1",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
"es-to-primitive": "^1.3.0",
|
"es-to-primitive": "^1.3.0",
|
||||||
"function.prototype.name": "^1.1.8",
|
"function.prototype.name": "^1.1.8",
|
||||||
"get-intrinsic": "^1.2.7",
|
"get-intrinsic": "^1.3.0",
|
||||||
"get-proto": "^1.0.0",
|
"get-proto": "^1.0.1",
|
||||||
"get-symbol-description": "^1.1.0",
|
"get-symbol-description": "^1.1.0",
|
||||||
"globalthis": "^1.0.4",
|
"globalthis": "^1.0.4",
|
||||||
"gopd": "^1.2.0",
|
"gopd": "^1.2.0",
|
||||||
@ -1408,21 +1410,24 @@
|
|||||||
"is-array-buffer": "^3.0.5",
|
"is-array-buffer": "^3.0.5",
|
||||||
"is-callable": "^1.2.7",
|
"is-callable": "^1.2.7",
|
||||||
"is-data-view": "^1.0.2",
|
"is-data-view": "^1.0.2",
|
||||||
|
"is-negative-zero": "^2.0.3",
|
||||||
"is-regex": "^1.2.1",
|
"is-regex": "^1.2.1",
|
||||||
|
"is-set": "^2.0.3",
|
||||||
"is-shared-array-buffer": "^1.0.4",
|
"is-shared-array-buffer": "^1.0.4",
|
||||||
"is-string": "^1.1.1",
|
"is-string": "^1.1.1",
|
||||||
"is-typed-array": "^1.1.15",
|
"is-typed-array": "^1.1.15",
|
||||||
"is-weakref": "^1.1.0",
|
"is-weakref": "^1.1.1",
|
||||||
"math-intrinsics": "^1.1.0",
|
"math-intrinsics": "^1.1.0",
|
||||||
"object-inspect": "^1.13.3",
|
"object-inspect": "^1.13.4",
|
||||||
"object-keys": "^1.1.1",
|
"object-keys": "^1.1.1",
|
||||||
"object.assign": "^4.1.7",
|
"object.assign": "^4.1.7",
|
||||||
"own-keys": "^1.0.1",
|
"own-keys": "^1.0.1",
|
||||||
"regexp.prototype.flags": "^1.5.3",
|
"regexp.prototype.flags": "^1.5.4",
|
||||||
"safe-array-concat": "^1.1.3",
|
"safe-array-concat": "^1.1.3",
|
||||||
"safe-push-apply": "^1.0.0",
|
"safe-push-apply": "^1.0.0",
|
||||||
"safe-regex-test": "^1.1.0",
|
"safe-regex-test": "^1.1.0",
|
||||||
"set-proto": "^1.0.0",
|
"set-proto": "^1.0.0",
|
||||||
|
"stop-iteration-iterator": "^1.1.0",
|
||||||
"string.prototype.trim": "^1.2.10",
|
"string.prototype.trim": "^1.2.10",
|
||||||
"string.prototype.trimend": "^1.0.9",
|
"string.prototype.trimend": "^1.0.9",
|
||||||
"string.prototype.trimstart": "^1.0.8",
|
"string.prototype.trimstart": "^1.0.8",
|
||||||
@ -1431,7 +1436,7 @@
|
|||||||
"typed-array-byte-offset": "^1.0.4",
|
"typed-array-byte-offset": "^1.0.4",
|
||||||
"typed-array-length": "^1.0.7",
|
"typed-array-length": "^1.0.7",
|
||||||
"unbox-primitive": "^1.1.0",
|
"unbox-primitive": "^1.1.0",
|
||||||
"which-typed-array": "^1.1.18"
|
"which-typed-array": "^1.1.19"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -1634,9 +1639,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-module-utils": {
|
"node_modules/eslint-module-utils": {
|
||||||
"version": "2.12.0",
|
"version": "2.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
|
||||||
"integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
|
"integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^3.2.7"
|
"debug": "^3.2.7"
|
||||||
@ -1660,29 +1665,29 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-import": {
|
"node_modules/eslint-plugin-import": {
|
||||||
"version": "2.31.0",
|
"version": "2.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
||||||
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.8",
|
"array-includes": "^3.1.9",
|
||||||
"array.prototype.findlastindex": "^1.2.5",
|
"array.prototype.findlastindex": "^1.2.6",
|
||||||
"array.prototype.flat": "^1.3.2",
|
"array.prototype.flat": "^1.3.3",
|
||||||
"array.prototype.flatmap": "^1.3.2",
|
"array.prototype.flatmap": "^1.3.3",
|
||||||
"debug": "^3.2.7",
|
"debug": "^3.2.7",
|
||||||
"doctrine": "^2.1.0",
|
"doctrine": "^2.1.0",
|
||||||
"eslint-import-resolver-node": "^0.3.9",
|
"eslint-import-resolver-node": "^0.3.9",
|
||||||
"eslint-module-utils": "^2.12.0",
|
"eslint-module-utils": "^2.12.1",
|
||||||
"hasown": "^2.0.2",
|
"hasown": "^2.0.2",
|
||||||
"is-core-module": "^2.15.1",
|
"is-core-module": "^2.16.1",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
"minimatch": "^3.1.2",
|
"minimatch": "^3.1.2",
|
||||||
"object.fromentries": "^2.0.8",
|
"object.fromentries": "^2.0.8",
|
||||||
"object.groupby": "^1.0.3",
|
"object.groupby": "^1.0.3",
|
||||||
"object.values": "^1.2.0",
|
"object.values": "^1.2.1",
|
||||||
"semver": "^6.3.1",
|
"semver": "^6.3.1",
|
||||||
"string.prototype.trimend": "^1.0.8",
|
"string.prototype.trimend": "^1.0.9",
|
||||||
"tsconfig-paths": "^3.15.0"
|
"tsconfig-paths": "^3.15.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -2501,6 +2506,18 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-negative-zero": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-number": {
|
"node_modules/is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
@ -3693,6 +3710,19 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stop-iteration-iterator": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"internal-slot": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string.prototype.matchall": {
|
"node_modules/string.prototype.matchall": {
|
||||||
"version": "4.0.12",
|
"version": "4.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
|
||||||
|
16
schema.yml
16
schema.yml
@ -34791,6 +34791,10 @@ paths:
|
|||||||
name: friendly_name
|
name: friendly_name
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: max_attempts
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
- in: query
|
- in: query
|
||||||
name: name
|
name: name
|
||||||
schema:
|
schema:
|
||||||
@ -42831,6 +42835,10 @@ components:
|
|||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/WebAuthnDeviceType'
|
$ref: '#/components/schemas/WebAuthnDeviceType'
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
max_attempts:
|
||||||
|
type: integer
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: 0
|
||||||
required:
|
required:
|
||||||
- component
|
- component
|
||||||
- device_type_restrictions_obj
|
- device_type_restrictions_obj
|
||||||
@ -42873,6 +42881,10 @@ components:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
max_attempts:
|
||||||
|
type: integer
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: 0
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
AuthorizationCodeAuthMethodEnum:
|
AuthorizationCodeAuthMethodEnum:
|
||||||
@ -52833,6 +52845,10 @@ components:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
max_attempts:
|
||||||
|
type: integer
|
||||||
|
maximum: 2147483647
|
||||||
|
minimum: 0
|
||||||
PatchedBlueprintInstanceRequest:
|
PatchedBlueprintInstanceRequest:
|
||||||
type: object
|
type: object
|
||||||
description: Info about a single blueprint instance file
|
description: Info about a single blueprint instance file
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
@ -7,7 +7,7 @@ services:
|
|||||||
network_mode: host
|
network_mode: host
|
||||||
restart: always
|
restart: always
|
||||||
mailpit:
|
mailpit:
|
||||||
image: docker.io/axllent/mailpit:v1.26.1
|
image: docker.io/axllent/mailpit:v1.26.2
|
||||||
ports:
|
ports:
|
||||||
- 1025:1025
|
- 1025:1025
|
||||||
- 8025:8025
|
- 8025:8025
|
||||||
|
124
web/package-lock.json
generated
124
web/package-lock.json
generated
@ -22,7 +22,7 @@
|
|||||||
"@floating-ui/dom": "^1.6.11",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@formatjs/intl-listformat": "^7.7.11",
|
"@formatjs/intl-listformat": "^7.7.11",
|
||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@goauthentik/api": "^2025.6.2-1750246811",
|
"@goauthentik/api": "^2025.6.2-1750636159",
|
||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@lit/localize": "^0.12.2",
|
"@lit/localize": "^0.12.2",
|
||||||
"@lit/reactive-element": "^2.0.4",
|
"@lit/reactive-element": "^2.0.4",
|
||||||
@ -1731,9 +1731,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@goauthentik/api": {
|
"node_modules/@goauthentik/api": {
|
||||||
"version": "2025.6.2-1750246811",
|
"version": "2025.6.2-1750636159",
|
||||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.6.2-1750246811.tgz",
|
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2025.6.2-1750636159.tgz",
|
||||||
"integrity": "sha512-ENHEi3kGAodf5tKQb5kziUrT1EcJw3z8tp2mU7LWqNlXr4eoAI15BjDfH5DW56l4jy3xKqTd+R2Ntnj4hiVhHw=="
|
"integrity": "sha512-LPseyRzzi5Wk/cP8suRYUhwe/sGdIsGIcaXUkl13jprkJCUXEfuLcfAgdJka2MnIPaMyBDv7oYxJ0IhV/sidEg=="
|
||||||
},
|
},
|
||||||
"node_modules/@goauthentik/core": {
|
"node_modules/@goauthentik/core": {
|
||||||
"resolved": "packages/core",
|
"resolved": "packages/core",
|
||||||
@ -10380,18 +10380,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/array-includes": {
|
"node_modules/array-includes": {
|
||||||
"version": "3.1.8",
|
"version": "3.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
|
||||||
"integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
|
"integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.7",
|
"call-bind": "^1.0.8",
|
||||||
|
"call-bound": "^1.0.4",
|
||||||
"define-properties": "^1.2.1",
|
"define-properties": "^1.2.1",
|
||||||
"es-abstract": "^1.23.2",
|
"es-abstract": "^1.24.0",
|
||||||
"es-object-atoms": "^1.0.0",
|
"es-object-atoms": "^1.1.1",
|
||||||
"get-intrinsic": "^1.2.4",
|
"get-intrinsic": "^1.3.0",
|
||||||
"is-string": "^1.0.7"
|
"is-string": "^1.1.1",
|
||||||
|
"math-intrinsics": "^1.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -13642,9 +13644,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/es-abstract": {
|
"node_modules/es-abstract": {
|
||||||
"version": "1.23.9",
|
"version": "1.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
|
||||||
"integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
|
"integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -13652,18 +13654,18 @@
|
|||||||
"arraybuffer.prototype.slice": "^1.0.4",
|
"arraybuffer.prototype.slice": "^1.0.4",
|
||||||
"available-typed-arrays": "^1.0.7",
|
"available-typed-arrays": "^1.0.7",
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.4",
|
||||||
"data-view-buffer": "^1.0.2",
|
"data-view-buffer": "^1.0.2",
|
||||||
"data-view-byte-length": "^1.0.2",
|
"data-view-byte-length": "^1.0.2",
|
||||||
"data-view-byte-offset": "^1.0.1",
|
"data-view-byte-offset": "^1.0.1",
|
||||||
"es-define-property": "^1.0.1",
|
"es-define-property": "^1.0.1",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"es-object-atoms": "^1.0.0",
|
"es-object-atoms": "^1.1.1",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
"es-to-primitive": "^1.3.0",
|
"es-to-primitive": "^1.3.0",
|
||||||
"function.prototype.name": "^1.1.8",
|
"function.prototype.name": "^1.1.8",
|
||||||
"get-intrinsic": "^1.2.7",
|
"get-intrinsic": "^1.3.0",
|
||||||
"get-proto": "^1.0.0",
|
"get-proto": "^1.0.1",
|
||||||
"get-symbol-description": "^1.1.0",
|
"get-symbol-description": "^1.1.0",
|
||||||
"globalthis": "^1.0.4",
|
"globalthis": "^1.0.4",
|
||||||
"gopd": "^1.2.0",
|
"gopd": "^1.2.0",
|
||||||
@ -13675,21 +13677,24 @@
|
|||||||
"is-array-buffer": "^3.0.5",
|
"is-array-buffer": "^3.0.5",
|
||||||
"is-callable": "^1.2.7",
|
"is-callable": "^1.2.7",
|
||||||
"is-data-view": "^1.0.2",
|
"is-data-view": "^1.0.2",
|
||||||
|
"is-negative-zero": "^2.0.3",
|
||||||
"is-regex": "^1.2.1",
|
"is-regex": "^1.2.1",
|
||||||
|
"is-set": "^2.0.3",
|
||||||
"is-shared-array-buffer": "^1.0.4",
|
"is-shared-array-buffer": "^1.0.4",
|
||||||
"is-string": "^1.1.1",
|
"is-string": "^1.1.1",
|
||||||
"is-typed-array": "^1.1.15",
|
"is-typed-array": "^1.1.15",
|
||||||
"is-weakref": "^1.1.0",
|
"is-weakref": "^1.1.1",
|
||||||
"math-intrinsics": "^1.1.0",
|
"math-intrinsics": "^1.1.0",
|
||||||
"object-inspect": "^1.13.3",
|
"object-inspect": "^1.13.4",
|
||||||
"object-keys": "^1.1.1",
|
"object-keys": "^1.1.1",
|
||||||
"object.assign": "^4.1.7",
|
"object.assign": "^4.1.7",
|
||||||
"own-keys": "^1.0.1",
|
"own-keys": "^1.0.1",
|
||||||
"regexp.prototype.flags": "^1.5.3",
|
"regexp.prototype.flags": "^1.5.4",
|
||||||
"safe-array-concat": "^1.1.3",
|
"safe-array-concat": "^1.1.3",
|
||||||
"safe-push-apply": "^1.0.0",
|
"safe-push-apply": "^1.0.0",
|
||||||
"safe-regex-test": "^1.1.0",
|
"safe-regex-test": "^1.1.0",
|
||||||
"set-proto": "^1.0.0",
|
"set-proto": "^1.0.0",
|
||||||
|
"stop-iteration-iterator": "^1.1.0",
|
||||||
"string.prototype.trim": "^1.2.10",
|
"string.prototype.trim": "^1.2.10",
|
||||||
"string.prototype.trimend": "^1.0.9",
|
"string.prototype.trimend": "^1.0.9",
|
||||||
"string.prototype.trimstart": "^1.0.8",
|
"string.prototype.trimstart": "^1.0.8",
|
||||||
@ -13698,7 +13703,7 @@
|
|||||||
"typed-array-byte-offset": "^1.0.4",
|
"typed-array-byte-offset": "^1.0.4",
|
||||||
"typed-array-length": "^1.0.7",
|
"typed-array-length": "^1.0.7",
|
||||||
"unbox-primitive": "^1.1.0",
|
"unbox-primitive": "^1.1.0",
|
||||||
"which-typed-array": "^1.1.18"
|
"which-typed-array": "^1.1.19"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@ -14623,9 +14628,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-module-utils": {
|
"node_modules/eslint-module-utils": {
|
||||||
"version": "2.12.0",
|
"version": "2.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
|
||||||
"integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
|
"integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -14651,30 +14656,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint-plugin-import": {
|
"node_modules/eslint-plugin-import": {
|
||||||
"version": "2.31.0",
|
"version": "2.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
|
||||||
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.8",
|
"array-includes": "^3.1.9",
|
||||||
"array.prototype.findlastindex": "^1.2.5",
|
"array.prototype.findlastindex": "^1.2.6",
|
||||||
"array.prototype.flat": "^1.3.2",
|
"array.prototype.flat": "^1.3.3",
|
||||||
"array.prototype.flatmap": "^1.3.2",
|
"array.prototype.flatmap": "^1.3.3",
|
||||||
"debug": "^3.2.7",
|
"debug": "^3.2.7",
|
||||||
"doctrine": "^2.1.0",
|
"doctrine": "^2.1.0",
|
||||||
"eslint-import-resolver-node": "^0.3.9",
|
"eslint-import-resolver-node": "^0.3.9",
|
||||||
"eslint-module-utils": "^2.12.0",
|
"eslint-module-utils": "^2.12.1",
|
||||||
"hasown": "^2.0.2",
|
"hasown": "^2.0.2",
|
||||||
"is-core-module": "^2.15.1",
|
"is-core-module": "^2.16.1",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
"minimatch": "^3.1.2",
|
"minimatch": "^3.1.2",
|
||||||
"object.fromentries": "^2.0.8",
|
"object.fromentries": "^2.0.8",
|
||||||
"object.groupby": "^1.0.3",
|
"object.groupby": "^1.0.3",
|
||||||
"object.values": "^1.2.0",
|
"object.values": "^1.2.1",
|
||||||
"semver": "^6.3.1",
|
"semver": "^6.3.1",
|
||||||
"string.prototype.trimend": "^1.0.8",
|
"string.prototype.trimend": "^1.0.9",
|
||||||
"tsconfig-paths": "^3.15.0"
|
"tsconfig-paths": "^3.15.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -17382,9 +17387,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.15.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
},
|
},
|
||||||
@ -17563,6 +17569,19 @@
|
|||||||
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/is-negative-zero": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-number": {
|
"node_modules/is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
@ -24021,14 +24040,17 @@
|
|||||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||||
},
|
},
|
||||||
"node_modules/regexp.prototype.flags": {
|
"node_modules/regexp.prototype.flags": {
|
||||||
"version": "1.5.3",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||||
"integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==",
|
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.7",
|
"call-bind": "^1.0.8",
|
||||||
"define-properties": "^1.2.1",
|
"define-properties": "^1.2.1",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"gopd": "^1.2.0",
|
||||||
"set-function-name": "^2.0.2"
|
"set-function-name": "^2.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -25530,6 +25552,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/stop-iteration-iterator": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"internal-slot": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/storybook": {
|
"node_modules/storybook": {
|
||||||
"version": "8.6.14",
|
"version": "8.6.14",
|
||||||
"resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.14.tgz",
|
"resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.14.tgz",
|
||||||
|
@ -93,7 +93,7 @@
|
|||||||
"@floating-ui/dom": "^1.6.11",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@formatjs/intl-listformat": "^7.7.11",
|
"@formatjs/intl-listformat": "^7.7.11",
|
||||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||||
"@goauthentik/api": "^2025.6.2-1750246811",
|
"@goauthentik/api": "^2025.6.2-1750636159",
|
||||||
"@lit/context": "^1.1.2",
|
"@lit/context": "^1.1.2",
|
||||||
"@lit/localize": "^0.12.2",
|
"@lit/localize": "^0.12.2",
|
||||||
"@lit/reactive-element": "^2.0.4",
|
"@lit/reactive-element": "^2.0.4",
|
||||||
|
@ -2,6 +2,7 @@ import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
|||||||
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
||||||
import { deviceTypeRestrictionPair } from "@goauthentik/admin/stages/authenticator_webauthn/utils";
|
import { deviceTypeRestrictionPair } from "@goauthentik/admin/stages/authenticator_webauthn/utils";
|
||||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||||
|
import "@goauthentik/components/ak-number-input";
|
||||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider";
|
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider";
|
||||||
import { DataProvision } from "@goauthentik/elements/ak-dual-select/types";
|
import { DataProvision } from "@goauthentik/elements/ak-dual-select/types";
|
||||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||||
@ -165,6 +166,15 @@ export class AuthenticatorWebAuthnStageForm extends BaseStageForm<AuthenticatorW
|
|||||||
>
|
>
|
||||||
</ak-radio>
|
</ak-radio>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
|
<ak-number-input
|
||||||
|
label=${msg("Maximum registration attempts")}
|
||||||
|
required
|
||||||
|
name="maxAttempts"
|
||||||
|
value="${this.instance?.maxAttempts || 0}"
|
||||||
|
help=${msg(
|
||||||
|
"Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.",
|
||||||
|
)}
|
||||||
|
></ak-number-input>
|
||||||
<ak-form-element-horizontal
|
<ak-form-element-horizontal
|
||||||
label=${msg("Device type restrictions")}
|
label=${msg("Device type restrictions")}
|
||||||
name="deviceTypeRestrictions"
|
name="deviceTypeRestrictions"
|
||||||
|
@ -93,7 +93,7 @@ export class UserSettingsFlowExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
updated(): void {
|
updated(): void {
|
||||||
if (!this.flowSlug && this.brand) {
|
if (!this.flowSlug && this.brand?.flowUserSettings) {
|
||||||
this.flowSlug = this.brand.flowUserSettings;
|
this.flowSlug = this.brand.flowUserSettings;
|
||||||
this.nextChallenge();
|
this.nextChallenge();
|
||||||
}
|
}
|
||||||
|
@ -9250,6 +9250,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -7760,6 +7760,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9311,6 +9311,12 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9879,6 +9879,12 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9862,6 +9862,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9218,6 +9218,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9122,6 +9122,12 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9545,6 +9545,12 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9554,4 +9554,10 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
</trans-unit>
|
||||||
</body></file></xliff>
|
</body></file></xliff>
|
||||||
|
@ -9637,6 +9637,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9609,6 +9609,12 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -6377,6 +6377,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -9892,6 +9892,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -7461,6 +7461,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -9197,6 +9197,12 @@ Bindings to groups/users are checked against the user of the event.</source>
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sd30f00ff2135589c">
|
<trans-unit id="sd30f00ff2135589c">
|
||||||
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
<source>When enabled, notification will be sent to the user that triggered the event in addition to any users in the group above. The event user will always be the first user, to send a notification only to the event user enabled 'Send once' in the notification transport.</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sbd65aeeb8a3b9bbc">
|
||||||
|
<source>Maximum registration attempts</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="s8495753cb15e8d8e">
|
||||||
|
<source>Maximum allowed registration attempts. When set to 0 attempts, attempts are not limited.</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
@ -8,9 +8,7 @@ To prevent infinite loops (events created by policies which are attached to a No
|
|||||||
|
|
||||||
## Filtering Events
|
## Filtering Events
|
||||||
|
|
||||||
Starting with authentik 0.15, you can create notification rules, which can alert you based on the creation of certain events.
|
An authentik administrator can create notification rules based on the creation of specified events. Filtering is done by using the Policy Engine. You can do simple filtering using the "Event Matcher Policy" type.
|
||||||
|
|
||||||
Filtering is done by using the Policy Engine. You can do simple filtering using the "Event Matcher Policy" type.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -128,9 +128,9 @@ To support the integration of Bitwarden with authentik, you need to create an ap
|
|||||||
|
|
||||||
### Download certificate file
|
### Download certificate file
|
||||||
|
|
||||||
|
1. Log in to authentik as an administrator and open the authentik Admin interface.
|
||||||
2. Navigate to **Applications** > **Providers** and click on the name of the provider that you created in the previous section (e.g. `Provider for Bitwarden`).
|
2. Navigate to **Applications** > **Providers** and click on the name of the provider that you created in the previous section (e.g. `Provider for Bitwarden`).
|
||||||
3. Navigate to **Applications** > **Providers** and click on the name of the provider that you created in the previous section (e.g. `Provider for bitwarden`).
|
3. Under **Related objects** > **Download signing certificate**, click on **Download**. This downloaded file is your certificate file and it will be required in the next section.
|
||||||
4. Under **Related objects** > **Download signing certificate**, click on **Download**. This downloaded file is your certificate file and it will be required in the next section.
|
|
||||||
|
|
||||||
## Bitwarden configuration
|
## Bitwarden configuration
|
||||||
|
|
||||||
|
49
website/package-lock.json
generated
49
website/package-lock.json
generated
@ -19,6 +19,7 @@
|
|||||||
"@goauthentik/docusaurus-config": "^1.1.0",
|
"@goauthentik/docusaurus-config": "^1.1.0",
|
||||||
"@goauthentik/tsconfig": "^1.0.4",
|
"@goauthentik/tsconfig": "^1.0.4",
|
||||||
"@mdx-js/react": "^3.1.0",
|
"@mdx-js/react": "^3.1.0",
|
||||||
|
"@swc/html-linux-x64-gnu": "1.12.5",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"docusaurus-plugin-openapi-docs": "^4.4.0",
|
"docusaurus-plugin-openapi-docs": "^4.4.0",
|
||||||
"docusaurus-theme-openapi-docs": "^4.4.0",
|
"docusaurus-theme-openapi-docs": "^4.4.0",
|
||||||
@ -64,12 +65,12 @@
|
|||||||
"@rspack/binding-darwin-arm64": "1.3.15",
|
"@rspack/binding-darwin-arm64": "1.3.15",
|
||||||
"@rspack/binding-linux-arm64-gnu": "1.3.15",
|
"@rspack/binding-linux-arm64-gnu": "1.3.15",
|
||||||
"@rspack/binding-linux-x64-gnu": "1.3.15",
|
"@rspack/binding-linux-x64-gnu": "1.3.15",
|
||||||
"@swc/core-darwin-arm64": "1.12.1",
|
"@swc/core-darwin-arm64": "1.12.5",
|
||||||
"@swc/core-linux-arm64-gnu": "1.12.1",
|
"@swc/core-linux-arm64-gnu": "1.12.5",
|
||||||
"@swc/core-linux-x64-gnu": "1.12.1",
|
"@swc/core-linux-x64-gnu": "1.12.5",
|
||||||
"@swc/html-darwin-arm64": "1.12.1",
|
"@swc/html-darwin-arm64": "1.12.5",
|
||||||
"@swc/html-linux-arm64-gnu": "1.12.1",
|
"@swc/html-linux-arm64-gnu": "1.12.5",
|
||||||
"@swc/html-linux-x64-gnu": "1.12.1",
|
"@swc/html-linux-x64-gnu": "1.12.5",
|
||||||
"lightningcss-darwin-arm64": "1.30.1",
|
"lightningcss-darwin-arm64": "1.30.1",
|
||||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
"lightningcss-linux-arm64-gnu": "1.30.1",
|
||||||
"lightningcss-linux-x64-gnu": "1.30.1"
|
"lightningcss-linux-x64-gnu": "1.30.1"
|
||||||
@ -5592,9 +5593,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-darwin-arm64": {
|
"node_modules/@swc/core-darwin-arm64": {
|
||||||
"version": "1.12.1",
|
"version": "1.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.12.5.tgz",
|
||||||
"integrity": "sha512-nUjWVcJ3YS2N40ZbKwYO2RJ4+o2tWYRzNOcIQp05FqW0+aoUCVMdAUUzQinPDynfgwVshDAXCKemY8X7nN5MaA==",
|
"integrity": "sha512-3WF+naP/qkt5flrTfJr+p07b522JcixKvIivM7FgvllA6LjJxf+pheoILrTS8IwrNAK/XtHfKWYcGY+3eaA4mA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -5640,9 +5641,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||||
"version": "1.12.1",
|
"version": "1.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.12.5.tgz",
|
||||||
"integrity": "sha512-BxJDIJPq1+aCh9UsaSAN6wo3tuln8UhNXruOrzTI8/ElIig/3sAueDM6Eq7GvZSGGSA7ljhNATMJ0elD7lFatQ==",
|
"integrity": "sha512-GkzgIUz+2r6J6Tn3hb7/4ByaWHRrRZt4vuN9BLAd+y65m2Bt0vlEpPtWhrB/TVe4hEkFR+W5PDETLEbUT4i0tQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -5672,9 +5673,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/core-linux-x64-gnu": {
|
"node_modules/@swc/core-linux-x64-gnu": {
|
||||||
"version": "1.12.1",
|
"version": "1.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.12.5.tgz",
|
||||||
"integrity": "sha512-CrYnV8SZIgArQ9LKH0xEF95PKXzX9WkRSc5j55arOSBeDCeDUQk1Bg/iKdnDiuj5HC1hZpvzwMzSBJjv+Z70jA==",
|
"integrity": "sha512-PeYoSziNy+iNiBHPtAsO84bzBne/mbCsG5ijYkAhS1GVsDgohClorUvRXXhcUZoX2gr8TfSI9WLHo30K+DKiHg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -5830,9 +5831,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/html-darwin-arm64": {
|
"node_modules/@swc/html-darwin-arm64": {
|
||||||
"version": "1.12.1",
|
"version": "1.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/html-darwin-arm64/-/html-darwin-arm64-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/html-darwin-arm64/-/html-darwin-arm64-1.12.5.tgz",
|
||||||
"integrity": "sha512-vbCqYgBBdoxlsnUe/G6irBJ69LUOrlLVXgdxWxDSZ3YcbnpVmwi5YEeaRvqf4vNzZ/nzBMd4DYl6KK2Qsi0prw==",
|
"integrity": "sha512-PE9cCiQUxxC15VrN9D+nDXS9R1z6Fxg04wRoEVg2imRa+I5uUSWjJwzfT3vHmNbvXdR0poybL9ro1Zr74EWliw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -5878,9 +5879,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/html-linux-arm64-gnu": {
|
"node_modules/@swc/html-linux-arm64-gnu": {
|
||||||
"version": "1.12.1",
|
"version": "1.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/html-linux-arm64-gnu/-/html-linux-arm64-gnu-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/html-linux-arm64-gnu/-/html-linux-arm64-gnu-1.12.5.tgz",
|
||||||
"integrity": "sha512-KbqPLtsPVt0/kjp7sUT1APfEtNQUqMam3S0RzJkvuMz9jB2F9DREvj5EG+DPnx2s/kxnDm4sh9vM2sG2xNHErQ==",
|
"integrity": "sha512-qNukrD+Nm4OjH6S+aiHHj7q5Bufhx/uPR3G3do6+xJLX97LsAONG0yQfgw1mRVoOtbVZ7JF2+y7VliFi+jcjow==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -5910,9 +5911,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@swc/html-linux-x64-gnu": {
|
"node_modules/@swc/html-linux-x64-gnu": {
|
||||||
"version": "1.12.1",
|
"version": "1.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/html-linux-x64-gnu/-/html-linux-x64-gnu-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/html-linux-x64-gnu/-/html-linux-x64-gnu-1.12.5.tgz",
|
||||||
"integrity": "sha512-9QNCTgCZtyQVifLXqDTW7v4lgaC11v0/iL9OhsSZ19ycJrBmnxBmZtDIbuQrXAIzE1GD8mMOK/GLey2IeceoDQ==",
|
"integrity": "sha512-RmGL1PFzFQ4IVABNEQQMnKH4kucbnYlh7Lro9GcfGyEHD+QowZF4JBCkhIYK6eL7eahGr8gVjyB7rApuj9+GbA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
@ -78,12 +78,12 @@
|
|||||||
"@rspack/binding-darwin-arm64": "1.3.15",
|
"@rspack/binding-darwin-arm64": "1.3.15",
|
||||||
"@rspack/binding-linux-arm64-gnu": "1.3.15",
|
"@rspack/binding-linux-arm64-gnu": "1.3.15",
|
||||||
"@rspack/binding-linux-x64-gnu": "1.3.15",
|
"@rspack/binding-linux-x64-gnu": "1.3.15",
|
||||||
"@swc/core-darwin-arm64": "1.12.1",
|
"@swc/core-darwin-arm64": "1.12.5",
|
||||||
"@swc/core-linux-arm64-gnu": "1.12.1",
|
"@swc/core-linux-arm64-gnu": "1.12.5",
|
||||||
"@swc/core-linux-x64-gnu": "1.12.1",
|
"@swc/core-linux-x64-gnu": "1.12.5",
|
||||||
"@swc/html-darwin-arm64": "1.12.1",
|
"@swc/html-darwin-arm64": "1.12.5",
|
||||||
"@swc/html-linux-arm64-gnu": "1.12.1",
|
"@swc/html-linux-arm64-gnu": "1.12.5",
|
||||||
"@swc/html-linux-x64-gnu": "1.12.1",
|
"@swc/html-linux-x64-gnu": "1.12.5",
|
||||||
"lightningcss-darwin-arm64": "1.30.1",
|
"lightningcss-darwin-arm64": "1.30.1",
|
||||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
"lightningcss-linux-arm64-gnu": "1.30.1",
|
||||||
"lightningcss-linux-x64-gnu": "1.30.1"
|
"lightningcss-linux-x64-gnu": "1.30.1"
|
||||||
|
Reference in New Issue
Block a user