stages/authenticator_sms: Add SMS Authenticator Stage (#1577)

* stages/authenticator_sms: initial implementation

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

* web/admin: add initial stage UI

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

* web/elements: clear invalid state when old input was invalid but new input is correct

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

* stages/authenticator_sms: add more logic

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

* web/user: add basic SMS settings

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

* stages/authenticator_sms: initial working version

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

* stages/authenticator_sms: add tests

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

* web/flows: optimise totp password manager entry on authenticator_validation stage

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

* web/elements: add grouping support for table

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

* web/admin: allow sms class in authenticator stage

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

* web/admin: add grouping to more pages

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

* stages/authenticator_validate: add SMS support

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

* api: add throttling for flow executor based on session key and pending user

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

* web: fix style issues

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

* ci: add workflow to compile backend translations

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens L 2021-10-11 17:51:49 +02:00 committed by GitHub
parent 7bf587af24
commit aef9d27706
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 2425 additions and 93 deletions

View File

@ -0,0 +1,43 @@
name: authentik-backend-translate-compile
on:
push:
branches: [ master ]
paths:
- '/locale/'
schedule:
- cron: "*/15 * * * *"
workflow_dispatch:
env:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
jobs:
compile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- id: cache-pipenv
uses: actions/cache@v2.1.6
with:
path: ~/.local/share/virtualenvs
key: ${{ runner.os }}-pipenv-v2-${{ hashFiles('**/Pipfile.lock') }}
- name: prepare
env:
INSTALL: ${{ steps.cache-pipenv.outputs.cache-hit }}
run: scripts/ci_prepare.sh
- name: run pylint
run: pipenv run ./manage.py compilemessages
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: compile-backend-translation
commit-message: "core: compile backend translations"
title: "core: compile backend translations"
delete-branch: true
signoff: true

View File

@ -73,4 +73,4 @@ migrate:
python -m lifecycle.migrate
run:
WORKERS=1 go run -v cmd/server/main.go
go run -v cmd/server/main.go

18
authentik/api/throttle.py Normal file
View File

@ -0,0 +1,18 @@
"""Throttling classes"""
from typing import Type
from django.views import View
from rest_framework.request import Request
from rest_framework.throttling import ScopedRateThrottle
class SessionThrottle(ScopedRateThrottle):
"""Throttle based on session key"""
def allow_request(self, request: Request, view):
if request._request.user.is_superuser:
return True
return super().allow_request(request, view)
def get_cache_key(self, request: Request, view: Type[View]) -> str:
return f"authentik-throttle-session-{request._request.session.session_key}"

View File

@ -68,6 +68,11 @@ from authentik.stages.authenticator_duo.api import (
DuoAdminDeviceViewSet,
DuoDeviceViewSet,
)
from authentik.stages.authenticator_sms.api import (
AuthenticatorSMSStageViewSet,
SMSAdminDeviceViewSet,
SMSDeviceViewSet,
)
from authentik.stages.authenticator_static.api import (
AuthenticatorStaticStageViewSet,
StaticAdminDeviceViewSet,
@ -165,6 +170,7 @@ router.register("propertymappings/scope", ScopeMappingViewSet)
router.register("propertymappings/notification", NotificationWebhookMappingViewSet)
router.register("authenticators/duo", DuoDeviceViewSet)
router.register("authenticators/sms", SMSDeviceViewSet)
router.register("authenticators/static", StaticDeviceViewSet)
router.register("authenticators/totp", TOTPDeviceViewSet)
router.register("authenticators/webauthn", WebAuthnDeviceViewSet)
@ -173,6 +179,11 @@ router.register(
DuoAdminDeviceViewSet,
basename="admin-duodevice",
)
router.register(
"authenticators/admin/sms",
SMSAdminDeviceViewSet,
basename="admin-smsdevice",
)
router.register(
"authenticators/admin/static",
StaticAdminDeviceViewSet,
@ -187,6 +198,7 @@ router.register(
router.register("stages/all", StageViewSet)
router.register("stages/authenticator/duo", AuthenticatorDuoStageViewSet)
router.register("stages/authenticator/sms", AuthenticatorSMSStageViewSet)
router.register("stages/authenticator/static", AuthenticatorStaticStageViewSet)
router.register("stages/authenticator/totp", AuthenticatorTOTPStageViewSet)
router.register("stages/authenticator/validate", AuthenticatorValidateStageViewSet)

View File

@ -17,10 +17,13 @@ from django.views.generic import View
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, PolymorphicProxySerializer, extend_schema
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.throttling import ScopedRateThrottle
from rest_framework.views import APIView
from sentry_sdk import capture_exception
from structlog.stdlib import BoundLogger, get_logger
from authentik.api.throttle import SessionThrottle
from authentik.core.models import USER_ATTRIBUTE_DEBUG
from authentik.events.models import Event, EventAction, cleanse_dict
from authentik.flows.challenge import (
@ -97,10 +100,33 @@ class InvalidStageError(SentryIgnoredException):
"""Error raised when a challenge from a stage is not valid"""
class FlowPendingUserThrottle(ScopedRateThrottle):
"""Custom throttle based on which user is pending"""
def get_cache_key(self, request: Request, view) -> str:
if SESSION_KEY_PLAN not in request._request.session:
return ""
if PLAN_CONTEXT_PENDING_USER not in request._request.session[SESSION_KEY_PLAN].context:
return ""
user = request._request.session[SESSION_KEY_PLAN].context[PLAN_CONTEXT_PENDING_USER]
return f"authentik-throttle-flow-pending-{user.uid}"
def allow_request(self, request: Request, view) -> bool:
if SESSION_KEY_PLAN not in request._request.session:
return True
if PLAN_CONTEXT_PENDING_USER not in request._request.session[SESSION_KEY_PLAN].context:
return True
if request._request.user.is_superuser:
return True
return super().allow_request(request, view)
@method_decorator(xframe_options_sameorigin, name="dispatch")
class FlowExecutorView(APIView):
"""Stage 1 Flow executor, passing requests to Stage Views"""
throttle_classes = [SessionThrottle, FlowPendingUserThrottle]
throttle_scope = "flow_executor"
permission_classes = [AllowAny]
flow: Flow

View File

@ -0,0 +1,79 @@
# Generated by Django 3.2.8 on 2021-10-09 17:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_policies_event_matcher", "0018_alter_eventmatcherpolicy_action"),
]
operations = [
migrations.AlterField(
model_name="eventmatcherpolicy",
name="app",
field=models.TextField(
blank=True,
choices=[
("authentik.admin", "authentik Admin"),
("authentik.api", "authentik API"),
("authentik.crypto", "authentik Crypto"),
("authentik.events", "authentik Events"),
("authentik.flows", "authentik Flows"),
("authentik.lib", "authentik lib"),
("authentik.outposts", "authentik Outpost"),
("authentik.policies.dummy", "authentik Policies.Dummy"),
("authentik.policies.event_matcher", "authentik Policies.Event Matcher"),
("authentik.policies.expiry", "authentik Policies.Expiry"),
("authentik.policies.expression", "authentik Policies.Expression"),
("authentik.policies.hibp", "authentik Policies.HaveIBeenPwned"),
("authentik.policies.password", "authentik Policies.Password"),
("authentik.policies.reputation", "authentik Policies.Reputation"),
("authentik.policies", "authentik Policies"),
("authentik.providers.ldap", "authentik Providers.LDAP"),
("authentik.providers.oauth2", "authentik Providers.OAuth2"),
("authentik.providers.proxy", "authentik Providers.Proxy"),
("authentik.providers.saml", "authentik Providers.SAML"),
("authentik.recovery", "authentik Recovery"),
("authentik.sources.ldap", "authentik Sources.LDAP"),
("authentik.sources.oauth", "authentik Sources.OAuth"),
("authentik.sources.plex", "authentik Sources.Plex"),
("authentik.sources.saml", "authentik Sources.SAML"),
("authentik.stages.authenticator_duo", "authentik Stages.Authenticator.Duo"),
("authentik.stages.authenticator_sms", "authentik Stages.Authenticator.SMS"),
(
"authentik.stages.authenticator_static",
"authentik Stages.Authenticator.Static",
),
("authentik.stages.authenticator_totp", "authentik Stages.Authenticator.TOTP"),
(
"authentik.stages.authenticator_validate",
"authentik Stages.Authenticator.Validate",
),
(
"authentik.stages.authenticator_webauthn",
"authentik Stages.Authenticator.WebAuthn",
),
("authentik.stages.captcha", "authentik Stages.Captcha"),
("authentik.stages.consent", "authentik Stages.Consent"),
("authentik.stages.deny", "authentik Stages.Deny"),
("authentik.stages.dummy", "authentik Stages.Dummy"),
("authentik.stages.email", "authentik Stages.Email"),
("authentik.stages.identification", "authentik Stages.Identification"),
("authentik.stages.invitation", "authentik Stages.User Invitation"),
("authentik.stages.password", "authentik Stages.Password"),
("authentik.stages.prompt", "authentik Stages.Prompt"),
("authentik.stages.user_delete", "authentik Stages.User Delete"),
("authentik.stages.user_login", "authentik Stages.User Login"),
("authentik.stages.user_logout", "authentik Stages.User Logout"),
("authentik.stages.user_write", "authentik Stages.User Write"),
("authentik.tenants", "authentik Tenants"),
("authentik.core", "authentik Core"),
("authentik.managed", "authentik Managed"),
],
default="",
help_text="Match events created by selected application. When left empty, all applications are matched.",
),
),
]

View File

@ -93,12 +93,11 @@ INSTALLED_APPS = [
"django.contrib.humanize",
"authentik.admin",
"authentik.api",
"authentik.events",
"authentik.crypto",
"authentik.events",
"authentik.flows",
"authentik.outposts",
"authentik.lib",
"authentik.policies",
"authentik.outposts",
"authentik.policies.dummy",
"authentik.policies.event_matcher",
"authentik.policies.expiry",
@ -106,9 +105,10 @@ INSTALLED_APPS = [
"authentik.policies.hibp",
"authentik.policies.password",
"authentik.policies.reputation",
"authentik.providers.proxy",
"authentik.policies",
"authentik.providers.ldap",
"authentik.providers.oauth2",
"authentik.providers.proxy",
"authentik.providers.saml",
"authentik.recovery",
"authentik.sources.ldap",
@ -116,6 +116,7 @@ INSTALLED_APPS = [
"authentik.sources.plex",
"authentik.sources.saml",
"authentik.stages.authenticator_duo",
"authentik.stages.authenticator_sms",
"authentik.stages.authenticator_static",
"authentik.stages.authenticator_totp",
"authentik.stages.authenticator_validate",
@ -204,6 +205,9 @@ REST_FRAMEWORK = {
],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"TEST_REQUEST_DEFAULT_FORMAT": "json",
"DEFAULT_THROTTLE_RATES": {
"flow_executor": "100/day",
},
}
REDIS_PROTOCOL_PREFIX = "redis://"

View File

@ -0,0 +1,79 @@
"""AuthenticatorSMSStage API Views"""
from django_filters.rest_framework.backends import DjangoFilterBackend
from rest_framework import mixins
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelViewSet
from authentik.api.authorization import OwnerFilter, OwnerPermissions
from authentik.core.api.used_by import UsedByMixin
from authentik.flows.api.stages import StageSerializer
from authentik.stages.authenticator_sms.models import AuthenticatorSMSStage, SMSDevice
class AuthenticatorSMSStageSerializer(StageSerializer):
"""AuthenticatorSMSStage Serializer"""
class Meta:
model = AuthenticatorSMSStage
fields = StageSerializer.Meta.fields + [
"configure_flow",
"provider",
"from_number",
"twilio_account_sid",
"twilio_auth",
]
class AuthenticatorSMSStageViewSet(UsedByMixin, ModelViewSet):
"""AuthenticatorSMSStage Viewset"""
queryset = AuthenticatorSMSStage.objects.all()
serializer_class = AuthenticatorSMSStageSerializer
filterset_fields = "__all__"
ordering = ["name"]
class SMSDeviceSerializer(ModelSerializer):
"""Serializer for sms authenticator devices"""
class Meta:
model = SMSDevice
fields = ["name", "pk", "phone_number"]
depth = 2
extra_kwargs = {
"phone_number": {"read_only": True},
}
class SMSDeviceViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
UsedByMixin,
mixins.ListModelMixin,
GenericViewSet,
):
"""Viewset for sms authenticator devices"""
queryset = SMSDevice.objects.all()
serializer_class = SMSDeviceSerializer
permission_classes = [OwnerPermissions]
filter_backends = [OwnerFilter, DjangoFilterBackend, OrderingFilter, SearchFilter]
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]
class SMSAdminDeviceViewSet(ReadOnlyModelViewSet):
"""Viewset for sms authenticator devices (for admins)"""
permission_classes = [IsAdminUser]
queryset = SMSDevice.objects.all()
serializer_class = SMSDeviceSerializer
search_fields = ["name"]
filterset_fields = ["name"]
ordering = ["name"]

View File

@ -0,0 +1,10 @@
"""SMS"""
from django.apps import AppConfig
class AuthentikStageAuthenticatorSMSConfig(AppConfig):
"""SMS App config"""
name = "authentik.stages.authenticator_sms"
label = "authentik_stages_authenticator_sms"
verbose_name = "authentik Stages.Authenticator.SMS"

View File

@ -0,0 +1,100 @@
# Generated by Django 3.2.8 on 2021-10-09 19:15
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("authentik_flows", "0024_alter_flow_compatibility_mode"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="AuthenticatorSMSStage",
fields=[
(
"stage_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="authentik_flows.stage",
),
),
("provider", models.TextField(choices=[("twilio", "Twilio")])),
("twilio_account_sid", models.TextField()),
("twilio_auth", models.TextField()),
(
"configure_flow",
models.ForeignKey(
blank=True,
help_text="Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="authentik_flows.flow",
),
),
],
options={
"verbose_name": "SMS Authenticator Setup Stage",
"verbose_name_plural": "SMS Authenticator Setup Stages",
},
bases=("authentik_flows.stage", models.Model),
),
migrations.CreateModel(
name="SMSDevice",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"name",
models.CharField(
help_text="The human-readable name of this device.", max_length=64
),
),
(
"confirmed",
models.BooleanField(default=True, help_text="Is this device ready for use?"),
),
("token", models.CharField(blank=True, max_length=16, null=True)),
(
"valid_until",
models.DateTimeField(
default=django.utils.timezone.now,
help_text="The timestamp of the moment of expiry of the saved token.",
),
),
("phone_number", models.TextField()),
(
"stage",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="authentik_stages_authenticator_sms.authenticatorsmsstage",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
],
options={
"verbose_name": "SMS Device",
"verbose_name_plural": "SMS Devices",
},
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.8 on 2021-10-09 20:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_authenticator_sms", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="authenticatorsmsstage",
name="from_number",
field=models.TextField(default=""),
preserve_default=False,
),
]

View File

@ -0,0 +1,118 @@
"""OTP Time-based models"""
from typing import Optional, Type
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.views import View
from django_otp.models import SideChannelDevice
from requests.exceptions import RequestException
from rest_framework.exceptions import ValidationError
from rest_framework.serializers import BaseSerializer
from structlog.stdlib import get_logger
from authentik.core.types import UserSettingSerializer
from authentik.flows.models import ConfigurableStage, Stage
from authentik.lib.utils.http import get_http_session
LOGGER = get_logger()
class SMSProviders(models.TextChoices):
"""Supported SMS Providers"""
TWILIO = "twilio"
class AuthenticatorSMSStage(ConfigurableStage, Stage):
"""Use SMS-based TOTP instead of authenticator-based."""
provider = models.TextField(choices=SMSProviders.choices)
from_number = models.TextField()
twilio_account_sid = models.TextField()
twilio_auth = models.TextField()
def send(self, token: str, device: "SMSDevice"):
"""Send message via selected provider"""
if self.provider == SMSProviders.TWILIO:
return self.send_twilio(token, device)
raise ValueError(f"invalid provider {self.provider}")
def send_twilio(self, token: str, device: "SMSDevice"):
"""send sms via twilio provider"""
response = get_http_session().post(
f"https://api.twilio.com/2010-04-01/Accounts/{self.twilio_account_sid}/Messages.json",
data={
"From": self.from_number,
"To": device.phone_number,
"Body": token,
},
auth=(self.twilio_account_sid, self.twilio_auth),
)
LOGGER.debug("Sent SMS", to=device.phone_number)
try:
response.raise_for_status()
except RequestException as exc:
LOGGER.warning("Error sending token by Twilio SMS", exc=exc, body=response.text)
if response.status_code == 400:
raise ValidationError(response.json().get("message"))
raise
if "sid" not in response.json():
message = response.json().get("message")
LOGGER.warning("Error sending token by Twilio SMS", message=message)
raise Exception(message)
@property
def serializer(self) -> BaseSerializer:
from authentik.stages.authenticator_sms.api import AuthenticatorSMSStageSerializer
return AuthenticatorSMSStageSerializer
@property
def type(self) -> Type[View]:
from authentik.stages.authenticator_sms.stage import AuthenticatorSMSStageView
return AuthenticatorSMSStageView
@property
def component(self) -> str:
return "ak-stage-authenticator-sms-form"
@property
def ui_user_settings(self) -> Optional[UserSettingSerializer]:
return UserSettingSerializer(
data={
"title": str(self._meta.verbose_name),
"component": "ak-user-settings-authenticator-sms",
}
)
def __str__(self) -> str:
return f"SMS Authenticator Setup Stage {self.name}"
class Meta:
verbose_name = _("SMS Authenticator Setup Stage")
verbose_name_plural = _("SMS Authenticator Setup Stages")
class SMSDevice(SideChannelDevice):
"""SMS Device"""
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
# Connect to the stage to when validating access we know the API Credentials
stage = models.ForeignKey(AuthenticatorSMSStage, on_delete=models.CASCADE)
phone_number = models.TextField()
def __str__(self):
return self.name or str(self.user)
class Meta:
verbose_name = _("SMS Device")
verbose_name_plural = _("SMS Devices")

View File

@ -0,0 +1,118 @@
"""SMS Setup stage"""
from typing import Optional
from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ValidationError
from rest_framework.fields import BooleanField, CharField, IntegerField
from structlog.stdlib import get_logger
from authentik.flows.challenge import (
Challenge,
ChallengeResponse,
ChallengeTypes,
WithUserInfoChallenge,
)
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_sms.models import AuthenticatorSMSStage, SMSDevice
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
LOGGER = get_logger()
SESSION_SMS_DEVICE = "sms_device"
class AuthenticatorSMSChallenge(WithUserInfoChallenge):
"""SMS Setup challenge"""
# Set to true if no previous prompt stage set the phone number
# this stage will also check prompt_data.phone
phone_number_required = BooleanField(default=True)
component = CharField(default="ak-stage-authenticator-sms")
class AuthenticatorSMSChallengeResponse(ChallengeResponse):
"""SMS Challenge response, device is set by get_response_instance"""
device: SMSDevice
code = IntegerField(required=False)
phone_number = CharField(required=False)
component = CharField(default="ak-stage-authenticator-sms")
def validate(self, attrs: dict) -> dict:
"""Check"""
stage: AuthenticatorSMSStage = self.device.stage
if "code" not in attrs:
self.device.phone_number = attrs["phone_number"]
# No code yet, but we have a phone number, so send a verification message
stage.send(self.device.token, self.device)
return super().validate(attrs)
if not self.device.verify_token(str(attrs["code"])):
raise ValidationError(_("Code does not match"))
self.device.confirmed = True
return super().validate(attrs)
class AuthenticatorSMSStageView(ChallengeStageView):
"""OTP sms Setup stage"""
response_class = AuthenticatorSMSChallengeResponse
def _has_phone_number(self) -> Optional[str]:
context = self.executor.plan.context
if "phone" in context.get(PLAN_CONTEXT_PROMPT, {}):
LOGGER.debug("got phone number from plan context")
return context.get(PLAN_CONTEXT_PROMPT, {}).get("phone")
if SESSION_SMS_DEVICE in self.request.session:
LOGGER.debug("got phone number from device in session")
device: SMSDevice = self.request.session[SESSION_SMS_DEVICE]
if device.phone_number == "":
return None
return device.phone_number
return None
def get_challenge(self, *args, **kwargs) -> Challenge:
return AuthenticatorSMSChallenge(
data={
"type": ChallengeTypes.NATIVE.value,
"phone_number_required": self._has_phone_number() is None,
}
)
def get_response_instance(self, data: QueryDict) -> ChallengeResponse:
response = super().get_response_instance(data)
response.device = self.request.session[SESSION_SMS_DEVICE]
return response
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
if not user:
LOGGER.debug("No pending user, continuing")
return self.executor.stage_ok()
# Currently, this stage only supports one device per user. If the user already
# has a device, just skip to the next stage
if SMSDevice.objects.filter(user=user).exists():
return self.executor.stage_ok()
stage: AuthenticatorSMSStage = self.executor.current_stage
if SESSION_SMS_DEVICE not in self.request.session:
device = SMSDevice(user=user, confirmed=False, stage=stage)
device.generate_token(commit=False)
if phone_number := self._has_phone_number():
device.phone_number = phone_number
self.request.session[SESSION_SMS_DEVICE] = device
return super().get(request, *args, **kwargs)
def challenge_valid(self, response: ChallengeResponse) -> HttpResponse:
"""SMS Token is validated by challenge"""
device: SMSDevice = self.request.session[SESSION_SMS_DEVICE]
if not device.confirmed:
return self.challenge_invalid(response)
device.save()
del self.request.session[SESSION_SMS_DEVICE]
return self.executor.stage_ok()

View File

@ -0,0 +1,95 @@
"""Test SMS API"""
from unittest.mock import MagicMock, patch
from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User
from authentik.flows.challenge import ChallengeTypes
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding
from authentik.stages.authenticator_sms.models import AuthenticatorSMSStage, SMSProviders
from authentik.stages.authenticator_sms.stage import SESSION_SMS_DEVICE
class AuthenticatorSMSStageTests(APITestCase):
"""Test SMS API"""
def setUp(self) -> None:
super().setUp()
self.flow = Flow.objects.create(
name="foo",
slug="foo",
designation=FlowDesignation.STAGE_CONFIGURATION,
)
self.stage = AuthenticatorSMSStage.objects.create(
name="foo",
provider=SMSProviders.TWILIO,
configure_flow=self.flow,
)
FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=0)
self.user = User.objects.create(username="foo")
self.client.force_login(self.user)
def test_stage_no_prefill(self):
"""test stage"""
self.client.get(
reverse("authentik_flows:configure", kwargs={"stage_uuid": self.stage.stage_uuid}),
)
response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
)
self.assertJSONEqual(
response.content,
{
"component": "ak-stage-authenticator-sms",
"flow_info": {
"background": self.flow.background_url,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
},
"pending_user": "foo",
"pending_user_avatar": (
"https://secure.gravatar.com/avatar/d41d8cd98f00"
"b204e9800998ecf8427e?s=158&amp;r=g"
),
"phone_number_required": True,
"type": ChallengeTypes.NATIVE.value,
},
)
def test_stage_submit(self):
"""test stage (submit)"""
# Prepares session etc
self.test_stage_no_prefill()
sms_send_mock = MagicMock()
with patch(
"authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send",
sms_send_mock,
):
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
data={"component": "ak-stage-authenticator-sms", "phone_number": "foo"},
)
self.assertEqual(response.status_code, 200)
sms_send_mock.assert_called_once()
def test_stage_submit_full(self):
"""test stage (submit)"""
# Prepares session etc
self.test_stage_submit()
sms_send_mock = MagicMock()
with patch(
"authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send",
sms_send_mock,
):
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
data={
"component": "ak-stage-authenticator-sms",
"phone_number": "foo",
"code": int(self.client.session[SESSION_SMS_DEVICE].token),
},
)
print(response.content)
self.assertEqual(response.status_code, 200)
sms_send_mock.assert_not_called()

