authentik.stages.authenticator_totp.models

OTP Time-based models

  1"""OTP Time-based models"""
  2
  3import time
  4from base64 import b32encode
  5from binascii import unhexlify
  6from urllib.parse import quote, urlencode
  7
  8from django.conf import settings
  9from django.db import models
 10from django.utils.translation import gettext_lazy as _
 11from django.views import View
 12from rest_framework.serializers import BaseSerializer
 13
 14from authentik.core.types import UserSettingSerializer
 15from authentik.flows.models import ConfigurableStage, FriendlyNamedStage, Stage
 16from authentik.lib.models import SerializerModel
 17from authentik.stages.authenticator.models import Device, ThrottlingMixin
 18from authentik.stages.authenticator.oath import TOTP
 19from authentik.stages.authenticator.util import hex_validator, random_hex
 20
 21
 22class TOTPDigits(models.TextChoices):
 23    """OTP Time Digits"""
 24
 25    SIX = "6", _("6 digits, widely compatible")
 26    EIGHT = "8", _("8 digits, not compatible with apps like Google Authenticator")
 27
 28
 29class AuthenticatorTOTPStage(ConfigurableStage, FriendlyNamedStage, Stage):
 30    """Setup Time-based OTP authentication for the user."""
 31
 32    digits = models.IntegerField(choices=TOTPDigits.choices)
 33
 34    @property
 35    def serializer(self) -> type[BaseSerializer]:
 36        from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageSerializer
 37
 38        return AuthenticatorTOTPStageSerializer
 39
 40    @property
 41    def view(self) -> type[View]:
 42        from authentik.stages.authenticator_totp.stage import AuthenticatorTOTPStageView
 43
 44        return AuthenticatorTOTPStageView
 45
 46    @property
 47    def component(self) -> str:
 48        return "ak-stage-authenticator-totp-form"
 49
 50    def ui_user_settings(self) -> UserSettingSerializer | None:
 51        return UserSettingSerializer(
 52            data={
 53                "title": self.friendly_name or str(self._meta.verbose_name),
 54                "component": "ak-user-settings-authenticator-totp",
 55            }
 56        )
 57
 58    def __str__(self) -> str:
 59        return f"TOTP Authenticator Setup Stage {self.name}"
 60
 61    class Meta:
 62        verbose_name = _("TOTP Authenticator Setup Stage")
 63        verbose_name_plural = _("TOTP Authenticator Setup Stages")
 64
 65
 66def default_key():
 67    """Default TOTP Device key"""
 68    return random_hex(20)
 69
 70
 71def key_validator(value):
 72    """Validate totp key"""
 73    return hex_validator()(value)
 74
 75
 76class TOTPDevice(SerializerModel, ThrottlingMixin, Device):
 77    """
 78    A generic TOTP :class:`~authentik.stages.authenticator.models.Device`. The model fields mostly
 79    correspond to the arguments to :func:`authentik.stages.authenticator.oath.totp`. They all have
 80    sensible defaults, including the key, which is randomly generated.
 81
 82    .. attribute:: key
 83
 84        *CharField*: A hex-encoded secret key of up to 40 bytes. (Default: 20
 85        random bytes)
 86
 87    .. attribute:: step
 88
 89        *PositiveSmallIntegerField*: The time step in seconds. (Default: 30)
 90
 91    .. attribute:: t0
 92
 93        *BigIntegerField*: The Unix time at which to begin counting steps.
 94        (Default: 0)
 95
 96    .. attribute:: digits
 97
 98        *PositiveSmallIntegerField*: The number of digits to expect in a token
 99        (6 or 8).  (Default: 6)
100
101    .. attribute:: tolerance
102
103        *PositiveSmallIntegerField*: The number of time steps in the past or
104        future to allow. For example, if this is 1, we'll accept any of three
105        tokens: the current one, the previous one, and the next one. (Default:
106        1)
107
108    .. attribute:: drift
109
110        *SmallIntegerField*: The number of time steps the prover is known to
111        deviate from our clock.  If :setting:`OTP_TOTP_SYNC` is ``True``, we'll
112        update this any time we match a token that is not the current one.
113        (Default: 0)
114
115    .. attribute:: last_t
116
117        *BigIntegerField*: The time step of the last verified token. To avoid
118        verifying the same token twice, this will be updated on each successful
119        verification. Only tokens at a higher time step will be verified
120        subsequently. (Default: -1)
121
122    """
123
124    key = models.CharField(
125        max_length=80,
126        validators=[key_validator],
127        default=default_key,
128        help_text="A hex-encoded secret key of up to 40 bytes.",
129    )
130    step = models.PositiveSmallIntegerField(default=30, help_text="The time step in seconds.")
131    t0 = models.BigIntegerField(
132        default=0, help_text="The Unix time at which to begin counting steps."
133    )
134    digits = models.PositiveSmallIntegerField(
135        choices=[(6, 6), (8, 8)],
136        default=6,
137        help_text="The number of digits to expect in a token.",
138    )
139    tolerance = models.PositiveSmallIntegerField(
140        default=1, help_text="The number of time steps in the past or future to allow."
141    )
142    drift = models.SmallIntegerField(
143        default=0,
144        help_text="The number of time steps the prover is known to deviate from our clock.",
145    )
146    last_t = models.BigIntegerField(
147        default=-1,
148        help_text=(
149            "The t value of the latest verified token. "
150            "The next token must be at a higher time step."
151        ),
152    )
153
154    @property
155    def serializer(self) -> type[BaseSerializer]:
156        from authentik.stages.authenticator_totp.api import TOTPDeviceSerializer
157
158        return TOTPDeviceSerializer
159
160    @property
161    def bin_key(self):
162        """
163        The secret key as a binary string.
164        """
165        return unhexlify(self.key.encode())
166
167    def verify_token(self, token):
168        otp_totp_sync = getattr(settings, "OTP_TOTP_SYNC", True)
169
170        verify_allowed, _ = self.verify_is_allowed()
171        if not verify_allowed:
172            return False
173
174        try:
175            token = int(token)
176        except ValueError:
177            verified = False
178        else:
179            key = self.bin_key
180
181            totp = TOTP(key, self.step, self.t0, self.digits, self.drift)
182            totp.time = time.time()
183
184            verified = totp.verify(token, self.tolerance, self.last_t + 1)
185            if verified:
186                self.last_t = totp.t()
187                if otp_totp_sync:
188                    self.drift = totp.drift
189                self.throttle_reset(commit=False)
190                self.save()
191
192        if not verified:
193            self.throttle_increment(commit=True)
194
195        return verified
196
197    @property
198    def config_url(self):
199        """
200        A URL for configuring Google Authenticator or similar.
201
202        See https://github.com/google/google-authenticator/wiki/Key-Uri-Format.
203        The issuer is taken from :setting:`OTP_TOTP_ISSUER`, if available.
204        The image (for e.g. FreeOTP) is taken from :setting:`OTP_TOTP_IMAGE`, if available.
205
206        """
207        label = str(self.user.username)
208        params = {
209            "secret": b32encode(self.bin_key),
210            "algorithm": "SHA1",
211            "digits": self.digits,
212            "period": self.step,
213        }
214        urlencoded_params = urlencode(params)
215
216        issuer = self._read_str_from_settings("OTP_TOTP_ISSUER")
217        if issuer:
218            issuer = issuer.replace(":", "")
219            label = f"{issuer}:{label}"
220            urlencoded_params += (
221                f"&issuer={quote(issuer)}"  # encode issuer as per RFC 3986, not quote_plus
222            )
223
224        image = self._read_str_from_settings("OTP_TOTP_IMAGE")
225        if image:
226            urlencoded_params += "&image={}".format(quote(image, safe=":/"))
227
228        url = f"otpauth://totp/{quote(label)}?{urlencoded_params}"
229
230        return url
231
232    def _read_str_from_settings(self, key):
233        val = getattr(settings, key, None)
234        if callable(val):
235            val = val(self)
236        if isinstance(val, str) and (val != ""):
237            return val
238        return None
239
240    class Meta(Device.Meta):
241        verbose_name = _("TOTP Device")
242        verbose_name_plural = _("TOTP Devices")
class TOTPDigits(django.db.models.enums.TextChoices):
23class TOTPDigits(models.TextChoices):
24    """OTP Time Digits"""
25
26    SIX = "6", _("6 digits, widely compatible")
27    EIGHT = "8", _("8 digits, not compatible with apps like Google Authenticator")

