stages/authenticator_sms: verify-only (#3011)

This commit is contained in:
Jens L 2022-06-01 23:16:28 +02:00 committed by GitHub
parent fc1c1a849a
commit c0cb891078
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 372 additions and 42 deletions

View File

@ -192,7 +192,7 @@ class User(GuardianUserMixin, AbstractUser):
@property @property
def uid(self) -> str: def uid(self) -> str:
"""Generate a globall unique UID, based on the user ID and the hashed secret key""" """Generate a globally unique UID, based on the user ID and the hashed secret key"""
return sha256(f"{self.id}-{settings.SECRET_KEY}".encode("ascii")).hexdigest() return sha256(f"{self.id}-{settings.SECRET_KEY}".encode("ascii")).hexdigest()
@property @property

View File

@ -26,6 +26,7 @@ class AuthenticatorSMSStageSerializer(StageSerializer):
"auth", "auth",
"auth_password", "auth_password",
"auth_type", "auth_type",
"verify_only",
] ]

View File

@ -0,0 +1,25 @@
# Generated by Django 4.0.4 on 2022-05-24 19:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentik_stages_authenticator_sms", "0003_smsdevice_last_used_on"),
]
operations = [
migrations.AddField(
model_name="authenticatorsmsstage",
name="verify_only",
field=models.BooleanField(
default=False,
help_text="When enabled, the Phone number is only used during enrollment to verify the users authenticity. Only a hash of the phone number is saved to ensure it is not re-used in the future.",
),
),
migrations.AlterUniqueTogether(
name="smsdevice",
unique_together={("stage", "phone_number")},
),
]

View File

@ -1,4 +1,5 @@
"""OTP Time-based models""" """SMS Authenticator models"""
from hashlib import sha256
from typing import Optional from typing import Optional
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -46,6 +47,15 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage):
auth_password = models.TextField(default="", blank=True) auth_password = models.TextField(default="", blank=True)
auth_type = models.TextField(choices=SMSAuthTypes.choices, default=SMSAuthTypes.BASIC) auth_type = models.TextField(choices=SMSAuthTypes.choices, default=SMSAuthTypes.BASIC)
verify_only = models.BooleanField(
default=False,
help_text=_(
"When enabled, the Phone number is only used during enrollment to verify the "
"users authenticity. Only a hash of the phone number is saved to ensure it is "
"not re-used in the future."
),
)
def send(self, token: str, device: "SMSDevice"): def send(self, token: str, device: "SMSDevice"):
"""Send message via selected provider""" """Send message via selected provider"""
if self.provider == SMSProviders.TWILIO: if self.provider == SMSProviders.TWILIO:
@ -158,6 +168,11 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage):
verbose_name_plural = _("SMS Authenticator Setup Stages") verbose_name_plural = _("SMS Authenticator Setup Stages")
def hash_phone_number(phone_number: str) -> str:
"""Hash phone number with prefix"""
return "hash:" + sha256(phone_number.encode()).hexdigest()
class SMSDevice(SideChannelDevice): class SMSDevice(SideChannelDevice):
"""SMS Device""" """SMS Device"""
@ -170,6 +185,15 @@ class SMSDevice(SideChannelDevice):
last_t = models.DateTimeField(auto_now=True) last_t = models.DateTimeField(auto_now=True)
def set_hashed_number(self):
"""Set phone_number to hashed number"""
self.phone_number = hash_phone_number(self.phone_number)
@property
def is_hashed(self) -> bool:
"""Check if the phone number is hashed"""
return self.phone_number.startswith("hash:")
def verify_token(self, token): def verify_token(self, token):
valid = super().verify_token(token) valid = super().verify_token(token)
if valid: if valid:
@ -182,3 +206,4 @@ class SMSDevice(SideChannelDevice):
class Meta: class Meta:
verbose_name = _("SMS Device") verbose_name = _("SMS Device")
verbose_name_plural = _("SMS Devices") verbose_name_plural = _("SMS Devices")
unique_together = (("stage", "phone_number"),)