View File

@ -6,7 +6,7 @@ from rest_framework.test import APITestCase
from authentik.core.models import User
class AuthenticatorStaticStage(APITestCase):
class AuthenticatorStaticStageTests(APITestCase):
"""Test Static API"""
def test_api_delete(self):

View File

@ -42,7 +42,7 @@ class AuthenticatorTOTPChallengeResponse(ChallengeResponse):
"""Validate totp code"""
if self.device is not None:
if not self.device.verify_token(code):
raise ValidationError(_("OTP Code does not match"))
raise ValidationError(_("Code does not match"))
return code

View File

@ -19,6 +19,7 @@ from authentik.core.api.utils import PassiveSerializer
from authentik.core.models import User
from authentik.lib.utils.http import get_client_ip
from authentik.stages.authenticator_duo.models import AuthenticatorDuoStage, DuoDevice
from authentik.stages.authenticator_sms.models import SMSDevice
from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
from authentik.stages.authenticator_webauthn.utils import generate_challenge, get_origin
@ -77,6 +78,18 @@ def get_webauthn_challenge(request: HttpRequest, device: WebAuthnDevice) -> dict
return assertion
def select_challenge(request: HttpRequest, device: Device):
"""Callback when the user selected a challenge in the frontend."""
if isinstance(device, SMSDevice):
select_challenge_sms(request, device)
def select_challenge_sms(request: HttpRequest, device: SMSDevice):
"""Send SMS"""
device.generate_token()
device.stage.send(device.token, device)
def validate_challenge_code(code: str, request: HttpRequest, user: User) -> str:
"""Validate code-based challenges. We test against every device, on purpose, as
the user mustn't choose between totp and static devices."""

