stages/authenticator_validate: remember (#2828)

* initial

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* web: cleanup timedelta help

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add tooltip

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add tests

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* assert response code in self.assertStageResponse

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add more tests, add duo

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* add docs

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>

* fix

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2022-05-10 21:05:22 +02:00 committed by GitHub
parent 4d755dc0f6
commit fd1d38f844
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 2006 additions and 1267 deletions

View File

@ -99,11 +99,18 @@ migrate:
run:
go run -v cmd/server/main.go
web-watch:
cd web && npm run watch
#########################
## Web
#########################
web: web-lint-fix web-lint web-extract
web-install:
cd web && npm ci
web-watch:
cd web && npm run watch
web-lint-fix:
cd web && npm run prettier
@ -114,6 +121,21 @@ web-lint:
web-extract:
cd web && npm run extract
#########################
## Website
#########################
website: website-lint-fix
website-install:
cd website && npm ci
website-lint-fix:
cd website && npm run prettier
website-watch:
cd website && npm run watch
# These targets are use by GitHub actions to allow usage of matrix
# which makes the YAML File a lot smaller
@ -139,10 +161,8 @@ ci-pyright: ci--meta-debug
ci-pending-migrations: ci--meta-debug
./manage.py makemigrations --check
install:
install: web-install website-install
poetry install
cd web && npm ci
cd website && npm ci
a: install
tmux \

View File

@ -12,3 +12,7 @@ class FlowNonApplicableException(SentryIgnoredException):
class EmptyFlowException(SentryIgnoredException):
"""Flow has no stages."""
class FlowSkipStageException(SentryIgnoredException):
"""Exception to skip a stage"""

View File

@ -23,6 +23,7 @@ class FlowTestCase(APITestCase):
**kwargs,
) -> dict[str, Any]:
"""Assert various attributes of a stage response"""
self.assertEqual(response.status_code, 200)
raw_response = loads(response.content.decode())
self.assertIsNotNone(raw_response["component"])
self.assertIsNotNone(raw_response["type"])

View File

@ -87,7 +87,6 @@ class TestFlowExecutor(FlowTestCase):
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
flow=flow,
@ -406,7 +405,6 @@ class TestFlowExecutor(FlowTestCase):
# A get request will evaluate the policies and this will return stage 4
# but it won't save it, hence we can't check the plan
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, flow, component="ak-stage-dummy")
# fourth request, this confirms the last stage (dummy4)
@ -479,7 +477,6 @@ class TestFlowExecutor(FlowTestCase):
exec_url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug})
# First request, run the planner
response = self.client.get(exec_url)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
flow,
@ -491,5 +488,4 @@ class TestFlowExecutor(FlowTestCase):
user_fields=[UserFields.E_MAIL],
)
response = self.client.post(exec_url, {"uid_field": "invalid-string"}, follow=True)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, flow, component="ak-stage-access-denied")

View File

@ -50,7 +50,6 @@ class TestPasswordPolicyFlow(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
{"password": "akadmin"},
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,

View File

@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2022-05-10 17:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_authenticator_duo", "0002_default_setup_flow"),
]
operations = [
migrations.AddField(
model_name="duodevice",
name="last_t",
field=models.DateTimeField(auto_now=True),
),
]

View File

@ -74,6 +74,8 @@ class DuoDevice(Device):
stage = models.ForeignKey(AuthenticatorDuoStage, on_delete=models.CASCADE)
duo_user_id = models.TextField()
last_t = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name or str(self.user)

View File

@ -1,5 +1,6 @@
"""Duo stage"""
from django.http import HttpRequest, HttpResponse
from django.utils.timezone import now
from rest_framework.fields import CharField
from structlog.stdlib import get_logger
@ -85,7 +86,11 @@ class AuthenticatorDuoStageView(ChallengeStageView):
self.request.session.pop(SESSION_KEY_DUO_ACTIVATION_CODE)
if not existing_device:
DuoDevice.objects.create(
name="Duo Device", user=self.get_pending_user(), duo_user_id=user_id, stage=stage
name="Duo Device",
user=self.get_pending_user(),
duo_user_id=user_id,
stage=stage,
last_t=now(),
)
else:
return self.executor.stage_invalid("Device with Credential ID already exists.")

View File

@ -0,0 +1,18 @@
# Generated by Django 4.0.4 on 2022-04-14 20:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_authenticator_sms", "0002_alter_authenticatorsmsstage_from_number"),
]
operations = [
migrations.AddField(
model_name="smsdevice",
name="last_t",
field=models.DateTimeField(auto_now=True),
),
]

View File

@ -168,6 +168,14 @@ class SMSDevice(SideChannelDevice):
phone_number = models.TextField()
last_t = models.DateTimeField(auto_now=True)
def verify_token(self, token):
valid = super().verify_token(token)
if valid:
self.save()
return valid
def __str__(self):
return self.name or str(self.user)

View File

@ -30,6 +30,7 @@ class AuthenticatorValidateStageSerializer(StageSerializer):
"not_configured_action",
"device_classes",
"configuration_stages",
"last_auth_threshold",
]

View File

@ -147,4 +147,5 @@ def validate_challenge_duo(device_pk: int, request: HttpRequest, user: User) ->
# {'result': 'allow', 'status': 'allow', 'status_msg': 'Success. Logging you in...'}
if response["result"] == "deny":
raise ValidationError("Duo denied access")
device.save()
return device_pk

View File

@ -0,0 +1,27 @@
# Generated by Django 4.0.4 on 2022-04-14 20:54
from django.db import migrations, models
import authentik.lib.utils.time
class Migration(migrations.Migration):
dependencies = [
(
"authentik_stages_authenticator_validate",
"0010_remove_authenticatorvalidatestage_configuration_stage_and_more",
),
]
operations = [
migrations.AddField(
model_name="authenticatorvalidatestage",
name="last_auth_threshold",
field=models.TextField(
default="seconds=0",
help_text="If any of the user's device has been used within this threshold, this stage will be skipped",
validators=[authentik.lib.utils.time.timedelta_string_validator],
),
),
]

View File

@ -7,6 +7,7 @@ from django.views import View
from rest_framework.serializers import BaseSerializer
from authentik.flows.models import NotConfiguredAction, Stage
from authentik.lib.utils.time import timedelta_string_validator
class DeviceClasses(models.TextChoices):
@ -57,6 +58,17 @@ class AuthenticatorValidateStage(Stage):
default=default_device_classes,
)
last_auth_threshold = models.TextField(
default="seconds=0",
validators=[timedelta_string_validator],
help_text=_(
(
"If any of the user's device has been used within this threshold, this "
"stage will be skipped"
)
),
)
@property
def serializer(self) -> BaseSerializer:
from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer

View File

