wc-django-2factor


Namewc-django-2factor JSON
Version 0.2.1 PyPI version JSON
download
home_pageNone
SummaryPackage to create general API for 2factor checkers.
upload_time2024-12-05 13:43:55
maintainerNone
docs_urlNone
authorWebCase
requires_python>=3.6
licenseMIT License
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # WebCase 2factor API

Package to create general API for 2factor checkers.

## Installation

```sh
pip install wc-django-2factor
```

In `settings.py`:

```python
INSTALLED_APPS += [
    # Dependencies:
    'pxd_admin_extensions',
    'django_jsonform',
    'wcd_settings',

    # 2Factor itself.
    'wcd_2factor',
]

WCD_2FACTOR = {
    # It will be empty by default:
    'METHODS': [
        # Simple builtin 2factor method.
        # Used to work like user secret confirmation.
        # But mostly serves as an example.
        'wcd_2factor.builtins.dummy.DUMMY_METHOD_DESCRIPTOR',
    ],
    # Custom json encoder
    'JSON_ENCODER': 'wcd_2factor.utils.types.EnvoyerJSONEncoder',
}
```

## Usage

### Confirmer

Service for confirmation state management.

```python
from wcd_2factor.confirmer import default_confirmer, Backend, Confirmer
from wcd_2factor.registries import method_config_registry
from wcd_2factor.models import MethodConfig, UserConfig, ConfirmationState

# Default registry

# Use:
default confirmer
# Or create another.
confirmer = Confirmer(method_config_registry)

# List of all available method keys:
default_confirmer.get_methods()

# List of all active `MethodConfig` instances:
default_confirmer.get_method_configs()

# List of all user `UserConfig` configurations:
default_confirmer.get_user_configs(
    # Provide user instance:
    user=user or None,
    # Or identifier:
    user_id=user.pk or None
)


# Creates backend for some method config or `None` if there is no such:
default_confirmer.make_backend(
    # Optional, if `user_config` will be provided, since it also has a
    # relation to a MethodConfig.
    method_config=MethodConfig() or None,
    # Optional, since confirmation could be done just using the MethodConfig 
    # by itself.
    user_config=UserConfig() or None,
    # Optional context to be passed to the backend.
    context={} or None,
    # Whether should raise an exception if backend could not be created.
    # For example when there is no registered method.
    should_raise=False,
)


# Method to change user confirmation.
# Id will check if the changes are significant enough to request a 
# confirmation from user.
# If it does - `make_confirmation` - will be a callable to create new 
# `ConfirmationState` instance. Else `None` will be returned.
instance = UserConfig()
make_confirmation = default_confirmer.change_user_config(
    # Current instance to apply changes to.
    instance,
    # New configuration object. Either a pydantic object or dataclass or just a 
    # simple dict, that will be internally converted to a pydantic object.
    DTO() or dict(),
    # If you already have an initialized backend, method could use it 
    # instead of creating a new one:
    backend=Backend() or None,
    # Optional method config object. If, for example `user_config` instance 
    # doesen't have one attached yet,
    method_config=MethodConfig() or None,
    # Optional context to be passed to the backend's method.
    context={} or None,
)
# Don't forget to save your configuration instance.
# It will not be saved by this method.
instance.save()

if make_confirmation is not None:
    confirmation: ConfirmationState = make_confirmation()

  
# Requesting any type of confirmation:
confirmation: ConfirmationState = default_confirmer.request_confirmation(
    # Optional, if `user_config` will be provided, since it also has a
    # relation to a MethodConfig.
    method_config=MethodConfig() or None,
    # Optional, since confirmation could be done just using the MethodConfig 
    # by itself.
    user_config=UserConfig() or None,
    # If you already have an initialized backend, method could use it 
    # instead of creating a new one:
    backend=Backend() or None,
    # User provided state.
    # It depends on backend what kind of parameters should and should not be 
    # present.
    # In most cases if `used_config` provided - no additional information 
    # required at all.
    state={} or None,
    # Optional context to be passed to the backend's method.
    context={} or None,
)


# If you have user data to confirm some `ConfirmationState` run this:
confirmation: ConfirmationState = default_confirmer.confirm(
    # Either identifier:
    id=uuid4() or None,
    # Or confirmation object itself must be provided:
    confirmation=confirmation or None,
    # User passed data, that confirms that user have control over the 
    # "second factor":
    data={} or None,
    # If you already have an initialized backend, method could use it 
    # instead of creating a new one:
    backend=Backend() or None,
    # Optional context to be passed to the backend's method.
    context={} or None,
)
# Method might return state, event when confirmation process failed for some 
# reason.
# So check the confirmation before using it:
if not confirmation.is_available():
    raise ValueError('Confirmation failed.')


# Checks if confirmation is confirmed and available to use:
available, optional_confirmation = default_confirmer.check(
    # Either identifier:
    id=uuid4() or None,
    # Or confirmation object itself must be provided:
    confirmation=confirmation or None,
    # Optional context to be passed to the backend's method.
    context={} or None,
)
# In some cases method might return None instead of confirmation object.
# That happens when confirmation was already used, or there were none at all.
if not available:
    raise ValueError('Confirmation unavailable.')


# And the last one.
# ConfirmationState object is a "one-time" password to perform some action.
# So after usage it will be deleted from the database.
used, optional_confirmation = default_confirmer.use(
    # Either identifier:
    id=uuid4() or None,
    # Or confirmation object itself must be provided:
    confirmation=confirmation or None,
    # Optional context to be passed to the backend's method.
    context={} or None,
)
# But you will still have object returned if `used` was true.
# You might need to do something with it afterwards.
if not used:
    raise ValueError('Confirmation failed.')
```