OTP Time Digits

30class AuthenticatorTOTPStage(ConfigurableStage, FriendlyNamedStage, Stage):
31    """Setup Time-based OTP authentication for the user."""
32
33    digits = models.IntegerField(choices=TOTPDigits.choices)
34
35    @property
36    def serializer(self) -> type[BaseSerializer]:
37        from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageSerializer
38
39        return AuthenticatorTOTPStageSerializer
40
41    @property
42    def view(self) -> type[View]:
43        from authentik.stages.authenticator_totp.stage import AuthenticatorTOTPStageView
44
45        return AuthenticatorTOTPStageView
46
47    @property
48    def component(self) -> str:
49        return "ak-stage-authenticator-totp-form"
50
51    def ui_user_settings(self) -> UserSettingSerializer | None:
52        return UserSettingSerializer(
53            data={
54                "title": self.friendly_name or str(self._meta.verbose_name),
55                "component": "ak-user-settings-authenticator-totp",
56            }
57        )
58
59    def __str__(self) -> str:
60        return f"TOTP Authenticator Setup Stage {self.name}"
61
62    class Meta:
63        verbose_name = _("TOTP Authenticator Setup Stage")
64        verbose_name_plural = _("TOTP Authenticator Setup Stages")

Setup Time-based OTP authentication for the user.

