django-drf-otp


Namedjango-drf-otp JSON
Version 0.0.10 PyPI version JSON
download
home_pagehttps://bitbucket.org/akinonteam/django_drf_otp/
SummaryA Django app to supports 2FA(Two Factor Authentication) on djangorestframework.
upload_time2024-05-14 05:52:10
maintainerNone
docs_urlNone
authorAkinon
requires_python>=3.8
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Django DRF OTP

Django DRF OTP enables otp authorization.
This module depends on
[Django Rest Framework](https://www.django-rest-framework.org) module.

## Installation

Add `django_drf_otp` and requirements to your `INSTALLED_APPS` setting.

``` python
INSTALLED_APPS = [
    ...,
    "rest_framework", # Requirement
    "rest_framework.authtoken", # Requirement
    "django_drf_otp",
    ...
]
```

If you're intending to use the base auth API you'll probably also want to
add Django DRF OTP's login and logout views.
Add the following to your root `urls.py` file.

```python
urlpatterns = [
    ...,
    path("api-auth/", include("django_drf_otp.urls")),
    ...
]
```

Django DRF OTP provides two endpoints ``login/``  and
``verify-token/``:

- ``login``: Validates user with given ``USERNAME_FIELD`` and ``password``.
By the default returns a success message.

- ``verify-token``: Validates the otp token with given ``token``.
By the default returns a token
(from Rest Framework's Token<sup>[[link][1]]</sup> model).

And finally run ``python manage.py migrate`` to create models.

## Contents

- [``Settings``](#settings)
- [``ViewSets``](#viewsets)
- [``Strategies``](#strategies)

## Settings

Configuration for Django DRF OTP is all namespaced inside
a single Django setting, named `OTP_SETTINGS`.

For example your project's settings.py file might include something like this:

```python
OTP_SETTINGS = {
    "ENABLE_OTP_AUTHENTICATION": True,
}
```

### Accessing settings

If you need to access the values of REST framework's API settings
in your project, you should use the api_settings object. For example.

```python
from django_drf_otp.settings import otp_settings

print(api_settings.ENABLE_OTP_AUTHORIZATION)
```

## API Reference

### API policy settings

The following settings control the basic API policies.

#### ENABLE_OTP_AUTHENTICATION

Enables or Disables OTP Authorization.

Default: ``True``

#### TOKEN_LENGTH

The default token length to use for otp token creation.

Default: ``True``

#### TOKEN_SECRET

The default token creation secret characters.

Default: ``'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'``

#### DEFAULT_TOKEN_TYPE

The default token type to use for otp token creation.

Default: ``TOTP``

#### TOTP_TOKEN_TTL

The default otp token ttl value.

Default: ``300``

#### EMAIL_CONFIG

A dict of email config that used by otp email template.

Default:

```python
{
    "SUBJECT": _("Verification Token"),
    "TEMPLATE_PATH": "django_drf_otp/email.html",
}
```

- ``SUBJECT``: Subject of email.
- ``TEMPLATE_PATH``: The template used for email body.

## ViewSets

### OTPTokenViewSet

The ``OTPTokenViewSet`` class inherits from Rest Framework's
``GenericViewSet``([link](https://www.django-rest-framework.org/api-guide/viewsets/#genericviewset)).
This viewset provides actions: [``login``](#login), [``verify_token``](#verify_token)
and provides methods: [``get_success_response_data``](#get_success_response_data),
[``get_extra_context_data``](#get_extra_context_data).

```python
from rest_framework import viewsets
from django_drf_otp.services import OTPService
from django_drf_otp.serializers import OTPTokenSerializer, OTPTokenVerifySerializer


class OTPTokenViewSet(viewsets.GenericViewSet):
    permission_classes = (AllowAny,)
    serializer_classes = {
        "login": OTPTokenSerializer,
        "verify_token": OTPTokenVerifySerializer,
    }

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(**kwargs)

        self.OTP_ENABLED = otp_settings.ENABLE_OTP_AUTHENTICATION

    def login(self, request: Request, *args, **kwargs):
        ...

    def verify_token(self, request: Request, *args, **kwargs):
        ...

    def get_serializer_class(self, *args, **kwargs) -> Type[serializers.Serializer]:
        ...

    def get_success_response_data(self, validated_data: Dict, **kwargs) -> Dict:
        ...

    def get_extra_context_data(self, request: Request, validated_data: Dict) -> Dict:
        ...
```

### API attributes

#### ``permission_classes``

A tuple. Rest Framework's ``permission_classes``<sup>[[link][2]]</sup>
attribute.

Default:

```python
permission_classes = (AllowAny,)
```

#### ``serializer_classes``

A dict contains the action&serializer pair. This attribute used by ``get_serializer_class``<sup>[[link][3]]</sup>
that returns a serializer according to ``action``<sup>[[link][4]]</sup>.

Default:

```python
serializer_classes = {
    "login": OTPTokenSerializer,
    "verify_token": OTPTokenVerifySerializer,
}
```

### API actions&methods

#### ``login``

This action validates the user and
send a token based on the user's notification preference(Currently only email available)
if [``ENABLE_OTP_AUTHENTICATION``](#enable_otp_authentication) is ``True``,
otherwise a token<sup>[[link][1]]</sup>.
By the default uses ``OTPTokenSerializer`` for validation.

#### ``verify_token``

This action validates the otp token and returns a token<sup>[[link][1]]</sup>
if [``ENABLE_OTP_AUTHENTICATION``](#enable_otp_authentication) is ``True``,
otherwise a error message. By the default uses ``OTPTokenVerifySerializer`` for validation.

>💡 **Note:** If you want to change validation serializer class edit
>[``get_serializer_class``](#get_serializer_class) method or
>[``serializer_classes``](#serializer_classes) attribute.
>For editing responses see [``get_success_response_data``](#get_success_response_data).

#### ``get_serializer_class``

This method is used to decide the serializer class and returns a serializer class.
By the default returns a serializer defined in
[``serializer_classes``](#serializer_classes) with the action<sup>[[link][4]]

Default:

```python
def get_serializer_class(self, *args, **kwargs) -> Type[serializers.Serializer]:
    return self.serializer_classes[self.action]
```

#### ``get_success_response_data``

This method is used to decide the response data to return to the user.
Takes the serializer's validated data is returned by
[``get_serializer_class``](#get_serializer_class) and returns a dict.
By the default returns the success message(``{"message": _("OTP token sent successfully.")}``),
othervise Rest Framework's token(``{"token": token.key}``).

>⚠️ Warning: If you are not override
[``get_success_response_data``](#get_success_response_data) and [``get_extra_context_data``](#get_extra_context_data),
you must provide User object with ``user`` key in your serializer's ``validated_data``

#### ``get_extra_context_data``

This method is returns data used by notifiers.
By the default returns a dict contains ``request`` object and user's ``first_name``,
``last_name`` and ``full_name``.

### Example

```python
from django_drf_otp.viewsets import OTPTokenViewSet
from django_drf_otp.settings import otp_settings
from .serializers import CustomLoginSerializer, CustomTokenVerifySerializer

class LoginViewSet(OTPTokenViewSet):
    def get_serializer_class(self, *args, **kwargs) -> Type[serializers.Serializer]:
        if self.action == "login":
            return CustomLoginSerializer
        else:
            return CustomTokenVerifySerializer

    def get_success_response_data(self, validated_data: Dict, **kwargs) -> Dict:
        if self.action == "login" and otp_settings.ENABLE_OTP_AUTHENTICATION:
            return validated_data
        else:
            token,
            return {"token":}
```

## Strategies

Strategies are used to generate otp tokens with different token algorithms
(only TOTP tokens are available for now).
This module uses ``pyotp`` module for a token creation.

### Class's attributes

#### ``token_type``

A string attribute. It can be one of the values defined in TokenType(defined in ``django_drf_otp.enums``).

### API methods

#### ``generate_token``

A abstract method. This method used to create a otp token.
Returns token string.
If you want to implement a new strategy, must be overrided.

Default (in ``BaseOTPStrategy``):

```python
@abstractmethod
def generate_token(self, extra_secret_chars: Optional[str] = None) -> str:
    NotImplementedError()
```

#### ``verify_token``

This method used to verify a otp token.
Returns ``True`` if the token is verified, otherwise ``False``.
if you want to implement a new strategy, must be overrided.

Default (in ``BaseOTPStrategy``):

```python
@abstractmethod
def verify_token(self, token: str, extra_secret_chars: Optional[str] = None) -> bool:
    NotImplementedError()
```

#### ``generate_token_for_user``

A abstract method. This method used to create a otp token for a user
via ``VerificationToken``(defined in ``django_drf_otp.models``).
If you want to implement a new strategy, must be overrided.

Default (in ``BaseOTPStrategy``):

```python
@abstractmethod
def generate_token_for_user(self, user: AbstractUser) -> VerificationToken:
    NotImplementedError()
```

#### ``create_verification_token``

A static method. This method used to create
a ``VerificationToken`` model instance for user.

Default (in ``BaseOTPStrategy``):

```python
@staticmethod
def create_verification_token(
    user: AbstractUser, token: str,
    token_type: TokenType,
    secret_chars: str,
) -> VerificationToken:
    return VerificationToken.objects.create(
        user=user,
        token=token,
        token_type=token_type,
        secret_chars=secret_chars
    )
```

### ``deactivate_old_verification_tokens``

A static method. This method used to deactivates
user's all ``VerificationToken`` instances excluding given token instance.

Default (in ``BaseOTPStrategy``):

```python
@staticmethod
def deactivate_old_verification_tokens(
    verification_token: VerificationToken
) -> None:
    VerificationToken.objects.filter(user=verification_token.user, is_active=True).exclude(
        id=verification_token.id
    ).update(is_active=False)
```

### Example

You can easily implement a new strategy for your special solutions
(with only defined token_types in ``TokenType`` class).

Then create a new strategy class:

```python
class CustomTOTPStrategy(BaseOTPStrategy):
    token_type = TokenType.TOTP

    def generate_token(self, extra_secret_chars: Optional[str] = None) -> str:
        # <Your code here>
```

[1]: https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication (Rest Framework - Token Authentication)
[2]: https://www.django-rest-framework.org/api-guide/views/#permission_classes (Rest Framework - permission_classes)
[3]: https://www.django-rest-framework.org/api-guide/generic-views/#get_serializer_classself (Rest Framework - get_serializer_class)
[4]: https://www.django-rest-framework.org/api-guide/viewsets/#introspecting-viewset-actions (Rest Framework - Introspecting ViewSet actions)

            

Raw data

            {
    "_id": null,
    "home_page": "https://bitbucket.org/akinonteam/django_drf_otp/",
    "name": "django-drf-otp",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": null,
    "author": "Akinon",
    "author_email": "dev@akinon.com",
    "download_url": "https://files.pythonhosted.org/packages/f0/47/5ef5ca66f39012fec3aa5d5c9d394f300eb9aa976a87e139807355d84936/django-drf-otp-0.0.10.tar.gz",
    "platform": null,
    "description": "# Django DRF OTP\n\nDjango DRF OTP enables otp authorization.\nThis module depends on\n[Django Rest Framework](https://www.django-rest-framework.org) module.\n\n## Installation\n\nAdd `django_drf_otp` and requirements to your `INSTALLED_APPS` setting.\n\n``` python\nINSTALLED_APPS = [\n    ...,\n    \"rest_framework\", # Requirement\n    \"rest_framework.authtoken\", # Requirement\n    \"django_drf_otp\",\n    ...\n]\n```\n\nIf you're intending to use the base auth API you'll probably also want to\nadd Django DRF OTP's login and logout views.\nAdd the following to your root `urls.py` file.\n\n```python\nurlpatterns = [\n    ...,\n    path(\"api-auth/\", include(\"django_drf_otp.urls\")),\n    ...\n]\n```\n\nDjango DRF OTP provides two endpoints ``login/``  and\n``verify-token/``:\n\n- ``login``: Validates user with given ``USERNAME_FIELD`` and ``password``.\nBy the default returns a success message.\n\n- ``verify-token``: Validates the otp token with given ``token``.\nBy the default returns a token\n(from Rest Framework's Token<sup>[[link][1]]</sup> model).\n\nAnd finally run ``python manage.py migrate`` to create models.\n\n## Contents\n\n- [``Settings``](#settings)\n- [``ViewSets``](#viewsets)\n- [``Strategies``](#strategies)\n\n## Settings\n\nConfiguration for Django DRF OTP is all namespaced inside\na single Django setting, named `OTP_SETTINGS`.\n\nFor example your project's settings.py file might include something like this:\n\n```python\nOTP_SETTINGS = {\n    \"ENABLE_OTP_AUTHENTICATION\": True,\n}\n```\n\n### Accessing settings\n\nIf you need to access the values of REST framework's API settings\nin your project, you should use the api_settings object. For example.\n\n```python\nfrom django_drf_otp.settings import otp_settings\n\nprint(api_settings.ENABLE_OTP_AUTHORIZATION)\n```\n\n## API Reference\n\n### API policy settings\n\nThe following settings control the basic API policies.\n\n#### ENABLE_OTP_AUTHENTICATION\n\nEnables or Disables OTP Authorization.\n\nDefault: ``True``\n\n#### TOKEN_LENGTH\n\nThe default token length to use for otp token creation.\n\nDefault: ``True``\n\n#### TOKEN_SECRET\n\nThe default token creation secret characters.\n\nDefault: ``'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'``\n\n#### DEFAULT_TOKEN_TYPE\n\nThe default token type to use for otp token creation.\n\nDefault: ``TOTP``\n\n#### TOTP_TOKEN_TTL\n\nThe default otp token ttl value.\n\nDefault: ``300``\n\n#### EMAIL_CONFIG\n\nA dict of email config that used by otp email template.\n\nDefault:\n\n```python\n{\n    \"SUBJECT\": _(\"Verification Token\"),\n    \"TEMPLATE_PATH\": \"django_drf_otp/email.html\",\n}\n```\n\n- ``SUBJECT``: Subject of email.\n- ``TEMPLATE_PATH``: The template used for email body.\n\n## ViewSets\n\n### OTPTokenViewSet\n\nThe ``OTPTokenViewSet`` class inherits from Rest Framework's\n``GenericViewSet``([link](https://www.django-rest-framework.org/api-guide/viewsets/#genericviewset)).\nThis viewset provides actions: [``login``](#login), [``verify_token``](#verify_token)\nand provides methods: [``get_success_response_data``](#get_success_response_data),\n[``get_extra_context_data``](#get_extra_context_data).\n\n```python\nfrom rest_framework import viewsets\nfrom django_drf_otp.services import OTPService\nfrom django_drf_otp.serializers import OTPTokenSerializer, OTPTokenVerifySerializer\n\n\nclass OTPTokenViewSet(viewsets.GenericViewSet):\n    permission_classes = (AllowAny,)\n    serializer_classes = {\n        \"login\": OTPTokenSerializer,\n        \"verify_token\": OTPTokenVerifySerializer,\n    }\n\n    def __init__(self, **kwargs: Any) -> None:\n        super().__init__(**kwargs)\n\n        self.OTP_ENABLED = otp_settings.ENABLE_OTP_AUTHENTICATION\n\n    def login(self, request: Request, *args, **kwargs):\n        ...\n\n    def verify_token(self, request: Request, *args, **kwargs):\n        ...\n\n    def get_serializer_class(self, *args, **kwargs) -> Type[serializers.Serializer]:\n        ...\n\n    def get_success_response_data(self, validated_data: Dict, **kwargs) -> Dict:\n        ...\n\n    def get_extra_context_data(self, request: Request, validated_data: Dict) -> Dict:\n        ...\n```\n\n### API attributes\n\n#### ``permission_classes``\n\nA tuple. Rest Framework's ``permission_classes``<sup>[[link][2]]</sup>\nattribute.\n\nDefault:\n\n```python\npermission_classes = (AllowAny,)\n```\n\n#### ``serializer_classes``\n\nA dict contains the action&serializer pair. This attribute used by ``get_serializer_class``<sup>[[link][3]]</sup>\nthat returns a serializer according to ``action``<sup>[[link][4]]</sup>.\n\nDefault:\n\n```python\nserializer_classes = {\n    \"login\": OTPTokenSerializer,\n    \"verify_token\": OTPTokenVerifySerializer,\n}\n```\n\n### API actions&methods\n\n#### ``login``\n\nThis action validates the user and\nsend a token based on the user's notification preference(Currently only email available)\nif [``ENABLE_OTP_AUTHENTICATION``](#enable_otp_authentication) is ``True``,\notherwise a token<sup>[[link][1]]</sup>.\nBy the default uses ``OTPTokenSerializer`` for validation.\n\n#### ``verify_token``\n\nThis action validates the otp token and returns a token<sup>[[link][1]]</sup>\nif [``ENABLE_OTP_AUTHENTICATION``](#enable_otp_authentication) is ``True``,\notherwise a error message. By the default uses ``OTPTokenVerifySerializer`` for validation.\n\n>\ud83d\udca1 **Note:** If you want to change validation serializer class edit\n>[``get_serializer_class``](#get_serializer_class) method or\n>[``serializer_classes``](#serializer_classes) attribute.\n>For editing responses see [``get_success_response_data``](#get_success_response_data).\n\n#### ``get_serializer_class``\n\nThis method is used to decide the serializer class and returns a serializer class.\nBy the default returns a serializer defined in\n[``serializer_classes``](#serializer_classes) with the action<sup>[[link][4]]\n\nDefault:\n\n```python\ndef get_serializer_class(self, *args, **kwargs) -> Type[serializers.Serializer]:\n    return self.serializer_classes[self.action]\n```\n\n#### ``get_success_response_data``\n\nThis method is used to decide the response data to return to the user.\nTakes the serializer's validated data is returned by\n[``get_serializer_class``](#get_serializer_class) and returns a dict.\nBy the default returns the success message(``{\"message\": _(\"OTP token sent successfully.\")}``),\nothervise Rest Framework's token(``{\"token\": token.key}``).\n\n>\u26a0\ufe0f Warning: If you are not override\n[``get_success_response_data``](#get_success_response_data) and [``get_extra_context_data``](#get_extra_context_data),\nyou must provide User object with ``user`` key in your serializer's ``validated_data``\n\n#### ``get_extra_context_data``\n\nThis method is returns data used by notifiers.\nBy the default returns a dict contains ``request`` object and user's ``first_name``,\n``last_name`` and ``full_name``.\n\n### Example\n\n```python\nfrom django_drf_otp.viewsets import OTPTokenViewSet\nfrom django_drf_otp.settings import otp_settings\nfrom .serializers import CustomLoginSerializer, CustomTokenVerifySerializer\n\nclass LoginViewSet(OTPTokenViewSet):\n    def get_serializer_class(self, *args, **kwargs) -> Type[serializers.Serializer]:\n        if self.action == \"login\":\n            return CustomLoginSerializer\n        else:\n            return CustomTokenVerifySerializer\n\n    def get_success_response_data(self, validated_data: Dict, **kwargs) -> Dict:\n        if self.action == \"login\" and otp_settings.ENABLE_OTP_AUTHENTICATION:\n            return validated_data\n        else:\n            token,\n            return {\"token\":}\n```\n\n## Strategies\n\nStrategies are used to generate otp tokens with different token algorithms\n(only TOTP tokens are available for now).\nThis module uses ``pyotp`` module for a token creation.\n\n### Class's attributes\n\n#### ``token_type``\n\nA string attribute. It can be one of the values defined in TokenType(defined in ``django_drf_otp.enums``).\n\n### API methods\n\n#### ``generate_token``\n\nA abstract method. This method used to create a otp token.\nReturns token string.\nIf you want to implement a new strategy, must be overrided.\n\nDefault (in ``BaseOTPStrategy``):\n\n```python\n@abstractmethod\ndef generate_token(self, extra_secret_chars: Optional[str] = None) -> str:\n    NotImplementedError()\n```\n\n#### ``verify_token``\n\nThis method used to verify a otp token.\nReturns ``True`` if the token is verified, otherwise ``False``.\nif you want to implement a new strategy, must be overrided.\n\nDefault (in ``BaseOTPStrategy``):\n\n```python\n@abstractmethod\ndef verify_token(self, token: str, extra_secret_chars: Optional[str] = None) -> bool:\n    NotImplementedError()\n```\n\n#### ``generate_token_for_user``\n\nA abstract method. This method used to create a otp token for a user\nvia ``VerificationToken``(defined in ``django_drf_otp.models``).\nIf you want to implement a new strategy, must be overrided.\n\nDefault (in ``BaseOTPStrategy``):\n\n```python\n@abstractmethod\ndef generate_token_for_user(self, user: AbstractUser) -> VerificationToken:\n    NotImplementedError()\n```\n\n#### ``create_verification_token``\n\nA static method. This method used to create\na ``VerificationToken`` model instance for user.\n\nDefault (in ``BaseOTPStrategy``):\n\n```python\n@staticmethod\ndef create_verification_token(\n    user: AbstractUser, token: str,\n    token_type: TokenType,\n    secret_chars: str,\n) -> VerificationToken:\n    return VerificationToken.objects.create(\n        user=user,\n        token=token,\n        token_type=token_type,\n        secret_chars=secret_chars\n    )\n```\n\n### ``deactivate_old_verification_tokens``\n\nA static method. This method used to deactivates\nuser's all ``VerificationToken`` instances excluding given token instance.\n\nDefault (in ``BaseOTPStrategy``):\n\n```python\n@staticmethod\ndef deactivate_old_verification_tokens(\n    verification_token: VerificationToken\n) -> None:\n    VerificationToken.objects.filter(user=verification_token.user, is_active=True).exclude(\n        id=verification_token.id\n    ).update(is_active=False)\n```\n\n### Example\n\nYou can easily implement a new strategy for your special solutions\n(with only defined token_types in ``TokenType`` class).\n\nThen create a new strategy class:\n\n```python\nclass CustomTOTPStrategy(BaseOTPStrategy):\n    token_type = TokenType.TOTP\n\n    def generate_token(self, extra_secret_chars: Optional[str] = None) -> str:\n        # <Your code here>\n```\n\n[1]: https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication (Rest Framework - Token Authentication)\n[2]: https://www.django-rest-framework.org/api-guide/views/#permission_classes (Rest Framework - permission_classes)\n[3]: https://www.django-rest-framework.org/api-guide/generic-views/#get_serializer_classself (Rest Framework - get_serializer_class)\n[4]: https://www.django-rest-framework.org/api-guide/viewsets/#introspecting-viewset-actions (Rest Framework - Introspecting ViewSet actions)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A Django app to supports 2FA(Two Factor Authentication) on djangorestframework.",
    "version": "0.0.10",
    "project_urls": {
        "Homepage": "https://bitbucket.org/akinonteam/django_drf_otp/"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7ef07a81d882089b8c2dfdf71919677f37403205feef8799a48c7f0bd5ba5230",
                "md5": "c4cca812bb7f55f640bb6d985201fa30",
                "sha256": "800470c46a1e6f4e1e78503f341a90a922143e57673979ff7e7b891dc4cfb4da"
            },
            "downloads": -1,
            "filename": "django_drf_otp-0.0.10-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c4cca812bb7f55f640bb6d985201fa30",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 22567,
            "upload_time": "2024-05-14T05:52:09",
            "upload_time_iso_8601": "2024-05-14T05:52:09.078993Z",
            "url": "https://files.pythonhosted.org/packages/7e/f0/7a81d882089b8c2dfdf71919677f37403205feef8799a48c7f0bd5ba5230/django_drf_otp-0.0.10-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f0475ef5ca66f39012fec3aa5d5c9d394f300eb9aa976a87e139807355d84936",
                "md5": "47ded1e72aba8af66cfaaff292173f5f",
                "sha256": "12b7ba58ca9f0a2d345ca65f1376e8447f375b857cdef16caf5f2b188561f1bf"
            },
            "downloads": -1,
            "filename": "django-drf-otp-0.0.10.tar.gz",
            "has_sig": false,
            "md5_digest": "47ded1e72aba8af66cfaaff292173f5f",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 18430,
            "upload_time": "2024-05-14T05:52:10",
            "upload_time_iso_8601": "2024-05-14T05:52:10.657542Z",
            "url": "https://files.pythonhosted.org/packages/f0/47/5ef5ca66f39012fec3aa5d5c9d394f300eb9aa976a87e139807355d84936/django-drf-otp-0.0.10.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-05-14 05:52:10",
    "github": false,
    "gitlab": false,
    "bitbucket": true,
    "codeberg": false,
    "bitbucket_user": "akinonteam",
    "bitbucket_project": "django_drf_otp",
    "lcname": "django-drf-otp"
}
        
Elapsed time: 0.26512s