@ -1,6 +1,10 @@
"""Authenticator Validation"""
from datetime import timezone
from django.http import HttpRequest, HttpResponse
from django.utils.timezone import datetime, now
from django_otp import devices_for_user
from django_otp.models import Device
from rest_framework.fields import CharField, IntegerField, JSONField, ListField, UUIDField
from rest_framework.serializers import ValidationError
from structlog.stdlib import get_logger
@ -10,9 +14,11 @@ from authentik.core.models import User
from authentik.events.models import Event, EventAction
from authentik.events.utils import cleanse_dict, sanitize_dict
from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUserInfoChallenge
from authentik.flows.exceptions import FlowSkipStageException
from authentik.flows.models import FlowDesignation, NotConfiguredAction, Stage
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.lib.utils.time import timedelta_from_string
from authentik.stages.authenticator_sms.models import SMSDevice
from authentik.stages.authenticator_validate.challenge import (
DeviceChallenge,
@ -121,6 +127,15 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
return attrs
def get_device_last_usage(device: Device) -> datetime:
"""Get a datetime object from last_t"""
if not hasattr(device, "last_t"):
return datetime.fromtimestamp(0, tz=timezone.utc)
if isinstance(device.last_t, datetime):
return device.last_t
return datetime.fromtimestamp(device.last_t * device.step, tz=timezone.utc)
class AuthenticatorValidateStageView(ChallengeStageView):
"""Authenticator Validation"""
@ -139,6 +154,9 @@ class AuthenticatorValidateStageView(ChallengeStageView):
stage: AuthenticatorValidateStage = self.executor.current_stage
_now = now()
threshold = timedelta_from_string(stage.last_auth_threshold)
for device in user_devices:
device_class = device.__class__.__name__.lower().replace("device", "")
if device_class not in stage.device_classes:
@ -148,6 +166,16 @@ class AuthenticatorValidateStageView(ChallengeStageView):
# WebAuthn does another device loop to find all webuahtn devices
if device_class in seen_classes:
continue
# check if device has been used within threshold and skip this stage if so
if threshold.total_seconds() > 0:
print("yeet")
print(get_device_last_usage(device))
print(_now - get_device_last_usage(device))
print(threshold)
print(_now - get_device_last_usage(device) <= threshold)
if _now - get_device_last_usage(device) <= threshold:
LOGGER.info("Device has been used within threshold", device=device)
raise FlowSkipStageException()
if device_class not in seen_classes:
seen_classes.append(device_class)
challenge = DeviceChallenge(
@ -181,7 +209,10 @@ class AuthenticatorValidateStageView(ChallengeStageView):
user = self.get_pending_user()
stage: AuthenticatorValidateStage = self.executor.current_stage
if user and not user.is_anonymous:
try:
challenges = self.get_device_challenges()
except FlowSkipStageException:
return self.executor.stage_ok()
else:
if self.executor.flow.designation != FlowDesignation.AUTHENTICATION:
LOGGER.debug("Refusing passwordless flow in non-authentication flow")

View File

@ -0,0 +1,56 @@
"""Test validator stage"""
from unittest.mock import MagicMock, patch
from django.test.client import RequestFactory
from rest_framework.exceptions import ValidationError
from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.tests import FlowTestCase
from authentik.lib.generators import generate_id, generate_key
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_validate.challenge import validate_challenge_duo
class AuthenticatorValidateStageDuoTests(FlowTestCase):
"""Test validator stage"""
def setUp(self) -> None:
self.user = create_test_admin_user()
self.request_factory = RequestFactory()
def test_device_challenge_duo(self):
"""Test duo"""
request = self.request_factory.get("/")
stage = AuthenticatorDuoStage.objects.create(
name="test",
client_id=generate_id(),
client_secret=generate_key(),
api_hostname="",
)
duo_device = DuoDevice.objects.create(
user=self.user,
stage=stage,
)
duo_mock = MagicMock(
auth=MagicMock(
return_value={
"result": "allow",
"status": "allow",
"status_msg": "Success. Logging you in...",
}
)
)
failed_duo_mock = MagicMock(auth=MagicMock(return_value={"result": "deny"}))
with patch(
"authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.client",
duo_mock,
):
self.assertEqual(
duo_device.pk, validate_challenge_duo(duo_device.pk, request, self.user)
)
with patch(
"authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.client",
failed_duo_mock,
):
with self.assertRaises(ValidationError):
validate_challenge_duo(duo_device.pk, request, self.user)

View File

@ -0,0 +1,106 @@
"""Test validator stage"""
from time import sleep
from django.test.client import RequestFactory
from django.urls.base import reverse
from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
from authentik.flows.tests import FlowTestCase
from authentik.stages.authenticator_sms.models import AuthenticatorSMSStage, SMSDevice, SMSProviders
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
from authentik.stages.identification.models import IdentificationStage, UserFields
class AuthenticatorValidateStageSMSTests(FlowTestCase):
"""Test validator stage"""
def setUp(self) -> None:
self.user = create_test_admin_user()
self.request_factory = RequestFactory()
self.stage = AuthenticatorSMSStage.objects.create(
name="sms",
provider=SMSProviders.GENERIC,
from_number="1234",
)
def test_last_auth_threshold(self):
"""Test last_auth_threshold"""
conf_stage = IdentificationStage.objects.create(
name="conf",
user_fields=[
UserFields.USERNAME,
],
)
device: SMSDevice = SMSDevice.objects.create(
user=self.user,
confirmed=True,
stage=self.stage,
)
# Verify token once here to set last_t etc
token = device.generate_token()
device.verify_token(token)
stage = AuthenticatorValidateStage.objects.create(
name="foo",
last_auth_threshold="milliseconds=0",
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.SMS],
)
sleep(1)
stage.configuration_stages.set([conf_stage])
flow = Flow.objects.create(name="test", slug="test", title="test")
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
{"uid_field": self.user.username},
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
follow=True,
)
self.assertStageResponse(
response,
flow,
component="ak-stage-authenticator-validate",
)
def test_last_auth_threshold_valid(self):
"""Test last_auth_threshold"""
conf_stage = IdentificationStage.objects.create(
name="conf",
user_fields=[
UserFields.USERNAME,
],
)
device: SMSDevice = SMSDevice.objects.create(
user=self.user,
confirmed=True,
stage=self.stage,
)
# Verify token once here to set last_t etc
token = device.generate_token()
device.verify_token(token)
stage = AuthenticatorValidateStage.objects.create(
name="foo",
last_auth_threshold="hours=1",
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.SMS],
)
stage.configuration_stages.set([conf_stage])
flow = Flow.objects.create(name="test", slug="test", title="test")
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
{"uid_field": self.user.username},
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
follow=True,
)
self.assertStageResponse(response, component="xak-flow-redirect", to="/")

View File

@ -1,34 +1,21 @@
"""Test validator stage"""
from unittest.mock import MagicMock, patch
from django.contrib.sessions.middleware import SessionMiddleware
from django.test.client import RequestFactory
from django.urls.base import reverse
from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.exceptions import ValidationError
from webauthn.helpers import bytes_to_base64url
from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
from authentik.flows.stage import StageView
from authentik.flows.tests import FlowTestCase
from authentik.flows.views.executor import FlowExecutorView
from authentik.lib.generators import generate_id, generate_key
from authentik.lib.tests.utils import dummy_get_response, get_request
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.lib.tests.utils import dummy_get_response
from authentik.stages.authenticator_validate.api import AuthenticatorValidateStageSerializer
from authentik.stages.authenticator_validate.challenge import (
get_challenge_for_device,
validate_challenge_code,
validate_challenge_duo,
validate_challenge_webauthn,
)
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage
from authentik.stages.authenticator_validate.stage import (
SESSION_DEVICE_CHALLENGES,
AuthenticatorValidationChallengeResponse,
)
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.identification.models import IdentificationStage, UserFields
@ -65,7 +52,6 @@ class AuthenticatorValidateStageTests(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
flow,
@ -90,83 +76,6 @@ class AuthenticatorValidateStageTests(FlowTestCase):
)
self.assertTrue(serializer.is_valid())
def test_device_challenge_totp(self):
"""Test device challenge"""
request = self.request_factory.get("/")
totp_device = TOTPDevice.objects.create(user=self.user, confirmed=True, digits=6)
self.assertEqual(get_challenge_for_device(request, totp_device), {})
with self.assertRaises(ValidationError):
validate_challenge_code("1234", request, self.user)
def test_device_challenge_webauthn(self):
"""Test webauthn"""
request = get_request("/")
request.user = self.user
webauthn_device = WebAuthnDevice.objects.create(
user=self.user,
public_key=bytes_to_base64url(b"qwerqwerqre"),
credential_id=bytes_to_base64url(b"foobarbaz"),
sign_count=0,
rp_id="foo",
)
challenge = get_challenge_for_device(request, webauthn_device)
del challenge["challenge"]
self.assertEqual(
challenge,
{
"allowCredentials": [
{
"id": "Zm9vYmFyYmF6",
"type": "public-key",
}
],
"rpId": "testserver",
"timeout": 60000,
"userVerification": "preferred",
},
)
with self.assertRaises(ValidationError):
validate_challenge_webauthn({}, request, self.user)
def test_device_challenge_duo(self):
"""Test duo"""
request = self.request_factory.get("/")
stage = AuthenticatorDuoStage.objects.create(
name="test",
client_id=generate_id(),
client_secret=generate_key(),
api_hostname="",
)
duo_device = DuoDevice.objects.create(
user=self.user,
stage=stage,
)
duo_mock = MagicMock(
auth=MagicMock(
return_value={
"result": "allow",
"status": "allow",
"status_msg": "Success. Logging you in...",
}
)
)
failed_duo_mock = MagicMock(auth=MagicMock(return_value={"result": "deny"}))
with patch(
"authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.client",
duo_mock,
):
self.assertEqual(
duo_device.pk, validate_challenge_duo(duo_device.pk, request, self.user)
)
with patch(
"authentik.stages.authenticator_duo.models.AuthenticatorDuoStage.client",
failed_duo_mock,
):
with self.assertRaises(ValidationError):
validate_challenge_duo(duo_device.pk, request, self.user)
def test_validate_selected_challenge(self):
"""Test validate_selected_challenge"""
# Prepare request with session

View File

