djoser-1.1.5/0000755000175000017500000000000013212504022014173 5ustar nimrootnimroot00000000000000djoser-1.1.5/README.rst0000644000175000017500000000661113206033444015676 0ustar nimrootnimroot00000000000000====== djoser ====== .. image:: https://img.shields.io/pypi/v/djoser.svg :target: https://pypi.org/project/djoser .. image:: https://img.shields.io/travis/sunscrapers/djoser.svg :target: https://travis-ci.org/sunscrapers/djoser .. image:: https://img.shields.io/codecov/c/github/sunscrapers/djoser.svg :target: https://codecov.io/gh/sunscrapers/djoser .. image:: https://img.shields.io/scrutinizer/g/sunscrapers/djoser.svg :target: https://scrutinizer-ci.com/g/sunscrapers/djoser REST implementation of `Django `_ authentication system. **djoser** library provides a set of `Django Rest Framework `_ views to handle basic actions such as registration, login, logout, password reset and account activation. It works with `custom user model `_. Instead of reusing Django code (e.g. ``PasswordResetForm``), we reimplemented few things to fit better into `Single Page App `_ architecture. Developed by `SUNSCRAPERS `_ with passion & patience. .. image:: https://asciinema.org/a/FBTYjfDPUr99jxZqbLOZhh9Pd.png :target: https://asciinema.org/a/FBTYjfDPUr99jxZqbLOZhh9Pd?autoplay=1&speed=2 Requirements ============ To be able to run **djoser** you have to meet following requirements: - Python (2.7, 3.4, 3.5, 3.6) - Django (1.10, 1.11) - Django REST Framework (3.7) Installation ============ Simply install using ``pip``: .. code-block:: bash $ pip install djoser And continue with the steps described at `configuration `_ guide. Documentation ============= Documentation is available to study at `http://djoser.readthedocs.io `_ and in ``docs`` directory. Contributing and development ============================ To start developing on **djoser**, clone the repository: .. code-block:: bash $ git clone git@github.com:sunscrapers/djoser.git If you are a **pipenv** user you can quickly setup testing environment by using Make commands: .. code-block:: bash $ make init $ make test Otherwise, if you cannot use Make commands, please create virtualenv and install requirements manually: .. code-block:: bash $ pip install django djangorestframework $ pip install -r requirements.txt If you are running djoser tests on Python 2.7 you also need to install **mock** library. .. code-block:: bash $ pip install mock # only on Python 2.7 $ cd testproject $ ./manage.py test If you need to run tests against all supported Python and Django versions then invoke: .. code-block:: bash $ pip install tox $ tox You can also play with test project by running following commands: .. code-block:: bash $ ./manage.py migrate $ ./manage.py runserver Similar projects ================ List of projects related to Django, REST and authentication: - `django-rest-framework-jwt `_ - `django-oauth-toolkit `_ - `django-rest-auth `_ - `django-rest-framework-digestauth `_ (not maintained) - `doac `_ (not maintained) djoser-1.1.5/djoser/0000755000175000017500000000000013212504022015461 5ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/conf.py0000644000175000017500000001154713177462464017017 0ustar nimrootnimroot00000000000000import warnings from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured from django.test.signals import setting_changed from django.utils import six from django.utils.functional import LazyObject from django.utils.module_loading import import_string DJOSER_SETTINGS_NAMESPACE = 'DJOSER' class ObjDict(dict): def __getattribute__(self, item): try: if isinstance(self[item], str): self[item] = import_string(self[item]) value = self[item] except KeyError: value = super(ObjDict, self).__getattribute__(item) return value default_settings = { 'SEND_ACTIVATION_EMAIL': False, 'SEND_CONFIRMATION_EMAIL': False, 'SET_PASSWORD_RETYPE': False, 'SET_USERNAME_RETYPE': False, 'PASSWORD_RESET_CONFIRM_RETYPE': False, 'PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND': False, 'PASSWORD_VALIDATORS': [], 'TOKEN_MODEL': 'rest_framework.authtoken.models.Token', 'SERIALIZERS': ObjDict({ 'activation': 'djoser.serializers.ActivationSerializer', 'password_reset': 'djoser.serializers.PasswordResetSerializer', 'password_reset_confirm': 'djoser.serializers.PasswordResetConfirmSerializer', 'password_reset_confirm_retype': 'djoser.serializers.PasswordResetConfirmRetypeSerializer', 'set_password': 'djoser.serializers.SetPasswordSerializer', 'set_password_retype': 'djoser.serializers.SetPasswordRetypeSerializer', 'set_username': 'djoser.serializers.SetUsernameSerializer', 'set_username_retype': 'djoser.serializers.SetUsernameRetypeSerializer', 'user_create': 'djoser.serializers.UserCreateSerializer', 'user_delete': 'djoser.serializers.UserDeleteSerializer', 'user': 'djoser.serializers.UserSerializer', 'token': 'djoser.serializers.TokenSerializer', 'token_create': 'djoser.serializers.TokenCreateSerializer', }), 'EMAIL': ObjDict({ 'activation': 'djoser.email.ActivationEmail', 'confirmation': 'djoser.email.ConfirmationEmail', 'password_reset': 'djoser.email.PasswordResetEmail', }), 'LOGOUT_ON_PASSWORD_CHANGE': False, 'USER_EMAIL_FIELD_NAME': 'email', 'SOCIAL_AUTH_TOKEN_STRATEGY': 'djoser.social.token.jwt.TokenStrategy', 'SOCIAL_AUTH_ALLOWED_REDIRECT_URIS': [], } SETTINGS_TO_IMPORT = ['TOKEN_MODEL', 'SOCIAL_AUTH_TOKEN_STRATEGY'] class Settings(object): def __init__(self, default_settings, explicit_overriden_settings=None): if explicit_overriden_settings is None: explicit_overriden_settings = {} overriden_settings = getattr( django_settings, DJOSER_SETTINGS_NAMESPACE, {} ) or explicit_overriden_settings self._load_default_settings() self._override_settings(overriden_settings) self._init_settings_to_import() def _load_default_settings(self): for setting_name, setting_value in six.iteritems(default_settings): if setting_name.isupper(): setattr(self, setting_name, setting_value) def _override_settings(self, overriden_settings): for setting_name, setting_value in six.iteritems(overriden_settings): value = setting_value if isinstance(setting_value, dict): value = getattr(self, setting_name, {}) value.update(ObjDict(setting_value)) setattr(self, setting_name, value) def _init_settings_to_import(self): for setting_name in SETTINGS_TO_IMPORT: value = getattr(self, setting_name) if isinstance(value, str): setattr(self, setting_name, import_string(value)) class LazySettings(LazyObject): def _setup(self, explicit_overriden_settings=None): self._wrapped = Settings(default_settings, explicit_overriden_settings) def get(self, key): """ This function is here only to provide backwards compatibility in case anyone uses old settings interface. It is strongly encouraged to use dot notation. """ warnings.warn( 'The settings.get(key) is superseded by the dot attribute access.', PendingDeprecationWarning ) try: return getattr(self, key) except AttributeError: raise ImproperlyConfigured('Missing settings: {}[\'{}\']'.format( DJOSER_SETTINGS_NAMESPACE, key) ) settings = LazySettings() def reload_djoser_settings(*args, **kwargs): global settings setting, value = kwargs['setting'], kwargs['value'] if setting == DJOSER_SETTINGS_NAMESPACE: settings._setup(explicit_overriden_settings=value) setting_changed.connect(reload_djoser_settings) djoser-1.1.5/djoser/utils.py0000644000175000017500000000173713161165470017220 0ustar nimrootnimroot00000000000000from django.contrib.auth import user_logged_in, user_logged_out from django.utils.encoding import force_bytes, force_text from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from djoser.conf import settings def encode_uid(pk): return urlsafe_base64_encode(force_bytes(pk)).decode() def decode_uid(pk): return force_text(urlsafe_base64_decode(pk)) def login_user(request, user): token, _ = settings.TOKEN_MODEL.objects.get_or_create(user=user) user_logged_in.send(sender=user.__class__, request=request, user=user) return token def logout_user(request): settings.TOKEN_MODEL.objects.filter(user=request.user).delete() user_logged_out.send( sender=request.user.__class__, request=request, user=request.user ) class ActionViewMixin(object): def post(self, request): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) return self._action(serializer) djoser-1.1.5/djoser/views.py0000644000175000017500000002065613177036027017220 0ustar nimrootnimroot00000000000000from django.contrib.auth import get_user_model from django.contrib.auth.tokens import default_token_generator from django.urls.exceptions import NoReverseMatch from rest_framework import generics, permissions, status, views from rest_framework.response import Response from rest_framework.reverse import reverse from djoser import utils, signals from djoser.compat import get_user_email, get_user_email_field_name from djoser.conf import settings User = get_user_model() class RootView(views.APIView): """ Root endpoint - use one of sub endpoints. """ permission_classes = [permissions.AllowAny] def aggregate_djoser_urlpattern_names(self): from djoser.urls import base, authtoken urlpattern_names = [pattern.name for pattern in base.urlpatterns] urlpattern_names += [pattern.name for pattern in authtoken.urlpatterns] urlpattern_names += self._get_jwt_urlpatterns() return urlpattern_names def get_urls_map(self, request, urlpattern_names, fmt): urls_map = {} for urlpattern_name in urlpattern_names: try: url = reverse(urlpattern_name, request=request, format=fmt) except NoReverseMatch: url = '' urls_map[urlpattern_name] = url return urls_map def get(self, request, fmt=None): urlpattern_names = self.aggregate_djoser_urlpattern_names() urls_map = self.get_urls_map(request, urlpattern_names, fmt) return Response(urls_map) def _get_jwt_urlpatterns(self): try: from djoser.urls import jwt return [pattern.name for pattern in jwt.urlpatterns] except ImportError: return [] class UserCreateView(generics.CreateAPIView): """ Use this endpoint to register new user. """ serializer_class = settings.SERIALIZERS.user_create permission_classes = [permissions.AllowAny] def perform_create(self, serializer): user = serializer.save() signals.user_registered.send( sender=self.__class__, user=user, request=self.request ) context = {'user': user} to = [get_user_email(user)] if settings.SEND_ACTIVATION_EMAIL: settings.EMAIL.activation(self.request, context).send(to) elif settings.SEND_CONFIRMATION_EMAIL: settings.EMAIL.confirmation(self.request, context).send(to) class UserDeleteView(generics.CreateAPIView): """ Use this endpoint to remove actually authenticated user """ serializer_class = settings.SERIALIZERS.user_delete permission_classes = [permissions.IsAuthenticated] def get_object(self): return self.request.user def post(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer(instance, data=request.data) serializer.is_valid(raise_exception=True) utils.logout_user(self.request) instance.delete() return Response(status=status.HTTP_204_NO_CONTENT) class TokenCreateView(utils.ActionViewMixin, generics.GenericAPIView): """ Use this endpoint to obtain user authentication token. """ serializer_class = settings.SERIALIZERS.token_create permission_classes = [permissions.AllowAny] def _action(self, serializer): token = utils.login_user(self.request, serializer.user) token_serializer_class = settings.SERIALIZERS.token return Response( data=token_serializer_class(token).data, status=status.HTTP_200_OK, ) class TokenDestroyView(views.APIView): """ Use this endpoint to logout user (remove user authentication token). """ permission_classes = [permissions.IsAuthenticated] def post(self, request): utils.logout_user(request) return Response(status=status.HTTP_204_NO_CONTENT) class PasswordResetView(utils.ActionViewMixin, generics.GenericAPIView): """ Use this endpoint to send email to user with password reset link. """ serializer_class = settings.SERIALIZERS.password_reset permission_classes = [permissions.AllowAny] _users = None def _action(self, serializer): for user in self.get_users(serializer.data['email']): self.send_password_reset_email(user) return Response(status=status.HTTP_204_NO_CONTENT) def get_users(self, email): if self._users is None: email_field_name = get_user_email_field_name(User) users = User._default_manager.filter(**{ email_field_name + '__iexact': email }) self._users = [ u for u in users if u.is_active and u.has_usable_password() ] return self._users def send_password_reset_email(self, user): context = {'user': user} to = [get_user_email(user)] settings.EMAIL.password_reset(self.request, context).send(to) class SetPasswordView(utils.ActionViewMixin, generics.GenericAPIView): """ Use this endpoint to change user password. """ permission_classes = [permissions.IsAuthenticated] def get_serializer_class(self): if settings.SET_PASSWORD_RETYPE: return settings.SERIALIZERS.set_password_retype return settings.SERIALIZERS.set_password def _action(self, serializer): self.request.user.set_password(serializer.data['new_password']) self.request.user.save() if settings.LOGOUT_ON_PASSWORD_CHANGE: utils.logout_user(self.request) return Response(status=status.HTTP_204_NO_CONTENT) class PasswordResetConfirmView(utils.ActionViewMixin, generics.GenericAPIView): """ Use this endpoint to finish reset password process. """ permission_classes = [permissions.AllowAny] token_generator = default_token_generator def get_serializer_class(self): if settings.PASSWORD_RESET_CONFIRM_RETYPE: return settings.SERIALIZERS.password_reset_confirm_retype return settings.SERIALIZERS.password_reset_confirm def _action(self, serializer): serializer.user.set_password(serializer.data['new_password']) serializer.user.save() return Response(status=status.HTTP_204_NO_CONTENT) class ActivationView(utils.ActionViewMixin, generics.GenericAPIView): """ Use this endpoint to activate user account. """ serializer_class = settings.SERIALIZERS.activation permission_classes = [permissions.AllowAny] token_generator = default_token_generator def _action(self, serializer): user = serializer.user user.is_active = True user.save() signals.user_activated.send( sender=self.__class__, user=user, request=self.request ) if settings.SEND_CONFIRMATION_EMAIL: context = {'user': user} to = [get_user_email(user)] settings.EMAIL.confirmation(self.request, context).send(to) return Response(status=status.HTTP_204_NO_CONTENT) class SetUsernameView(utils.ActionViewMixin, generics.GenericAPIView): """ Use this endpoint to change user username. """ permission_classes = [permissions.IsAuthenticated] def get_serializer_class(self): if settings.SET_USERNAME_RETYPE: return settings.SERIALIZERS.set_username_retype return settings.SERIALIZERS.set_username def _action(self, serializer): user = self.request.user new_username = serializer.data['new_' + User.USERNAME_FIELD] setattr(user, User.USERNAME_FIELD, new_username) if settings.SEND_ACTIVATION_EMAIL: user.is_active = False context = {'user': user} to = [get_user_email(user)] settings.EMAIL.activation(self.request, context).send(to) user.save() return Response(status=status.HTTP_204_NO_CONTENT) class UserView(generics.RetrieveUpdateAPIView): """ Use this endpoint to retrieve/update user. """ model = User serializer_class = settings.SERIALIZERS.user permission_classes = [permissions.IsAuthenticated] def get_object(self, *args, **kwargs): return self.request.user def perform_update(self, serializer): super(UserView, self).perform_update(serializer) user = serializer.instance if settings.SEND_ACTIVATION_EMAIL and not user.is_active: context = {'user': user} to = [get_user_email(user)] settings.EMAIL.activation(self.request, context).send(to) djoser-1.1.5/djoser/social/0000755000175000017500000000000013212504022016733 5ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/social/backends/0000755000175000017500000000000013212504022020505 5ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/social/backends/facebook.py0000644000175000017500000000020413177462464022653 0ustar nimrootnimroot00000000000000from social_core.backends.facebook import FacebookOAuth2 class FacebookOAuth2Override(FacebookOAuth2): REDIRECT_STATE = False djoser-1.1.5/djoser/social/backends/__init__.py0000644000175000017500000000000013177462464022633 0ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/social/views.py0000644000175000017500000000201513177462464020467 0ustar nimrootnimroot00000000000000from rest_framework import generics, permissions, status from rest_framework.response import Response from social_django.utils import load_backend, load_strategy from djoser.conf import settings from djoser.social.serializers import ProviderAuthSerializer class ProviderAuthView(generics.CreateAPIView): permission_classes = [permissions.AllowAny] serializer_class = ProviderAuthSerializer def get(self, request, *args, **kwargs): redirect_uri = request.GET.get('redirect_uri') if redirect_uri not in settings.SOCIAL_AUTH_ALLOWED_REDIRECT_URIS: return Response(status=status.HTTP_400_BAD_REQUEST) strategy = load_strategy(request) strategy.session_set('redirect_uri', redirect_uri) backend_name = self.kwargs['provider'] backend = load_backend( strategy, backend_name, redirect_uri=redirect_uri ) authorization_url = backend.auth_url() return Response(data={ 'authorization_url': authorization_url, }) djoser-1.1.5/djoser/social/urls.py0000644000175000017500000000032013177462464020314 0ustar nimrootnimroot00000000000000from django.conf.urls import url from djoser.social import views urlpatterns = [ url( r'^o/(?P\S+)/$', views.ProviderAuthView.as_view(), name='provider-auth' ), ] djoser-1.1.5/djoser/social/token/0000755000175000017500000000000013212504022020053 5ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/social/token/jwt.py0000644000175000017500000000045213177554046021257 0ustar nimrootnimroot00000000000000class TokenStrategy: @classmethod def obtain(cls, user): from rest_framework_jwt.settings import api_settings payload = api_settings.JWT_PAYLOAD_HANDLER(user) return { 'token': api_settings.JWT_ENCODE_HANDLER(payload), 'user': user } djoser-1.1.5/djoser/social/token/__init__.py0000644000175000017500000000000013177462464022201 0ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/social/serializers.py0000644000175000017500000000376413212472704021666 0ustar nimrootnimroot00000000000000from rest_framework import serializers from social_core import exceptions from social_django.utils import load_backend, load_strategy from djoser.conf import settings class ProviderAuthSerializer(serializers.Serializer): # GET auth token token = serializers.CharField(read_only=True) user = serializers.CharField(read_only=True) def create(self, validated_data): user = validated_data['user'] return settings.SOCIAL_AUTH_TOKEN_STRATEGY.obtain(user) def validate(self, attrs): request = self.context['request'] if 'state' in request.GET: self._validate_state(request.GET['state']) strategy = load_strategy(request) redirect_uri = strategy.session_get('redirect_uri') backend_name = self.context['view'].kwargs['provider'] backend = load_backend( strategy, backend_name, redirect_uri=redirect_uri ) try: user = backend.auth_complete() except exceptions.AuthException as e: raise serializers.ValidationError(str(e)) return {'user': user} def _validate_state(self, value): request = self.context['request'] strategy = load_strategy(request) redirect_uri = strategy.session_get('redirect_uri') backend_name = self.context['view'].kwargs['provider'] backend = load_backend( strategy, backend_name, redirect_uri=redirect_uri ) try: backend.validate_state() except exceptions.AuthMissingParameter: raise serializers.ValidationError( 'State could not be found in request data.' ) except exceptions.AuthStateMissing: raise serializers.ValidationError( 'State could not be found in server-side session data.' ) except exceptions.AuthStateForbidden: raise serializers.ValidationError( 'Invalid state has been provided.' ) return value djoser-1.1.5/djoser/social/__init__.py0000644000175000017500000000000013177462464021061 0ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/compat.py0000644000175000017500000000063713166200545017337 0ustar nimrootnimroot00000000000000from djoser.conf import settings __all__ = ['settings'] def get_user_email(user): email_field_name = get_user_email_field_name(user) return getattr(user, email_field_name, None) def get_user_email_field_name(user): try: # Assume we are Django >= 1.11 return user.get_email_field_name() except AttributeError: # we are using Django < 1.11 return settings.USER_EMAIL_FIELD_NAME djoser-1.1.5/djoser/serializers.py0000644000175000017500000002123213177036027020406 0ustar nimrootnimroot00000000000000from django.contrib.auth import authenticate, get_user_model from django.contrib.auth.password_validation import validate_password from django.core import exceptions as django_exceptions from django.db import IntegrityError, transaction from rest_framework import exceptions, serializers from djoser import constants, utils from djoser.compat import get_user_email, get_user_email_field_name from djoser.conf import settings User = get_user_model() class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = tuple(User.REQUIRED_FIELDS) + ( User._meta.pk.name, User.USERNAME_FIELD, ) read_only_fields = (User.USERNAME_FIELD,) def update(self, instance, validated_data): email_field = get_user_email_field_name(User) if settings.SEND_ACTIVATION_EMAIL and email_field in validated_data: instance_email = get_user_email(instance) if instance_email != validated_data[email_field]: instance.is_active = False instance.save(update_fields=['is_active']) return super(UserSerializer, self).update(instance, validated_data) class UserCreateSerializer(serializers.ModelSerializer): password = serializers.CharField( style={'input_type': 'password'}, write_only=True ) default_error_messages = { 'cannot_create_user': constants.CANNOT_CREATE_USER_ERROR, } class Meta: model = User fields = tuple(User.REQUIRED_FIELDS) + ( User.USERNAME_FIELD, User._meta.pk.name, 'password', ) def validate(self, attrs): user = User(**attrs) password = attrs.get('password') try: validate_password(password, user) except django_exceptions.ValidationError as e: raise serializers.ValidationError({'password': list(e.messages)}) return attrs def create(self, validated_data): try: user = self.perform_create(validated_data) except IntegrityError: self.fail('cannot_create_user') return user def perform_create(self, validated_data): with transaction.atomic(): user = User.objects.create_user(**validated_data) if settings.SEND_ACTIVATION_EMAIL: user.is_active = False user.save(update_fields=['is_active']) return user class TokenCreateSerializer(serializers.Serializer): password = serializers.CharField( required=False, style={'input_type': 'password'} ) default_error_messages = { 'invalid_credentials': constants.INVALID_CREDENTIALS_ERROR, 'inactive_account': constants.INACTIVE_ACCOUNT_ERROR, } def __init__(self, *args, **kwargs): super(TokenCreateSerializer, self).__init__(*args, **kwargs) self.user = None self.fields[User.USERNAME_FIELD] = serializers.CharField( required=False ) def validate(self, attrs): self.user = authenticate( username=attrs.get(User.USERNAME_FIELD), password=attrs.get('password') ) self._validate_user_exists(self.user) self._validate_user_is_active(self.user) return attrs def _validate_user_exists(self, user): if not user: self.fail('invalid_credentials') def _validate_user_is_active(self, user): if not user.is_active: self.fail('inactive_account') class PasswordResetSerializer(serializers.Serializer): email = serializers.EmailField() default_error_messages = {'email_not_found': constants.EMAIL_NOT_FOUND} def validate_email(self, value): users = self.context['view'].get_users(value) if settings.PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND and not users: self.fail('email_not_found') else: return value class UidAndTokenSerializer(serializers.Serializer): uid = serializers.CharField() token = serializers.CharField() default_error_messages = { 'invalid_token': constants.INVALID_TOKEN_ERROR, 'invalid_uid': constants.INVALID_UID_ERROR, } def validate_uid(self, value): try: uid = utils.decode_uid(value) self.user = User.objects.get(pk=uid) except (User.DoesNotExist, ValueError, TypeError, OverflowError): self.fail('invalid_uid') return value def validate(self, attrs): attrs = super(UidAndTokenSerializer, self).validate(attrs) is_token_valid = self.context['view'].token_generator.check_token( self.user, attrs['token'] ) if is_token_valid: return attrs else: self.fail('invalid_token') class ActivationSerializer(UidAndTokenSerializer): default_error_messages = {'stale_token': constants.STALE_TOKEN_ERROR} def validate(self, attrs): attrs = super(ActivationSerializer, self).validate(attrs) if not self.user.is_active: return attrs raise exceptions.PermissionDenied(self.error_messages['stale_token']) class PasswordSerializer(serializers.Serializer): new_password = serializers.CharField(style={'input_type': 'password'}) def validate(self, attrs): user = self.context['request'].user or self.user assert user is not None try: validate_password(attrs['new_password'], user) except django_exceptions.ValidationError as e: raise serializers.ValidationError({ 'new_password': list(e.messages) }) return super(PasswordSerializer, self).validate(attrs) class PasswordRetypeSerializer(PasswordSerializer): re_new_password = serializers.CharField(style={'input_type': 'password'}) default_error_messages = { 'password_mismatch': constants.PASSWORD_MISMATCH_ERROR, } def validate(self, attrs): attrs = super(PasswordRetypeSerializer, self).validate(attrs) if attrs['new_password'] == attrs['re_new_password']: return attrs else: self.fail('password_mismatch') class CurrentPasswordSerializer(serializers.Serializer): current_password = serializers.CharField(style={'input_type': 'password'}) default_error_messages = { 'invalid_password': constants.INVALID_PASSWORD_ERROR, } def validate_current_password(self, value): is_password_valid = self.context['request'].user.check_password(value) if is_password_valid: return value else: self.fail('invalid_password') class SetPasswordSerializer(PasswordSerializer, CurrentPasswordSerializer): pass class SetPasswordRetypeSerializer(PasswordRetypeSerializer, CurrentPasswordSerializer): pass class PasswordResetConfirmSerializer(UidAndTokenSerializer, PasswordSerializer): pass class PasswordResetConfirmRetypeSerializer(UidAndTokenSerializer, PasswordRetypeSerializer): pass class UserDeleteSerializer(CurrentPasswordSerializer): pass class SetUsernameSerializer(serializers.ModelSerializer, CurrentPasswordSerializer): class Meta(object): model = User fields = (User.USERNAME_FIELD, 'current_password') def __init__(self, *args, **kwargs): """ This method should probably be replaced by a better solution. Its purpose is to replace USERNAME_FIELD with 'new_' + USERNAME_FIELD so that the new field is being assigned a field for USERNAME_FIELD """ super(SetUsernameSerializer, self).__init__(*args, **kwargs) username_field = User.USERNAME_FIELD self.fields['new_' + username_field] = self.fields.pop(username_field) class SetUsernameRetypeSerializer(SetUsernameSerializer): default_error_messages = { 'username_mismatch': constants.USERNAME_MISMATCH_ERROR.format( User.USERNAME_FIELD ), } def __init__(self, *args, **kwargs): super(SetUsernameRetypeSerializer, self).__init__(*args, **kwargs) self.fields['re_new_' + User.USERNAME_FIELD] = serializers.CharField() def validate(self, attrs): attrs = super(SetUsernameRetypeSerializer, self).validate(attrs) new_username = attrs[User.USERNAME_FIELD] if new_username != attrs['re_new_' + User.USERNAME_FIELD]: self.fail('username_mismatch') else: return attrs class TokenSerializer(serializers.ModelSerializer): auth_token = serializers.CharField(source='key') class Meta: model = settings.TOKEN_MODEL fields = ('auth_token',) djoser-1.1.5/djoser/email.py0000644000175000017500000000217313172220231017127 0ustar nimrootnimroot00000000000000from django.contrib.auth.tokens import default_token_generator from templated_mail.mail import BaseEmailMessage from djoser import utils from djoser.conf import settings class ActivationEmail(BaseEmailMessage): template_name = 'email/activation.html' def get_context_data(self): context = super(ActivationEmail, self).get_context_data() user = context.get('user') context['uid'] = utils.encode_uid(user.pk) context['token'] = default_token_generator.make_token(user) context['url'] = settings.ACTIVATION_URL.format(**context) return context class ConfirmationEmail(BaseEmailMessage): template_name = 'email/confirmation.html' class PasswordResetEmail(BaseEmailMessage): template_name = 'email/password_reset.html' def get_context_data(self): context = super(PasswordResetEmail, self).get_context_data() user = context.get('user') context['uid'] = utils.encode_uid(user.pk) context['token'] = default_token_generator.make_token(user) context['url'] = settings.PASSWORD_RESET_CONFIRM_URL.format(**context) return context djoser-1.1.5/djoser/constants.py0000644000175000017500000000156013134346130020060 0ustar nimrootnimroot00000000000000from django.utils.translation import ugettext_lazy as _ INVALID_CREDENTIALS_ERROR = _('Unable to login with provided credentials.') INACTIVE_ACCOUNT_ERROR = _('User account is disabled.') INVALID_TOKEN_ERROR = _('Invalid token for given user.') INVALID_UID_ERROR = _('Invalid user id or user doesn\'t exist.') STALE_TOKEN_ERROR = _('Stale token for given user.') PASSWORD_MISMATCH_ERROR = _('The two password fields didn\'t match.') USERNAME_MISMATCH_ERROR = _('The two {0} fields didn\'t match.') INVALID_PASSWORD_ERROR = _('Invalid password.') EMAIL_NOT_FOUND = _('User with given email does not exist.') CANNOT_CREATE_USER_ERROR = _('Unable to create account.') USER_WITHOUT_EMAIL_FIELD_ERROR = _( 'User model does not contain specified email field. ' 'Please see http://djoser.readthedocs.io/en/latest/settings.html#' 'USER_EMAIL_FIELD_NAME for more details.' ) djoser-1.1.5/djoser/urls/0000755000175000017500000000000013212504022016446 5ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/urls/authtoken.py0000644000175000017500000000046413166203501021034 0ustar nimrootnimroot00000000000000from django.conf.urls import url from djoser import views urlpatterns = [ url( r'^token/create/$', views.TokenCreateView.as_view(), name='token-create' ), url( r'^token/destroy/$', views.TokenDestroyView.as_view(), name='token-destroy' ), ] djoser-1.1.5/djoser/urls/jwt.py0000644000175000017500000000045513163500113017632 0ustar nimrootnimroot00000000000000from django.conf.urls import url from rest_framework_jwt import views urlpatterns = [ url(r'^jwt/create/', views.obtain_jwt_token, name='jwt-create'), url(r'^jwt/refresh/', views.refresh_jwt_token, name='jwt-refresh'), url(r'^jwt/verify/', views.verify_jwt_token, name='jwt-verify'), ] djoser-1.1.5/djoser/urls/base.py0000644000175000017500000000206113164466040017746 0ustar nimrootnimroot00000000000000from django.conf.urls import url from django.contrib.auth import get_user_model from djoser import views User = get_user_model() urlpatterns = [ url(r'^me/$', views.UserView.as_view(), name='user'), url( r'^users/create/$', views.UserCreateView.as_view(), name='user-create' ), url( r'^users/delete/$', views.UserDeleteView.as_view(), name='user-delete' ), url( r'^users/activate/$', views.ActivationView.as_view(), name='user-activate' ), url( r'^{0}/$'.format(User.USERNAME_FIELD), views.SetUsernameView.as_view(), name='set_username' ), url(r'^password/$', views.SetPasswordView.as_view(), name='set_password'), url( r'^password/reset/$', views.PasswordResetView.as_view(), name='password_reset' ), url( r'^password/reset/confirm/$', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm' ), url(r'^$', views.RootView.as_view(), name='root'), ] djoser-1.1.5/djoser/urls/__init__.py0000644000175000017500000000007113145541001020560 0ustar nimrootnimroot00000000000000from .base import urlpatterns __all__ = ['urlpatterns'] djoser-1.1.5/djoser/__init__.py0000644000175000017500000000000013120707604017571 0ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/signals.py0000644000175000017500000000034313120707604017504 0ustar nimrootnimroot00000000000000from django.dispatch import Signal # New user has registered. user_registered = Signal(providing_args=["user", "request"]) # User has activated his or her account. user_activated = Signal(providing_args=["user", "request"]) djoser-1.1.5/djoser/templates/0000755000175000017500000000000013212504022017457 5ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/templates/email/0000755000175000017500000000000013212504022020546 5ustar nimrootnimroot00000000000000djoser-1.1.5/djoser/templates/email/activation.html0000644000175000017500000000175013161165470023615 0ustar nimrootnimroot00000000000000{% load i18n %} {% block subject %} {% blocktrans %}Account activation on {{ site_name }}{% endblocktrans %} {% endblock subject %} {% block text_body %} {% blocktrans %}You're receiving this email because you need to finish activation process on {{ site_name }}.{% endblocktrans %} {% trans "Please go to the following page to activate account:" %} {{ protocol }}://{{ domain }}/{{ url }} {% trans "Thanks for using our site!" %} {% blocktrans %}The {{ site_name }} team{% endblocktrans %} {% endblock text_body %} {% block html_body %} {% blocktrans %}

