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:
parent
7bf587af24
commit
aef9d27706
43
.github/workflows/translation-compile.yml
vendored
Normal file
43
.github/workflows/translation-compile.yml
vendored
Normal 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
|
2
Makefile
2
Makefile
|
@ -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
18
authentik/api/throttle.py
Normal 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}"
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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://"
|
||||
|
|
0
authentik/stages/authenticator_sms/__init__.py
Normal file
0
authentik/stages/authenticator_sms/__init__.py
Normal file
79
authentik/stages/authenticator_sms/api.py
Normal file
79
authentik/stages/authenticator_sms/api.py
Normal 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"]
|
10
authentik/stages/authenticator_sms/apps.py
Normal file
10
authentik/stages/authenticator_sms/apps.py
Normal 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"
|
100
authentik/stages/authenticator_sms/migrations/0001_initial.py
Normal file
100
authentik/stages/authenticator_sms/migrations/0001_initial.py
Normal 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",
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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,
|
||||
),
|
||||
]
|
118
authentik/stages/authenticator_sms/models.py
Normal file
118
authentik/stages/authenticator_sms/models.py
Normal 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")
|
118
authentik/stages/authenticator_sms/stage.py
Normal file
118
authentik/stages/authenticator_sms/stage.py
Normal 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()
|
95
authentik/stages/authenticator_sms/tests.py
Normal file
95
authentik/stages/authenticator_sms/tests.py
Normal 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&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()
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
783
schema.yml
783
schema.yml
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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" : ""}"
|
||||
|
|
|
@ -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}
|
||||
|
|
158
web/src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
Normal file
158
web/src/flows/stages/authenticator_sms/AuthenticatorSMSStage.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -5,6 +5,7 @@ import { ErrorDetail } from "@goauthentik/api";
|
|||
|
||||
export interface StageHost {
|
||||
challenge?: unknown;
|
||||
flowSlug: string;
|
||||
submit(payload: unknown): Promise<void>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
22
web/src/pages/flows/utils.ts
Normal file
22
web/src/pages/flows/utils.ts
Normal 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`;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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>`;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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.`}
|
||||
|
|
|
@ -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>`;
|
||||
}
|
||||
|
|
|
@ -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>`;
|
||||
}
|
||||
}
|
Reference in a new issue