@ -0,0 +1,114 @@
"""Test validator stage"""
from time import sleep
from django.test.client import RequestFactory
from django.urls.base import reverse
from django_otp.oath import TOTP
from django_otp.plugins.otp_totp.models import TOTPDevice
from rest_framework.exceptions import ValidationError
from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
from authentik.flows.tests import FlowTestCase
from authentik.stages.authenticator_validate.challenge import (
get_challenge_for_device,
validate_challenge_code,
)
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
from authentik.stages.identification.models import IdentificationStage, UserFields
class AuthenticatorValidateStageTOTPTests(FlowTestCase):
"""Test validator stage"""
def setUp(self) -> None:
self.user = create_test_admin_user()
self.request_factory = RequestFactory()
def test_last_auth_threshold(self):
"""Test last_auth_threshold"""
conf_stage = IdentificationStage.objects.create(
name="conf",
user_fields=[
UserFields.USERNAME,
],
)
device: TOTPDevice = TOTPDevice.objects.create(
user=self.user,
confirmed=True,
)
# Verify token once here to set last_t etc
totp = TOTP(device.bin_key)
sleep(1)
self.assertTrue(device.verify_token(totp.token()))
stage = AuthenticatorValidateStage.objects.create(
name="foo",
last_auth_threshold="milliseconds=0",
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.TOTP],
)
stage.configuration_stages.set([conf_stage])
flow = Flow.objects.create(name="test", slug="test", title="test")
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
{"uid_field": self.user.username},
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
follow=True,
)
self.assertStageResponse(
response,
flow,
component="ak-stage-authenticator-validate",
)
def test_last_auth_threshold_valid(self):
"""Test last_auth_threshold"""
conf_stage = IdentificationStage.objects.create(
name="conf",
user_fields=[
UserFields.USERNAME,
],
)
device: TOTPDevice = TOTPDevice.objects.create(
user=self.user,
confirmed=True,
)
# Verify token once here to set last_t etc
totp = TOTP(device.bin_key)
sleep(1)
self.assertTrue(device.verify_token(totp.token()))
stage = AuthenticatorValidateStage.objects.create(
name="foo",
last_auth_threshold="hours=1",
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.TOTP],
)
stage.configuration_stages.set([conf_stage])
flow = Flow.objects.create(name="test", slug="test", title="test")
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
{"uid_field": self.user.username},
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
follow=True,
)
self.assertStageResponse(response, component="xak-flow-redirect", to="/")
def test_device_challenge_totp(self):
"""Test device challenge"""
request = self.request_factory.get("/")
totp_device = TOTPDevice.objects.create(user=self.user, confirmed=True, digits=6)
self.assertEqual(get_challenge_for_device(request, totp_device), {})
with self.assertRaises(ValidationError):
validate_challenge_code("1234", request, self.user)

View File

@ -0,0 +1,134 @@
"""Test validator stage"""
from time import sleep
from django.test.client import RequestFactory
from django.urls.base import reverse
from rest_framework.exceptions import ValidationError
from webauthn.helpers import bytes_to_base64url
from authentik.core.tests.utils import create_test_admin_user
from authentik.flows.models import Flow, FlowStageBinding, NotConfiguredAction
from authentik.flows.tests import FlowTestCase
from authentik.lib.tests.utils import get_request
from authentik.stages.authenticator_validate.challenge import (
get_challenge_for_device,
validate_challenge_webauthn,
)
from authentik.stages.authenticator_validate.models import AuthenticatorValidateStage, DeviceClasses
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.identification.models import IdentificationStage, UserFields
class AuthenticatorValidateStageWebAuthnTests(FlowTestCase):
"""Test validator stage"""
def setUp(self) -> None:
self.user = create_test_admin_user()
self.request_factory = RequestFactory()
def test_last_auth_threshold(self):
"""Test last_auth_threshold"""
conf_stage = IdentificationStage.objects.create(
name="conf",
user_fields=[
UserFields.USERNAME,
],
)
device: WebAuthnDevice = WebAuthnDevice.objects.create(
user=self.user,
confirmed=True,
)
device.set_sign_count(device.sign_count + 1)
stage = AuthenticatorValidateStage.objects.create(
name="foo",
last_auth_threshold="milliseconds=0",
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.WEBAUTHN],
)
sleep(1)
stage.configuration_stages.set([conf_stage])
flow = Flow.objects.create(name="test", slug="test", title="test")
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
{"uid_field": self.user.username},
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
follow=True,
)
self.assertStageResponse(
response,
flow,
component="ak-stage-authenticator-validate",
)
def test_last_auth_threshold_valid(self):
"""Test last_auth_threshold"""
conf_stage = IdentificationStage.objects.create(
name="conf",
user_fields=[
UserFields.USERNAME,
],
)
device: WebAuthnDevice = WebAuthnDevice.objects.create(
user=self.user,
confirmed=True,
)
device.set_sign_count(device.sign_count + 1)
stage = AuthenticatorValidateStage.objects.create(
name="foo",
last_auth_threshold="hours=1",
not_configured_action=NotConfiguredAction.CONFIGURE,
device_classes=[DeviceClasses.WEBAUTHN],
)
stage.configuration_stages.set([conf_stage])
flow = Flow.objects.create(name="test", slug="test", title="test")
FlowStageBinding.objects.create(target=flow, stage=conf_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
{"uid_field": self.user.username},
)
self.assertEqual(response.status_code, 302)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
follow=True,
)
self.assertStageResponse(response, component="xak-flow-redirect", to="/")
def test_device_challenge_webauthn(self):
"""Test webauthn"""
request = get_request("/")
request.user = self.user
webauthn_device = WebAuthnDevice.objects.create(
user=self.user,
public_key=bytes_to_base64url(b"qwerqwerqre"),
credential_id=bytes_to_base64url(b"foobarbaz"),
sign_count=0,
rp_id="foo",
)
challenge = get_challenge_for_device(request, webauthn_device)
del challenge["challenge"]
self.assertEqual(
challenge,
{
"allowCredentials": [
{
"id": "Zm9vYmFyYmF6",
"type": "public-key",
}
],
"rpId": "testserver",
"timeout": 60000,
"userVerification": "preferred",
},
)
with self.assertRaises(ValidationError):
validate_challenge_webauthn({}, request, self.user)

View File

@ -0,0 +1,21 @@
# Generated by Django 4.0.4 on 2022-04-14 20:58
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
(
"authentik_stages_authenticator_webauthn",
"0006_authenticatewebauthnstage_authenticator_attachment_and_more",
),
]
operations = [
migrations.RenameField(
model_name="webauthndevice",
old_name="last_used_on",
new_name="last_t",
),
]

View File

@ -125,7 +125,7 @@ class WebAuthnDevice(Device):
rp_id = models.CharField(max_length=253)
created_on = models.DateTimeField(auto_now_add=True)
last_used_on = models.DateTimeField(default=now)
last_t = models.DateTimeField(default=now)
@property
def descriptor(self) -> PublicKeyCredentialDescriptor:
@ -133,9 +133,9 @@ class WebAuthnDevice(Device):
return PublicKeyCredentialDescriptor(id=base64url_to_bytes(self.credential_id))
def set_sign_count(self, sign_count: int) -> None:
"""Set the sign_count and update the last_used_on datetime."""
"""Set the sign_count and update the last_t datetime."""
self.sign_count = sign_count
self.last_used_on = now()
self.last_t = now()
self.save()
def __str__(self):

View File

@ -36,7 +36,6 @@ class TestUserDenyStage(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, self.flow, component="ak-stage-access-denied")
def test_valid_post(self):
@ -50,5 +49,4 @@ class TestUserDenyStage(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, self.flow, component="ak-stage-access-denied")

View File

@ -79,7 +79,6 @@ class TestIdentificationStage(FlowTestCase):
}
url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
response = self.client.post(url, form_data)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,
@ -122,7 +121,6 @@ class TestIdentificationStage(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
form_data,
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,
@ -175,7 +173,6 @@ class TestIdentificationStage(FlowTestCase):
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,
@ -218,7 +215,6 @@ class TestIdentificationStage(FlowTestCase):
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,

View File

@ -55,7 +55,6 @@ class TestUserLoginStage(FlowTestCase):
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
flow=self.flow,

View File

@ -53,7 +53,6 @@ class TestPasswordStage(FlowTestCase):
{"password": self.password},
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,
@ -159,7 +158,6 @@ class TestPasswordStage(FlowTestCase):
{"password": self.password + "test"},
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,

View File

@ -43,7 +43,6 @@ class TestUserDeleteStage(FlowTestCase):
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, self.flow, component="ak-stage-access-denied")
def test_user_delete_get(self):

View File