View File

@ -1,6 +1,7 @@
"""SMS Setup stage""" """SMS Setup stage"""
from typing import Optional from typing import Optional
from django.db.models import Q
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict from django.http.request import QueryDict
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -15,7 +16,11 @@ from authentik.flows.challenge import (
) )
from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER
from authentik.flows.stage import ChallengeStageView from authentik.flows.stage import ChallengeStageView
from authentik.stages.authenticator_sms.models import AuthenticatorSMSStage, SMSDevice from authentik.stages.authenticator_sms.models import (
AuthenticatorSMSStage,
SMSDevice,
hash_phone_number,
)
from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT from authentik.stages.prompt.stage import PLAN_CONTEXT_PROMPT
SESSION_KEY_SMS_DEVICE = "authentik/stages/authenticator_sms/sms_device" SESSION_KEY_SMS_DEVICE = "authentik/stages/authenticator_sms/sms_device"
@ -45,6 +50,10 @@ class AuthenticatorSMSChallengeResponse(ChallengeResponse):
stage: AuthenticatorSMSStage = self.device.stage stage: AuthenticatorSMSStage = self.device.stage
if "code" not in attrs: if "code" not in attrs:
self.device.phone_number = attrs["phone_number"] self.device.phone_number = attrs["phone_number"]
hashed_number = hash_phone_number(self.device.phone_number)
query = Q(phone_number=hashed_number) | Q(phone_number=self.device.phone_number)
if SMSDevice.objects.filter(query, stage=self.stage.executor.current_stage.pk).exists():
raise ValidationError(_("Invalid phone number"))
# No code yet, but we have a phone number, so send a verification message # No code yet, but we have a phone number, so send a verification message
stage.send(self.device.token, self.device) stage.send(self.device.token, self.device)
return super().validate(attrs) return super().validate(attrs)
@ -111,6 +120,10 @@ class AuthenticatorSMSStageView(ChallengeStageView):
device: SMSDevice = self.request.session[SESSION_KEY_SMS_DEVICE] device: SMSDevice = self.request.session[SESSION_KEY_SMS_DEVICE]
if not device.confirmed: if not device.confirmed:
return self.challenge_invalid(response) return self.challenge_invalid(response)
stage: AuthenticatorSMSStage = self.executor.current_stage
if stage.verify_only:
self.logger.debug("Hashing number on device")
device.set_hashed_number()
device.save() device.save()
del self.request.session[SESSION_KEY_SMS_DEVICE] del self.request.session[SESSION_KEY_SMS_DEVICE]
return self.executor.stage_ok() return self.executor.stage_ok()

View File

