Compare commits

...

4 Commits

Author SHA1 Message Date
Marcelo Elizeche Landó
7325bfde36 add monkeytype_config.py 2025-07-23 13:10:03 -03:00
Marcelo Elizeche Landó
6383d0bfc0 Add types using typemonkey 2025-07-18 02:28:56 -03:00
authentik-automation[bot]
4265e7b0af core, web: update translations (#15639)
Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
2025-07-18 05:32:15 +02:00
Jens L.
41df11d5dc stages/authenticator_sms: allow custom message for twilio provider, pass request (#15629)
* stages/authenticator_sms: allow custom message for twilio provider, pass request

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove old version

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Update web/src/admin/stages/authenticator_sms/AuthenticatorSMSStageForm.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Jens L. <jens@beryju.org>

* Update web/src/admin/stages/authenticator_sms/AuthenticatorSMSStageForm.ts

Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Signed-off-by: Jens L. <jens@beryju.org>

* redo headers

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* Apply suggestions from code review

Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>

* format

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
Signed-off-by: Jens L. <jens@beryju.org>
Co-authored-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com>
Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
2025-07-18 01:21:23 +02:00
31 changed files with 226 additions and 176 deletions

View File

@@ -77,7 +77,7 @@ class GroupSerializer(ModelSerializer):
return None
return GroupMemberSerializer(instance.users, many=True).data
def validate_parent(self, parent: Group | None):
def validate_parent(self, parent: Group | None) -> None:
"""Validate group parent (if set), ensuring the parent isn't itself"""
if not self.instance or not parent:
return parent
@@ -85,7 +85,7 @@ class GroupSerializer(ModelSerializer):
raise ValidationError(_("Cannot set group as parent of itself."))
return parent
def validate_is_superuser(self, superuser: bool):
def validate_is_superuser(self, superuser: bool) -> bool:
"""Ensure that the user creating this group has permissions to set the superuser flag"""
request: Request = self.context.get("request", None)
if not request:
@@ -210,7 +210,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
OpenApiParameter("include_users", bool, default=True),
]
)
def list(self, request, *args, **kwargs):
def list(self, request: Request, *args, **kwargs) -> Response:
return super().list(request, *args, **kwargs)
@extend_schema(
@@ -218,7 +218,7 @@ class GroupViewSet(UsedByMixin, ModelViewSet):
OpenApiParameter("include_users", bool, default=True),
]
)
def retrieve(self, request, *args, **kwargs):
def retrieve(self, request: Request, *args, **kwargs) -> Response:
return super().retrieve(request, *args, **kwargs)
@permission_required("authentik_core.add_user_to_group")

View File

@@ -5,6 +5,7 @@ from django.db.models.query import Q
from django.utils.translation import gettext_lazy as _
from django_filters.filters import BooleanFilter
from django_filters.filterset import FilterSet
from model_utils.managers import InheritanceQuerySet
from rest_framework import mixins
from rest_framework.fields import ReadOnlyField, SerializerMethodField
from rest_framework.viewsets import GenericViewSet
@@ -99,5 +100,5 @@ class ProviderViewSet(
"application__name",
]
def get_queryset(self): # pragma: no cover
def get_queryset(self) -> InheritanceQuerySet: # pragma: no cover
return Provider.objects.select_subclasses()

View File