@ -94,7 +94,6 @@ class TestUserLoginStage(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,

View File

@ -112,7 +112,6 @@ class TestUserWriteStage(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,
@ -139,7 +138,6 @@ class TestUserWriteStage(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,
@ -167,7 +165,6 @@ class TestUserWriteStage(FlowTestCase):
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(
response,
self.flow,

View File

@ -19894,6 +19894,10 @@ components:
description: Stages used to configure Authenticator when user doesn't have
any compatible devices. After this configuration Stage passes, the user
is not prompted again.
last_auth_threshold:
type: string
description: If any of the user's device has been used within this threshold,
this stage will be skipped
required:
- component
- meta_model_name
@ -19927,6 +19931,11 @@ components:
description: Stages used to configure Authenticator when user doesn't have
any compatible devices. After this configuration Stage passes, the user
is not prompted again.
last_auth_threshold:
type: string
minLength: 1
description: If any of the user's device has been used within this threshold,
this stage will be skipped
required:
- name
AuthenticatorValidationChallenge:
@ -26797,6 +26806,11 @@ components:
description: Stages used to configure Authenticator when user doesn't have
any compatible devices. After this configuration Stage passes, the user
is not prompted again.
last_auth_threshold:
type: string
minLength: 1
description: If any of the user's device has been used within this threshold,
this stage will be skipped
PatchedCaptchaStageRequest:
type: object
description: CaptchaStage Serializer

View File

@ -0,0 +1,54 @@
import { CSSResult, LitElement, TemplateResult, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import AKGlobal from "../authentik.css";
import PFTooltip from "@patternfly/patternfly/components/Tooltip/Tooltip.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
@customElement("ak-tooltip")
export class Tooltip extends LitElement {
@state()
open = false;
static get styles(): CSSResult[] {
return [
PFBase,
PFTooltip,
AKGlobal,
css`
.pf-c-tooltip__content {
text-align: inherit;
}
.outer {
position: relative;
}
.pf-c-tooltip {
position: absolute;
}
`,
];
}
render(): TemplateResult {
return html`<slot
@mouseenter=${() => {
this.open = true;
}}
@mouseleave=${() => {
this.open = false;
}}
name="trigger"
></slot>
${this.open
? html`<div class="outer">
<div class="pf-c-tooltip" role="tooltip">
<div class="pf-c-tooltip__arrow"></div>
<div class="pf-c-tooltip__content">
<slot name="tooltip"></slot>
</div>
</div>
</div>`
: html``}`;
}
}

View File

@ -0,0 +1,45 @@
import { t } from "@lingui/macro";
import { CSSResult, LitElement, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import AKGlobal from "../../authentik.css";
import PFForm from "@patternfly/patternfly/components/Form/Form.css";
import PFList from "@patternfly/patternfly/components/List/list.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import "../Tooltip";
@customElement("ak-utils-time-delta-help")
export class TimeDeltaHelp extends LitElement {
@property({ type: Boolean })
negative = false;
static get styles(): CSSResult[] {
return [PFBase, PFForm, PFList, AKGlobal];
}
render(): TemplateResult {
return html` <ak-tooltip>
<p class="pf-c-form__helper-text" slot="trigger">
${this.negative
? t`(Format: hours=-1;minutes=-2;seconds=-3).`
: t`(Format: hours=1;minutes=2;seconds=3).`}
<i class="pf-icon fa fa-question-circle" aria-hidden="true"></i>
</p>
<div slot="tooltip">
${t`The following keywords are supported:`}
<ul class="pf-c-list">
<li><pre>microseconds</pre></li>
<li><pre>milliseconds</pre></li>
<li><pre>seconds</pre></li>
<li><pre>minutes</pre></li>
<li><pre>hours</pre></li>
<li><pre>days</pre></li>
<li><pre>weeks</pre></li>
</ul>
</div>
</ak-tooltip>`;
}
}

View File

@ -33,14 +33,15 @@ msgstr ""
#~ msgid "#/identity/users/{0}"
#~ msgstr "#/Identität/Benutzer/{0}"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
#~ msgid "(Format: days=-1;minutes=-2;seconds=-3)."
#~ msgstr ""
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=-1;minutes=-2;seconds=-3)."
msgstr "(Format: hours=-1;minutes=-2;seconds=-3)."
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=1;minutes=2;seconds=3)."
msgstr "(Format: hours=-1;minutes=-2;seconds=-3)."
@ -458,8 +459,12 @@ msgid "Are you sure you want to update {0} \"{1}\"?"
msgstr "Sind Sie sicher, dass Sie {0} \"{1}\" aktualisieren wollen?"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "SAML Assertion nicht gültig am oder nach der aktuellen Uhrzeit + diesem Wert (Format: Stunden=1;Minuten=2;Sekunden=3)."
#~ msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "SAML Assertion nicht gültig am oder nach der aktuellen Uhrzeit + diesem Wert (Format: Stunden=1;Minuten=2;Sekunden=3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion valid not before"
@ -2496,6 +2501,10 @@ msgstr "Kennung"
#~ msgid "Identity & Cryptography"
#~ msgstr "Identität & Kryptographie"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
msgstr ""
#: src/pages/outposts/ServiceConnectionDockerForm.ts
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
msgid "If enabled, use the local connection. Required Docker socket/Kubernetes Integration."
@ -2769,6 +2778,10 @@ msgstr "Überprüft: {0}"
msgid "Last sync: {0}"
msgstr "Letzte Synchronisierung: {0}"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Last validation threshold"
msgstr ""
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
msgid "Launch"
@ -3490,8 +3503,12 @@ msgid "Objects created"
msgstr "Objekte erstellt"
#: src/pages/stages/consent/ConsentStageForm.ts
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Die Einwilligung erlischt in. (Format: Stunden=1;Minuten=2;Sekunden=3)."
msgid "Offset after which consent expires."
msgstr ""
#: src/pages/stages/consent/ConsentStageForm.ts
#~ msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Die Einwilligung erlischt in. (Format: Stunden=1;Minuten=2;Sekunden=3)."
#: src/elements/events/ObjectChangelog.ts
#: src/elements/events/UserEvents.ts
@ -4522,8 +4539,12 @@ msgid "Session duration"
msgstr "Sessionsdauer"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "Session am oder nach der aktuellen Uhrzeit + diesem Wert nicht gültig (Format: Stunden=1;Minuten=2;Sekunden=3)."
#~ msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Session am oder nach der aktuellen Uhrzeit + diesem Wert nicht gültig (Format: Stunden=1;Minuten=2;Sekunden=3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session valid not on or after"
@ -5331,6 +5352,10 @@ msgstr "Die externe URL, unter der Sie auf die Anwendung zugreifen. Schließen S
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
msgstr "Die externe URL, bei der Sie sich authentifizieren. Unter dieser URL sollte der Authentik Core Server erreichbar sein."
#: src/elements/utils/TimeDeltaHelp.ts
msgid "The following keywords are supported:"
msgstr ""
#~ msgid "The following objects use {0}:"
#~ msgstr "Die folgenden Objekte verwenden {0}:"
@ -5440,8 +5465,12 @@ msgid "Time in minutes the token sent is valid."
msgstr "Zeit in Minuten wie lange der verschickte Token gültig ist"
#: src/pages/sources/saml/SAMLSourceForm.ts
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Zeitversatz, wann temporäre Benutzer gelöscht werden sollen. Dies gilt nur, wenn Ihr IDP das NameID-Format „transient“ verwendet und der Benutzer sich nicht manuell abmeldet. (Format: Stunden=1;Minuten=2;Sekunden=3)."
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually."
msgstr ""
#: src/pages/sources/saml/SAMLSourceForm.ts
#~ msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Zeitversatz, wann temporäre Benutzer gelöscht werden sollen. Dies gilt nur, wenn Ihr IDP das NameID-Format „transient“ verwendet und der Benutzer sich nicht manuell abmeldet. (Format: Stunden=1;Minuten=2;Sekunden=3)."
#~ msgid "Time-based One-Time Passwords"
#~ msgstr "Zeitbasierte Einmalpasswörter"

View File

@ -17,14 +17,15 @@ msgstr ""
#~ msgid "#/identity/users/{0}"
#~ msgstr "#/identity/users/{0}"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
#~ msgid "(Format: days=-1;minutes=-2;seconds=-3)."
#~ msgstr "(Format: days=-1;minutes=-2;seconds=-3)."
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=-1;minutes=-2;seconds=-3)."
msgstr "(Format: hours=-1;minutes=-2;seconds=-3)."
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=1;minutes=2;seconds=3)."
msgstr "(Format: hours=1;minutes=2;seconds=3)."
@ -448,8 +449,12 @@ msgid "Are you sure you want to update {0} \"{1}\"?"
msgstr "Are you sure you want to update {0} \"{1}\"?"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value."
msgstr "Assertion not valid on or after current time + this value."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion valid not before"
@ -2538,6 +2543,10 @@ msgstr "Identifier"
#~ msgid "Identity & Cryptography"
#~ msgstr "Identity & Cryptography"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
msgstr "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
#: src/pages/outposts/ServiceConnectionDockerForm.ts
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
msgid "If enabled, use the local connection. Required Docker socket/Kubernetes Integration."
@ -2820,6 +2829,10 @@ msgstr "Last seen: {0}"
msgid "Last sync: {0}"
msgstr "Last sync: {0}"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Last validation threshold"
msgstr "Last validation threshold"
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
msgid "Launch"
@ -3548,8 +3561,12 @@ msgid "Objects created"
msgstr "Objects created"
#: src/pages/stages/consent/ConsentStageForm.ts
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgid "Offset after which consent expires."
msgstr "Offset after which consent expires."
#: src/pages/stages/consent/ConsentStageForm.ts
#~ msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
#: src/elements/events/ObjectChangelog.ts
#: src/elements/events/UserEvents.ts
@ -4610,8 +4627,12 @@ msgid "Session duration"
msgstr "Session duration"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value."
msgstr "Session not valid on or after current time + this value."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session valid not on or after"
@ -5445,6 +5466,10 @@ msgstr "The external URL you'll access the application at. Include any non-stand
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
msgstr "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
#: src/elements/utils/TimeDeltaHelp.ts
msgid "The following keywords are supported:"
msgstr "The following keywords are supported:"
#:
#~ msgid "The following objects use {0}:"
#~ msgstr "The following objects use {0}:"
@ -5556,8 +5581,12 @@ msgid "Time in minutes the token sent is valid."
msgstr "Time in minutes the token sent is valid."
#: src/pages/sources/saml/SAMLSourceForm.ts
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually."
msgstr "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually."
#: src/pages/sources/saml/SAMLSourceForm.ts
#~ msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#:
#~ msgid "Time-based One-Time Passwords"

View File

@ -20,14 +20,15 @@ msgstr ""
#~ msgid "#/identity/users/{0}"
#~ msgstr "#/identidad/usuarios/ {0}"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
#~ msgid "(Format: days=-1;minutes=-2;seconds=-3)."
#~ msgstr ""
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=-1;minutes=-2;seconds=-3)."
msgstr "(Formato: horas = -1; minutos = -2; segundos = -3)."
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=1;minutes=2;seconds=3)."
msgstr "(Formato: horas = 1; minutos = 2; segundos = 3)."
@ -445,8 +446,12 @@ msgid "Are you sure you want to update {0} \"{1}\"?"
msgstr "¿Seguro que quieres actualizar {0} «{1}»?"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "La afirmación no es válida en o después de la hora actual + este valor (Formato: horas = 1; minutos = 2; segundos = 3)."
#~ msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "La afirmación no es válida en o después de la hora actual + este valor (Formato: horas = 1; minutos = 2; segundos = 3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion valid not before"
@ -2487,6 +2492,10 @@ msgstr "Identificador"
#~ msgid "Identity & Cryptography"
#~ msgstr "Identidad y criptografía"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
msgstr ""
#: src/pages/outposts/ServiceConnectionDockerForm.ts
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
msgid "If enabled, use the local connection. Required Docker socket/Kubernetes Integration."
@ -2762,6 +2771,10 @@ msgstr "Visto por última vez: {0}"
msgid "Last sync: {0}"
msgstr "Última sincronización: {0}"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Last validation threshold"
msgstr ""
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
msgid "Launch"
@ -3483,8 +3496,12 @@ msgid "Objects created"
msgstr "Objetos creados"
#: src/pages/stages/consent/ConsentStageForm.ts
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Compensación después de la cual caduca el consentimiento. (Formato: horas = 1; minutos = 2; segundos = 3)."
msgid "Offset after which consent expires."
msgstr ""
#: src/pages/stages/consent/ConsentStageForm.ts
#~ msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Compensación después de la cual caduca el consentimiento. (Formato: horas = 1; minutos = 2; segundos = 3)."
#: src/elements/events/ObjectChangelog.ts
#: src/elements/events/UserEvents.ts
@ -4515,8 +4532,12 @@ msgid "Session duration"
msgstr "Duración de la sesión"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "La sesión no es válida a partir de la hora actual + este valor (Formato: horas=1; minutos=2; segundos=3)."
#~ msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "La sesión no es válida a partir de la hora actual + este valor (Formato: horas=1; minutos=2; segundos=3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session valid not on or after"
@ -5325,6 +5346,10 @@ msgstr "La URL externa en la que accederás a la aplicación. Incluya cualquier
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
msgstr "La URL externa en la que te autenticarás. Se debe poder acceder al servidor principal de authentik en esta URL."
#: src/elements/utils/TimeDeltaHelp.ts
msgid "The following keywords are supported:"
msgstr ""
#~ msgid "The following objects use {0}:"
#~ msgstr "Los siguientes objetos usan {0}:"
@ -5434,8 +5459,12 @@ msgid "Time in minutes the token sent is valid."
msgstr "El tiempo en minutos que se envía el token es válido."
#: src/pages/sources/saml/SAMLSourceForm.ts
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Desplazamiento temporal en el que se deben eliminar los usuarios temporales. Esto solo se aplica si su IDP utiliza el formato NameID «transitorio» y el usuario no cierra sesión manualmente. (Formato: horas = 1; minutos = 2; segundos = 3)."
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually."
msgstr ""
#: src/pages/sources/saml/SAMLSourceForm.ts
#~ msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Desplazamiento temporal en el que se deben eliminar los usuarios temporales. Esto solo se aplica si su IDP utiliza el formato NameID «transitorio» y el usuario no cierra sesión manualmente. (Formato: horas = 1; minutos = 2; segundos = 3)."
#~ msgid "Time-based One-Time Passwords"
#~ msgstr "Contraseñas únicas basadas en tiempo"