@ -2,32 +2,31 @@
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from django.urls import reverse from django.urls import reverse
from rest_framework.test import APITestCase
from authentik.core.models import User from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.challenge import ChallengeTypes from authentik.flows.models import FlowStageBinding
from authentik.flows.models import Flow, FlowDesignation, FlowStageBinding from authentik.flows.tests import FlowTestCase
from authentik.stages.authenticator_sms.models import AuthenticatorSMSStage, SMSProviders from authentik.stages.authenticator_sms.models import (
from authentik.stages.authenticator_sms.stage import SESSION_KEY_SMS_DEVICE AuthenticatorSMSStage,
SMSDevice,
SMSProviders,
hash_phone_number,
)
class AuthenticatorSMSStageTests(APITestCase): class AuthenticatorSMSStageTests(FlowTestCase):
"""Test SMS API""" """Test SMS API"""
def setUp(self) -> None: def setUp(self) -> None:
super().setUp() super().setUp()
self.flow = Flow.objects.create( self.flow = create_test_flow()
name="foo", self.stage: AuthenticatorSMSStage = AuthenticatorSMSStage.objects.create(
slug="foo",
designation=FlowDesignation.STAGE_CONFIGURATION,
)
self.stage = AuthenticatorSMSStage.objects.create(
name="foo", name="foo",
provider=SMSProviders.TWILIO, provider=SMSProviders.TWILIO,
configure_flow=self.flow, configure_flow=self.flow,
) )
FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=0) FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=0)
self.user = User.objects.create(username="foo") self.user = create_test_admin_user()
self.client.force_login(self.user) self.client.force_login(self.user)
def test_stage_no_prefill(self): def test_stage_no_prefill(self):
@ -38,27 +37,29 @@ class AuthenticatorSMSStageTests(APITestCase):
response = self.client.get( response = self.client.get(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
) )
self.assertJSONEqual( self.assertStageResponse(
response.content, response,
{ self.flow,
"component": "ak-stage-authenticator-sms", self.user,
"flow_info": { component="ak-stage-authenticator-sms",
"background": self.flow.background_url, phone_number_required=True,
"cancel_url": reverse("authentik_flows:cancel"),
"title": "",
"layout": "stacked",
},
"pending_user": "foo",
"pending_user_avatar": "/static/dist/assets/images/user_default.png",
"phone_number_required": True,
"type": ChallengeTypes.NATIVE.value,
},
) )
def test_stage_submit(self): def test_stage_submit(self):
"""test stage (submit)""" """test stage (submit)"""
# Prepares session etc self.client.get(
self.test_stage_no_prefill() 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.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-authenticator-sms",
phone_number_required=True,
)
sms_send_mock = MagicMock() sms_send_mock = MagicMock()
with patch( with patch(
"authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send",
@ -70,23 +71,156 @@ class AuthenticatorSMSStageTests(APITestCase):
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
sms_send_mock.assert_called_once() sms_send_mock.assert_called_once()
self.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-authenticator-sms",
response_errors={},
phone_number_required=False,
)
def test_stage_submit_full(self): def test_stage_submit_full(self):
"""test stage (submit)""" """test stage (submit)"""
# Prepares session etc self.client.get(
self.test_stage_submit() 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.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-authenticator-sms",
phone_number_required=True,
)
sms_send_mock = MagicMock() sms_send_mock = MagicMock()
with patch( with patch(
"authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send", "authentik.stages.authenticator_sms.models.AuthenticatorSMSStage.send",
sms_send_mock, 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()
self.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-authenticator-sms",
response_errors={},
phone_number_required=False,
)
with patch(
"authentik.stages.authenticator_sms.models.SMSDevice.verify_token",
MagicMock(return_value=True),
): ):
response = self.client.post( response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}), reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}),
data={ data={
"component": "ak-stage-authenticator-sms", "component": "ak-stage-authenticator-sms",
"phone_number": "foo", "phone_number": "foo",
"code": int(self.client.session[SESSION_KEY_SMS_DEVICE].token), "code": "123456",
}, },
) )
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
def test_stage_hash(self):
"""test stage (verify_only)"""
self.stage.verify_only = True
self.stage.save()
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.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-authenticator-sms",
phone_number_required=True,
)
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) self.assertEqual(response.status_code, 200)
sms_send_mock.assert_not_called() sms_send_mock.assert_called_once()
self.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-authenticator-sms",
response_errors={},
phone_number_required=False,
)
with patch(
"authentik.stages.authenticator_sms.models.SMSDevice.verify_token",
MagicMock(return_value=True),
):
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": "123456",
},
)
self.assertEqual(response.status_code, 200)
self.assertStageRedirects(response, reverse("authentik_core:root-redirect"))
device: SMSDevice = SMSDevice.objects.filter(user=self.user).first()
self.assertTrue(device.is_hashed)
def test_stage_hash_twice(self):
"""test stage (hash + duplicate)"""
SMSDevice.objects.create(
user=create_test_admin_user(),
stage=self.stage,
phone_number=hash_phone_number("foo"),
)
self.stage.verify_only = True
self.stage.save()
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.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-authenticator-sms",
phone_number_required=True,
)
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)
self.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-authenticator-sms",
response_errors={
"non_field_errors": [{"code": "invalid", "string": "Invalid phone number"}]
},
phone_number_required=False,
)