View File

@ -18,6 +18,7 @@ class DeviceClasses(models.TextChoices):
TOTP = "totp", _("TOTP")
WEBAUTHN = "webauthn", _("WebAuthn")
DUO = "duo", _("Duo")
SMS = "sms", _("SMS")
def default_device_classes() -> list:

View File

@ -9,9 +9,11 @@ from authentik.flows.challenge import ChallengeResponse, ChallengeTypes, WithUse
from authentik.flows.models import NotConfiguredAction, Stage
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_sms.models import SMSDevice
from authentik.stages.authenticator_validate.challenge import (
DeviceChallenge,
get_challenge_for_device,
select_challenge,
validate_challenge_code,
validate_challenge_duo,
validate_challenge_webauthn,
@ -31,6 +33,8 @@ class AuthenticatorValidationChallenge(WithUserInfoChallenge):
class AuthenticatorValidationChallengeResponse(ChallengeResponse):
"""Challenge used for Code-based and WebAuthn authenticators"""
selected_challenge = DeviceChallenge(required=False)
code = CharField(required=False)
webauthn = JSONField(required=False)
duo = IntegerField(required=False)
@ -43,7 +47,7 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
def validate_code(self, code: str) -> str:
"""Validate code-based response, raise error if code isn't allowed"""
self._challenge_allowed([DeviceClasses.TOTP, DeviceClasses.STATIC])
self._challenge_allowed([DeviceClasses.TOTP, DeviceClasses.STATIC, DeviceClasses.SMS])
return validate_challenge_code(code, self.stage.request, self.stage.get_pending_user())
def validate_webauthn(self, webauthn: dict) -> dict:
@ -59,6 +63,22 @@ class AuthenticatorValidationChallengeResponse(ChallengeResponse):
self._challenge_allowed([DeviceClasses.DUO])
return validate_challenge_duo(duo, self.stage.request, self.stage.get_pending_user())
def validate_selected_challenge(self, challenge: dict) -> dict:
"""Check which challenge the user has selected. Actual logic only used for SMS stage."""
# First check if the challenge is valid
for device_challenge in self.stage.request.session.get("device_challenges"):
if device_challenge.get("device_class", "") != challenge.get("device_class", ""):
raise ValidationError("invalid challenge selected")
if device_challenge.get("device_uid", "") != challenge.get("device_uid", ""):
raise ValidationError("invalid challenge selected")
if challenge.get("device_class", "") != "sms":
return challenge
devices = SMSDevice.objects.filter(pk=int(challenge.get("device_uid", "0")))
if not devices.exists():
raise ValidationError("device does not exist")
select_challenge(self.stage.request, devices.first())
return challenge
def validate(self, attrs: dict):
# Checking if the given data is from a valid device class is done above
# Here we only check if the any data was sent at all

View File

@ -20,6 +20,7 @@ var running = true
func main() {
log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.JSONFormatter{})
config.DefaultConfig()
err := config.LoadConfig("./authentik/lib/default.yml")
if err != nil {
@ -80,7 +81,7 @@ func attemptStartBackend(g *gounicorn.GoUnicorn) {
if !running {
return
}
log.WithField("logger", "authentik.g").WithError(err).Warning("gunicorn process died, restarting")
log.WithField("logger", "authentik.router").WithError(err).Warning("gunicorn process died, restarting")
}
}

View File

@ -19,7 +19,7 @@ type GoUnicorn struct {
}
func NewGoUnicorn() *GoUnicorn {
logger := log.WithField("logger", "authentik.g.unicorn")
logger := log.WithField("logger", "authentik.router.unicorn")
g := &GoUnicorn{
log: logger,
started: false,

View File

@ -10,7 +10,7 @@ import (
func recoveryMiddleware() func(next http.Handler) http.Handler {
sentryHandler := sentryhttp.New(sentryhttp.Options{})
l := log.WithField("logger", "authentik.sentry")
l := log.WithField("logger", "authentik.router.sentry")
return func(next http.Handler) http.Handler {
sentryHandler.Handle(next)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View File

@ -32,7 +32,7 @@ type WebServer struct {
}
func NewWebServer(g *gounicorn.GoUnicorn) *WebServer {
l := log.WithField("logger", "authentik.g.web")
l := log.WithField("logger", "authentik.router")
mainHandler := mux.NewRouter()
if config.G.ErrorReporting.Enabled {
mainHandler.Use(recoveryMiddleware())

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-09 18:07+0000\n"
"POT-Creation-Date: 2021-10-11 14:12+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -1214,6 +1214,27 @@ msgstr ""
msgid "Duo Devices"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:97
msgid "SMS Authenticator Setup Stage"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:98
msgid "SMS Authenticator Setup Stages"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:116
msgid "SMS Device"
msgstr ""
#: authentik/stages/authenticator_sms/models.py:117
msgid "SMS Devices"
msgstr ""
#: authentik/stages/authenticator_sms/stage.py:54
#: authentik/stages/authenticator_totp/stage.py:45
msgid "Code does not match"
msgstr ""
#: authentik/stages/authenticator_static/models.py:48
msgid "Static Authenticator Stage"
msgstr ""
@ -1238,10 +1259,6 @@ msgstr ""
msgid "TOTP Authenticator Setup Stages"
msgstr ""
#: authentik/stages/authenticator_totp/stage.py:45
msgid "OTP Code does not match"
msgstr ""
#: authentik/stages/authenticator_validate/challenge.py:85
msgid "Invalid Token"
msgstr ""
@ -1258,15 +1275,19 @@ msgstr ""
msgid "Duo"
msgstr ""
#: authentik/stages/authenticator_validate/models.py:56
#: authentik/stages/authenticator_validate/models.py:21
msgid "SMS"
msgstr ""
#: authentik/stages/authenticator_validate/models.py:57
msgid "Device classes which can be used to authenticate"
msgstr ""
#: authentik/stages/authenticator_validate/models.py:78
#: authentik/stages/authenticator_validate/models.py:79
msgid "Authenticator Validation Stage"
msgstr ""
#: authentik/stages/authenticator_validate/models.py:79
#: authentik/stages/authenticator_validate/models.py:80
msgid "Authenticator Validation Stages"
msgstr ""

View File

@ -359,6 +359,80 @@ paths:
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/authenticators/admin/sms/:
get:
operationId: authenticators_admin_sms_list
description: Viewset for sms authenticator devices (for admins)
parameters:
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- name: search
required: false
in: query
description: A search term.
schema:
type: string
tags:
- authenticators
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedSMSDeviceList'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/authenticators/admin/sms/{id}/:
get:
operationId: authenticators_admin_sms_retrieve
description: Viewset for sms authenticator devices (for admins)
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this SMS Device.
required: true
tags:
- authenticators
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SMSDevice'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/authenticators/admin/static/:
get:
operationId: authenticators_admin_static_list
@ -765,6 +839,190 @@ paths:
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/authenticators/sms/:
get:
operationId: authenticators_sms_list
description: Viewset for sms authenticator devices
parameters:
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- name: search
required: false
in: query
description: A search term.
schema:
type: string
tags:
- authenticators
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedSMSDeviceList'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/authenticators/sms/{id}/:
get:
operationId: authenticators_sms_retrieve
description: Viewset for sms authenticator devices
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this SMS Device.
required: true
tags:
- authenticators
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SMSDevice'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
put:
operationId: authenticators_sms_update
description: Viewset for sms authenticator devices
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this SMS Device.
required: true
tags:
- authenticators
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SMSDeviceRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SMSDevice'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
patch:
operationId: authenticators_sms_partial_update
description: Viewset for sms authenticator devices
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this SMS Device.
required: true
tags:
- authenticators
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedSMSDeviceRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SMSDevice'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
delete:
operationId: authenticators_sms_destroy
description: Viewset for sms authenticator devices
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this SMS Device.
required: true
tags:
- authenticators
security:
- authentik: []
responses:
'204':
description: No response body
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/authenticators/sms/{id}/used_by/:
get:
operationId: authenticators_sms_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: id
schema:
type: integer
description: A unique integer value identifying this SMS Device.
required: true
tags:
- authenticators
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/authenticators/static/:
get:
operationId: authenticators_static_list
@ -7282,6 +7540,7 @@ paths:
- authentik.sources.plex
- authentik.sources.saml
- authentik.stages.authenticator_duo
- authentik.stages.authenticator_sms
- authentik.stages.authenticator_static
- authentik.stages.authenticator_totp
- authentik.stages.authenticator_validate
@ -13877,6 +14136,247 @@ paths:
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/stages/authenticator/sms/:
get:
operationId: stages_authenticator_sms_list
description: AuthenticatorSMSStage Viewset
parameters:
- in: query
name: configure_flow
schema:
type: string
format: uuid
- in: query
name: from_number
schema:
type: string
- in: query
name: name
schema:
type: string
- name: ordering
required: false
in: query
description: Which field to use when ordering the results.
schema:
type: string
- name: page
required: false
in: query
description: A page number within the paginated result set.
schema:
type: integer
- name: page_size
required: false
in: query
description: Number of results to return per page.
schema:
type: integer
- in: query
name: provider
schema:
type: string
enum:
- twilio
- name: search
required: false
in: query
description: A search term.
schema:
type: string
- in: query
name: stage_uuid
schema:
type: string
format: uuid
- in: query
name: twilio_account_sid
schema:
type: string
- in: query
name: twilio_auth
schema:
type: string
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedAuthenticatorSMSStageList'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
post:
operationId: stages_authenticator_sms_create
description: AuthenticatorSMSStage Viewset
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorSMSStageRequest'
required: true
security:
- authentik: []
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorSMSStage'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/stages/authenticator/sms/{stage_uuid}/:
get:
operationId: stages_authenticator_sms_retrieve
description: AuthenticatorSMSStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this SMS Authenticator Setup Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorSMSStage'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
put:
operationId: stages_authenticator_sms_update
description: AuthenticatorSMSStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this SMS Authenticator Setup Stage.
required: true
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorSMSStageRequest'
required: true
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorSMSStage'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
patch:
operationId: stages_authenticator_sms_partial_update
description: AuthenticatorSMSStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this SMS Authenticator Setup Stage.
required: true
tags:
- stages
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PatchedAuthenticatorSMSStageRequest'
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AuthenticatorSMSStage'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
delete:
operationId: stages_authenticator_sms_destroy
description: AuthenticatorSMSStage Viewset
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this SMS Authenticator Setup Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'204':
description: No response body
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/stages/authenticator/sms/{stage_uuid}/used_by/:
get:
operationId: stages_authenticator_sms_used_by_list
description: Get a list of all objects that use this object
parameters:
- in: path
name: stage_uuid
schema:
type: string
format: uuid
description: A UUID string identifying this SMS Authenticator Setup Stage.
required: true
tags:
- stages
security:
- authentik: []
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UsedBy'
description: ''
'400':
$ref: '#/components/schemas/ValidationError'
'403':
$ref: '#/components/schemas/GenericError'
/stages/authenticator/static/:
get:
operationId: stages_authenticator_static_list
@ -18240,12 +18740,11 @@ components:
enum:
- authentik.admin
- authentik.api
- authentik.events
- authentik.crypto
- authentik.events
- authentik.flows
- authentik.outposts
- authentik.lib
- authentik.policies
- authentik.outposts
- authentik.policies.dummy
- authentik.policies.event_matcher
- authentik.policies.expiry
@ -18253,9 +18752,10 @@ components:
- authentik.policies.hibp
- authentik.policies.password
- authentik.policies.reputation
- authentik.providers.proxy
- authentik.policies
- authentik.providers.ldap
- authentik.providers.oauth2
- authentik.providers.proxy
- authentik.providers.saml
- authentik.recovery
- authentik.sources.ldap
@ -18263,6 +18763,7 @@ components:
- authentik.sources.plex
- authentik.sources.saml
- authentik.stages.authenticator_duo
- authentik.stages.authenticator_sms
- authentik.stages.authenticator_static
- authentik.stages.authenticator_totp
- authentik.stages.authenticator_validate
@ -18609,6 +19110,123 @@ components:
- client_id
- client_secret
- name
AuthenticatorSMSChallenge:
type: object
description: SMS Setup challenge
properties:
type:
$ref: '#/components/schemas/ChallengeChoices'
flow_info:
$ref: '#/components/schemas/ContextualFlowInfo'
component:
type: string
default: ak-stage-authenticator-sms
response_errors:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/ErrorDetail'
pending_user:
type: string
pending_user_avatar:
type: string
phone_number_required:
type: boolean
default: true
required:
- pending_user
- pending_user_avatar
- type
AuthenticatorSMSChallengeResponseRequest:
type: object
description: SMS Challenge response, device is set by get_response_instance
properties:
component:
type: string
default: ak-stage-authenticator-sms
code:
type: integer
phone_number:
type: string
AuthenticatorSMSStage:
type: object
description: AuthenticatorSMSStage Serializer
properties:
pk:
type: string
format: uuid
readOnly: true
title: Stage uuid
name:
type: string
component:
type: string
readOnly: true
verbose_name:
type: string
readOnly: true
verbose_name_plural:
type: string
readOnly: true
flow_set:
type: array
items:
$ref: '#/components/schemas/Flow'
configure_flow:
type: string
format: uuid
nullable: true
description: Flow used by an authenticated user to configure this Stage.
If empty, user will not be able to configure this stage.
provider:
$ref: '#/components/schemas/ProviderEnum'
from_number:
type: string
twilio_account_sid:
type: string
twilio_auth:
type: string
required:
- component
- from_number
- name
- pk
- provider
- twilio_account_sid
- twilio_auth
- verbose_name
- verbose_name_plural
AuthenticatorSMSStageRequest:
type: object
description: AuthenticatorSMSStage Serializer
properties:
name:
type: string
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowRequest'
configure_flow:
type: string
format: uuid
nullable: true
description: Flow used by an authenticated user to configure this Stage.
If empty, user will not be able to configure this stage.
provider:
$ref: '#/components/schemas/ProviderEnum'
from_number:
type: string
twilio_account_sid:
type: string
twilio_auth:
type: string
required:
- from_number
- name
- provider
- twilio_account_sid
- twilio_auth
AuthenticatorStaticChallenge:
type: object
description: Static authenticator challenge
@ -18920,6 +19538,8 @@ components:
component:
type: string
default: ak-stage-authenticator-validate
selected_challenge:
$ref: '#/components/schemas/DeviceChallengeRequest'
code:
type: string
webauthn:
@ -19232,6 +19852,7 @@ components:
oneOf:
- $ref: '#/components/schemas/AccessDeniedChallenge'
- $ref: '#/components/schemas/AuthenticatorDuoChallenge'
- $ref: '#/components/schemas/AuthenticatorSMSChallenge'
- $ref: '#/components/schemas/AuthenticatorStaticChallenge'
- $ref: '#/components/schemas/AuthenticatorTOTPChallenge'
- $ref: '#/components/schemas/AuthenticatorValidationChallenge'
@ -19252,6 +19873,7 @@ components:
mapping:
ak-stage-access-denied: '#/components/schemas/AccessDeniedChallenge'
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallenge'
ak-stage-authenticator-sms: '#/components/schemas/AuthenticatorSMSChallenge'
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallenge'
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallenge'
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallenge'
@ -19524,12 +20146,28 @@ components:
- challenge
- device_class
- device_uid
DeviceChallengeRequest:
type: object
description: Single device challenge
properties:
device_class:
type: string
device_uid:
type: string
challenge:
type: object
additionalProperties: {}
required:
- challenge
- device_class
- device_uid
DeviceClassesEnum:
enum:
- static
- totp
- webauthn
- duo
- sms
type: string
DigestAlgorithmEnum:
enum:
@ -20283,6 +20921,7 @@ components:
FlowChallengeResponseRequest:
oneOf:
- $ref: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorSMSChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorStaticChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
- $ref: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
@ -20300,6 +20939,7 @@ components:
propertyName: component
mapping:
ak-stage-authenticator-duo: '#/components/schemas/AuthenticatorDuoChallengeResponseRequest'
ak-stage-authenticator-sms: '#/components/schemas/AuthenticatorSMSChallengeResponseRequest'
ak-stage-authenticator-static: '#/components/schemas/AuthenticatorStaticChallengeResponseRequest'
ak-stage-authenticator-totp: '#/components/schemas/AuthenticatorTOTPChallengeResponseRequest'
ak-stage-authenticator-validate: '#/components/schemas/AuthenticatorValidationChallengeResponseRequest'
@ -22411,6 +23051,41 @@ components:
required:
- pagination
- results
PaginatedAuthenticatorSMSStageList:
type: object
properties:
pagination:
type: object
properties:
next:
type: number
previous:
type: number
count:
type: number
current:
type: number
total_pages:
type: number
start_index:
type: number
end_index:
type: number
required:
- next
- previous
- count
- current
- total_pages
- start_index
- end_index
results:
type: array
items:
$ref: '#/components/schemas/AuthenticatorSMSStage'
required:
- pagination
- results
PaginatedAuthenticatorStaticStageList:
type: object
properties:
@ -24301,6 +24976,41 @@ components:
required:
- pagination
- results
PaginatedSMSDeviceList:
type: object
properties:
pagination:
type: object
properties:
next:
type: number
previous:
type: number
count:
type: number
current:
type: number
total_pages:
type: number
start_index:
type: number
end_index:
type: number
required:
- next
- previous
- count
- current
- total_pages
- start_index
- end_index
results:
type: array
items:
$ref: '#/components/schemas/SMSDevice'
required:
- pagination
- results
PaginatedScopeMappingList:
type: object
properties:
@ -25233,6 +25943,30 @@ components:
writeOnly: true
api_hostname:
type: string
PatchedAuthenticatorSMSStageRequest:
type: object
description: AuthenticatorSMSStage Serializer
properties:
name:
type: string
flow_set:
type: array
items:
$ref: '#/components/schemas/FlowRequest'
configure_flow:
type: string
format: uuid
nullable: true
description: Flow used by an authenticated user to configure this Stage.
If empty, user will not be able to configure this stage.
provider:
$ref: '#/components/schemas/ProviderEnum'
from_number:
type: string
twilio_account_sid:
type: string
twilio_auth:
type: string
PatchedAuthenticatorStaticStageRequest:
type: object
description: AuthenticatorStaticStage Serializer
@ -26546,6 +27280,14 @@ components:
description: '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).'
PatchedSMSDeviceRequest:
type: object
description: Serializer for sms authenticator devices
properties:
name:
type: string
description: The human-readable name of this device.
maxLength: 64
PatchedScopeMappingRequest:
type: object
description: ScopeMapping Serializer
@ -27402,6 +28144,10 @@ components:
- pk
- verbose_name
- verbose_name_plural
ProviderEnum:
enum:
- twilio
type: string
ProviderRequest:
type: object
description: Provider Serializer
@ -28225,6 +28971,35 @@ components:
- pre_authentication_flow
- slug
- sso_url
SMSDevice:
type: object
description: Serializer for sms authenticator devices
properties:
name:
type: string
description: The human-readable name of this device.
maxLength: 64
pk:
type: integer
readOnly: true
title: ID
phone_number:
type: string
readOnly: true
required:
- name
- phone_number
- pk
SMSDeviceRequest:
type: object
description: Serializer for sms authenticator devices
properties:
name:
type: string
description: The human-readable name of this device.
maxLength: 64
required:
- name
ScopeMapping:
type: object
description: ScopeMapping Serializer

View File

@ -219,6 +219,9 @@ export class Form<T> extends LitElement {
element.errorMessage =
errorMessage[camelToSnake(elementName)].join(", ");
element.invalid = true;
} else {
element.errorMessage = "";
element.invalid = false;
}
});
if ("non_field_errors" in errorMessage) {

View File

@ -15,6 +15,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { AKResponse } from "../../api/Client";
import { EVENT_REFRESH } from "../../constants";
import { groupBy } from "../../utils";
import "../EmptyState";
import "../chips/Chip";
import "../chips/ChipGroup";
@ -154,6 +155,12 @@ export abstract class Table<T> extends LitElement {
});
}
public groupBy(items: T[]): [string, T[]][] {
return groupBy(items, () => {
return "";
});
}
public fetch(): void {
if (this.isLoading) {
return;
@ -213,7 +220,22 @@ export abstract class Table<T> extends LitElement {
if (this.data.pagination.count === 0) {
return [this.renderEmpty()];
}
return this.data.results.map((item: T) => {
const groupedResults = this.groupBy(this.data.results);
if (groupedResults.length === 1) {
return this.renderRowGroup(groupedResults[0][1]);
}
return groupedResults.map(([group, items]) => {
return html`<thead>
<tr role="row">
<th role="columnheader" scope="row" colspan="200">${group}</th>
</tr>
</thead>
${this.renderRowGroup(items)}`;
});
}
private renderRowGroup(items: T[]): TemplateResult[] {
return items.map((item) => {
return html`<tbody
role="rowgroup"
class="${this.expandedElements.indexOf(item) > -1 ? "pf-m-expanded" : ""}"

View File

@ -36,6 +36,7 @@ import "./access_denied/FlowAccessDenied";
import "./sources/plex/PlexLoginInit";
import "./stages/RedirectStage";
import "./stages/authenticator_duo/AuthenticatorDuoStage";
import "./stages/authenticator_sms/AuthenticatorSMSStage";
import "./stages/authenticator_static/AuthenticatorStaticStage";
import "./stages/authenticator_totp/AuthenticatorTOTPStage";
import "./stages/authenticator_validate/AuthenticatorValidateStage";
@ -311,6 +312,11 @@ export class FlowExecutor extends LitElement implements StageHost {
.host=${this as StageHost}
.challenge=${this.challenge}
></ak-stage-authenticator-validate>`;
case "ak-stage-authenticator-sms":
return html`<ak-stage-authenticator-sms
.host=${this as StageHost}
.challenge=${this.challenge}
></ak-stage-authenticator-sms>`;
case "ak-flow-sources-plex":
return html`<ak-flow-sources-plex
.host=${this as StageHost}

View File

@ -0,0 +1,158 @@
import { t } from "@lingui/macro";
import { CSSResult, html, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import AKGlobal from "../../../authentik.css";
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import PFLogin from "@patternfly/patternfly/components/Login/login.css";
import PFTitle from "@patternfly/patternfly/components/Title/title.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import {
AuthenticatorSMSChallenge,
AuthenticatorSMSChallengeResponseRequest,
} from "@goauthentik/api";
import "../../../elements/EmptyState";
import "../../../elements/forms/FormElement";
import "../../FormStatic";
import { BaseStage } from "../base";
@customElement("ak-stage-authenticator-sms")
export class AuthenticatorSMSStage extends BaseStage<
AuthenticatorSMSChallenge,
AuthenticatorSMSChallengeResponseRequest
> {
static get styles(): CSSResult[] {
return [PFBase, PFAlert, PFLogin, PFForm, PFFormControl, PFTitle, PFButton, AKGlobal];
}
renderPhoneNumber(): TemplateResult {
return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
</header>
<div class="pf-c-login__main-body">
<form
class="pf-c-form"
@submit=${(e: Event) => {
this.submitForm(e);
}}
>
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${t`Not you?`}</a
>
</div>
</ak-form-static>
<ak-form-element
label="${t`Phone number`}"
?required="${true}"
class="pf-c-form__group"
.errors=${(this.challenge?.responseErrors || {})["phone_number"]}
>
<!-- @ts-ignore -->
<input
type="tel"
name="phoneNumber"
placeholder="${t`Please enter your Phone number.`}"
autofocus=""
autocomplete="tel"
class="pf-c-form-control"
required
/>
</ak-form-element>
${"non_field_errors" in (this.challenge?.responseErrors || {})
? this.renderNonFieldErrors(
this.challenge?.responseErrors?.non_field_errors || [],
)
: html``}
<div class="pf-c-form__group pf-m-action">
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
${t`Continue`}
</button>
</div>
</form>
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links"></ul>
</footer>`;
}
renderCode(): TemplateResult {
return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
</header>
<div class="pf-c-login__main-body">
<form
class="pf-c-form"
@submit=${(e: Event) => {
this.submitForm(e);
}}
>
<ak-form-static
class="pf-c-form__group"
userAvatar="${this.challenge.pendingUserAvatar}"
user=${this.challenge.pendingUser}
>
<div slot="link">
<a href="${ifDefined(this.challenge.flowInfo?.cancelUrl)}"
>${t`Not you?`}</a
>
</div>
</ak-form-static>
<ak-form-element
label="${t`Code`}"
?required="${true}"
class="pf-c-form__group"
.errors=${(this.challenge?.responseErrors || {})["code"]}
>
<!-- @ts-ignore -->
<input
type="text"
name="code"
inputmode="numeric"
pattern="[0-9]*"
placeholder="${t`Please enter your TOTP Code`}"
autofocus=""
autocomplete="one-time-code"
class="pf-c-form-control"
required
/>
</ak-form-element>
${"non_field_errors" in (this.challenge?.responseErrors || {})
? this.renderNonFieldErrors(
this.challenge?.responseErrors?.non_field_errors || [],
)
: html``}
<div class="pf-c-form__group pf-m-action">
<button type="submit" class="pf-c-button pf-m-primary pf-m-block">
${t`Continue`}
</button>
</div>
</form>
</div>
<footer class="pf-c-login__main-footer">
<ul class="pf-c-login__main-footer-links"></ul>
</footer>`;
}
render(): TemplateResult {
if (!this.challenge) {
return html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`;
}
if (this.challenge.phoneNumberRequired) {
return this.renderPhoneNumber();
}
return this.renderCode();
}
}

View File

@ -15,21 +15,17 @@ import {
AuthenticatorValidationChallenge,
AuthenticatorValidationChallengeResponseRequest,
DeviceChallenge,
DeviceClassesEnum,
FlowsApi,
} from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { BaseStage, StageHost } from "../base";
import { PasswordManagerPrefill } from "../identification/IdentificationStage";
import "./AuthenticatorValidateStageCode";
import "./AuthenticatorValidateStageDuo";
import "./AuthenticatorValidateStageWebAuthn";
export enum DeviceClasses {
STATIC = "static",
TOTP = "totp",
WEBAUTHN = "webauthn",
DUO = "duo",
}
@customElement("ak-stage-authenticator-validate")
export class AuthenticatorValidateStage
extends BaseStage<
@ -38,8 +34,29 @@ export class AuthenticatorValidateStage
>
implements StageHost
{
flowSlug = "";
_selectedDeviceChallenge?: DeviceChallenge;
@property({ attribute: false })
selectedDeviceChallenge?: DeviceChallenge;
set selectedDeviceChallenge(value: DeviceChallenge | undefined) {
this._selectedDeviceChallenge = value;
// We don't use this.submit here, as we don't want to advance the flow.
// We just want to notify the backend which challenge has been selected.
new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolve({
flowSlug: this.host.flowSlug,
query: window.location.search.substring(1),
flowChallengeResponseRequest: {
// @ts-ignore
component: this.challenge.component || "",
selectedChallenge: value,
},
});
}
get selectedDeviceChallenge(): DeviceChallenge | undefined {
return this._selectedDeviceChallenge;
}
submit(payload: AuthenticatorValidationChallengeResponseRequest): Promise<void> {
return this.host?.submit(payload) || Promise.resolve();
@ -74,7 +91,7 @@ export class AuthenticatorValidateStage
renderDevicePickerSingle(deviceChallenge: DeviceChallenge): TemplateResult {
switch (deviceChallenge.deviceClass) {
case DeviceClasses.DUO:
case DeviceClassesEnum.Duo:
return html`<i class="fas fa-mobile-alt"></i>
<div class="right">
<p>${t`Duo push-notifications`}</p>
@ -82,37 +99,30 @@ export class AuthenticatorValidateStage
>${t`Receive a push notification on your phone to prove your identity.`}</small
>
</div>`;
case DeviceClasses.WEBAUTHN:
case DeviceClassesEnum.Webauthn:
return html`<i class="fas fa-mobile-alt"></i>
<div class="right">
<p>${t`Authenticator`}</p>
<small>${t`Use a security key to prove your identity.`}</small>
</div>`;
case DeviceClasses.TOTP:
// TOTP is a bit special, assuming that TOTP is allowed from the backend,
// and we have a pre-filled value from the password manager,
// directly set the the TOTP device Challenge as active.
if (PasswordManagerPrefill.totp) {
console.debug(
"authentik/stages/authenticator_validate: found prefill totp code, selecting totp challenge",
);
this.selectedDeviceChallenge = deviceChallenge;
// Delay the update as a re-render isn't triggered from here
setTimeout(() => {
this.requestUpdate();
}, 100);
}
case DeviceClassesEnum.Totp:
return html`<i class="fas fa-clock"></i>
<div class="right">
<p>${t`Traditional authenticator`}</p>
<small>${t`Use a code-based authenticator.`}</small>
</div>`;
case DeviceClasses.STATIC:
case DeviceClassesEnum.Static:
return html`<i class="fas fa-key"></i>
<div class="right">
<p>${t`Recovery keys`}</p>
<small>${t`In case you can't access any other method.`}</small>
</div>`;
case DeviceClassesEnum.Sms:
return html`<i class="fas fa-mobile"></i>
<div class="right">
<p>${t`SMS`}</p>
<small>${t`Tokens sent via SMS.`}</small>
</div>`;
default:
break;
}
@ -142,8 +152,9 @@ export class AuthenticatorValidateStage
return html``;
}
switch (this.selectedDeviceChallenge?.deviceClass) {
case DeviceClasses.STATIC:
case DeviceClasses.TOTP:
case DeviceClassesEnum.Static:
case DeviceClassesEnum.Totp:
case DeviceClassesEnum.Sms:
return html`<ak-stage-authenticator-validate-code
.host=${this}
.challenge=${this.challenge}
@ -151,7 +162,7 @@ export class AuthenticatorValidateStage
.showBackButton=${(this.challenge?.deviceChallenges.length || []) > 1}
>
</ak-stage-authenticator-validate-code>`;
case DeviceClasses.WEBAUTHN:
case DeviceClassesEnum.Webauthn:
return html`<ak-stage-authenticator-validate-webauthn
.host=${this}
.challenge=${this.challenge}
@ -159,7 +170,7 @@ export class AuthenticatorValidateStage
.showBackButton=${(this.challenge?.deviceChallenges.length || []) > 1}
>
</ak-stage-authenticator-validate-webauthn>`;
case DeviceClasses.DUO:
case DeviceClassesEnum.Duo:
return html`<ak-stage-authenticator-validate-duo
.host=${this}
.challenge=${this.challenge}
@ -179,6 +190,18 @@ export class AuthenticatorValidateStage
if (this.challenge?.deviceChallenges.length === 1) {
this.selectedDeviceChallenge = this.challenge.deviceChallenges[0];
}
// TOTP is a bit special, assuming that TOTP is allowed from the backend,
// and we have a pre-filled value from the password manager,
// directly set the the TOTP device Challenge as active.
const totpChallenge = this.challenge.deviceChallenges.find(
(challenge) => challenge.deviceClass === DeviceClassesEnum.Totp,
);
if (PasswordManagerPrefill.totp && totpChallenge) {
console.debug(
"authentik/stages/authenticator_validate: found prefill totp code, selecting totp challenge",
);
this.selectedDeviceChallenge = totpChallenge;
}
return html`<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">${this.challenge.flowInfo?.title}</h1>
${this.selectedDeviceChallenge

View File

@ -16,6 +16,7 @@ import {
AuthenticatorValidationChallenge,
AuthenticatorValidationChallengeResponseRequest,
DeviceChallenge,
DeviceClassesEnum,
} from "@goauthentik/api";
import "../../../elements/EmptyState";
@ -62,6 +63,9 @@ export class AuthenticatorValidateStageWebCode extends BaseStage<
>
</div>
</ak-form-static>
${this.deviceChallenge?.deviceClass == DeviceClassesEnum.Sms
? html`<p>${t`A code has been sent to you via SMS.`}</p>`
: html``}
<ak-form-element
label="${t`Code`}"
?required="${true}"
@ -74,7 +78,7 @@ export class AuthenticatorValidateStageWebCode extends BaseStage<
name="code"
inputmode="numeric"
pattern="[0-9]*"
placeholder="${t`Please enter your TOTP Code`}"
placeholder="${t`Please enter your Code`}"
autofocus=""
autocomplete="one-time-code"
class="pf-c-form-control"

View File

@ -5,6 +5,7 @@ import { ErrorDetail } from "@goauthentik/api";
export interface StageHost {
challenge?: unknown;
flowSlug: string;
submit(payload: unknown): Promise<void>;
}

View File

@ -56,6 +56,10 @@ msgstr "6 digits, widely compatible"
msgid "8 digits, not compatible with apps like Google Authenticator"
msgstr "8 digits, not compatible with apps like Google Authenticator"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "A code has been sent to you via SMS."
msgstr "A code has been sent to you via SMS."
#: src/interfaces/AdminInterface.ts
msgid "A newer version of the frontend is available."
msgstr "A newer version of the frontend is available."
@ -415,7 +419,7 @@ msgstr "Audience"
msgid "Authenticating with Plex..."
msgstr "Authenticating with Plex..."
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Authentication"
msgstr "Authentication"
@ -431,7 +435,7 @@ msgstr "Authentication flow"
msgid "Authenticator"
msgstr "Authenticator"
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Authorization"
msgstr "Authorization"
@ -805,6 +809,7 @@ msgstr "Client type"
msgid "Close"
msgstr "Close"
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "Code"
@ -835,6 +840,7 @@ msgid "Configuration error"
msgstr "Configuration error"
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/password/PasswordStageForm.ts
@ -960,6 +966,8 @@ msgstr "Consumer secret"
msgid "Context"
msgstr "Context"
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
@ -1333,7 +1341,6 @@ msgid "Designates whether this user should be treated as active. Unselect this i
msgstr "Designates whether this user should be treated as active. Unselect this instead of deleting accounts."
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/FlowListPage.ts
msgid "Designation"
msgstr "Designation"
@ -1383,6 +1390,10 @@ msgstr "Digits"
msgid "Disable Duo authenticator"
msgstr "Disable Duo authenticator"
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
msgid "Disable SMS authenticator"
msgstr "Disable SMS authenticator"
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
msgid "Disable Static Tokens"
msgstr "Disable Static Tokens"
@ -1540,6 +1551,10 @@ msgstr "Embedded outpost is not configured correctly."
msgid "Enable Duo authenticator"
msgstr "Enable Duo authenticator"
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
msgid "Enable SMS authenticator"
msgstr "Enable SMS authenticator"
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Enable StartTLS"
msgstr "Enable StartTLS"
@ -1569,7 +1584,7 @@ msgstr "Enabled"
msgid "Enabling this toggle will create a group named after the user, with the user as member."
msgstr "Enabling this toggle will create a group named after the user, with the user as member."
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Enrollment"
msgstr "Enrollment"
@ -1870,6 +1885,7 @@ msgid "Flow used by an authenticated user to configure their password. If empty,
msgstr "Flow used by an authenticated user to configure their password. If empty, user will not be able to configure change their password."
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage."
@ -1954,6 +1970,10 @@ msgstr "From"
msgid "From address"
msgstr "From address"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "From number"
msgstr "From number"
#: src/pages/providers/ldap/LDAPProviderForm.ts
msgid "GID start number"
msgstr "GID start number"
@ -1975,6 +1995,11 @@ msgstr "Generate"
msgid "Generate Certificate-Key Pair"
msgstr "Generate Certificate-Key Pair"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Get this value from https://console.twilio.com"
msgstr "Get this value from https://console.twilio.com"
#:
#~ msgid "Go to admin interface"
#~ msgstr "Go to admin interface"
@ -2260,7 +2285,7 @@ msgstr "Internal host SSL Validation"
msgid "Invalid response action"
msgstr "Invalid response action"
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Invalidation"
msgstr "Invalidation"
@ -2415,6 +2440,7 @@ msgstr "Load servers"
#: src/flows/FlowInspector.ts
#: src/flows/access_denied/FlowAccessDenied.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
@ -2484,6 +2510,7 @@ msgstr "Loading"
#: src/pages/sources/saml/SAMLSourceForm.ts
#: src/pages/sources/saml/SAMLSourceForm.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -2721,6 +2748,7 @@ msgstr "My applications"
#: src/pages/sources/saml/SAMLSourceViewPage.ts
#: src/pages/stages/StageListPage.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -2883,6 +2911,8 @@ msgid "Not used by any other object."
msgstr "Not used by any other object."
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
@ -2931,6 +2961,10 @@ msgstr "Notifications"
msgid "Number"
msgstr "Number"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr "Number the SMS will be sent from."
#: src/pages/users/UserViewPage.ts
msgid "OAuth Authorization Codes"
msgstr "OAuth Authorization Codes"
@ -3147,6 +3181,10 @@ msgstr "Password: Masked input, password is validated against sources. Policies
msgid "Persistent"
msgstr "Persistent"
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
msgid "Phone number"
msgstr "Phone number"
#: src/pages/stages/prompt/PromptForm.ts
msgid "Placeholder"
msgstr "Placeholder"
@ -3155,8 +3193,16 @@ msgstr "Placeholder"
msgid "Plan history"
msgstr "Plan history"
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "Please enter your Code"
msgstr "Please enter your Code"
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
msgid "Please enter your Phone number."
msgstr "Please enter your Phone number."
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
msgid "Please enter your TOTP Code"
msgstr "Please enter your TOTP Code"
@ -3339,6 +3385,7 @@ msgstr "Provide support for protocols like SAML and OAuth to assigned applicatio
#: src/pages/applications/ApplicationForm.ts
#: src/pages/applications/ApplicationListPage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Provider"
msgstr "Provider"
@ -3434,7 +3481,7 @@ msgstr "Re-evaluate policies"
msgid "Receive a push notification on your phone to prove your identity."
msgstr "Receive a push notification on your phone to prove your identity."
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/users/UserListPage.ts
msgid "Recovery"
@ -3622,6 +3669,15 @@ msgstr "SHA512"
msgid "SLO URL"
msgstr "SLO URL"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
msgid "SMS"
msgstr "SMS"
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "SMS-based Authenticators"
msgstr "SMS-based Authenticators"
#: src/pages/stages/email/EmailStageForm.ts
msgid "SMTP Host"
msgstr "SMTP Host"
@ -3978,7 +4034,7 @@ msgstr "Stage"
msgid "Stage Bindings"
msgstr "Stage Bindings"
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Stage Configuration"
msgstr "Stage Configuration"
@ -4026,6 +4082,10 @@ msgstr "Stage used to configure a duo-based authenticator. This stage should be
msgid "Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows."
msgstr "Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows."
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Stage used to configure an SMS-based TOTP authenticator."
msgstr "Stage used to configure an SMS-based TOTP authenticator."
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to validate any authenticator. This stage should be used during authentication or authorization flows."
msgstr "Stage used to validate any authenticator. This stage should be used during authentication or authorization flows."
@ -4035,6 +4095,7 @@ msgid "Stage(s)"
msgstr "Stage(s)"
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -4086,12 +4147,14 @@ msgid "Status"
msgstr "Status"
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Disabled"
msgstr "Status: Disabled"
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Enabled"
@ -4218,6 +4281,7 @@ msgid "Successfully created source."
msgstr "Successfully created source."
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -4375,6 +4439,7 @@ msgid "Successfully updated source."
msgstr "Successfully updated source."
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -4731,6 +4796,10 @@ msgstr "Tokens and App passwords"
msgid "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access."
msgstr "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access."
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
msgid "Tokens sent via SMS."
msgstr "Tokens sent via SMS."
#: src/pages/admin-overview/charts/FlowStatusChart.ts
msgid "Total flows"
msgstr "Total flows"
@ -4759,6 +4828,18 @@ msgstr "Transient"
msgid "Transports"
msgstr "Transports"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio"
msgstr "Twilio"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio Account SID"
msgstr "Twilio Account SID"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio Auth Token"
msgstr "Twilio Auth Token"
#: src/pages/flows/BoundStagesList.ts
#: src/pages/outposts/OutpostForm.ts
#: src/pages/outposts/ServiceConnectionListPage.ts
@ -4816,7 +4897,7 @@ msgstr "URL used to request the initial token. This URL is only required for OAu
msgid "Unbound policies"
msgstr "Unbound policies"
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Unenrollment"
msgstr "Unenrollment"

View File

@ -62,6 +62,10 @@ msgstr "6 chiffres, compatibilité large"
msgid "8 digits, not compatible with apps like Google Authenticator"
msgstr "8 chiffres, incompatible avec certaines applications telles que Google Authenticator"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "A code has been sent to you via SMS."
msgstr ""
#: src/interfaces/AdminInterface.ts
msgid "A newer version of the frontend is available."
msgstr "Une nouvelle version de l'interface est disponible."
@ -419,7 +423,7 @@ msgstr "Audience"
msgid "Authenticating with Plex..."
msgstr "Authentification avec Plex..."
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Authentication"
msgstr "Authentification"
@ -435,7 +439,7 @@ msgstr "Flux d'authentification"
msgid "Authenticator"
msgstr "Authentificateur"
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Authorization"
msgstr "Authorisation"
@ -806,6 +810,7 @@ msgstr "Type du client"
msgid "Close"
msgstr "Fermer"
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "Code"
@ -836,6 +841,7 @@ msgid "Configuration error"
msgstr "Erreur de configuration"
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/password/PasswordStageForm.ts
@ -958,6 +964,8 @@ msgstr "Secret consumer"
msgid "Context"
msgstr "Contexte"
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
@ -1323,7 +1331,6 @@ msgid "Designates whether this user should be treated as active. Unselect this i
msgstr "Indique si cet utilisateur doit être traité comme actif. Désélectionnez cette option au lieu de supprimer les comptes."
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/FlowListPage.ts
msgid "Designation"
msgstr "Désignation"
@ -1371,6 +1378,10 @@ msgstr "Chiffres"
msgid "Disable Duo authenticator"
msgstr "Désactiver l'authentificateur Duo"
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
msgid "Disable SMS authenticator"
msgstr ""
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
msgid "Disable Static Tokens"
msgstr "Désactiver les jetons statiques"
@ -1526,6 +1537,10 @@ msgstr "L'avant poste intégré n'est pas configuré correctement"
msgid "Enable Duo authenticator"
msgstr "Activer l'authentificateur Duo"
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
msgid "Enable SMS authenticator"
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Enable StartTLS"
msgstr "Activer StartTLS"
@ -1555,7 +1570,7 @@ msgstr "Activé"
msgid "Enabling this toggle will create a group named after the user, with the user as member."
msgstr "Activer cette option va créer un groupe du même nom que l'utilisateur dont il sera membre."
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Enrollment"
msgstr "Inscription"
@ -1855,6 +1870,7 @@ msgid "Flow used by an authenticated user to configure their password. If empty,
msgstr "Flux utilisé par un utilisateur authentifié pour configurer son mot de passe. S'il est vide, l'utilisateur ne sera pas en mesure de changer son mot de passe."
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage."
@ -1939,6 +1955,10 @@ msgstr "De"
msgid "From address"
msgstr "Adresse d'origine"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "From number"
msgstr ""
#: src/pages/providers/ldap/LDAPProviderForm.ts
msgid "GID start number"
msgstr "Numéro de départ du GID"
@ -1960,6 +1980,11 @@ msgstr "Générer"
msgid "Generate Certificate-Key Pair"
msgstr "Générer une paire clé/certificat"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Get this value from https://console.twilio.com"
msgstr ""
#~ msgid "Go to admin interface"
#~ msgstr "Aller à l'interface d'administration"
@ -2243,7 +2268,7 @@ msgstr "Validation SSL de l'hôte interne"
msgid "Invalid response action"
msgstr "Action de réponse invalide"
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Invalidation"
msgstr "Invalidation"
@ -2396,6 +2421,7 @@ msgstr "Charger les serveurs"
#: src/flows/FlowInspector.ts
#: src/flows/access_denied/FlowAccessDenied.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
@ -2465,6 +2491,7 @@ msgstr "Chargement en cours"
#: src/pages/sources/saml/SAMLSourceForm.ts
#: src/pages/sources/saml/SAMLSourceForm.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -2701,6 +2728,7 @@ msgstr "Mes applications"
#: src/pages/sources/saml/SAMLSourceViewPage.ts
#: src/pages/stages/StageListPage.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -2863,6 +2891,8 @@ msgid "Not used by any other object."
msgstr "Pas utilisé par un autre objet."
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
@ -2909,6 +2939,10 @@ msgstr "Notifications"
msgid "Number"
msgstr "Nombre"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr ""
#: src/pages/users/UserViewPage.ts
msgid "OAuth Authorization Codes"
msgstr "Code d'autorisation OAuth"
@ -3121,6 +3155,10 @@ msgstr "Mot de passe : Entrée masquée, le mot de passe est vérifié par les s
msgid "Persistent"
msgstr "Persistant"
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
msgid "Phone number"
msgstr ""
#: src/pages/stages/prompt/PromptForm.ts
msgid "Placeholder"
msgstr "Par défaut"
@ -3129,8 +3167,16 @@ msgstr "Par défaut"
msgid "Plan history"
msgstr "Historique du plan"
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "Please enter your Code"
msgstr ""
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
msgid "Please enter your Phone number."
msgstr ""
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
msgid "Please enter your TOTP Code"
msgstr "Veuillez saisirvotre code TOTP"
@ -3310,6 +3356,7 @@ msgstr "Assure la prise en charge de protocoles tels que SAML et OAuth aux appli
#: src/pages/applications/ApplicationForm.ts
#: src/pages/applications/ApplicationListPage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Provider"
msgstr "Fournisseur"
@ -3404,7 +3451,7 @@ msgstr "Ré-évaluer les politiques"
msgid "Receive a push notification on your phone to prove your identity."
msgstr "Recevez une notification push sur votre téléphone pour prouver votre identité."
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/users/UserListPage.ts
msgid "Recovery"
@ -3590,6 +3637,15 @@ msgstr "SHA512"
msgid "SLO URL"
msgstr "URL SLO"
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
msgid "SMS"
msgstr ""
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "SMS-based Authenticators"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts
msgid "SMTP Host"
msgstr "Hôte SMTP"
@ -3937,7 +3993,7 @@ msgstr "Étape"
msgid "Stage Bindings"
msgstr "Liaisons de l'étape"
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Stage Configuration"
msgstr "Configuration de l'étape"
@ -3984,6 +4040,10 @@ msgstr "Étape de configuration d'un authentificateur Duo. Cette étape devrait
msgid "Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows."
msgstr "Étape de configuration d'un authentificateur statique (jetons statiques). Cette étape devrait être utilisée en flux de configuration."
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Stage used to configure an SMS-based TOTP authenticator."
msgstr ""
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to validate any authenticator. This stage should be used during authentication or authorization flows."
msgstr "Étape utilisée pour valider tout type d'authentificateur. Cette étape devrait être utilisée en flux d'authentification ou d'autorisation."
@ -3993,6 +4053,7 @@ msgid "Stage(s)"
msgstr "Étape(s)"
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -4044,12 +4105,14 @@ msgid "Status"
msgstr "Statut"
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Disabled"
msgstr "Statut : Désactivé"
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Enabled"
@ -4174,6 +4237,7 @@ msgid "Successfully created source."
msgstr "Source créée avec succès"
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -4329,6 +4393,7 @@ msgid "Successfully updated source."
msgstr "Source mise à jour avec succès"
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -4673,6 +4738,10 @@ msgstr "Jetons et mots de passe d'application"
msgid "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access."
msgstr "Les jetons sont utilisés dans authentik pour les étapes de validation des courriels, les clés de récupération et l'accès aux API."
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
msgid "Tokens sent via SMS."
msgstr ""
#: src/pages/admin-overview/charts/FlowStatusChart.ts
msgid "Total flows"
msgstr "Flux totaux"
@ -4701,6 +4770,18 @@ msgstr "Transitoire"
msgid "Transports"
msgstr "Transports"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio Account SID"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio Auth Token"
msgstr ""
#: src/pages/flows/BoundStagesList.ts
#: src/pages/outposts/OutpostForm.ts
#: src/pages/outposts/ServiceConnectionListPage.ts
@ -4758,7 +4839,7 @@ msgstr "URL utilisée pour demander le jeton initial. Cette URL est uniquement r
msgid "Unbound policies"
msgstr "Politiques non liées"
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Unenrollment"
msgstr "Désinscription"

View File

@ -56,6 +56,10 @@ msgstr ""
msgid "8 digits, not compatible with apps like Google Authenticator"
msgstr ""
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "A code has been sent to you via SMS."
msgstr ""
#: src/interfaces/AdminInterface.ts
msgid "A newer version of the frontend is available."
msgstr ""
@ -411,7 +415,7 @@ msgstr ""
msgid "Authenticating with Plex..."
msgstr ""
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Authentication"
msgstr ""
@ -427,7 +431,7 @@ msgstr ""
msgid "Authenticator"
msgstr ""
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Authorization"
msgstr ""
@ -799,6 +803,7 @@ msgstr ""
msgid "Close"
msgstr ""
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "Code"
@ -829,6 +834,7 @@ msgid "Configuration error"
msgstr ""
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/password/PasswordStageForm.ts
@ -954,6 +960,8 @@ msgstr ""
msgid "Context"
msgstr ""
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
@ -1325,7 +1333,6 @@ msgid "Designates whether this user should be treated as active. Unselect this i
msgstr ""
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/FlowListPage.ts
msgid "Designation"
msgstr ""
@ -1375,6 +1382,10 @@ msgstr ""
msgid "Disable Duo authenticator"
msgstr ""
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
msgid "Disable SMS authenticator"
msgstr ""
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
msgid "Disable Static Tokens"
msgstr ""
@ -1532,6 +1543,10 @@ msgstr ""
msgid "Enable Duo authenticator"
msgstr ""
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
msgid "Enable SMS authenticator"
msgstr ""
#: src/pages/sources/ldap/LDAPSourceForm.ts
msgid "Enable StartTLS"
msgstr ""
@ -1561,7 +1576,7 @@ msgstr ""
msgid "Enabling this toggle will create a group named after the user, with the user as member."
msgstr ""
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Enrollment"
msgstr ""
@ -1862,6 +1877,7 @@ msgid "Flow used by an authenticated user to configure their password. If empty,
msgstr ""
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
msgid "Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage."
@ -1946,6 +1962,10 @@ msgstr ""
msgid "From address"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "From number"
msgstr ""
#: src/pages/providers/ldap/LDAPProviderForm.ts
msgid "GID start number"
msgstr ""
@ -1967,6 +1987,11 @@ msgstr ""
msgid "Generate Certificate-Key Pair"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Get this value from https://console.twilio.com"
msgstr ""
#:
#~ msgid "Go to admin interface"
#~ msgstr ""
@ -2252,7 +2277,7 @@ msgstr ""
msgid "Invalid response action"
msgstr ""
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Invalidation"
msgstr ""
@ -2407,6 +2432,7 @@ msgstr ""
#: src/flows/FlowInspector.ts
#: src/flows/access_denied/FlowAccessDenied.ts
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
@ -2476,6 +2502,7 @@ msgstr ""
#: src/pages/sources/saml/SAMLSourceForm.ts
#: src/pages/sources/saml/SAMLSourceForm.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -2713,6 +2740,7 @@ msgstr ""
#: src/pages/sources/saml/SAMLSourceViewPage.ts
#: src/pages/stages/StageListPage.ts
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -2875,6 +2903,8 @@ msgid "Not used by any other object."
msgstr ""
#: src/flows/stages/authenticator_duo/AuthenticatorDuoStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_static/AuthenticatorStaticStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
@ -2923,6 +2953,10 @@ msgstr ""
msgid "Number"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr ""
#: src/pages/users/UserViewPage.ts
msgid "OAuth Authorization Codes"
msgstr ""
@ -3139,6 +3173,10 @@ msgstr ""
msgid "Persistent"
msgstr ""
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
msgid "Phone number"
msgstr ""
#: src/pages/stages/prompt/PromptForm.ts
msgid "Placeholder"
msgstr ""
@ -3147,8 +3185,16 @@ msgstr ""
msgid "Plan history"
msgstr ""
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStageCode.ts
msgid "Please enter your Code"
msgstr ""
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
msgid "Please enter your Phone number."
msgstr ""
#: src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
#: src/flows/stages/authenticator_totp/AuthenticatorTOTPStage.ts
msgid "Please enter your TOTP Code"
msgstr ""
@ -3331,6 +3377,7 @@ msgstr ""
#: src/pages/applications/ApplicationForm.ts
#: src/pages/applications/ApplicationListPage.ts
#: src/pages/applications/ApplicationViewPage.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Provider"
msgstr ""
@ -3426,7 +3473,7 @@ msgstr ""
msgid "Receive a push notification on your phone to prove your identity."
msgstr ""
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
#: src/pages/tokens/TokenListPage.ts
#: src/pages/users/UserListPage.ts
msgid "Recovery"
@ -3614,6 +3661,15 @@ msgstr ""
msgid "SLO URL"
msgstr ""
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
msgid "SMS"
msgstr ""
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "SMS-based Authenticators"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts
msgid "SMTP Host"
msgstr ""
@ -3970,7 +4026,7 @@ msgstr ""
msgid "Stage Bindings"
msgstr ""
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Stage Configuration"
msgstr ""
@ -4018,6 +4074,10 @@ msgstr ""
msgid "Stage used to configure a static authenticator (i.e. static tokens). This stage should be used for configuration flows."
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Stage used to configure an SMS-based TOTP authenticator."
msgstr ""
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
msgid "Stage used to validate any authenticator. This stage should be used during authentication or authorization flows."
msgstr ""
@ -4027,6 +4087,7 @@ msgid "Stage(s)"
msgstr ""
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -4078,12 +4139,14 @@ msgid "Status"
msgstr ""
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Disabled"
msgstr ""
#: src/user/user-settings/stages/UserSettingsAuthenticatorDuo.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorSMS.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorStatic.ts
#: src/user/user-settings/stages/UserSettingsAuthenticatorTOTP.ts
msgid "Status: Enabled"
@ -4210,6 +4273,7 @@ msgid "Successfully created source."
msgstr ""
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -4367,6 +4431,7 @@ msgid "Successfully updated source."
msgstr ""
#: src/pages/stages/authenticator_duo/AuthenticatorDuoStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_static/AuthenticatorStaticStageForm.ts
#: src/pages/stages/authenticator_totp/AuthenticatorTOTPStageForm.ts
#: src/pages/stages/authenticator_validate/AuthenticatorValidateStageForm.ts
@ -4716,6 +4781,10 @@ msgstr ""
msgid "Tokens are used throughout authentik for Email validation stages, Recovery keys and API access."
msgstr ""
#: src/flows/stages/authenticator_validate/AuthenticatorValidateStage.ts
msgid "Tokens sent via SMS."
msgstr ""
#: src/pages/admin-overview/charts/FlowStatusChart.ts
msgid "Total flows"
msgstr ""
@ -4744,6 +4813,18 @@ msgstr ""
msgid "Transports"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio Account SID"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Twilio Auth Token"
msgstr ""
#: src/pages/flows/BoundStagesList.ts
#: src/pages/outposts/OutpostForm.ts
#: src/pages/outposts/ServiceConnectionListPage.ts
@ -4801,7 +4882,7 @@ msgstr ""
msgid "Unbound policies"
msgstr ""
#: src/pages/flows/FlowForm.ts
#: src/pages/flows/utils.ts
msgid "Unenrollment"
msgstr ""

View File

@ -17,6 +17,7 @@ import { config, DEFAULT_CONFIG } from "../../api/Config";
import "../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../elements/forms/ModelForm";
import { first } from "../../utils";
import { DesignationToLabel } from "./utils";
@customElement("ak-flow-form")
export class FlowForm extends ModelForm<Flow, string> {
@ -80,43 +81,43 @@ export class FlowForm extends ModelForm<Flow, string> {
value=${FlowDesignationEnum.Authentication}
?selected=${this.instance?.designation === FlowDesignationEnum.Authentication}
>
${t`Authentication`}
${DesignationToLabel(FlowDesignationEnum.Authentication)}
</option>
<option
value=${FlowDesignationEnum.Authorization}
?selected=${this.instance?.designation === FlowDesignationEnum.Authorization}
>
${t`Authorization`}
${DesignationToLabel(FlowDesignationEnum.Authorization)}
</option>
<option
value=${FlowDesignationEnum.Enrollment}
?selected=${this.instance?.designation === FlowDesignationEnum.Enrollment}
>
${t`Enrollment`}
${DesignationToLabel(FlowDesignationEnum.Enrollment)}
</option>
<option
value=${FlowDesignationEnum.Invalidation}
?selected=${this.instance?.designation === FlowDesignationEnum.Invalidation}
>
${t`Invalidation`}
${DesignationToLabel(FlowDesignationEnum.Invalidation)}
</option>
<option
value=${FlowDesignationEnum.Recovery}
?selected=${this.instance?.designation === FlowDesignationEnum.Recovery}
>
${t`Recovery`}
${DesignationToLabel(FlowDesignationEnum.Recovery)}
</option>
<option
value=${FlowDesignationEnum.StageConfiguration}
?selected=${this.instance?.designation === FlowDesignationEnum.StageConfiguration}
>
${t`Stage Configuration`}
${DesignationToLabel(FlowDesignationEnum.StageConfiguration)}
</option>
<option
value=${FlowDesignationEnum.Unenrollment}
?selected=${this.instance?.designation === FlowDesignationEnum.Unenrollment}
>
${t`Unenrollment`}
${DesignationToLabel(FlowDesignationEnum.Unenrollment)}
</option>
`;
}

View File

@ -14,8 +14,10 @@ import "../../elements/forms/DeleteBulkForm";
import "../../elements/forms/ModalForm";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
import { groupBy } from "../../utils";
import "./FlowForm";
import "./FlowImportForm";
import { DesignationToLabel } from "./utils";
@customElement("ak-flow-list")
export class FlowListPage extends TablePage<Flow> {
@ -46,11 +48,19 @@ export class FlowListPage extends TablePage<Flow> {
});
}
groupBy(items: Flow[]): [string, Flow[]][] {
return groupBy(items, (flow) => {
if (!flow.designation) {
return "";
}
return DesignationToLabel(flow.designation);
});
}
columns(): TableColumn[] {
return [
new TableColumn(t`Identifier`, "slug"),
new TableColumn(t`Name`, "name"),
new TableColumn(t`Designation`, "designation"),
new TableColumn(t`Stages`),
new TableColumn(t`Policies`),
new TableColumn(t`Actions`),
@ -85,7 +95,6 @@ export class FlowListPage extends TablePage<Flow> {
<code>${item.slug}</code>
</a>`,
html`${item.name}`,
html`${item.designation}`,
html`${Array.from(item.stages || []).length}`,
html`${Array.from(item.policies || []).length}`,
html` <ak-forms-modal>

View File

@ -0,0 +1,22 @@
import { t } from "@lingui/macro";
import { FlowDesignationEnum } from "@goauthentik/api";
export function DesignationToLabel(designation: FlowDesignationEnum): string {
switch (designation) {
case FlowDesignationEnum.Authentication:
return t`Authentication`;
case FlowDesignationEnum.Authorization:
return t`Authorization`;
case FlowDesignationEnum.Enrollment:
return t`Enrollment`;
case FlowDesignationEnum.Invalidation:
return t`Invalidation`;
case FlowDesignationEnum.Recovery:
return t`Recovery`;
case FlowDesignationEnum.StageConfiguration:
return t`Stage Configuration`;
case FlowDesignationEnum.Unenrollment:
return t`Unenrollment`;
}
}

View File

@ -18,6 +18,7 @@ import "../../elements/forms/ModalForm";
import "../../elements/forms/ProxyForm";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
import { groupBy } from "../../utils";
import "./PolicyTestForm";
import "./dummy/DummyPolicyForm";
import "./event_matcher/EventMatcherPolicyForm";
@ -64,6 +65,10 @@ export class PolicyListPage extends TablePage<Policy> {
];
}
groupBy(items: Policy[]): [string, Policy[]][] {
return groupBy(items, (policy) => policy.verboseNamePlural);
}
row(item: Policy): TemplateResult[] {
return [
html`<div>

View File

@ -17,6 +17,7 @@ import "../../elements/forms/ModalForm";
import "../../elements/forms/ProxyForm";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
import { groupBy } from "../../utils";
import "./PropertyMappingLDAPForm";
import "./PropertyMappingNotification";
import "./PropertyMappingSAMLForm";
@ -56,6 +57,10 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
});
}
groupBy(items: PropertyMapping[]): [string, PropertyMapping[]][] {
return groupBy(items, (mapping) => mapping.verboseNamePlural);
}
columns(): TableColumn[] {
return [
new TableColumn(t`Name`, "name"),

View File

@ -17,7 +17,9 @@ import "../../elements/forms/ModalForm";
import "../../elements/forms/ProxyForm";
import { TableColumn } from "../../elements/table/Table";
import { TablePage } from "../../elements/table/TablePage";
import { groupBy } from "../../utils";
import "./authenticator_duo/AuthenticatorDuoStageForm.ts";
import "./authenticator_sms/AuthenticatorSMSStageForm.ts";
import "./authenticator_static/AuthenticatorStaticStageForm.ts";
import "./authenticator_totp/AuthenticatorTOTPStageForm.ts";
import "./authenticator_validate/AuthenticatorValidateStageForm.ts";
@ -65,6 +67,10 @@ export class StageListPage extends TablePage<Stage> {
});
}
groupBy(items: Stage[]): [string, Stage[]][] {
return groupBy(items, (stage) => stage.verboseNamePlural);
}
columns(): TableColumn[] {
return [
new TableColumn(t`Name`, "name"),

View File

@ -0,0 +1,169 @@
import { t } from "@lingui/macro";
import { html, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import { until } from "lit/directives/until";
import {
FlowsApi,
StagesApi,
FlowsInstancesListDesignationEnum,
AuthenticatorSMSStage,
ProviderEnum,
} from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/FormGroup";
import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm";
@customElement("ak-stage-authenticator-sms-form")
export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage, string> {
loadInstance(pk: string): Promise<AuthenticatorSMSStage> {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorSmsRetrieve({
stageUuid: pk,
});
}
getSuccessMessage(): string {
if (this.instance) {
return t`Successfully updated stage.`;
} else {
return t`Successfully created stage.`;
}
}
send = (data: AuthenticatorSMSStage): Promise<AuthenticatorSMSStage> => {
if (this.instance) {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorSmsUpdate({
stageUuid: this.instance.pk || "",
authenticatorSMSStageRequest: data,
});
} else {
return new StagesApi(DEFAULT_CONFIG).stagesAuthenticatorSmsCreate({
authenticatorSMSStageRequest: data,
});
}
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<div class="form-help-text">
${t`Stage used to configure an SMS-based TOTP authenticator.`}
</div>
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
<input
type="text"
value="${ifDefined(this.instance?.name || "")}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>
<ak-form-group .expanded=${true}>
<span slot="header"> ${t`Stage-specific settings`} </span>
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal
label=${t`Provider`}
?required=${true}
name="provider"
>
<select name="users" class="pf-c-form-control">
<option
value="${ProviderEnum.Twilio}"
?selected=${this.instance?.provider === ProviderEnum.Twilio}
>
${t`Twilio`}
</option>
</select>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`From number`}
?required=${true}
name="fromNumber"
>
<input
type="text"
value="${ifDefined(this.instance?.fromNumber || "")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Number the SMS will be sent from.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Twilio Account SID`}
?required=${true}
name="twilioAccountSid"
>
<input
type="text"
value="${ifDefined(this.instance?.twilioAccountSid || "")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Get this value from https://console.twilio.com`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${t`Twilio Auth Token`}
?required=${true}
name="twilioAuth"
>
<input
type="text"
value="${ifDefined(this.instance?.twilioAuth || "")}"
class="pf-c-form-control"
required
/>
<p class="pf-c-form__helper-text">
${t`Get this value from https://console.twilio.com`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Configuration flow`} name="configureFlow">
<select class="pf-c-form-control">
<option
value=""
?selected=${this.instance?.configureFlow === undefined}
>
---------
</option>
${until(
new FlowsApi(DEFAULT_CONFIG)
.flowsInstancesList({
ordering: "pk",
designation:
FlowsInstancesListDesignationEnum.StageConfiguration,
})
.then((flows) => {
return flows.results.map((flow) => {
let selected = this.instance?.configureFlow === flow.pk;
if (
!this.instance?.pk &&
!this.instance?.configureFlow &&
flow.slug === "default-otp-time-configure"
) {
selected = true;
}
return html`<option
value=${ifDefined(flow.pk)}
?selected=${selected}
>
${flow.name} (${flow.slug})
</option>`;
});
}),
html`<option>${t`Loading...`}</option>`,
)}
</select>
<p class="pf-c-form__helper-text">
${t`Flow used by an authenticated user to configure this Stage. If empty, user will not be able to configure this stage.`}
</p>
</ak-form-element-horizontal>
</div>
</ak-form-group>
</form>`;
}
}

View File

@ -10,6 +10,7 @@ import {
AuthenticatorTOTPStage,
StagesApi,
FlowsInstancesListDesignationEnum,
DigitsEnum,
} from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../../../api/Config";
@ -64,10 +65,16 @@ export class AuthenticatorTOTPStageForm extends ModelForm<AuthenticatorTOTPStage
<div slot="body" class="pf-c-form">
<ak-form-element-horizontal label=${t`Digits`} ?required=${true} name="digits">
<select name="users" class="pf-c-form-control">
<option value="6" ?selected=${this.instance?.digits === 6}>
<option
value="${DigitsEnum.NUMBER_6}"
?selected=${this.instance?.digits === DigitsEnum.NUMBER_6}
>
${t`6 digits, widely compatible`}
</option>
<option value="8" ?selected=${this.instance?.digits === 8}>
<option
value="${DigitsEnum.NUMBER_8}"
?selected=${this.instance?.digits === DigitsEnum.NUMBER_8}
>
${t`8 digits, not compatible with apps like Google Authenticator`}
</option>
</select>

View File

@ -109,6 +109,12 @@ export class AuthenticatorValidateStageForm extends ModelForm<AuthenticatorValid
>
${t`Duo Authenticators`}
</option>
<option
value=${DeviceClassesEnum.Sms}
?selected=${this.isDeviceClassSelected(DeviceClassesEnum.Sms)}
>
${t`SMS-based Authenticators`}
</option>
</select>
<p class="pf-c-form__helper-text">
${t`Device classes which can be used to authenticate.`}

View File

@ -12,6 +12,7 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import { EVENT_REFRESH } from "../../../constants";
import "../../../elements/EmptyState";
import "./UserSettingsAuthenticatorDuo";
import "./UserSettingsAuthenticatorSMS";
import "./UserSettingsAuthenticatorStatic";
import "./UserSettingsAuthenticatorTOTP";
import "./UserSettingsAuthenticatorWebAuthn";
@ -69,6 +70,12 @@ export class UserStageSettingsPage extends LitElement {
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-duo>`;
case "ak-user-settings-authenticator-sms":
return html`<ak-user-settings-authenticator-sms
objectId=${stage.objectUid}
.configureUrl=${stage.configureUrl}
>
</ak-user-settings-authenticator-sms>`;
default:
return html`<p>${t`Error: unsupported stage settings: ${stage.component}`}</p>`;
}

View File

@ -0,0 +1,83 @@
import { t } from "@lingui/macro";
import { html, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import { until } from "lit/directives/until";
import { AuthenticatorsApi } from "@goauthentik/api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { EVENT_REFRESH } from "../../../constants";
import { BaseUserSettings } from "../BaseUserSettings";
@customElement("ak-user-settings-authenticator-sms")
export class UserSettingsAuthenticatorSMS extends BaseUserSettings {
renderEnabled(): TemplateResult {
return html`<div class="pf-c-card__body">
<p>
${t`Status: Enabled`}
<i class="pf-icon pf-icon-ok"></i>
</p>
</div>
<div class="pf-c-card__footer">
<button
class="pf-c-button pf-m-danger"
@click=${() => {
return new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsSmsList({})
.then((devices) => {
if (devices.results.length < 1) {
return;
}
// TODO: Handle multiple devices, currently we assume only one TOTP Device
return new AuthenticatorsApi(DEFAULT_CONFIG)
.authenticatorsSmsDestroy({
id: devices.results[0].pk || 0,
})
.then(() => {
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
});
});
}}
>
${t`Disable SMS authenticator`}
</button>
</div>`;
}
renderDisabled(): TemplateResult {
return html` <div class="pf-c-card__body">
<p>
${t`Status: Disabled`}
<i class="pf-icon pf-icon-error-circle-o"></i>
</p>
</div>
<div class="pf-c-card__footer">
${this.configureUrl
? html`<a
href="${this.configureUrl}?next=/${encodeURIComponent("#/settings")}"
class="pf-c-button pf-m-primary"
>${t`Enable SMS authenticator`}
</a>`
: html``}
</div>`;
}
render(): TemplateResult {
return html`<div class="pf-c-card">
<div class="pf-c-card__title">${t`SMS`}</div>
${until(
new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsSmsList({}).then((devices) => {
return devices.results.length > 0
? this.renderEnabled()
: this.renderDisabled();
}),
)}
</div>`;
}
}