### Registry and custom Backends

Registry is a simple dict with some additional methods to register new confirmation methods.

For every method that could be used in your application `MethodConfigDescriptor` should be defined and added to registry.

For example:

```python
from wcd_2factor.registries import (
    method_config_registry, Registry,
    MethodConfigDescriptor, DTO,
)

# This is a default method's registry. 
# It will be autopopulated with descriptors from 
# django_settings.WCD_2FACTOR['METHODS'].
method_config_registry

# But nothing stops you from creating your own registry.
my_registry = Registry()

# And after that you may add descriptors to it.
MY_METHOD_DESCRIPTOR = my_registry.register(MethodConfigDescriptor(
    # Unique method key.
    key='my_method',
    # Verbose method name.
    verbose_name=gettext_lazy('My Method'),
    # Backend class is required, since it will be used to execute every
    # `Confirmer` method.
    backend_class=Backend,
    # Other data object classes and schemas are optional:
    # MethodConfig pydantic class.
    # Configuration model for MethodConfig.
    config_global_dto=BaseModel or None,
    # JSONSchema for that configuration.
    config_global_schema=BaseModel.model_json_schema() or None,
    # Configuration model for UserConfig.
    config_user_dto=BaseModel or None,
    # JSONSchema for that configuration.
    config_user_schema=BaseModel.model_json_schema() or None,
))
```

But descriptor is only a simple definition with and additional configuration inside.

All the work with message sending and request confirmation are on your `Backend` implementation.