You're receiving this email because you need to finish activation process on {{ site_name }}.

{% endblocktrans %}

{% trans "Please go to the following page to activate account:" %}

{{ protocol }}://{{ domain }}/{{ url }}

{% trans "Thanks for using our site!" %}

{% blocktrans %}The {{ site_name }} team{% endblocktrans %}

{% endblock html_body %} djoser-1.1.5/djoser/templates/email/password_reset.html0000644000175000017500000000226113161165470024516 0ustar nimrootnimroot00000000000000{% load i18n %} {% block subject %} {% blocktrans %}Password reset on {{ site_name }}{% endblocktrans %} {% endblock subject %} {% block text_body %} {% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %} {% trans "Please go to the following page and choose a new password:" %} {{ protocol }}://{{ domain }}/{{ url }} {% trans "Your username, in case you've forgotten:" %} {{ user.get_username }} {% trans "Thanks for using our site!" %} {% blocktrans %}The {{ site_name }} team{% endblocktrans %} {% endblock text_body %} {% block html_body %}

{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}

{% trans "Please go to the following page and choose a new password:" %}

{{ protocol }}://{{ domain }}/{{ url }}

{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}

{% trans "Thanks for using our site!" %}

{% blocktrans %}The {{ site_name }} team{% endblocktrans %}