View File

@ -23,14 +23,15 @@ msgstr ""
#~ msgid "#/identity/users/{0}"
#~ msgstr ""
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
#~ msgid "(Format: days=-1;minutes=-2;seconds=-3)."
#~ msgstr ""
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=-1;minutes=-2;seconds=-3)."
msgstr "(Format : heures=-1;minutes=-2;seconds=-3)"
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=1;minutes=2;seconds=3)."
msgstr ""
@ -449,8 +450,12 @@ msgid "Are you sure you want to update {0} \"{1}\"?"
msgstr "Êtes-vous sûr de vouloir modifier {0} {1} ?"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "Assertion non valide après écoulement de ce délai (format : hours=1;minutes=2;seconds=3)"
#~ msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Assertion non valide après écoulement de ce délai (format : hours=1;minutes=2;seconds=3)"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion valid not before"
@ -2516,6 +2521,10 @@ msgstr "Identifiant"
#~ msgid "Identity & Cryptography"
#~ msgstr "Identité et chiffrement"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
msgstr ""
#: src/pages/outposts/ServiceConnectionDockerForm.ts
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
msgid "If enabled, use the local connection. Required Docker socket/Kubernetes Integration."
@ -2793,6 +2802,10 @@ msgstr "Vu le : {0}"
msgid "Last sync: {0}"
msgstr "Dernière synchro : {0}"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Last validation threshold"
msgstr ""
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
msgid "Launch"
@ -3517,8 +3530,12 @@ msgid "Objects created"
msgstr ""
#: src/pages/stages/consent/ConsentStageForm.ts
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Durée d'expiration du consentement (Format : hours=1;minutes=2;seconds=3)."
msgid "Offset after which consent expires."
msgstr ""
#: src/pages/stages/consent/ConsentStageForm.ts
#~ msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Durée d'expiration du consentement (Format : hours=1;minutes=2;seconds=3)."
#: src/elements/events/ObjectChangelog.ts
#: src/elements/events/UserEvents.ts
@ -4564,8 +4581,12 @@ msgid "Session duration"
msgstr "Durée de la session"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "La session n'est plus valide à partir de l'heure actuelle + cette valeur (Format: hours=1;minutes=2;seconds=3)."
#~ msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "La session n'est plus valide à partir de l'heure actuelle + cette valeur (Format: hours=1;minutes=2;seconds=3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session valid not on or after"
@ -5390,6 +5411,10 @@ msgstr "L'URL externe par laquelle vous accéderez à l'application. Incluez un
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
msgstr ""
#: src/elements/utils/TimeDeltaHelp.ts
msgid "The following keywords are supported:"
msgstr ""
#~ msgid "The following objects use {0}:"
#~ msgstr "Les objets suivants utilisent {0} :"
@ -5490,8 +5515,12 @@ msgid "Time in minutes the token sent is valid."
msgstr "Temps en minutes durant lequel le jeton envoyé est valide."
#: src/pages/sources/saml/SAMLSourceForm.ts
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Délai de suppression des utilisateurs temporaires. Ceci s'applique uniquement si votre fournisseur d'identité utilise le format NameID transitoire ('transient') et que l'utilisateur ne se déconnecte pas manuellement. (Format : heures=1;minutes=2;secondes=3)."
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually."
msgstr ""
#: src/pages/sources/saml/SAMLSourceForm.ts
#~ msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Délai de suppression des utilisateurs temporaires. Ceci s'applique uniquement si votre fournisseur d'identité utilise le format NameID transitoire ('transient') et que l'utilisateur ne se déconnecte pas manuellement. (Format : heures=1;minutes=2;secondes=3)."
#:
#~ msgid "Time-based One-Time Passwords"