@@ -3,6 +3,7 @@
from collections.abc import Iterable
from drf_spectacular.utils import OpenApiResponse, extend_schema
from model_utils.managers import InheritanceQuerySet
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
@@ -88,7 +89,7 @@ class SourceViewSet(
search_fields = ["slug", "name"]
filterset_fields = ["slug", "name", "managed", "pbm_uuid"]
def get_queryset(self): # pragma: no cover
def get_queryset(self) -> InheritanceQuerySet: # pragma: no cover
return Source.objects.select_subclasses()
@permission_required("authentik_core.change_source")

View File

@@ -2,6 +2,7 @@
from typing import Any
from django.db.models.query import QuerySet
from django.utils.timezone import now
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
from guardian.shortcuts import assign_perm, get_anonymous_user
@@ -41,7 +42,7 @@ class TokenSerializer(ManagedSerializer, ModelSerializer):
if SERIALIZER_CONTEXT_BLUEPRINT in self.context:
self.fields["key"] = CharField(required=False)
def validate_user(self, user: User):
def validate_user(self, user: User) -> User:
"""Ensure user of token cannot be changed"""
if self.instance and self.instance.user_id:
if user.pk != self.instance.user_id:
@@ -138,13 +139,13 @@ class TokenViewSet(UsedByMixin, ModelViewSet):
owner_field = "user"
rbac_allow_create_without_perm = True
def get_queryset(self):
def get_queryset(self) -> QuerySet:
user = self.request.user if self.request else get_anonymous_user()
if user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(user=user.pk)
def perform_create(self, serializer: TokenSerializer):
def perform_create(self, serializer: TokenSerializer) -> Token:
if not self.request.user.is_superuser:
instance = serializer.save(
user=self.request.user,

View File

@@ -21,6 +21,7 @@ from django_filters.filters import (
UUIDFilter,
)
from django_filters.filterset import FilterSet
from djangoql.schema import BoolField, StrField
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import (
OpenApiParameter,
@@ -72,8 +73,10 @@ from authentik.core.models import (
Token,
TokenIntents,
User,
UserQuerySet,
UserTypes,
)
from authentik.enterprise.search.fields import ChoiceSearchField, JSONSearchField
from authentik.events.models import Event, EventAction
from authentik.flows.exceptions import FlowNonApplicableException
from authentik.flows.models import FlowToken
@@ -349,7 +352,7 @@ class UsersFilter(FilterSet):
queryset=Group.objects.all().order_by("name"),
)
def filter_is_superuser(self, queryset, name, value):
def filter_is_superuser(self, queryset: UserQuerySet, name: str, value: bool) -> UserQuerySet:
if value:
return queryset.filter(ak_groups__is_superuser=True).distinct()
return queryset.exclude(ak_groups__is_superuser=True).distinct()
@@ -395,7 +398,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
filterset_class = UsersFilter
search_fields = ["username", "name", "is_active", "email", "uuid", "attributes"]
def get_ql_fields(self):
def get_ql_fields(self) -> list[StrField | BoolField | ChoiceSearchField | JSONSearchField]:
from djangoql.schema import BoolField, StrField
from authentik.enterprise.search.fields import ChoiceSearchField, JSONSearchField
@@ -410,7 +413,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
JSONSearchField(User, "attributes", suggest_nested=False),
]
def get_queryset(self):
def get_queryset(self) -> UserQuerySet:
base_qs = User.objects.all().exclude_anonymous()
if self.serializer_class(context={"request": self.request})._should_include_groups:
base_qs = base_qs.prefetch_related("ak_groups")
@@ -421,10 +424,10 @@ class UserViewSet(UsedByMixin, ModelViewSet):
OpenApiParameter("include_groups", bool, default=True),
]
)
def list(self, request, *args, **kwargs):
def list(self, request: Request, *args, **kwargs) -> Response:
return super().list(request, *args, **kwargs)
def _create_recovery_link(self, for_email=False) -> tuple[str, Token]:
def _create_recovery_link(self, for_email: bool = False) -> tuple[str, Token]:
"""Create a recovery link (when the current brand has a recovery flow set),
that can either be shown to an admin or sent to the user directly"""
brand: Brand = self.request._request.brand

View File

@@ -42,7 +42,7 @@ class JSONExtension(OpenApiSerializerFieldExtension):
target_class = "authentik.core.api.utils.JSONDictField"
def map_serializer_field(self, auto_schema, direction):
def map_serializer_field(self, auto_schema, direction: str) -> dict[str, str]:
return build_basic_type(OpenApiTypes.OBJECT)
@@ -52,7 +52,7 @@ class ModelSerializer(BaseModelSerializer):
serializer_field_mapping = BaseModelSerializer.serializer_field_mapping.copy()
serializer_field_mapping[models.JSONField] = JSONDictField
def create(self, validated_data):
def create(self, validated_data: dict[str, Any]):
instance = super().create(validated_data)
request = self.context.get("request")
@@ -61,7 +61,7 @@ class ModelSerializer(BaseModelSerializer):
return instance
def update(self, instance: Model, validated_data):
def update(self, instance: Model, validated_data: dict[str, Any]):
raise_errors_on_nested_writes("update", self, validated_data)
info = model_meta.get_field_info(instance)

View File