```python
from wcd_2factor.confirmer import Backend
from wcd_2factor.registries import DTO, MethodConfigDescriptor
from wcd_2factor.models import ConfirmationState, UserConfig


class YourBackend(Backend):
    method_config: YourMethodDTO
    user_config: Optional[YourUserDTO]

    # Method that checks if user configuration changed.
    # And if this change is significant enough to request a confirmation.
    def change_user_config(
        self,
        # New configuration to check for changes.
        new: YourMethodDTO,
        context: Optional[dict] = None,
    ) -> Tuple[bool, Optional[dict]]:
        # Pseudocode:

        if (
            self.user_config is None
            or
            self.user_config != new
        ):
            # Then user configuration changed and confirmation with
            # some "state" should be created to confirm the change.
            return True, {'some': 'state'}

        # Otherwise - do nothing.
        return False, None

    # This is method for all confirmation requests creation.
    # Whether it's for user confirmation or not, with empty `self.user_config` 
    # and only `self.method_config` available or "fully configured"".
    def request_confirmation(
        self,
        # User or application provided state.
        state: dict,
        context: Optional[dict] = None,
    ) -> Tuple[ConfirmationState.Status, dict]:
        return ConfirmationState.Status.IN_PROCESS, {
            **state,
            'some_confirmation_token_to_check': 'value',
        }

    # To confirm saved confirmation state, `Confirmer` will call this method.
    def confirm(
        self,
        # State from `ConfirmationState` object.
        state: dict,
        # User-provided data to validate against.
        user_data: Any,
        context: Optional[dict] = None,
    ) -> bool:
        # Return True if user provided something that is somehow valid 
        # against the stored state.
        # User will never have access to the ConfigurationState data from 
        # your `request_confirmation` object.
        # At least he should not.
        return (
            state.get('some_confirmation_token_to_check')
            ==
            user_data.get('validation_token')
        )
```

### Frontend/DRF

Library has an API implmentation based on DjangoRestFramework.

It is available in `wcd_2factor.contrib.dtf` module.

In `urls.py`:

```python
from wcd_2factor.contrib.drf.views import make_urlpatterns as twofactor_make_urlpatterns


urlpatters = [
  # ...
  path(
    'api/v1/auth/2factor/',
    include(
      (twofactor_make_urlpatterns(), 'wcd_2factor'),
      namespace='2factor',
    )
  ),
]
```

And after the `/api/v1/auth/2factor/` you will have several endpoints:

#### Method configurations

**GET:** `method-config/list/active/` - List of active method configs.

#### User configurations

**GET:** `user-config/own/list/` - List of user's configurations.

**POST:** `user-config/own/create/` - Creating a new user configuration.
```json
{
  // Selected global method config.
  "method_config_id": 1,
  // Configuration data. 
  "config": {"email": "ad@ad.com"},
  // 2Factor method could be deactivated by user.
  "is_active": false,
  // Setting some method as a default.
  "is_default": false,
}
```

**POST:** `user-config/own/confirm/` - Confirming unconfirmed user configuration.
```json
{
  // User config id.
  "id": 1,
  // Unconfirmed confirmation identifier.
  "confirmation_id": "uuid-confirmation-identifier-0000",
  // Data to confirm with.
  "data": {"code": "some"},
}
```

**PUT:** `user-config/own/<int:pk>/update/` - Updating user configuration.
```json
{
  // Configuration data. 
  "config": {"email": "ad@ad.com"},
  // 2Factor method could be deactivated by user.
  "is_active": false,
  // Setting some method as a default.
  "is_default": false,
}
```

**DELETE:** `user-config/own/<int:pk>/destroy/` - Deletes user configuration.

#### Confirmation

**POST:** `confirmation/request/` - Creating a new confirmation.
```json
{
  // One of `method_config_id` or `user_config_id` must be provided:
  // Selected global method config. For example if user doesn't have it's own.
  "method_config_id": 1,
  // Selected user configuration method.
  "user_config_id": 1,
  // Additional data, if required. For example an "email" to confirm.
  "data": {"some": "data"},
}
```

Result will have a confirmation identifier. So on the frontend side it should be saved to confirm later.

**GET:** `confirmation/{confirmation_id}/check/` - Will return current `ConfirmationState` status.

**POST:** `confirmation/confirm/` - Method to confirm previously created "request".
```json
{
  // ConfirmationState identifier.
  "id": "uuid-confirmation-identifier-0000",
  // User data, that backend will use to validate the confirmation.
  "data": {"code": "confirmation-000-code"},
}
```