View File

@ -168,6 +168,9 @@ class AuthenticatorValidateStageView(ChallengeStageView):
if device_class not in stage.device_classes: if device_class not in stage.device_classes:
self.logger.debug("device class not allowed", device_class=device_class) self.logger.debug("device class not allowed", device_class=device_class)
continue continue
if isinstance(device, SMSDevice) and device.is_hashed:
LOGGER.debug("Hashed SMS device, skipping")
continue
allowed_devices.append(device) allowed_devices.append(device)
# Ensure only one challenge per device class # Ensure only one challenge per device class
# WebAuthn does another device loop to find all WebAuthn devices # WebAuthn does another device loop to find all WebAuthn devices

View File

@ -116,3 +116,37 @@ class AuthenticatorValidateStageSMSTests(FlowTestCase):
) )
self.assertIn(COOKIE_NAME_MFA, response.cookies) self.assertIn(COOKIE_NAME_MFA, response.cookies)
self.assertStageResponse(response, component="xak-flow-redirect", to="/") self.assertStageResponse(response, component="xak-flow-redirect", to="/")
def test_sms_hashed(self):
"""Test hashed SMS device"""
ident_stage = IdentificationStage.objects.create(
name="conf",
user_fields=[
UserFields.USERNAME,
],
)
SMSDevice.objects.create(
user=self.user,
confirmed=True,
stage=self.stage,
phone_number="hash:foo",
)
stage = AuthenticatorValidateStage.objects.create(
name="foo",
last_auth_threshold="hours=1",
not_configured_action=NotConfiguredAction.DENY,
device_classes=[DeviceClasses.SMS],
)
stage.configuration_stages.set([ident_stage])
flow = Flow.objects.create(name="test", slug="test", title="test")
FlowStageBinding.objects.create(target=flow, stage=ident_stage, order=0)
FlowStageBinding.objects.create(target=flow, stage=stage, order=1)
response = self.client.post(
reverse("authentik_api:flow-executor", kwargs={"flow_slug": flow.slug}),
{"uid_field": self.user.username},
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertStageResponse(response, flow, self.user, component="ak-stage-access-denied")

View File

@ -14436,6 +14436,10 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
- in: query
name: verify_only
schema:
type: boolean
tags: tags:
- stages - stages
security: security:
@ -19608,6 +19612,11 @@ components:
type: string type: string
auth_type: auth_type:
$ref: '#/components/schemas/AuthTypeEnum' $ref: '#/components/schemas/AuthTypeEnum'
verify_only:
type: boolean
description: When enabled, the Phone number is only used during enrollment
to verify the users authenticity. Only a hash of the phone number is saved
to ensure it is not re-used in the future.
required: required:
- account_sid - account_sid
- auth - auth
@ -19651,6 +19660,11 @@ components:
type: string type: string
auth_type: auth_type:
$ref: '#/components/schemas/AuthTypeEnum' $ref: '#/components/schemas/AuthTypeEnum'
verify_only:
type: boolean
description: When enabled, the Phone number is only used during enrollment
to verify the users authenticity. Only a hash of the phone number is saved
to ensure it is not re-used in the future.
required: required:
- account_sid - account_sid
- auth - auth
@ -23042,7 +23056,6 @@ components:
description: Only send notification once, for example when sending a webhook description: Only send notification once, for example when sending a webhook
into a chat channel. into a chat channel.
required: required:
- mode
- mode_verbose - mode_verbose
- name - name
- pk - pk
@ -23074,7 +23087,6 @@ components:
description: Only send notification once, for example when sending a webhook description: Only send notification once, for example when sending a webhook
into a chat channel. into a chat channel.
required: required:
- mode
- name - name
NotificationTransportTest: NotificationTransportTest:
type: object type: object
@ -26804,6 +26816,11 @@ components:
type: string type: string
auth_type: auth_type:
$ref: '#/components/schemas/AuthTypeEnum' $ref: '#/components/schemas/AuthTypeEnum'
verify_only:
type: boolean
description: When enabled, the Phone number is only used during enrollment
to verify the users authenticity. Only a hash of the phone number is saved
to ensure it is not re-used in the future.
PatchedAuthenticatorStaticStageRequest: PatchedAuthenticatorStaticStageRequest:
type: object type: object
description: AuthenticatorStaticStage Serializer description: AuthenticatorStaticStage Serializer

View File

@ -3510,6 +3510,7 @@ msgstr "Benachrichtigungen"
msgid "Number" msgid "Number"
msgstr "Nummer" msgstr "Nummer"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from." msgid "Number the SMS will be sent from."
msgstr "Nummer, von der die SMS gesendet wird" msgstr "Nummer, von der die SMS gesendet wird"
@ -6277,6 +6278,10 @@ msgstr "Zertifikat zur Überprüfung"
msgid "Verification certificates" msgid "Verification certificates"
msgstr "" msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
msgstr "Überprüfen Sie die E-Mail-Adresse des Benutzers, indem Sie ihm einen einmaligen Link senden. Kann auch für die Wiederherstellung verwendet werden, um die Authentizität des Benutzers zu überprüfen." msgstr "Überprüfen Sie die E-Mail-Adresse des Benutzers, indem Sie ihm einen einmaligen Link senden. Kann auch für die Wiederherstellung verwendet werden, um die Authentizität des Benutzers zu überprüfen."

View File

@ -3568,6 +3568,7 @@ msgstr "Notifications"
msgid "Number" msgid "Number"
msgstr "Number" msgstr "Number"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from." msgid "Number the SMS will be sent from."
msgstr "Number the SMS will be sent from." msgstr "Number the SMS will be sent from."
@ -6403,6 +6404,10 @@ msgstr "Verification Certificate"
msgid "Verification certificates" msgid "Verification certificates"
msgstr "Verification certificates" msgstr "Verification certificates"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr "Verify only"
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
msgstr "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgstr "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."

View File

@ -3503,6 +3503,7 @@ msgstr "Notificaciones"
msgid "Number" msgid "Number"
msgstr "Número" msgstr "Número"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from." msgid "Number the SMS will be sent from."
msgstr "Número desde el que se enviará el SMS." msgstr "Número desde el que se enviará el SMS."
@ -6271,6 +6272,10 @@ msgstr "Certificado de verificación"
msgid "Verification certificates" msgid "Verification certificates"
msgstr "" msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
msgstr "Verifique la dirección de correo electrónico del usuario enviándole un enlace único. También se puede utilizar para la recuperación para verificar la autenticidad del usuario." msgstr "Verifique la dirección de correo electrónico del usuario enviándole un enlace único. También se puede utilizar para la recuperación para verificar la autenticidad del usuario."

View File

@ -3537,6 +3537,7 @@ msgstr "Notifications"
msgid "Number" msgid "Number"
msgstr "Nombre" msgstr "Nombre"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from." msgid "Number the SMS will be sent from."
msgstr "" msgstr ""
@ -6332,6 +6333,10 @@ msgstr "Certificat de validation"
msgid "Verification certificates" msgid "Verification certificates"
msgstr "" msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
msgstr "Vérifier le courriel de l'utilisateur en lui envoyant un lien à usage unique. Peut également être utilisé lors de la récupération afin de vérifier l'authenticité de l'utilisateur." msgstr "Vérifier le courriel de l'utilisateur en lui envoyant un lien à usage unique. Peut également être utilisé lors de la récupération afin de vérifier l'authenticité de l'utilisateur."

View File

@ -3500,6 +3500,7 @@ msgstr "Powiadomienia"
msgid "Number" msgid "Number"
msgstr "Numer" msgstr "Numer"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from." msgid "Number the SMS will be sent from."
msgstr "Numer, z którego zostanie wysłana wiadomość SMS." msgstr "Numer, z którego zostanie wysłana wiadomość SMS."
@ -6268,6 +6269,10 @@ msgstr "Certyfikat weryfikacji"
msgid "Verification certificates" msgid "Verification certificates"
msgstr "" msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
msgstr "Zweryfikuj adres e-mail użytkownika, wysyłając mu jednorazowy link. Może być również używany do odzyskiwania w celu weryfikacji autentyczności użytkownika." msgstr "Zweryfikuj adres e-mail użytkownika, wysyłając mu jednorazowy link. Może być również używany do odzyskiwania w celu weryfikacji autentyczności użytkownika."

View File

@ -3550,6 +3550,7 @@ msgstr ""
msgid "Number" msgid "Number"
msgstr "" msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from." msgid "Number the SMS will be sent from."
msgstr "" msgstr ""
@ -6373,6 +6374,10 @@ msgstr ""
msgid "Verification certificates" msgid "Verification certificates"
msgstr "" msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
msgstr "" msgstr ""

View File

@ -3505,6 +3505,7 @@ msgstr "Bildirimler"
msgid "Number" msgid "Number"
msgstr "Numara" msgstr "Numara"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from." msgid "Number the SMS will be sent from."
msgstr "Numara SMS gönderilecektir." msgstr "Numara SMS gönderilecektir."
@ -6273,6 +6274,10 @@ msgstr "Doğrulama Sertifikası"
msgid "Verification certificates" msgid "Verification certificates"
msgstr "" msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
msgstr "Kullanıcının e-posta adresini bir kerelik bağlantı göndererek doğrulayın. Kullanıcının orijinalliğini doğrulamak için kurtarma için de kullanılabilir." msgstr "Kullanıcının e-posta adresini bir kerelik bağlantı göndererek doğrulayın. Kullanıcının orijinalliğini doğrulamak için kurtarma için de kullanılabilir."

View File

@ -3485,6 +3485,7 @@ msgstr "通知"
msgid "Number" msgid "Number"
msgstr "数字" msgstr "数字"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from." msgid "Number the SMS will be sent from."
msgstr "短信的发信人号码。" msgstr "短信的发信人号码。"
@ -6230,6 +6231,10 @@ msgstr "验证证书"
msgid "Verification certificates" msgid "Verification certificates"
msgstr "验证证书" msgstr "验证证书"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
msgstr "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于在恢复时验证用户的真实性。" msgstr "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于在恢复时验证用户的真实性。"

View File

@ -3489,6 +3489,7 @@ msgstr "通知"
msgid "Number" msgid "Number"
msgstr "编号" msgstr "编号"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from." msgid "Number the SMS will be sent from."
msgstr "发送短信的来源号码。" msgstr "发送短信的来源号码。"
@ -6239,6 +6240,10 @@ msgstr "验证证书"
msgid "Verification certificates" msgid "Verification certificates"
msgstr "验证证书" msgstr "验证证书"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
msgstr "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于恢复,以验证用户的真实性。" msgstr "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于恢复,以验证用户的真实性。"

View File

@ -3489,6 +3489,7 @@ msgstr "通知"
msgid "Number" msgid "Number"
msgstr "编号" msgstr "编号"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts #: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from." msgid "Number the SMS will be sent from."
msgstr "发送短信的来源号码。" msgstr "发送短信的来源号码。"
@ -6239,6 +6240,10 @@ msgstr "验证证书"
msgid "Verification certificates" msgid "Verification certificates"
msgstr "验证证书" msgstr "验证证书"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: src/pages/stages/email/EmailStageForm.ts #: src/pages/stages/email/EmailStageForm.ts
msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity." msgid "Verify the user's email address by sending them a one-time-link. Can also be used for recovery to verify the user's authenticity."
msgstr "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于恢复,以验证用户的真实性。" msgstr "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于恢复,以验证用户的真实性。"

View File

@ -18,6 +18,7 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/FormGroup"; import "../../../elements/forms/FormGroup";
import "../../../elements/forms/HorizontalFormElement"; import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm"; import { ModelForm } from "../../../elements/forms/ModelForm";
import { first } from "../../../utils";
@customElement("ak-stage-authenticator-sms-form") @customElement("ak-stage-authenticator-sms-form")
export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage, string> { export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage, string> {
@ -215,6 +216,19 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage,
${this.provider === ProviderEnum.Generic ${this.provider === ProviderEnum.Generic
? this.renderProviderGeneric() ? this.renderProviderGeneric()
: this.renderProviderTwillio()} : this.renderProviderTwillio()}
<ak-form-element-horizontal name="verifyOnly">
<div class="pf-c-check">
<input
type="checkbox"
class="pf-c-check__input"
?checked=${first(this.instance?.verifyOnly, false)}
/>
<label class="pf-c-check__label">${t`Hash phone number`}</label>
</div>
<p class="pf-c-form__helper-text">
${t`If enabled, only a hash of the phone number will be saved. This can be done for data-protection reasons.Devices created from a stage with this enabled cannot be used with the authenticator validation stage.`}
</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal label=${t`Configuration flow`} name="configureFlow"> <ak-form-element-horizontal label=${t`Configuration flow`} name="configureFlow">
<select class="pf-c-form-control"> <select class="pf-c-form-control">
<option <option

View File

@ -4,7 +4,9 @@ title: SMS authenticator setup stage
This stage configures an SMS-based authenticator using either Twilio, or a generic HTTP endpoint. This stage configures an SMS-based authenticator using either Twilio, or a generic HTTP endpoint.
## Twilio ## Providers
#### Twilio
Navigate to https://console.twilio.com/, and log in to your existing account, or create a new one. Navigate to https://console.twilio.com/, and log in to your existing account, or create a new one.
@ -22,7 +24,7 @@ Afterwards, copy the value of **Messaging Service SID**. This is the value for t
Navigate back to the root of your Twilio console, and copy the Auth token. This is the value for the _Twilio Auth Token_ field in authentik. Navigate back to the root of your Twilio console, and copy the Auth token. This is the value for the _Twilio Auth Token_ field in authentik.
## Generic #### Generic
For the generic provider, a POST request will be sent to the URL you have specified in the _External API URL_ field. The request payload looks like this For the generic provider, a POST request will be sent to the URL you have specified in the _External API URL_ field. The request payload looks like this
@ -35,3 +37,11 @@ For the generic provider, a POST request will be sent to the URL you have specif
``` ```
Authentication can either be done as HTTP Basic, or via a Bearer Token. Any response with status 400 or above is counted as failed, and will prevent the user from proceeding. Authentication can either be done as HTTP Basic, or via a Bearer Token. Any response with status 400 or above is counted as failed, and will prevent the user from proceeding.
## Verify only
:::info
Requires authentik 2022.6
:::
To only verify the validity of a users' phone number, without saving it in an easily accessible way, you can enable this option. Phone numbers from devices enrolled through this stage will only have their hashed phone number saved. These devices can also not be used with the [Authenticator validation](../authenticator_validate/) stage.

View File

@ -19,6 +19,10 @@ slug: "2022.6"
Last MFA validation is now saved in a signed cookie, which changes the behavior so that only the current browser is affected by MFA validation, and an attacker cannot exploit the fact that a user has recently authenticated with MFA. Last MFA validation is now saved in a signed cookie, which changes the behavior so that only the current browser is affected by MFA validation, and an attacker cannot exploit the fact that a user has recently authenticated with MFA.
- Verification-only SMS Devices
SMS authenticator stages can now be configured to hash the phone number. This is useful if you want to require your users to configure and confirm their phone numbers, without saving them in a readable-format.
## Minor changes/fixes ## Minor changes/fixes
## Upgrading ## Upgrading