{% endblock html_body %} djoser-1.1.5/djoser/templates/email/confirmation.html0000644000175000017500000000115013161165470024136 0ustar nimrootnimroot00000000000000{% load i18n %} {% block subject %} {% blocktrans %}{{ site_name }} - Your account has been successfully created and activated!{% endblocktrans %} {% endblock %} {% block text_body %} {% trans "Your account has been created and is ready to use!" %} {% trans "Thanks for using our site!" %} {% blocktrans %}The {{ site_name }} team{% endblocktrans %} {% endblock text_body %} {% block html_body %}

{% trans "Your account has been created and is ready to use!" %}

{% trans "Thanks for using our site!" %}

{% blocktrans %}The {{ site_name }} team{% endblocktrans %}

{% endblock html_body %} djoser-1.1.5/PKG-INFO0000644000175000017500000001217213212504022015273 0ustar nimrootnimroot00000000000000Metadata-Version: 1.1 Name: djoser Version: 1.1.5 Summary: REST version of Django authentication system. Home-page: https://github.com/sunscrapers/djoser Author: SUNSCRAPERS Author-email: info@sunscrapers.com License: MIT Description-Content-Type: UNKNOWN Description: ====== djoser ====== .. image:: https://img.shields.io/pypi/v/djoser.svg :target: https://pypi.org/project/djoser .. image:: https://img.shields.io/travis/sunscrapers/djoser.svg :target: https://travis-ci.org/sunscrapers/djoser .. image:: https://img.shields.io/codecov/c/github/sunscrapers/djoser.svg :target: https://codecov.io/gh/sunscrapers/djoser .. image:: https://img.shields.io/scrutinizer/g/sunscrapers/djoser.svg :target: https://scrutinizer-ci.com/g/sunscrapers/djoser REST implementation of `Django `_ authentication system. **djoser** library provides a set of `Django Rest Framework `_ views to handle basic actions such as registration, login, logout, password reset and account activation. It works with `custom user model `_. Instead of reusing Django code (e.g. ``PasswordResetForm``), we reimplemented few things to fit better into `Single Page App `_ architecture. Developed by `SUNSCRAPERS `_ with passion & patience. .. image:: https://asciinema.org/a/FBTYjfDPUr99jxZqbLOZhh9Pd.png :target: https://asciinema.org/a/FBTYjfDPUr99jxZqbLOZhh9Pd?autoplay=1&speed=2 Requirements ============ To be able to run **djoser** you have to meet following requirements: - Python (2.7, 3.4, 3.5, 3.6) - Django (1.10, 1.11) - Django REST Framework (3.7) Installation ============ Simply install using ``pip``: .. code-block:: bash $ pip install djoser And continue with the steps described at `configuration `_ guide. Documentation ============= Documentation is available to study at `http://djoser.readthedocs.io `_ and in ``docs`` directory. Contributing and development ============================ To start developing on **djoser**, clone the repository: .. code-block:: bash $ git clone git@github.com:sunscrapers/djoser.git If you are a **pipenv** user you can quickly setup testing environment by using Make commands: .. code-block:: bash $ make init $ make test Otherwise, if you cannot use Make commands, please create virtualenv and install requirements manually: .. code-block:: bash $ pip install django djangorestframework $ pip install -r requirements.txt If you are running djoser tests on Python 2.7 you also need to install **mock** library. .. code-block:: bash $ pip install mock # only on Python 2.7 $ cd testproject $ ./manage.py test If you need to run tests against all supported Python and Django versions then invoke: .. code-block:: bash $ pip install tox $ tox You can also play with test project by running following commands: .. code-block:: bash $ ./manage.py migrate $ ./manage.py runserver Similar projects ================ List of projects related to Django, REST and authentication: - `django-rest-framework-jwt `_ - `django-oauth-toolkit `_ - `django-rest-auth `_ - `django-rest-framework-digestauth `_ (not maintained) - `doac `_ (not maintained) Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Django Classifier: Framework :: Django :: 1.10 Classifier: Framework :: Django :: 1.11 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 djoser-1.1.5/setup.cfg0000644000175000017500000000004613212504022016014 0ustar nimrootnimroot00000000000000[egg_info] tag_build = tag_date = 0 djoser-1.1.5/LICENSE0000644000175000017500000000206613120707604015215 0ustar nimrootnimroot00000000000000The MIT License (MIT) Copyright (c) 2013 SUNSCRAPERS Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. djoser-1.1.5/MANIFEST.in0000644000175000017500000000014513163131753015745 0ustar nimrootnimroot00000000000000include README.md include LICENSE recursive-include djoser/templates *.html include requirements.txt djoser-1.1.5/requirements.txt0000644000175000017500000000027713177462464017514 0ustar nimrootnimroot00000000000000-e . django-templated-mail==1.0.0 djangorestframework-jwt==1.11.0 djet==0.2.1 social-auth-app-django==2.0.0 coverage==4.3.1 pytest==3.1.3 pytest-cov==2.5.1 pytest-django==3.1.2 flake8==3.4.1djoser-1.1.5/djoser.egg-info/0000755000175000017500000000000013212504022017153 5ustar nimrootnimroot00000000000000djoser-1.1.5/djoser.egg-info/PKG-INFO0000644000175000017500000001217213212504022020253 0ustar nimrootnimroot00000000000000Metadata-Version: 1.1 Name: djoser Version: 1.1.5 Summary: REST version of Django authentication system. Home-page: https://github.com/sunscrapers/djoser Author: SUNSCRAPERS Author-email: info@sunscrapers.com License: MIT Description-Content-Type: UNKNOWN Description: ====== djoser ====== .. image:: https://img.shields.io/pypi/v/djoser.svg :target: https://pypi.org/project/djoser .. image:: https://img.shields.io/travis/sunscrapers/djoser.svg :target: https://travis-ci.org/sunscrapers/djoser .. image:: https://img.shields.io/codecov/c/github/sunscrapers/djoser.svg :target: https://codecov.io/gh/sunscrapers/djoser .. image:: https://img.shields.io/scrutinizer/g/sunscrapers/djoser.svg :target: https://scrutinizer-ci.com/g/sunscrapers/djoser REST implementation of `Django `_ authentication system. **djoser** library provides a set of `Django Rest Framework `_ views to handle basic actions such as registration, login, logout, password reset and account activation. It works with `custom user model `_. Instead of reusing Django code (e.g. ``PasswordResetForm``), we reimplemented few things to fit better into `Single Page App `_ architecture. Developed by `SUNSCRAPERS `_ with passion & patience. .. image:: https://asciinema.org/a/FBTYjfDPUr99jxZqbLOZhh9Pd.png :target: https://asciinema.org/a/FBTYjfDPUr99jxZqbLOZhh9Pd?autoplay=1&speed=2 Requirements ============ To be able to run **djoser** you have to meet following requirements: - Python (2.7, 3.4, 3.5, 3.6) - Django (1.10, 1.11) - Django REST Framework (3.7) Installation ============ Simply install using ``pip``: .. code-block:: bash $ pip install djoser And continue with the steps described at `configuration `_ guide. Documentation ============= Documentation is available to study at `http://djoser.readthedocs.io `_ and in ``docs`` directory. Contributing and development ============================ To start developing on **djoser**, clone the repository: .. code-block:: bash $ git clone git@github.com:sunscrapers/djoser.git If you are a **pipenv** user you can quickly setup testing environment by using Make commands: .. code-block:: bash $ make init $ make test Otherwise, if you cannot use Make commands, please create virtualenv and install requirements manually: .. code-block:: bash $ pip install django djangorestframework $ pip install -r requirements.txt If you are running djoser tests on Python 2.7 you also need to install **mock** library. .. code-block:: bash $ pip install mock # only on Python 2.7 $ cd testproject $ ./manage.py test If you need to run tests against all supported Python and Django versions then invoke: .. code-block:: bash $ pip install tox $ tox You can also play with test project by running following commands: .. code-block:: bash $ ./manage.py migrate $ ./manage.py runserver Similar projects ================ List of projects related to Django, REST and authentication: - `django-rest-framework-jwt `_ - `django-oauth-toolkit `_ - `django-rest-auth `_ - `django-rest-framework-digestauth `_ (not maintained) - `doac `_ (not maintained) Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Django Classifier: Framework :: Django :: 1.10 Classifier: Framework :: Django :: 1.11 Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 djoser-1.1.5/djoser.egg-info/dependency_links.txt0000644000175000017500000000000113212504022023221 0ustar nimrootnimroot00000000000000 djoser-1.1.5/djoser.egg-info/SOURCES.txt0000644000175000017500000000145613212504022021045 0ustar nimrootnimroot00000000000000LICENSE MANIFEST.in README.rst requirements.txt setup.cfg setup.py djoser/__init__.py djoser/compat.py djoser/conf.py djoser/constants.py djoser/email.py djoser/serializers.py djoser/signals.py djoser/utils.py djoser/views.py djoser.egg-info/PKG-INFO djoser.egg-info/SOURCES.txt djoser.egg-info/dependency_links.txt djoser.egg-info/requires.txt djoser.egg-info/top_level.txt djoser/social/__init__.py djoser/social/serializers.py djoser/social/urls.py djoser/social/views.py djoser/social/backends/__init__.py djoser/social/backends/facebook.py djoser/social/token/__init__.py djoser/social/token/jwt.py djoser/templates/email/activation.html djoser/templates/email/confirmation.html djoser/templates/email/password_reset.html djoser/urls/__init__.py djoser/urls/authtoken.py djoser/urls/base.py djoser/urls/jwt.pydjoser-1.1.5/djoser.egg-info/top_level.txt0000644000175000017500000000011413212504022021701 0ustar nimrootnimroot00000000000000djoser djoser/social djoser/social/backends djoser/social/token djoser/urls djoser-1.1.5/djoser.egg-info/requires.txt0000644000175000017500000000002613212504022021551 0ustar nimrootnimroot00000000000000django-templated-mail djoser-1.1.5/setup.py0000644000175000017500000000235713212503600015715 0ustar nimrootnimroot00000000000000#!/usr/bin/env python import os from setuptools import setup with open('README.rst', 'r') as f: readme = f.read() def get_packages(package): return [ dirpath for dirpath, dirnames, filenames in os.walk(package) if os.path.exists(os.path.join(dirpath, '__init__.py')) ] setup( name='djoser', version='1.1.5', packages=get_packages('djoser'), license='MIT', author='SUNSCRAPERS', description='REST version of Django authentication system.', author_email='info@sunscrapers.com', long_description=readme, install_requires=['django-templated-mail'], include_package_data=True, url='https://github.com/sunscrapers/djoser', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Framework :: Django', 'Framework :: Django :: 1.10', 'Framework :: Django :: 1.11', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', ] )