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
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()
@property

View file

@ -26,6 +26,7 @@ class AuthenticatorSMSStageSerializer(StageSerializer):
"auth",
"auth_password",
"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 django.contrib.auth import get_user_model
@ -46,6 +47,15 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage):
auth_password = models.TextField(default="", blank=True)
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"):
"""Send message via selected provider"""
if self.provider == SMSProviders.TWILIO:
@ -158,6 +168,11 @@ class AuthenticatorSMSStage(ConfigurableStage, Stage):
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):
"""SMS Device"""
@ -170,6 +185,15 @@ class SMSDevice(SideChannelDevice):
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):
valid = super().verify_token(token)
if valid:
@ -182,3 +206,4 @@ class SMSDevice(SideChannelDevice):
class Meta:
verbose_name = _("SMS Device")
verbose_name_plural = _("SMS Devices")
unique_together = (("stage", "phone_number"),)

View file

@ -1,6 +1,7 @@
"""SMS Setup stage"""
from typing import Optional
from django.db.models import Q
from django.http import HttpRequest, HttpResponse
from django.http.request import QueryDict
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.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
SESSION_KEY_SMS_DEVICE = "authentik/stages/authenticator_sms/sms_device"
@ -45,6 +50,10 @@ class AuthenticatorSMSChallengeResponse(ChallengeResponse):
stage: AuthenticatorSMSStage = self.device.stage
if "code" not in attrs:
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
stage.send(self.device.token, self.device)
return super().validate(attrs)
@ -111,6 +120,10 @@ class AuthenticatorSMSStageView(ChallengeStageView):
device: SMSDevice = self.request.session[SESSION_KEY_SMS_DEVICE]
if not device.confirmed:
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()
del self.request.session[SESSION_KEY_SMS_DEVICE]
return self.executor.stage_ok()

View file