def digits(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

serializer: type[rest_framework.serializers.BaseSerializer]
35    @property
36    def serializer(self) -> type[BaseSerializer]:
37        from authentik.stages.authenticator_totp.api import AuthenticatorTOTPStageSerializer
38
39        return AuthenticatorTOTPStageSerializer

Get serializer for this model

view: type[django.views.generic.base.View]
41    @property
42    def view(self) -> type[View]:
43        from authentik.stages.authenticator_totp.stage import AuthenticatorTOTPStageView
44
45        return AuthenticatorTOTPStageView

Return StageView class that implements logic for this stage

component: str
47    @property
48    def component(self) -> str:
49        return "ak-stage-authenticator-totp-form"

Return component used to edit this object

def ui_user_settings(self) -> authentik.core.types.UserSettingSerializer | None:
51    def ui_user_settings(self) -> UserSettingSerializer | None:
52        return UserSettingSerializer(
53            data={
54                "title": self.friendly_name or str(self._meta.verbose_name),
55                "component": "ak-user-settings-authenticator-totp",
56            }
57        )

Entrypoint to integrate with User settings. Can either return None if no user settings are available, or a challenge.

configure_flow

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

def friendly_name(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def get_digits_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

configure_flow_id
stage_ptr_id
stage_ptr

Accessor to the related object on the forward side of a one-to-one relation.

In the example::

class Restaurant(Model):
    place = OneToOneField(Place, related_name='restaurant')

Restaurant.place is a ForwardOneToOneDescriptor instance.

class AuthenticatorTOTPStage.DoesNotExist(authentik.flows.models.Stage.DoesNotExist):

The requested object does not exist

class AuthenticatorTOTPStage.MultipleObjectsReturned(authentik.flows.models.Stage.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.

def default_key():
67def default_key():
68    """Default TOTP Device key"""
69    return random_hex(20)

Default TOTP Device key

def key_validator(value):
72def key_validator(value):
73    """Validate totp key"""
74    return hex_validator()(value)

Validate totp key

 77class TOTPDevice(SerializerModel, ThrottlingMixin, Device):
 78    """
 79    A generic TOTP :class:`~authentik.stages.authenticator.models.Device`. The model fields mostly
 80    correspond to the arguments to :func:`authentik.stages.authenticator.oath.totp`. They all have
 81    sensible defaults, including the key, which is randomly generated.
 82
 83    .. attribute:: key
 84
 85        *CharField*: A hex-encoded secret key of up to 40 bytes. (Default: 20
 86        random bytes)
 87
 88    .. attribute:: step
 89
 90        *PositiveSmallIntegerField*: The time step in seconds. (Default: 30)
 91
 92    .. attribute:: t0
 93
 94        *BigIntegerField*: The Unix time at which to begin counting steps.
 95        (Default: 0)
 96
 97    .. attribute:: digits
 98
 99        *PositiveSmallIntegerField*: The number of digits to expect in a token
100        (6 or 8).  (Default: 6)
101
102    .. attribute:: tolerance
103
104        *PositiveSmallIntegerField*: The number of time steps in the past or
105        future to allow. For example, if this is 1, we'll accept any of three
106        tokens: the current one, the previous one, and the next one. (Default:
107        1)
108
109    .. attribute:: drift
110
111        *SmallIntegerField*: The number of time steps the prover is known to
112        deviate from our clock.  If :setting:`OTP_TOTP_SYNC` is ``True``, we'll
113        update this any time we match a token that is not the current one.
114        (Default: 0)
115
116    .. attribute:: last_t
117
118        *BigIntegerField*: The time step of the last verified token. To avoid
119        verifying the same token twice, this will be updated on each successful
120        verification. Only tokens at a higher time step will be verified
121        subsequently. (Default: -1)
122
123    """
124
125    key = models.CharField(
126        max_length=80,
127        validators=[key_validator],
128        default=default_key,
129        help_text="A hex-encoded secret key of up to 40 bytes.",
130    )
131    step = models.PositiveSmallIntegerField(default=30, help_text="The time step in seconds.")
132    t0 = models.BigIntegerField(
133        default=0, help_text="The Unix time at which to begin counting steps."
134    )
135    digits = models.PositiveSmallIntegerField(
136        choices=[(6, 6), (8, 8)],
137        default=6,
138        help_text="The number of digits to expect in a token.",
139    )
140    tolerance = models.PositiveSmallIntegerField(
141        default=1, help_text="The number of time steps in the past or future to allow."
142    )
143    drift = models.SmallIntegerField(
144        default=0,
145        help_text="The number of time steps the prover is known to deviate from our clock.",
146    )
147    last_t = models.BigIntegerField(
148        default=-1,
149        help_text=(
150            "The t value of the latest verified token. "
151            "The next token must be at a higher time step."
152        ),
153    )
154
155    @property
156    def serializer(self) -> type[BaseSerializer]:
157        from authentik.stages.authenticator_totp.api import TOTPDeviceSerializer
158
159        return TOTPDeviceSerializer
160
161    @property
162    def bin_key(self):
163        """
164        The secret key as a binary string.
165        """
166        return unhexlify(self.key.encode())
167
168    def verify_token(self, token):
169        otp_totp_sync = getattr(settings, "OTP_TOTP_SYNC", True)
170
171        verify_allowed, _ = self.verify_is_allowed()
172        if not verify_allowed:
173            return False
174
175        try:
176            token = int(token)
177        except ValueError:
178            verified = False
179        else:
180            key = self.bin_key
181
182            totp = TOTP(key, self.step, self.t0, self.digits, self.drift)
183            totp.time = time.time()
184
185            verified = totp.verify(token, self.tolerance, self.last_t + 1)
186            if verified:
187                self.last_t = totp.t()
188                if otp_totp_sync:
189                    self.drift = totp.drift
190                self.throttle_reset(commit=False)
191                self.save()
192
193        if not verified:
194            self.throttle_increment(commit=True)
195
196        return verified
197
198    @property
199    def config_url(self):
200        """
201        A URL for configuring Google Authenticator or similar.
202
203        See https://github.com/google/google-authenticator/wiki/Key-Uri-Format.
204        The issuer is taken from :setting:`OTP_TOTP_ISSUER`, if available.
205        The image (for e.g. FreeOTP) is taken from :setting:`OTP_TOTP_IMAGE`, if available.
206
207        """
208        label = str(self.user.username)
209        params = {
210            "secret": b32encode(self.bin_key),
211            "algorithm": "SHA1",
212            "digits": self.digits,
213            "period": self.step,
214        }
215        urlencoded_params = urlencode(params)
216
217        issuer = self._read_str_from_settings("OTP_TOTP_ISSUER")
218        if issuer:
219            issuer = issuer.replace(":", "")
220            label = f"{issuer}:{label}"
221            urlencoded_params += (
222                f"&issuer={quote(issuer)}"  # encode issuer as per RFC 3986, not quote_plus
223            )
224
225        image = self._read_str_from_settings("OTP_TOTP_IMAGE")
226        if image:
227            urlencoded_params += "&image={}".format(quote(image, safe=":/"))
228
229        url = f"otpauth://totp/{quote(label)}?{urlencoded_params}"
230
231        return url
232
233    def _read_str_from_settings(self, key):
234        val = getattr(settings, key, None)
235        if callable(val):
236            val = val(self)
237        if isinstance(val, str) and (val != ""):
238            return val
239        return None
240
241    class Meta(Device.Meta):
242        verbose_name = _("TOTP Device")
243        verbose_name_plural = _("TOTP Devices")

A generic TOTP :class:~authentik.stages.authenticator.models.Device. The model fields mostly correspond to the arguments to :func:authentik.stages.authenticator.oath.totp. They all have sensible defaults, including the key, which is randomly generated.

.. attribute:: key

*CharField*: A hex-encoded secret key of up to 40 bytes. (Default: 20
random bytes)

.. attribute:: step

*PositiveSmallIntegerField*: The time step in seconds. (Default: 30)

.. attribute:: t0

*BigIntegerField*: The Unix time at which to begin counting steps.
(Default: 0)

.. attribute:: digits

*PositiveSmallIntegerField*: The number of digits to expect in a token
(6 or 8).  (Default: 6)

.. attribute:: tolerance

*PositiveSmallIntegerField*: The number of time steps in the past or
future to allow. For example, if this is 1, we'll accept any of three
tokens: the current one, the previous one, and the next one. (Default:
1)

.. attribute:: drift

*SmallIntegerField*: The number of time steps the prover is known to
deviate from our clock.  If :setting:`OTP_TOTP_SYNC` is ``True``, we'll
update this any time we match a token that is not the current one.
(Default: 0)

.. attribute:: last_t

*BigIntegerField*: The time step of the last verified token. To avoid
verifying the same token twice, this will be updated on each successful
verification. Only tokens at a higher time step will be verified
subsequently. (Default: -1)
def key(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def step(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def t0(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def digits(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def tolerance(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def drift(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def last_t(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

serializer: type[rest_framework.serializers.BaseSerializer]
155    @property
156    def serializer(self) -> type[BaseSerializer]:
157        from authentik.stages.authenticator_totp.api import TOTPDeviceSerializer
158
159        return TOTPDeviceSerializer

Get serializer for this model

bin_key
161    @property
162    def bin_key(self):
163        """
164        The secret key as a binary string.
165        """
166        return unhexlify(self.key.encode())

The secret key as a binary string.

def verify_token(self, token):
168    def verify_token(self, token):
169        otp_totp_sync = getattr(settings, "OTP_TOTP_SYNC", True)
170
171        verify_allowed, _ = self.verify_is_allowed()
172        if not verify_allowed:
173            return False
174
175        try:
176            token = int(token)
177        except ValueError:
178            verified = False
179        else:
180            key = self.bin_key
181
182            totp = TOTP(key, self.step, self.t0, self.digits, self.drift)
183            totp.time = time.time()
184
185            verified = totp.verify(token, self.tolerance, self.last_t + 1)
186            if verified:
187                self.last_t = totp.t()
188                if otp_totp_sync:
189                    self.drift = totp.drift
190                self.throttle_reset(commit=False)
191                self.save()
192
193        if not verified:
194            self.throttle_increment(commit=True)
195
196        return verified

Verifies a token. As a rule, the token should no longer be valid if this returns True.

:param str token: The OTP token provided by the user. :rtype: bool

config_url
198    @property
199    def config_url(self):
200        """
201        A URL for configuring Google Authenticator or similar.
202
203        See https://github.com/google/google-authenticator/wiki/Key-Uri-Format.
204        The issuer is taken from :setting:`OTP_TOTP_ISSUER`, if available.
205        The image (for e.g. FreeOTP) is taken from :setting:`OTP_TOTP_IMAGE`, if available.
206
207        """
208        label = str(self.user.username)
209        params = {
210            "secret": b32encode(self.bin_key),
211            "algorithm": "SHA1",
212            "digits": self.digits,
213            "period": self.step,
214        }
215        urlencoded_params = urlencode(params)
216
217        issuer = self._read_str_from_settings("OTP_TOTP_ISSUER")
218        if issuer:
219            issuer = issuer.replace(":", "")
220            label = f"{issuer}:{label}"
221            urlencoded_params += (
222                f"&issuer={quote(issuer)}"  # encode issuer as per RFC 3986, not quote_plus
223            )
224
225        image = self._read_str_from_settings("OTP_TOTP_IMAGE")
226        if image:
227            urlencoded_params += "&image={}".format(quote(image, safe=":/"))
228
229        url = f"otpauth://totp/{quote(label)}?{urlencoded_params}"
230
231        return url

A URL for configuring Google Authenticator or similar.

See https://github.com/google/google-authenticator/wiki/Key-Uri-Format. The issuer is taken from :setting:OTP_TOTP_ISSUER, if available. The image (for e.g. FreeOTP) is taken from :setting:OTP_TOTP_IMAGE, if available.

def throttling_failure_timestamp(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def throttling_failure_count(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

user

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example::

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

Child.parent is a ForwardManyToOneDescriptor instance.

def name(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def confirmed(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def last_used(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def created(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def last_updated(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

def get_digits_display(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_next_by_created(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_created(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_next_by_last_updated(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

def get_previous_by_last_updated(unknown):

Method descriptor with partial application of the given arguments and keywords.

Supports wrapping existing descriptors and handles non-descriptor callables as instance methods.

user_id
def id(unknown):

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

class TOTPDevice.DoesNotExist(django.core.exceptions.ObjectDoesNotExist):

The requested object does not exist

class TOTPDevice.MultipleObjectsReturned(django.core.exceptions.MultipleObjectsReturned):

The query returned multiple objects when only one was expected.