View File

@ -20,14 +20,15 @@ msgstr ""
#~ msgid "#/identity/users/{0}"
#~ msgstr "#/identity/users/{0}"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
#~ msgid "(Format: days=-1;minutes=-2;seconds=-3)."
#~ msgstr ""
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=-1;minutes=-2;seconds=-3)."
msgstr "(Format: hours=-1;minutes=-2;seconds=-3)."
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=1;minutes=2;seconds=3)."
msgstr "(Format: hours=1;minutes=2;seconds=3)."
@ -445,8 +446,12 @@ msgid "Are you sure you want to update {0} \"{1}\"?"
msgstr "Czy na pewno chcesz zaktualizować {0} \"{1}”?"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "Asercja nieważna w bieżącym lub późniejszym czasie + ta wartość (Format: hours=1;minutes=2;seconds=3)."
#~ msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Asercja nieważna w bieżącym lub późniejszym czasie + ta wartość (Format: hours=1;minutes=2;seconds=3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion valid not before"
@ -2484,6 +2489,10 @@ msgstr "Identyfikator"
#~ msgid "Identity & Cryptography"
#~ msgstr "Tożsamość i Kryptografia"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
msgstr ""
#: src/pages/outposts/ServiceConnectionDockerForm.ts
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
msgid "If enabled, use the local connection. Required Docker socket/Kubernetes Integration."
@ -2759,6 +2768,10 @@ msgstr "Ostatnio widziany: {0}"
msgid "Last sync: {0}"
msgstr "Ostatnia synchronizacja: {0}"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Last validation threshold"
msgstr ""
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
msgid "Launch"
@ -3480,8 +3493,12 @@ msgid "Objects created"
msgstr "Utworzone obiekty"
#: src/pages/stages/consent/ConsentStageForm.ts
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Przesunięcie, po którym zgoda wygasa. (Format: hours=1;minutes=2;seconds=3)."
msgid "Offset after which consent expires."
msgstr ""
#: src/pages/stages/consent/ConsentStageForm.ts
#~ msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Przesunięcie, po którym zgoda wygasa. (Format: hours=1;minutes=2;seconds=3)."
#: src/elements/events/ObjectChangelog.ts
#: src/elements/events/UserEvents.ts
@ -4512,8 +4529,12 @@ msgid "Session duration"
msgstr "Długość sesji"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "Sesja nieważna w bieżącym czasie lub później + ta wartość (Format: hours=1;minutes=2;seconds=3)."
#~ msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Sesja nieważna w bieżącym czasie lub później + ta wartość (Format: hours=1;minutes=2;seconds=3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session valid not on or after"
@ -5322,6 +5343,10 @@ msgstr "Zewnętrzny adres URL, pod którym uzyskasz dostęp do aplikacji. Uwzgl
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
msgstr "Zewnętrzny adres URL, pod którym będziesz się uwierzytelniać. Jądro serwera authentik powinien być dostępny pod tym adresem URL."
#: src/elements/utils/TimeDeltaHelp.ts
msgid "The following keywords are supported:"
msgstr ""
#~ msgid "The following objects use {0}:"
#~ msgstr "Następujące obiekty używają {0}:"
@ -5431,8 +5456,12 @@ msgid "Time in minutes the token sent is valid."
msgstr "Czas w minutach, w którym wysłany token jest ważny."
#: src/pages/sources/saml/SAMLSourceForm.ts
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Przesunięcie czasowe, kiedy użytkownicy tymczasowi powinni zostać usunięci. Ma to zastosowanie tylko wtedy, gdy IDP używa „przejściowego” formatu NameID, a użytkownik nie wylogowuje się ręcznie. (Format: hours=1;minutes=2;seconds=3)."
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually."
msgstr ""
#: src/pages/sources/saml/SAMLSourceForm.ts
#~ msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Przesunięcie czasowe, kiedy użytkownicy tymczasowi powinni zostać usunięci. Ma to zastosowanie tylko wtedy, gdy IDP używa „przejściowego” formatu NameID, a użytkownik nie wylogowuje się ręcznie. (Format: hours=1;minutes=2;seconds=3)."
#~ msgid "Time-based One-Time Passwords"
#~ msgstr "Hasła jednorazowe oparte na czasie"

View File

@ -17,14 +17,15 @@ msgstr ""
#~ msgid "#/identity/users/{0}"
#~ msgstr ""
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
#~ msgid "(Format: days=-1;minutes=-2;seconds=-3)."
#~ msgstr ""
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=-1;minutes=-2;seconds=-3)."
msgstr ""
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=1;minutes=2;seconds=3)."
msgstr ""
@ -440,7 +441,11 @@ msgid "Are you sure you want to update {0} \"{1}\"?"
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
@ -2524,6 +2529,10 @@ msgstr ""
#~ msgid "Identity & Cryptography"
#~ msgstr ""
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
msgstr ""
#: src/pages/outposts/ServiceConnectionDockerForm.ts
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
msgid "If enabled, use the local connection. Required Docker socket/Kubernetes Integration."
@ -2802,6 +2811,10 @@ msgstr ""
msgid "Last sync: {0}"
msgstr ""
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Last validation threshold"
msgstr ""
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
msgid "Launch"
@ -3530,9 +3543,13 @@ msgid "Objects created"
msgstr ""
#: src/pages/stages/consent/ConsentStageForm.ts
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgid "Offset after which consent expires."
msgstr ""
#: src/pages/stages/consent/ConsentStageForm.ts
#~ msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr ""
#: src/elements/events/ObjectChangelog.ts
#: src/elements/events/UserEvents.ts
#: src/pages/events/EventListPage.ts
@ -4590,7 +4607,11 @@ msgid "Session duration"
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
@ -5425,6 +5446,10 @@ msgstr ""
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
msgstr ""
#: src/elements/utils/TimeDeltaHelp.ts
msgid "The following keywords are supported:"
msgstr ""
#:
#~ msgid "The following objects use {0}:"
#~ msgstr ""
@ -5526,9 +5551,13 @@ msgid "Time in minutes the token sent is valid."
msgstr ""
#: src/pages/sources/saml/SAMLSourceForm.ts
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually."
msgstr ""
#: src/pages/sources/saml/SAMLSourceForm.ts
#~ msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr ""
#:
#~ msgid "Time-based One-Time Passwords"
#~ msgstr ""

View File