It will return the same data as previous requests, but this time confirmation `status` will be `confirmed`, or not if confirmation failed.
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.2.0]
### Changed
- Total rewrite. See docs.

## [0.1.7]
### Added
- Default to confirmation states admin list.
- New django unified `JSONField` support.

## [0.1.6]
### Added
- Translation strings.

## [0.1.3]
### Added
- Admin search ui for confirmation state model.

## [0.1.1]
### Added
- `DEBUG_CODE_RESPONSE` setting. It adds generated 'code' field to a request confirmation response for easier debug.

## [0.1.0]
Initial version.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "wc-django-2factor",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": null,
    "keywords": null,
    "author": "WebCase",
    "author_email": "info@webcase.studio",
    "download_url": "https://files.pythonhosted.org/packages/27/d1/4b70a2029c3c6b472cb3599805a168ae3ab63dc395f43dee48c977020883/wc_django_2factor-0.2.1.tar.gz",
    "platform": null,
    "description": "# WebCase 2factor API\n\nPackage to create general API for 2factor checkers.\n\n## Installation\n\n```sh\npip install wc-django-2factor\n```\n\nIn `settings.py`:\n\n```python\nINSTALLED_APPS += [\n    # Dependencies:\n    'pxd_admin_extensions',\n    'django_jsonform',\n    'wcd_settings',\n\n    # 2Factor itself.\n    'wcd_2factor',\n]\n\nWCD_2FACTOR = {\n    # It will be empty by default:\n    'METHODS': [\n        # Simple builtin 2factor method.\n        # Used to work like user secret confirmation.\n        # But mostly serves as an example.\n        'wcd_2factor.builtins.dummy.DUMMY_METHOD_DESCRIPTOR',\n    ],\n    # Custom json encoder\n    'JSON_ENCODER': 'wcd_2factor.utils.types.EnvoyerJSONEncoder',\n}\n```\n\n## Usage\n\n### Confirmer\n\nService for confirmation state management.\n\n```python\nfrom wcd_2factor.confirmer import default_confirmer, Backend, Confirmer\nfrom wcd_2factor.registries import method_config_registry\nfrom wcd_2factor.models import MethodConfig, UserConfig, ConfirmationState\n\n# Default registry\n\n# Use:\ndefault confirmer\n# Or create another.\nconfirmer = Confirmer(method_config_registry)\n\n# List of all available method keys:\ndefault_confirmer.get_methods()\n\n# List of all active `MethodConfig` instances:\ndefault_confirmer.get_method_configs()\n\n# List of all user `UserConfig` configurations:\ndefault_confirmer.get_user_configs(\n    # Provide user instance:\n    user=user or None,\n    # Or identifier:\n    user_id=user.pk or None\n)\n\n\n# Creates backend for some method config or `None` if there is no such:\ndefault_confirmer.make_backend(\n    # Optional, if `user_config` will be provided, since it also has a\n    # relation to a MethodConfig.\n    method_config=MethodConfig() or None,\n    # Optional, since confirmation could be done just using the MethodConfig \n    # by itself.\n    user_config=UserConfig() or None,\n    # Optional context to be passed to the backend.\n    context={} or None,\n    # Whether should raise an exception if backend could not be created.\n    # For example when there is no registered method.\n    should_raise=False,\n)\n\n\n# Method to change user confirmation.\n# Id will check if the changes are significant enough to request a \n# confirmation from user.\n# If it does - `make_confirmation` - will be a callable to create new \n# `ConfirmationState` instance. Else `None` will be returned.\ninstance = UserConfig()\nmake_confirmation = default_confirmer.change_user_config(\n    # Current instance to apply changes to.\n    instance,\n    # New configuration object. Either a pydantic object or dataclass or just a \n    # simple dict, that will be internally converted to a pydantic object.\n    DTO() or dict(),\n    # If you already have an initialized backend, method could use it \n    # instead of creating a new one:\n    backend=Backend() or None,\n    # Optional method config object. If, for example `user_config` instance \n    # doesen't have one attached yet,\n    method_config=MethodConfig() or None,\n    # Optional context to be passed to the backend's method.\n    context={} or None,\n)\n# Don't forget to save your configuration instance.\n# It will not be saved by this method.\ninstance.save()\n\nif make_confirmation is not None:\n    confirmation: ConfirmationState = make_confirmation()\n\n  \n# Requesting any type of confirmation:\nconfirmation: ConfirmationState = default_confirmer.request_confirmation(\n    # Optional, if `user_config` will be provided, since it also has a\n    # relation to a MethodConfig.\n    method_config=MethodConfig() or None,\n    # Optional, since confirmation could be done just using the MethodConfig \n    # by itself.\n    user_config=UserConfig() or None,\n    # If you already have an initialized backend, method could use it \n    # instead of creating a new one:\n    backend=Backend() or None,\n    # User provided state.\n    # It depends on backend what kind of parameters should and should not be \n    # present.\n    # In most cases if `used_config` provided - no additional information \n    # required at all.\n    state={} or None,\n    # Optional context to be passed to the backend's method.\n    context={} or None,\n)\n\n\n# If you have user data to confirm some `ConfirmationState` run this:\nconfirmation: ConfirmationState = default_confirmer.confirm(\n    # Either identifier:\n    id=uuid4() or None,\n    # Or confirmation object itself must be provided:\n    confirmation=confirmation or None,\n    # User passed data, that confirms that user have control over the \n    # \"second factor\":\n    data={} or None,\n    # If you already have an initialized backend, method could use it \n    # instead of creating a new one:\n    backend=Backend() or None,\n    # Optional context to be passed to the backend's method.\n    context={} or None,\n)\n# Method might return state, event when confirmation process failed for some \n# reason.\n# So check the confirmation before using it:\nif not confirmation.is_available():\n    raise ValueError('Confirmation failed.')\n\n\n# Checks if confirmation is confirmed and available to use:\navailable, optional_confirmation = default_confirmer.check(\n    # Either identifier:\n    id=uuid4() or None,\n    # Or confirmation object itself must be provided:\n    confirmation=confirmation or None,\n    # Optional context to be passed to the backend's method.\n    context={} or None,\n)\n# In some cases method might return None instead of confirmation object.\n# That happens when confirmation was already used, or there were none at all.\nif not available:\n    raise ValueError('Confirmation unavailable.')\n\n\n# And the last one.\n# ConfirmationState object is a \"one-time\" password to perform some action.\n# So after usage it will be deleted from the database.\nused, optional_confirmation = default_confirmer.use(\n    # Either identifier:\n    id=uuid4() or None,\n    # Or confirmation object itself must be provided:\n    confirmation=confirmation or None,\n    # Optional context to be passed to the backend's method.\n    context={} or None,\n)\n# But you will still have object returned if `used` was true.\n# You might need to do something with it afterwards.\nif not used:\n    raise ValueError('Confirmation failed.')\n```\n\n### Registry and custom Backends\n\nRegistry is a simple dict with some additional methods to register new confirmation methods.\n\nFor every method that could be used in your application `MethodConfigDescriptor` should be defined and added to registry.\n\nFor example:\n\n```python\nfrom wcd_2factor.registries import (\n    method_config_registry, Registry,\n    MethodConfigDescriptor, DTO,\n)\n\n# This is a default method's registry. \n# It will be autopopulated with descriptors from \n# django_settings.WCD_2FACTOR['METHODS'].\nmethod_config_registry\n\n# But nothing stops you from creating your own registry.\nmy_registry = Registry()\n\n# And after that you may add descriptors to it.\nMY_METHOD_DESCRIPTOR = my_registry.register(MethodConfigDescriptor(\n    # Unique method key.\n    key='my_method',\n    # Verbose method name.\n    verbose_name=gettext_lazy('My Method'),\n    # Backend class is required, since it will be used to execute every\n    # `Confirmer` method.\n    backend_class=Backend,\n    # Other data object classes and schemas are optional:\n    # MethodConfig pydantic class.\n    # Configuration model for MethodConfig.\n    config_global_dto=BaseModel or None,\n    # JSONSchema for that configuration.\n    config_global_schema=BaseModel.model_json_schema() or None,\n    # Configuration model for UserConfig.\n    config_user_dto=BaseModel or None,\n    # JSONSchema for that configuration.\n    config_user_schema=BaseModel.model_json_schema() or None,\n))\n```\n\nBut descriptor is only a simple definition with and additional configuration inside.\n\nAll the work with message sending and request confirmation are on your `Backend` implementation.\n\n```python\nfrom wcd_2factor.confirmer import Backend\nfrom wcd_2factor.registries import DTO, MethodConfigDescriptor\nfrom wcd_2factor.models import ConfirmationState, UserConfig\n\n\nclass YourBackend(Backend):\n    method_config: YourMethodDTO\n    user_config: Optional[YourUserDTO]\n\n    # Method that checks if user configuration changed.\n    # And if this change is significant enough to request a confirmation.\n    def change_user_config(\n        self,\n        # New configuration to check for changes.\n        new: YourMethodDTO,\n        context: Optional[dict] = None,\n    ) -> Tuple[bool, Optional[dict]]:\n        # Pseudocode:\n\n        if (\n            self.user_config is None\n            or\n            self.user_config != new\n        ):\n            # Then user configuration changed and confirmation with\n            # some \"state\" should be created to confirm the change.\n            return True, {'some': 'state'}\n\n        # Otherwise - do nothing.\n        return False, None\n\n    # This is method for all confirmation requests creation.\n    # Whether it's for user confirmation or not, with empty `self.user_config` \n    # and only `self.method_config` available or \"fully configured\"\".\n    def request_confirmation(\n        self,\n        # User or application provided state.\n        state: dict,\n        context: Optional[dict] = None,\n    ) -> Tuple[ConfirmationState.Status, dict]:\n        return ConfirmationState.Status.IN_PROCESS, {\n            **state,\n            'some_confirmation_token_to_check': 'value',\n        }\n\n    # To confirm saved confirmation state, `Confirmer` will call this method.\n    def confirm(\n        self,\n        # State from `ConfirmationState` object.\n        state: dict,\n        # User-provided data to validate against.\n        user_data: Any,\n        context: Optional[dict] = None,\n    ) -> bool:\n        # Return True if user provided something that is somehow valid \n        # against the stored state.\n        # User will never have access to the ConfigurationState data from \n        # your `request_confirmation` object.\n        # At least he should not.\n        return (\n            state.get('some_confirmation_token_to_check')\n            ==\n            user_data.get('validation_token')\n        )\n```\n\n### Frontend/DRF\n\nLibrary has an API implmentation based on DjangoRestFramework.\n\nIt is available in `wcd_2factor.contrib.dtf` module.\n\nIn `urls.py`:\n\n```python\nfrom wcd_2factor.contrib.drf.views import make_urlpatterns as twofactor_make_urlpatterns\n\n\nurlpatters = [\n  # ...\n  path(\n    'api/v1/auth/2factor/',\n    include(\n      (twofactor_make_urlpatterns(), 'wcd_2factor'),\n      namespace='2factor',\n    )\n  ),\n]\n```\n\nAnd after the `/api/v1/auth/2factor/` you will have several endpoints:\n\n#### Method configurations\n\n**GET:** `method-config/list/active/` - List of active method configs.\n\n#### User configurations\n\n**GET:** `user-config/own/list/` - List of user's configurations.\n\n**POST:** `user-config/own/create/` - Creating a new user configuration.\n```json\n{\n  // Selected global method config.\n  \"method_config_id\": 1,\n  // Configuration data. \n  \"config\": {\"email\": \"ad@ad.com\"},\n  // 2Factor method could be deactivated by user.\n  \"is_active\": false,\n  // Setting some method as a default.\n  \"is_default\": false,\n}\n```\n\n**POST:** `user-config/own/confirm/` - Confirming unconfirmed user configuration.\n```json\n{\n  // User config id.\n  \"id\": 1,\n  // Unconfirmed confirmation identifier.\n  \"confirmation_id\": \"uuid-confirmation-identifier-0000\",\n  // Data to confirm with.\n  \"data\": {\"code\": \"some\"},\n}\n```\n\n**PUT:** `user-config/own/<int:pk>/update/` - Updating user configuration.\n```json\n{\n  // Configuration data. \n  \"config\": {\"email\": \"ad@ad.com\"},\n  // 2Factor method could be deactivated by user.\n  \"is_active\": false,\n  // Setting some method as a default.\n  \"is_default\": false,\n}\n```\n\n**DELETE:** `user-config/own/<int:pk>/destroy/` - Deletes user configuration.\n\n#### Confirmation\n\n**POST:** `confirmation/request/` - Creating a new confirmation.\n```json\n{\n  // One of `method_config_id` or `user_config_id` must be provided:\n  // Selected global method config. For example if user doesn't have it's own.\n  \"method_config_id\": 1,\n  // Selected user configuration method.\n  \"user_config_id\": 1,\n  // Additional data, if required. For example an \"email\" to confirm.\n  \"data\": {\"some\": \"data\"},\n}\n```\n\nResult will have a confirmation identifier. So on the frontend side it should be saved to confirm later.\n\n**GET:** `confirmation/{confirmation_id}/check/` - Will return current `ConfirmationState` status.\n\n**POST:** `confirmation/confirm/` - Method to confirm previously created \"request\".\n```json\n{\n  // ConfirmationState identifier.\n  \"id\": \"uuid-confirmation-identifier-0000\",\n  // User data, that backend will use to validate the confirmation.\n  \"data\": {\"code\": \"confirmation-000-code\"},\n}\n```\n\nIt will return the same data as previous requests, but this time confirmation `status` will be `confirmed`, or not if confirmation failed.\n# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n## [0.2.0]\n### Changed\n- Total rewrite. See docs.\n\n## [0.1.7]\n### Added\n- Default to confirmation states admin list.\n- New django unified `JSONField` support.\n\n## [0.1.6]\n### Added\n- Translation strings.\n\n## [0.1.3]\n### Added\n- Admin search ui for confirmation state model.\n\n## [0.1.1]\n### Added\n- `DEBUG_CODE_RESPONSE` setting. It adds generated 'code' field to a request confirmation response for easier debug.\n\n## [0.1.0]\nInitial version.\n",
    "bugtrack_url": null,
    "license": "MIT License",
    "summary": "Package to create general API for 2factor checkers.",
    "version": "0.2.1",
    "project_urls": null,
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "27d14b70a2029c3c6b472cb3599805a168ae3ab63dc395f43dee48c977020883",
                "md5": "58ecd73222847e9c5494821b8f73311e",
                "sha256": "3417f2a3d1b4472beee88767a28b093cd9cf81f6b483464dccd6e1a239686ffb"
            },
            "downloads": -1,
            "filename": "wc_django_2factor-0.2.1.tar.gz",
            "has_sig": false,
            "md5_digest": "58ecd73222847e9c5494821b8f73311e",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 43538,
            "upload_time": "2024-12-05T13:43:55",
            "upload_time_iso_8601": "2024-12-05T13:43:55.724792Z",
            "url": "https://files.pythonhosted.org/packages/27/d1/4b70a2029c3c6b472cb3599805a168ae3ab63dc395f43dee48c977020883/wc_django_2factor-0.2.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-12-05 13:43:55",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "wc-django-2factor"
}
        
Elapsed time: 1.01909s