stages/authenticator_sms: verify-only (#3011)
This commit is contained in:
parent
fc1c1a849a
commit
c0cb891078
|
@ -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
|
||||
|
|
|
@ -26,6 +26,7 @@ class AuthenticatorSMSStageSerializer(StageSerializer):
|
|||
"auth",
|
||||
"auth_password",
|
||||
"auth_type",
|
||||
"verify_only",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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")},
|
||||
),
|
||||
]
|
|
@ -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"),)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
21
schema.yml
21
schema.yml
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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 "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于在恢复时验证用户的真实性。"
|
||||
|
|
|
@ -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 "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于恢复,以验证用户的真实性。"
|
||||
|
|
|
@ -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 "通过向用户发送一次性链接来验证用户的电子邮件地址。也可用于恢复,以验证用户的真实性。"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in a new issue