@ -20,14 +20,15 @@ msgstr ""
#~ msgid "#/identity/users/{0}"
#~ msgstr "#/kimlik/kullanıcılar/ {0}"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
#~ msgid "(Format: days=-1;minutes=-2;seconds=-3)."
#~ msgstr ""
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=-1;minutes=-2;seconds=-3)."
msgstr "(Biçim: saat=-1; dakika=-2; ikincil=-3)."
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=1;minutes=2;seconds=3)."
msgstr "(Biçim: saat=1; dakika=2; saniye= 3)."
@ -445,8 +446,12 @@ msgid "Are you sure you want to update {0} \"{1}\"?"
msgstr "{0} “{1}” güncellemesini istediğinizden emin misiniz?"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "Onay işlemi geçerli saat+bu değerden sonra geçerli değil (Biçim: hours=1; Dakika=2; ikinci=3)."
#~ msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Onay işlemi geçerli saat+bu değerden sonra geçerli değil (Biçim: hours=1; Dakika=2; ikinci=3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion valid not before"
@ -2487,6 +2492,10 @@ msgstr "Tanımlayıcı"
#~ msgid "Identity & Cryptography"
#~ msgstr "Kimlik ve Kriptografi"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
msgstr ""
#: src/pages/outposts/ServiceConnectionDockerForm.ts
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
msgid "If enabled, use the local connection. Required Docker socket/Kubernetes Integration."
@ -2763,6 +2772,10 @@ msgstr "Son görüldü: {0}"
msgid "Last sync: {0}"
msgstr "Son senkronizasyon: {0}"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Last validation threshold"
msgstr ""
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
msgid "Launch"
@ -3485,8 +3498,12 @@ msgid "Objects created"
msgstr "Oluşturulan nesneler"
#: src/pages/stages/consent/ConsentStageForm.ts
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Onay sona erdikten sonra ofset. (Biçim: saat=1; dakika=2; saniye/= 3)."
msgid "Offset after which consent expires."
msgstr ""
#: src/pages/stages/consent/ConsentStageForm.ts
#~ msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Onay sona erdikten sonra ofset. (Biçim: saat=1; dakika=2; saniye/= 3)."
#: src/elements/events/ObjectChangelog.ts
#: src/elements/events/UserEvents.ts
@ -4517,8 +4534,12 @@ msgid "Session duration"
msgstr "Oturum süresi"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "Oturum geçerli saat+bu değerden sonra geçerli değil (Biçim: hours=1; Dakika=2; ikinci=3)."
#~ msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Oturum geçerli saat+bu değerden sonra geçerli değil (Biçim: hours=1; Dakika=2; ikinci=3)."
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session valid not on or after"
@ -5327,6 +5348,10 @@ msgstr "Uygulamaya erişeceğiniz harici URL. Standart olmayan herhangi bir bağ
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
msgstr "Kimlik doğrulayacağınız harici URL. Auentik çekirdek sunucusuna bu URL altında erişilebilir olmalıdır."
#: src/elements/utils/TimeDeltaHelp.ts
msgid "The following keywords are supported:"
msgstr ""
#~ msgid "The following objects use {0}:"
#~ msgstr "Aşağıdaki nesneler {0} kullanır:"
@ -5436,8 +5461,12 @@ msgid "Time in minutes the token sent is valid."
msgstr "Gönderilen belirtecin dakika cinsinden geçerlilik süresi."
#: src/pages/sources/saml/SAMLSourceForm.ts
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr "Geçici kullanıcıların silinmesi gerektiğinde zaman uzaklığı. Bu yalnızca IDP'niz NameID Biçimi 'geçici' kullanıyorsa ve kullanıcı el ile oturumu kapatmazsa geçerlidir. (Biçim: saat=1; dakika=2; saniye/= 3)."
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually."
msgstr ""
#: src/pages/sources/saml/SAMLSourceForm.ts
#~ msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "Geçici kullanıcıların silinmesi gerektiğinde zaman uzaklığı. Bu yalnızca IDP'niz NameID Biçimi 'geçici' kullanıyorsa ve kullanıcı el ile oturumu kapatmazsa geçerlidir. (Biçim: saat=1; dakika=2; saniye/= 3)."
#~ msgid "Time-based One-Time Passwords"
#~ msgstr "Zaman Tabanlı Tek seferlik Parolalar"

File diff suppressed because it is too large Load Diff

View File

@ -22,14 +22,15 @@ msgstr ""
#~ msgid "#/identity/users/{0}"
#~ msgstr "#/identity/users/{0}"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
#~ msgid "(Format: days=-1;minutes=-2;seconds=-3)."
#~ msgstr ""
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=-1;minutes=-2;seconds=-3)."
msgstr "(格式: hours=-1;minutes=-2;seconds=-3)."
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=1;minutes=2;seconds=3)."
msgstr "(格式: hours=1;minutes=2;seconds=3)."
@ -446,8 +447,12 @@ msgid "Are you sure you want to update {0} \"{1}\"?"
msgstr "你确定要更新 {0} \"{1}\" 吗?"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "断言在当前时间+此值时或之后无效格式hours=1minutes=2seconds=3。"
#~ msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "断言在当前时间+此值时或之后无效格式hours=1minutes=2seconds=3。"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion valid not before"
@ -2476,6 +2481,10 @@ msgstr "标识符"
#~ msgid "Identity & Cryptography"
#~ msgstr "身份与加密"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
msgstr ""
#: src/pages/outposts/ServiceConnectionDockerForm.ts
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
msgid "If enabled, use the local connection. Required Docker socket/Kubernetes Integration."
@ -2749,6 +2758,10 @@ msgstr "最后显示:{0}"
msgid "Last sync: {0}"
msgstr "上次同步:{0}"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Last validation threshold"
msgstr ""
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
msgid "Launch"
@ -3466,8 +3479,12 @@ msgid "Objects created"
msgstr "已创建对象"
#: src/pages/stages/consent/ConsentStageForm.ts
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgstr "偏移量在此之后同意过期。格式hours=1;minutes=2;seconds=3。"
msgid "Offset after which consent expires."
msgstr ""
#: src/pages/stages/consent/ConsentStageForm.ts
#~ msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "偏移量在此之后同意过期。格式hours=1;minutes=2;seconds=3。"
#: src/elements/events/ObjectChangelog.ts
#: src/elements/events/UserEvents.ts
@ -4485,8 +4502,12 @@ msgid "Session duration"
msgstr "会话持续时间"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "在当前时间+此值时或之后会话无效格式hours=1;minutes=2;seconds=3。"
#~ msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "在当前时间+此值时或之后会话无效格式hours=1;minutes=2;seconds=3。"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session valid not on or after"
@ -5293,6 +5314,10 @@ msgstr "您将通过其访问应用程序的外部 URL。包括任何非标准
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
msgstr "您将在其中进行身份验证的外部 URL。在此 URL 下应该可以访问身份验证核心服务器。"
#: src/elements/utils/TimeDeltaHelp.ts
msgid "The following keywords are supported:"
msgstr ""
#~ msgid "The following objects use {0}:"
#~ msgstr "以下对象使用 {0}"
@ -5402,8 +5427,12 @@ msgid "Time in minutes the token sent is valid."
msgstr "发送的令牌的有效时间(以分钟为单位)。"
#: src/pages/sources/saml/SAMLSourceForm.ts
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr "删除临时用户的时间偏移。这仅适用于您的 IDP 使用 NameID 格式为 “瞬态” 且用户未手动注销的情况。格式hours=1;minutes=2;seconds=3。"
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually."
msgstr ""
#: src/pages/sources/saml/SAMLSourceForm.ts
#~ msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "删除临时用户的时间偏移。这仅适用于您的 IDP 使用 NameID 格式为 “瞬态” 且用户未手动注销的情况。格式hours=1;minutes=2;seconds=3。"
#~ msgid "Time-based One-Time Passwords"
#~ msgstr "基于时间的一次性密码"

View File