@@ -7,6 +7,7 @@ from uuid import uuid4
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ImproperlyConfigured
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpRequest, HttpResponse
from django.utils.deprecation import MiddlewareMixin
from django.utils.functional import SimpleLazyObject
@@ -14,6 +15,8 @@ from django.utils.translation import override
from sentry_sdk.api import set_tag
from structlog.contextvars import STRUCTLOG_KEY_PREFIX
from authentik.core.models import User
SESSION_KEY_IMPERSONATE_USER = "authentik/impersonate/user"
SESSION_KEY_IMPERSONATE_ORIGINAL_USER = "authentik/impersonate/original_user"
RESPONSE_HEADER_ID = "X-authentik-id"
@@ -25,7 +28,7 @@ CTX_HOST = ContextVar[str | None](STRUCTLOG_KEY_PREFIX + "host", default=None)
CTX_AUTH_VIA = ContextVar[str | None](STRUCTLOG_KEY_PREFIX + KEY_AUTH_VIA, default=None)
def get_user(request):
def get_user(request: WSGIRequest) -> AnonymousUser | User:
if not hasattr(request, "_cached_user"):
user = None
if (authenticated_session := request.session.get("authenticatedsession", None)) is not None:
@@ -46,7 +49,7 @@ async def aget_user(request):
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
def process_request(self, request: WSGIRequest):
if not hasattr(request, "session"):
raise ImproperlyConfigured(
"The Django authentication middleware requires session "

View File

@@ -11,6 +11,7 @@ from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.contrib.sessions.base_session import AbstractBaseSession
from django.core.handlers.wsgi import WSGIRequest
from django.db import models
from django.db.models import Q, QuerySet, options
from django.db.models.constants import LOOKUP_SEP
@@ -22,6 +23,7 @@ from django_cte import CTE, with_cte
from guardian.conf import settings
from guardian.mixins import GuardianUserMixin
from model_utils.managers import InheritanceManager
from rest_framework.request import Request
from rest_framework.serializers import Serializer
from structlog.stdlib import get_logger
@@ -137,7 +139,7 @@ class AttributesMixin(models.Model):
class GroupQuerySet(QuerySet):
def with_children_recursive(self):
def with_children_recursive(self) -> "GroupQuerySet":
"""Recursively get all groups that have the current queryset as parents
or are indirectly related."""
@@ -210,7 +212,7 @@ class Group(SerializerModel, AttributesMixin):
("disable_group_superuser", _("Disable superuser status")),
]
def __str__(self):
def __str__(self) -> str:
return f"Group {self.name}"
@property
@@ -241,7 +243,7 @@ class Group(SerializerModel, AttributesMixin):
class UserQuerySet(models.QuerySet):
"""User queryset"""
def exclude_anonymous(self):
def exclude_anonymous(self) -> "UserQuerySet":
"""Exclude anonymous user"""
return self.exclude(**{User.USERNAME_FIELD: settings.ANONYMOUS_USER_NAME})
@@ -249,7 +251,7 @@ class UserQuerySet(models.QuerySet):
class UserManager(DjangoUserManager):
"""User manager that doesn't assign is_superuser and is_staff"""
def get_queryset(self):
def get_queryset(self) -> UserQuerySet:
"""Create special user queryset"""
return UserQuerySet(self.model, using=self._db)
@@ -295,7 +297,7 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
models.Index(fields=["type"]),
]
def __str__(self):
def __str__(self) -> str:
return self.username
@staticmethod
@@ -360,7 +362,13 @@ class User(SerializerModel, GuardianUserMixin, AttributesMixin, AbstractUser):
"""superuser == staff user"""
return self.is_superuser # type: ignore
def set_password(self, raw_password, signal=True, sender=None, request=None):
def set_password(
self,
raw_password: str,
signal: bool = True,
sender: None = None,
request: WSGIRequest | Request | None = None,
) -> None:
if self.pk and signal:
from authentik.core.signals import password_changed
@@ -479,7 +487,7 @@ class Provider(SerializerModel):
"""Get serializer for this model"""
raise NotImplementedError
def __str__(self):
def __str__(self) -> str:
return str(self.name)
@@ -611,7 +619,7 @@ class Application(SerializerModel, PolicyBindingModel):
)
return getattr(providers.first(), provider_type._meta.model_name)
def __str__(self):
def __str__(self) -> str:
return str(self.name)
class Meta:
@@ -631,7 +639,7 @@ class ApplicationEntitlement(AttributesMixin, SerializerModel, PolicyBindingMode
verbose_name_plural = _("Application Entitlements")
unique_together = (("app", "name"),)
def __str__(self):
def __str__(self) -> str:
return f"Application Entitlement {self.name} for app {self.app_id}"
@property
@@ -640,7 +648,7 @@ class ApplicationEntitlement(AttributesMixin, SerializerModel, PolicyBindingMode
return ApplicationEntitlementSerializer
def supported_policy_binding_targets(self):
def supported_policy_binding_targets(self) -> list[str]:
return ["group", "user"]
@@ -812,7 +820,7 @@ class Source(ManagedModel, SerializerModel, PolicyBindingModel):
return {}
raise NotImplementedError
def __str__(self):
def __str__(self) -> str:
return str(self.name)
class Meta:
@@ -895,7 +903,7 @@ class ExpiringModel(models.Model):
models.Index(fields=["expiring", "expires"]),
]
def expire_action(self, *args, **kwargs):
def expire_action(self, *args, **kwargs) -> tuple[int, dict[str, int]]:
"""Handler which is called when this object is expired. By
default the object is deleted. This is less efficient compared
to bulk deleting objects, but classes like Token() need to change
@@ -958,7 +966,7 @@ class Token(SerializerModel, ManagedModel, ExpiringModel):
("set_token_key", _("Set a token's key")),
]
def __str__(self):
def __str__(self) -> str:
description = f"{self.identifier}"
if self.expiring:
description += f" (expires={self.expires})"
@@ -1023,7 +1031,7 @@ class PropertyMapping(SerializerModel, ManagedModel):
except Exception as exc:
raise PropertyMappingExpressionException(exc, self) from exc
def __str__(self):
def __str__(self) -> str:
return f"Property Mapping {self.name}"
class Meta:
@@ -1051,7 +1059,7 @@ class Session(ExpiringModel, AbstractBaseSession):
]
default_permissions = []
def __str__(self):
def __str__(self) -> str:
return self.session_key
class Keys(StrEnum):

View File

@@ -1,6 +1,7 @@
"""authentik sessions engine"""
import pickle # nosec
from typing import Any
from django.contrib.auth import BACKEND_SESSION_KEY, HASH_SESSION_KEY, SESSION_KEY
from django.contrib.sessions.backends.db import SessionStore as SessionBase
@@ -9,13 +10,19 @@ from django.utils import timezone
from django.utils.functional import cached_property
from structlog.stdlib import get_logger
from authentik.core.models import Session
from authentik.root.middleware import ClientIPMiddleware
LOGGER = get_logger()
class SessionStore(SessionBase):
def __init__(self, session_key=None, last_ip=None, last_user_agent=""):
def __init__(
self,
session_key: str | None = None,
last_ip: str | None = None,
last_user_agent: str = "",
):
super().__init__(session_key)
self._create_kwargs = {
"last_ip": last_ip or ClientIPMiddleware.default_ip,
@@ -23,16 +30,16 @@ class SessionStore(SessionBase):
}
@classmethod
def get_model_class(cls):
def get_model_class(cls) -> type[Session]:
from authentik.core.models import Session
return Session
@cached_property
def model_fields(self):
def model_fields(self) -> list[str]:
return [k.value for k in self.model.Keys]
def _get_session_from_db(self):
def _get_session_from_db(self) -> Session:
try:
return (
self.model.objects.select_related(
@@ -74,10 +81,10 @@ class SessionStore(SessionBase):
LOGGER.warning(str(exc))
self._session_key = None
def encode(self, session_dict):
def encode(self, session_dict: dict[str, Any]) -> bytes:
return pickle.dumps(session_dict, protocol=pickle.HIGHEST_PROTOCOL)
def decode(self, session_data):
def decode(self, session_data: bytes) -> dict[str, Any]:
try:
return pickle.loads(session_data) # nosec
except pickle.PickleError:
@@ -86,7 +93,7 @@ class SessionStore(SessionBase):
pass
return {}
def load(self):
def load(self) -> dict[str, Any]:
s = self._get_session_from_db()
if s:
return {
@@ -108,7 +115,7 @@ class SessionStore(SessionBase):
else:
return {}
def create_model_instance(self, data):
def create_model_instance(self, data: dict[str, Any]) -> Session:
args = {
"session_key": self._get_or_create_session_key(),
"expires": self.get_expiry_date(),

View File

@@ -3,6 +3,7 @@
from django.http.response import (
HttpResponseBadRequest,
HttpResponseForbidden,
HttpResponseNotAllowed,
HttpResponseNotFound,
HttpResponseServerError,
)
@@ -61,6 +62,6 @@ class ServerErrorView(TemplateView):
response_class = ServerErrorTemplateResponse
template_name = "if/error.html"
def dispatch(self, *args, **kwargs): # pragma: no cover
def dispatch(self, *args, **kwargs) -> HttpResponseNotAllowed: # pragma: no cover
"""Little wrapper so django accepts this function"""
return super().dispatch(*args, **kwargs)

View File

@@ -4,7 +4,7 @@ from hashlib import sha256
from django.contrib.auth import get_user_model
from django.db import models
from django.http import HttpResponseBadRequest
from django.http import HttpRequest, HttpResponseBadRequest
from django.utils.translation import gettext_lazy as _
from django.views import View
from requests.exceptions import RequestException
@@ -68,32 +68,44 @@ class AuthenticatorSMSStage(ConfigurableStage, FriendlyNamedStage, Stage):
help_text=_("Optionally modify the payload being sent to custom providers."),
)
def send(self, token: str, device: "SMSDevice"):
def send(self, request: HttpRequest, token: str, device: "SMSDevice"):
"""Send message via selected provider"""
if self.provider == SMSProviders.TWILIO:
return self.send_twilio(token, device)
return self.send_twilio(request, token, device)
if self.provider == SMSProviders.GENERIC:
return self.send_generic(token, device)
return self.send_generic(request, token, device)
raise ValueError(f"invalid provider {self.provider}")
def get_message(self, token: str) -> str:
"""Get SMS message"""
return _("Use this code to authenticate in authentik: {token}".format_map({"token": token}))
def send_twilio(self, token: str, device: "SMSDevice"):
def send_twilio(self, request: HttpRequest, token: str, device: "SMSDevice"):
"""send sms via twilio provider"""
client = Client(self.account_sid, self.auth)
message_body = str(self.get_message(token))
if self.mapping:
payload = sanitize_item(
self.mapping.evaluate(
user=device.user,
request=request,
device=device,
token=token,
stage=self,
)
)
message_body = payload.get("message", message_body)
try:
message = client.messages.create(
to=device.phone_number, from_=self.from_number, body=str(self.get_message(token))
to=device.phone_number, from_=self.from_number, body=message_body
)
LOGGER.debug("Sent SMS", to=device, message=message.sid)
except TwilioRestException as exc:
LOGGER.warning("Error sending token by Twilio SMS", exc=exc, msg=exc.msg)
raise ValidationError(exc.msg) from None
def send_generic(self, token: str, device: "SMSDevice"):
def send_generic(self, request: HttpRequest, token: str, device: "SMSDevice"):
"""Send SMS via outside API"""
payload = {
"From": self.from_number,
@@ -106,7 +118,7 @@ class AuthenticatorSMSStage(ConfigurableStage, FriendlyNamedStage, Stage):
payload = sanitize_item(
self.mapping.evaluate(
user=device.user,
request=None,
request=request,
device=device,
token=token,
stage=self,

View File

@@ -71,7 +71,7 @@ class AuthenticatorSMSStageView(ChallengeStageView):
raise ValidationError(_("Invalid phone number"))
# No code yet, but we have a phone number, so send a verification message
device: SMSDevice = self.request.session[SESSION_KEY_SMS_DEVICE]
stage.send(device.token, device)
stage.send(self.request, device.token, device)
def _has_phone_number(self) -> str | None:
context = self.executor.plan.context

View File

@@ -124,7 +124,7 @@ def select_challenge(request: HttpRequest, device: Device):
def select_challenge_sms(request: HttpRequest, device: SMSDevice):
"""Send SMS"""
device.generate_token()
device.stage.send(device.token, device)
device.stage.send(request, device.token, device)
def select_challenge_email(request: HttpRequest, device: EmailDevice):

20
monkeytype_config.py Normal file
View File

@@ -0,0 +1,20 @@
# Standard Library
import os
from collections.abc import Iterator
from contextlib import contextmanager
# 3rd-party
from monkeytype.config import DefaultConfig
class MonkeyConfig(DefaultConfig):
@contextmanager
def cli_context(self, command: str) -> Iterator[None]:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "authentik.root.settings")
import django
django.setup()
yield
CONFIG = MonkeyConfig()

View File

@@ -161,38 +161,6 @@ export class AuthenticatorSMSStageForm extends BaseStageForm<AuthenticatorSMSSta
${msg("This is the password to be used with basic auth")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Mapping")} name="mapping">
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<NotificationWebhookMapping[]> => {
const args: PropertymappingsNotificationListRequest = {
ordering: "saml_name",
};
if (query !== undefined) {
args.search = query;
}
const items = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsNotificationList(args);
return items.results;
}}
.renderElement=${(item: NotificationWebhookMapping): string => {
return item.name;
}}
.value=${(item: NotificationWebhookMapping | undefined): string | undefined => {
return item?.pk;
}}
.selected=${(item: NotificationWebhookMapping): boolean => {
return this.instance?.mapping === item.pk;
}}
blankable
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${msg("Modify the payload sent to the custom provider.")}
</p>
</ak-form-element-horizontal>
`;
}
@@ -269,6 +237,38 @@ export class AuthenticatorSMSStageForm extends BaseStageForm<AuthenticatorSMSSta
${this.provider === ProviderEnum.Generic
? this.renderProviderGeneric()
: this.renderProviderTwillio()}
<ak-form-element-horizontal label=${msg("Mapping")} name="mapping">
<ak-search-select
.fetchObjects=${async (
query?: string,
): Promise<NotificationWebhookMapping[]> => {
const args: PropertymappingsNotificationListRequest = {
ordering: "name",
};
if (query) {
args.search = query;
}
const items = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsNotificationList(args);
return items.results;
}}
.renderElement=${(item: NotificationWebhookMapping): string => {
return item.name;
}}
.value=${(item?: NotificationWebhookMapping) => {
return item?.pk;
}}
.selected=${(item: NotificationWebhookMapping): boolean => {
return this.instance?.mapping === item.pk;
}}
blankable
>
</ak-search-select>
<p class="pf-c-form__helper-text">
${msg("Modify the payload sent to the provider.")}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal name="verifyOnly">
<label class="pf-c-switch">
<input

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" ?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<?xml version="1.0"?><xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file target-language="de" source-language="en" original="lit-localize-inputs" datatype="plaintext">
<body>
<trans-unit id="s4caed5b7a7e5d89b">
@@ -596,9 +596,9 @@
</trans-unit>
<trans-unit id="saa0e2675da69651b">
<source>The URL &quot;<x id="0" equiv-text="${this.url}"/>&quot; was not found.</source>
<target>Die URL &quot;
<x id="0" equiv-text="${this.url}"/>&quot; wurde nicht gefunden.</target>
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
<target>Die URL "
<x id="0" equiv-text="${this.url}"/>" wurde nicht gefunden.</target>
</trans-unit>
<trans-unit id="s58cd9c2fe836d9c6">
@@ -1709,8 +1709,8 @@
</trans-unit>
<trans-unit id="sa90b7809586c35ce">
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon &quot;fa-test&quot;.</source>
<target>Geben Sie entweder eine vollständige URL oder einen relativen Pfad ein oder geben Sie 'fa://fa-test' ein, um das Font Awesome-Icon &quot;fa-test&quot; zu verwenden</target>
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
<target>Geben Sie entweder eine vollständige URL oder einen relativen Pfad ein oder geben Sie 'fa://fa-test' ein, um das Font Awesome-Icon "fa-test" zu verwenden</target>
</trans-unit>
<trans-unit id="s0410779cb47de312">
@@ -3756,10 +3756,10 @@ Hier können nur Policies verwendet werden, da der Zugriff geprüft wird, bevor
</trans-unit>
<trans-unit id="sa95a538bfbb86111">
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> &quot;<x id="1" equiv-text="${this.obj?.name}"/>&quot;?</source>
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
<target>Bist du sicher, dass du
<x id="0" equiv-text="${this.objectLabel}"/>&quot;
<x id="1" equiv-text="${this.obj?.name}"/>&quot;aktualisieren möchtest?</target>
<x id="0" equiv-text="${this.objectLabel}"/>"
<x id="1" equiv-text="${this.obj?.name}"/>"aktualisieren möchtest?</target>
</trans-unit>
<trans-unit id="sc92d7cfb6ee1fec6">
@@ -4632,11 +4632,6 @@ Beim Erstellen eines festen Auswahlfelds aktiviere „Als Ausdruck interpretiere
<source>Mapping</source>
<target>Zuordnung</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>Passe die an den Custom Provider gesendete Payload an.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -4824,8 +4819,8 @@ Beim Erstellen eines festen Auswahlfelds aktiviere „Als Ausdruck interpretiere
</trans-unit>
<trans-unit id="sdf1d8edef27236f0">
<source>A &quot;roaming&quot; authenticator, like a YubiKey</source>
<target>Ein &quot;Roaming&quot;-Authentifikator, wie ein YubiKey</target>
<source>A "roaming" authenticator, like a YubiKey</source>
<target>Ein "Roaming"-Authentifikator, wie ein YubiKey</target>
</trans-unit>
<trans-unit id="sfffba7b23d8fb40c">
@@ -5183,7 +5178,7 @@ Beim Erstellen eines festen Auswahlfelds aktiviere „Als Ausdruck interpretiere
</trans-unit>
<trans-unit id="s1608b2f94fa0dbd4">
<source>If set to a duration above 0, the user will have the option to choose to &quot;stay signed in&quot;, which will extend their session by the time specified here.</source>
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
<target>Wenn auf eine Dauer größer als 0 gesetzt, hat der Benutzer die Option „Angemeldet bleiben“, wodurch seine Sitzung um die hier angegebene Zeit verlängert wird.</target>
</trans-unit>
@@ -7442,7 +7437,7 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<target>Benutzer erfolgreich erstellt und zu Gruppe <x id="0" equiv-text="${this.group.name}"/> hinzugefügt.</target>
</trans-unit>
<trans-unit id="s824e0943a7104668">
<source>This user will be added to the group &quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&quot;.</source>
<source>This user will be added to the group "<x id="0" equiv-text="${this.targetGroup.name}"/>".</source>
<target>Dieser Benutzer wird der Gruppe &amp;quot;<x id="0" equiv-text="${this.targetGroup.name}"/>&amp;quot; hinzugefügt.</target>
</trans-unit>
<trans-unit id="s62e7f6ed7d9cb3ca">
@@ -8719,7 +8714,7 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<target>Gruppe synchronisieren</target>
</trans-unit>
<trans-unit id="s2d5f69929bb7221d">
<source><x id="0" equiv-text="${p.name}"/> (&quot;<x id="1" equiv-text="${p.fieldKey}"/>&quot;, of type <x id="2" equiv-text="${p.type}"/>)</source>
<source><x id="0" equiv-text="${p.name}"/> ("<x id="1" equiv-text="${p.fieldKey}"/>", of type <x id="2" equiv-text="${p.type}"/>)</source>
<target><x id="0" equiv-text="${p.name}"/> (&amp;quot;<x id="1" equiv-text="${p.fieldKey}"/>&amp;quot;, vom typ <x id="2" equiv-text="${p.type}"/>)</target>
</trans-unit>
<trans-unit id="s25bacc19d98b444e">
@@ -8967,8 +8962,8 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<target>Gültige Redirect-URIs nach einem erfolgreichen Autorisierungsflow. Gib hier auch alle Origins für Implicit-Flows an.</target>
</trans-unit>
<trans-unit id="s4c49d27de60a532b">
<source>To allow any redirect URI, set the mode to Regex and the value to &quot;.*&quot;. Be aware of the possible security implications this can have.</source>
<target>Um jede Redirect-URI zu erlauben, setze den Modus auf Regex und den Wert auf &quot;.*&quot;. Beachte die möglichen Sicherheitsimplikationen.</target>
<source>To allow any redirect URI, set the mode to Regex and the value to ".*". Be aware of the possible security implications this can have.</source>
<target>Um jede Redirect-URI zu erlauben, setze den Modus auf Regex und den Wert auf ".*". Beachte die möglichen Sicherheitsimplikationen.</target>
</trans-unit>
<trans-unit id="sa52bf79fe1ccb13e">
<source>Federated OIDC Sources</source>
@@ -9717,7 +9712,7 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<target>Wie die Authentifizierung während eines Authorization-Code-Token-Anforderungsflows durchgeführt wird</target>
</trans-unit>
<trans-unit id="s844baf19a6c4a9b4">
<source>Enable &quot;Remember me on this device&quot;</source>
<source>Enable "Remember me on this device"</source>
<target>„Angemeldet bleiben auf diesem Gerät“ aktivieren</target>
</trans-unit>
<trans-unit id="sfa72bca733f40692">
@@ -9916,7 +9911,10 @@ Bindings zu Gruppen/Benutzern werden mit dem Benutzer des Ereignisses abgegliche
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
<target><x id="0" equiv-text="${item.name || item.username}"/> - Passwort ändern.</target>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>
</xliff>
</xliff>

View File

@@ -3708,10 +3708,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Mapping</source>
<target>Mapping</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>Modify the payload sent to the custom provider.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
<target>Stage used to configure an SMS-based TOTP authenticator.</target>
@@ -7777,6 +7773,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -4569,11 +4569,6 @@ no se aprueba cuando una o ambas de las opciones seleccionadas son iguales o sup
<source>Mapping</source>
<target>Asignación</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>Modificar la carga útil enviada al proveedor personalizado.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9328,6 +9323,9 @@ Las vinculaciones a grupos o usuarios se comparan con el usuario del evento.</ta
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -4633,11 +4633,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Mapping</source>
<target>Mappage</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>Modifier le contenu envoyé aux fournisseurs personnalisés.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9893,6 +9888,9 @@ Les liaisons avec les groupes/utilisateurs sont vérifiées par rapport à l'uti
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -4634,11 +4634,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Mapping</source>
<target>Mappatura</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>Modifica il payload inviato al provider personalizzato.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9920,6 +9915,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
<target>Aggiorna <x id="0" equiv-text="${item.name || item.username}"/> password</target>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -4579,11 +4579,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Mapping</source>
<target>매핑</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>사용자 지정 공급자로 전송되는 페이로드를 수정합니다.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9234,6 +9229,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -4590,11 +4590,6 @@ slaagt niet wanneer een of beide geselecteerde opties gelijk zijn aan of boven d
<source>Mapping</source>
<target>Toewijzing</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>Wijzig het payload-bericht dat naar de aangepaste provider wordt verzonden.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9138,6 +9133,9 @@ Bindingen naar groepen/gebruikers worden gecontroleerd tegen de gebruiker van de
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -4635,11 +4635,6 @@ Można tu używać tylko zasad, ponieważ dostęp jest sprawdzany przed uwierzyt
<source>Mapping</source>
<target>Mapowanie</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>Zmodyfikuj payload wysłany do niestandardowego dostawcy.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9560,6 +9555,9 @@ Powiązania z grupami/użytkownikami są sprawdzane względem użytkownika zdarz
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -4603,11 +4603,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Mapping</source>
<target>Ḿàƥƥĩńĝ</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>Ḿōďĩƒŷ ţĥē ƥàŷĺōàď śēńţ ţō ţĥē ćũśţōḿ ƥŕōvĩďēŕ.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9569,4 +9564,7 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body></file></xliff>

View File

@@ -4634,11 +4634,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Mapping</source>
<target>Сопоставление</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>Изменить данные, отправляемые пользовательскому провайдеру.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9652,6 +9647,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -4603,11 +4603,6 @@ Belirlenen seçeneklerden biri veya her ikisi de eşiğe eşit veya eşiğin üz
<source>Mapping</source>
<target>Eşleme</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>Özel sağlayıcıya gönderilen yükü değiştirin.</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9624,6 +9619,9 @@ Gruplara/kullanıcılara yapılan bağlamalar, etkinliğin kullanıcısına kar
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -3309,9 +3309,6 @@ doesn't pass when either or both of the selected options are equal or above the
<trans-unit id="sa92398dba8b12d85">
<source>Mapping</source>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
</trans-unit>
@@ -6395,6 +6392,9 @@ Bindings to groups/users are checked against the user of the event.</source>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>
</xliff>

View File

@@ -4634,11 +4634,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Mapping</source>
<target>映射</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>修改发送到自定义提供程序的载荷。</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9917,6 +9912,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -3510,9 +3510,6 @@ doesn't pass when either or both of the selected options are equal or above the
<trans-unit id="sa92398dba8b12d85">
<source>Mapping</source>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
<target>用于配置基于短信的 TOTP 身份验证器的阶段。</target>
@@ -7479,6 +7476,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -4571,11 +4571,6 @@ doesn't pass when either or both of the selected options are equal or above the
<source>Mapping</source>
<target>對應</target>
</trans-unit>
<trans-unit id="s38162f615710c7b4">
<source>Modify the payload sent to the custom provider.</source>
<target>修改發送至客製化供應商的酬載。</target>
</trans-unit>
<trans-unit id="s5e830ae7688d1219">
<source>Stage used to configure an SMS-based TOTP authenticator.</source>
@@ -9213,6 +9208,9 @@ Bindings to groups/users are checked against the user of the event.</source>
</trans-unit>
<trans-unit id="s547b687213f48489">
<source>Update <x id="0" equiv-text="${item.name || item.username}"/>'s password</source>
</trans-unit>
<trans-unit id="s3c619d54c995d4ab">
<source>Modify the payload sent to the provider.</source>
</trans-unit>
</body>
</file>

View File

@@ -6,7 +6,7 @@ This stage configures an SMS-based authenticator using either Twilio, or a gener
## Providers
#### Twilio
### Twilio
Navigate to https://console.twilio.com/, and log in to your existing account, or create a new one.
@@ -22,7 +22,23 @@ The other two steps can be skipped using the _Skip setup_ button.
Navigate back to the root of your Twilio console, and copy the Auth token. This is the value for the _Twilio Auth Token_ field in authentik. Copy the value of **Account SID**. This is the value for the _Twilio Account SID_ field in authentik.
#### Generic
#### Custom SMS message :ak-version[2025.8]
Using a property mapping, it is possible to customize the SMS message sent via Twilio. The mapping should return a dictionary with the `message` key, which will be sent to the user. For example:
```python
return {
"message": f"This is a custom message for {request.http_request.brand.branding_title} SMS authentication. The code is {token}".
}
```
Variables related to different objects can be used within the message:
- The end user device, for example: `device.phone_number`
- The stage sending the message, for example: `stage.from_number`
- The request this verification code is being sent from, for example: `request.http_request.brand`
### Generic
For the generic provider, a POST request will be sent to the URL you have specified in the _External API URL_ field. The request payload looks like this
@@ -36,7 +52,9 @@ For the generic provider, a POST request will be sent to the URL you have specif
Authentication can either be done as HTTP Basic, or via a Bearer Token. Any response with status 400 or above is counted as failed, and will prevent the user from proceeding.
Starting with authentik 2022.10, a custom webhook mapping can be specified to freely customize the payload of the request. For example:
#### Custom SMS message
A custom webhook mapping can be used to customize the SMS message sent to users. For example:
```python
return {