@ -2,32 +2,31 @@
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_KEY_SMS_DEVICE
from authentik.core.tests.utils import create_test_admin_user, create_test_flow
from authentik.flows.models import FlowStageBinding
from authentik.flows.tests import FlowTestCase
from authentik.stages.authenticator_sms.models import (
AuthenticatorSMSStage,
SMSDevice,
SMSProviders,
hash_phone_number,
)
class AuthenticatorSMSStageTests(APITestCase):
class AuthenticatorSMSStageTests(FlowTestCase):
"""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(
self.flow = create_test_flow()
self.stage: AuthenticatorSMSStage = 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.user = create_test_admin_user()
self.client.force_login(self.user)
def test_stage_no_prefill(self):
@ -38,27 +37,29 @@ class AuthenticatorSMSStageTests(APITestCase):
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": "",
"layout": "stacked",
},
"pending_user": "foo",
"pending_user_avatar": "/static/dist/assets/images/user_default.png",
"phone_number_required": True,
"type": ChallengeTypes.NATIVE.value,
},
self.assertStageResponse(
response,
self.flow,
self.user,
component="ak-stage-authenticator-sms",
phone_number_required=True,
)
def test_stage_submit(self):
"""test stage (submit)"""
# Prepares session etc
self.test_stage_no_prefill()
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",
@ -70,23 +71,156 @@ class AuthenticatorSMSStageTests(APITestCase):
)
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,
)
def test_stage_submit_full(self):
"""test stage (submit)"""
# Prepares session etc
self.test_stage_submit()
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)
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": int(self.client.session[SESSION_KEY_SMS_DEVICE].token),
"code": "123456",
},
)
self.assertEqual(response.status_code, 200)
sms_send_mock.assert_not_called()
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)
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:
self.logger.debug("device class not allowed", device_class=device_class)
continue
if isinstance(device, SMSDevice) and device.is_hashed:
LOGGER.debug("Hashed SMS device, skipping")
continue
allowed_devices.append(device)
# Ensure only one challenge per device class
# 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.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:
type: string
format: uuid
- in: query
name: verify_only
schema:
type: boolean
tags:
- stages
security:
@ -19608,6 +19612,11 @@ components:
type: string
auth_type:
$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:
- account_sid
- auth
@ -19651,6 +19660,11 @@ components:
type: string
auth_type:
$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:
- account_sid
- auth
@ -23042,7 +23056,6 @@ components:
description: Only send notification once, for example when sending a webhook
into a chat channel.
required:
- mode
- mode_verbose
- name
- pk
@ -23074,7 +23087,6 @@ components:
description: Only send notification once, for example when sending a webhook
into a chat channel.
required:
- mode
- name
NotificationTransportTest:
type: object
@ -26804,6 +26816,11 @@ components:
type: string
auth_type:
$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:
type: object
description: AuthenticatorStaticStage Serializer

View file

@ -3510,6 +3510,7 @@ msgstr "Benachrichtigungen"
msgid "Number"
msgstr "Nummer"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr "Nummer, von der die SMS gesendet wird"
@ -6277,6 +6278,10 @@ msgstr "Zertifikat zur Überprüfung"
msgid "Verification certificates"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: 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."
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"
msgstr "Number"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "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"
msgstr "Verification certificates"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr "Verify only"
#: 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."
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"
msgstr "Número"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr "Número desde el que se enviará el SMS."
@ -6271,6 +6272,10 @@ msgstr "Certificado de verificación"
msgid "Verification certificates"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: 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."
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"
msgstr "Nombre"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr ""
@ -6332,6 +6333,10 @@ msgstr "Certificat de validation"
msgid "Verification certificates"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: 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."
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"
msgstr "Numer"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr "Numer, z którego zostanie wysłana wiadomość SMS."
@ -6268,6 +6269,10 @@ msgstr "Certyfikat weryfikacji"
msgid "Verification certificates"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: 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."
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"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr ""
@ -6373,6 +6374,10 @@ msgstr ""
msgid "Verification certificates"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: 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."
msgstr ""

View file

@ -3505,6 +3505,7 @@ msgstr "Bildirimler"
msgid "Number"
msgstr "Numara"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr "Numara SMS gönderilecektir."
@ -6273,6 +6274,10 @@ msgstr "Doğrulama Sertifikası"
msgid "Verification certificates"
msgstr ""
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: 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."
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"
msgstr "数字"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr "短信的发信人号码。"
@ -6230,6 +6231,10 @@ msgstr "验证证书"
msgid "Verification certificates"
msgstr "验证证书"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: 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."
msgstr "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于在恢复时验证用户的真实性。"

View file

@ -3489,6 +3489,7 @@ msgstr "通知"
msgid "Number"
msgstr "编号"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr "发送短信的来源号码。"
@ -6239,6 +6240,10 @@ msgstr "验证证书"
msgid "Verification certificates"
msgstr "验证证书"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: 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."
msgstr "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于恢复,以验证用户的真实性。"

View file

@ -3489,6 +3489,7 @@ msgstr "通知"
msgid "Number"
msgstr "编号"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Number the SMS will be sent from."
msgstr "发送短信的来源号码。"
@ -6239,6 +6240,10 @@ msgstr "验证证书"
msgid "Verification certificates"
msgstr "验证证书"
#: src/pages/stages/authenticator_sms/AuthenticatorSMSStageForm.ts
msgid "Verify only"
msgstr ""
#: 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."
msgstr "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于恢复,以验证用户的真实性。"

View file

@ -18,6 +18,7 @@ import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/FormGroup";
import "../../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../../elements/forms/ModelForm";
import { first } from "../../../utils";
@customElement("ak-stage-authenticator-sms-form")
export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage, string> {
@ -215,6 +216,19 @@ export class AuthenticatorSMSStageForm extends ModelForm<AuthenticatorSMSStage,
${this.provider === ProviderEnum.Generic
? this.renderProviderGeneric()
: 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">
<select class="pf-c-form-control">
<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.
## Twilio
## Providers
#### Twilio
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.
## 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
@ -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.
## 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.
- 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
## Upgrading