@ -22,14 +22,15 @@ msgstr ""
#~ msgid "#/identity/users/{0}"
#~ msgstr "#/identity/users/{0}"
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
#: src/pages/providers/proxy/ProxyProviderForm.ts
#: src/pages/providers/saml/SAMLProviderForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
#~ msgid "(Format: days=-1;minutes=-2;seconds=-3)."
#~ msgstr ""
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=-1;minutes=-2;seconds=-3)."
msgstr "(格式: hours=-1;minutes=-2;seconds=-3)."
#: src/pages/stages/user_login/UserLoginStageForm.ts
#: src/elements/utils/TimeDeltaHelp.ts
msgid "(Format: hours=1;minutes=2;seconds=3)."
msgstr "(格式: hours=1;minutes=2;seconds=3)."
@ -446,8 +447,12 @@ msgid "Are you sure you want to update {0} \"{1}\"?"
msgstr "你确定要更新 {0} \"{1}\" 吗?"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "断言在当前时间+此值时或之后无效格式hours=1minutes=2seconds=3。"
#~ msgid "Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "断言在当前时间+此值时或之后无效格式hours=1minutes=2seconds=3。"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Assertion valid not before"
@ -2476,6 +2481,10 @@ msgstr "标识符"
#~ msgid "Identity & Cryptography"
#~ msgstr "身份与加密"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "If any of the devices user of the types selected above have been used within this duration, this stage will be skipped."
msgstr ""
#: src/pages/outposts/ServiceConnectionDockerForm.ts
#: src/pages/outposts/ServiceConnectionKubernetesForm.ts
msgid "If enabled, use the local connection. Required Docker socket/Kubernetes Integration."
@ -2749,6 +2758,10 @@ msgstr "最后显示:{0}"
msgid "Last sync: {0}"
msgstr "上次同步:{0}"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Last validation threshold"
msgstr ""
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/applications/ApplicationViewPage.ts
msgid "Launch"
@ -3466,8 +3479,12 @@ msgid "Objects created"
msgstr "已创建对象"
#: src/pages/stages/consent/ConsentStageForm.ts
msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
msgstr "偏移量在此之后同意过期。格式hours=1;minutes=2;seconds=3。"
msgid "Offset after which consent expires."
msgstr ""
#: src/pages/stages/consent/ConsentStageForm.ts
#~ msgid "Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "偏移量在此之后同意过期。格式hours=1;minutes=2;seconds=3。"
#: src/elements/events/ObjectChangelog.ts
#: src/elements/events/UserEvents.ts
@ -4485,8 +4502,12 @@ msgid "Session duration"
msgstr "会话持续时间"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
msgstr "在当前时间+此值时或之后会话无效格式hours=1;minutes=2;seconds=3。"
#~ msgid "Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "在当前时间+此值时或之后会话无效格式hours=1;minutes=2;seconds=3。"
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session not valid on or after current time + this value."
msgstr ""
#: src/pages/providers/saml/SAMLProviderForm.ts
msgid "Session valid not on or after"
@ -5293,6 +5314,10 @@ msgstr "您将通过其访问应用程序的外部 URL。包括任何非标准
msgid "The external URL you'll authenticate at. The authentik core server should be reachable under this URL."
msgstr "您将在其中进行身份验证的外部 URL。在此 URL 下应该可以访问身份验证核心服务器。"
#: src/elements/utils/TimeDeltaHelp.ts
msgid "The following keywords are supported:"
msgstr ""
#~ msgid "The following objects use {0}:"
#~ msgstr "以下对象使用 {0}"
@ -5402,8 +5427,12 @@ msgid "Time in minutes the token sent is valid."
msgstr "发送的令牌的有效时间(以分钟为单位)。"
#: src/pages/sources/saml/SAMLSourceForm.ts
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
msgstr "删除临时用户的时间偏移。这仅适用于您的 IDP 使用 NameID 格式为 “瞬态” 且用户未手动注销的情况。格式hours=1;minutes=2;seconds=3。"
msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually."
msgstr ""
#: src/pages/sources/saml/SAMLSourceForm.ts
#~ msgid "Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3)."
#~ msgstr "删除临时用户的时间偏移。这仅适用于您的 IDP 使用 NameID 格式为 “瞬态” 且用户未手动注销的情况。格式hours=1;minutes=2;seconds=3。"
#~ msgid "Time-based One-Time Passwords"
#~ msgstr "基于时间的一次性密码"

View File

@ -21,6 +21,7 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/FormGroup";
import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm";
import "../../../elements/utils/TimeDeltaHelp";
import { first, randomString } from "../../../utils";
@customElement("ak-provider-oauth2-form")
@ -230,9 +231,7 @@ ${this.instance?.redirectUris}</textarea
<p class="pf-c-form__helper-text">
${t`If you are using an Implicit, client-side flow (where the token-endpoint isn't used), you probably want to increase this time.`}
</p>
<p class="pf-c-form__helper-text">
${t`(Format: hours=-1;minutes=-2;seconds=-3).`}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Token validity`}
@ -248,9 +247,7 @@ ${this.instance?.redirectUris}</textarea
<p class="pf-c-form__helper-text">
${t`Configure how long refresh tokens and their id_tokens are valid for.`}
</p>
<p class="pf-c-form__helper-text">
${t`(Format: hours=-1;minutes=-2;seconds=-3).`}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Scopes`} name="propertyMappings">
<select class="pf-c-form-control" multiple>

View File

@ -25,6 +25,7 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/FormGroup";
import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm";
import "../../../elements/utils/TimeDeltaHelp";
import { first } from "../../../utils";
@customElement("ak-provider-proxy-form")
@ -329,9 +330,7 @@ export class ProxyProviderFormPage extends ModelForm<ProxyProvider, number> {
class="pf-c-form-control"
/>
<p class="pf-c-form__helper-text">${t`Configure how long tokens are valid for.`}</p>
<p class="pf-c-form__helper-text">
${t`(Format: hours=-1;minutes=-2;seconds=-3).`}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-group>

View File

@ -21,6 +21,7 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/FormGroup";
import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm";
import "../../../elements/utils/TimeDeltaHelp";
@customElement("ak-provider-saml-form")
export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
@ -299,9 +300,7 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
<p class="pf-c-form__helper-text">
${t`Configure the maximum allowed time drift for an assertion.`}
</p>
<p class="pf-c-form__helper-text">
${t`(Format: hours=-1;minutes=-2;seconds=-3).`}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Assertion valid not on or after`}
@ -315,8 +314,9 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
required
/>
<p class="pf-c-form__helper-text">
${t`Assertion not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).`}
${t`Assertion not valid on or after current time + this value.`}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Session valid not on or after`}
@ -330,8 +330,9 @@ export class SAMLProviderFormPage extends ModelForm<SAMLProvider, number> {
required
/>
<p class="pf-c-form__helper-text">
${t`Session not valid on or after current time + this value (Format: hours=1;minutes=2;seconds=3).`}
${t`Session not valid on or after current time + this value.`}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-element-horizontal

View File

@ -21,6 +21,7 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/FormGroup";
import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm";
import "../../../elements/utils/TimeDeltaHelp";
import { first } from "../../../utils";
@customElement("ak-source-saml-form")
@ -243,8 +244,9 @@ export class SAMLSourceForm extends ModelForm<SAMLSource, string> {
required
/>
<p class="pf-c-form__helper-text">
${t`Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually. (Format: hours=1;minutes=2;seconds=3).`}
${t`Time offset when temporary users should be deleted. This only applies if your IDP uses the NameID Format 'transient', and the user doesn't log out manually.`}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Digest algorithm`}

View File

@ -16,6 +16,7 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/FormGroup";
import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm";
import "../../../elements/utils/TimeDeltaHelp";
@customElement("ak-stage-authenticator-validate-form")
export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValidateStage, string> {
@ -123,6 +124,22 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
${t`Hold control/command to select multiple items.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Last validation threshold`}
?required=${true}
name="lastAuthThreshold"
>
<input
type="text"
value="${this.instance?.lastAuthThreshold || "seconds=0"}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`If any of the devices user of the types selected above have been used within this duration, this stage will be skipped.`}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Not configured action`}
?required=${true}

View File

@ -10,6 +10,7 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/FormGroup";
import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm";
import "../../../elements/utils/TimeDeltaHelp";
@customElement("ak-stage-consent-form")
export class ConsentStageForm extends ModelForm<ConsentStage, string> {
@ -113,8 +114,9 @@ export class ConsentStageForm extends ModelForm<ConsentStage, string> {
required
/>
<p class="pf-c-form__helper-text">
${t`Offset after which consent expires. (Format: hours=1;minutes=2;seconds=3).`}
${t`Offset after which consent expires.`}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
</div>
</ak-form-group>

View File

@ -9,6 +9,7 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/FormGroup";
import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm";
import "../../../elements/utils/TimeDeltaHelp";
import { first } from "../../../utils";
@customElement("ak-stage-user-login-form")
@ -68,9 +69,7 @@ export class UserLoginStageForm extends ModelForm<UserLoginStage, string> {
<p class="pf-c-form__helper-text">
${t`Determines how long a session lasts. Default of 0 seconds means that the sessions lasts until the browser is closed.`}
</p>
<p class="pf-c-form__helper-text">
${t`(Format: hours=1;minutes=2;seconds=3).`}
</p>
<ak-utils-time-delta-help></ak-utils-time-delta-help>
</ak-form-element-horizontal>
</div>
</ak-form-group>

View File

@ -18,6 +18,9 @@ Using the `Not configured action`, you can choose what happens when a user does
- Deny: Access is denied, the flow execution ends
- Configure: This option requires a _Configuration stage_ to be set. The validation stage will be marked as successful, and the configuration stage will be injected into the flow.
By default, authenticator validation is required every time the flow containing this stage is executed. To only change this behavior, set _Last validation threshold_ to a non-zero value. (Requires authentik 2022.5)
Keep in mind that when using Code-based devices (TOTP, Static and SMS), values lower than `seconds=30` cannot be used, as with the way TOTP devices are saved, there is no exact timestamp.
## Passwordless authentication
:::info