diff --git a/docs/api_endpoints.rst b/docs/api_endpoints.rst index 21cb62f..05e8691 100644 --- a/docs/api_endpoints.rst +++ b/docs/api_endpoints.rst @@ -16,7 +16,7 @@ Basic - email -- /rest-auth/password/reset/confim/ (POST) +- /rest-auth/password/reset/confirm/ (POST) - uid - token diff --git a/docs/demo.rst b/docs/demo.rst index 70659b8..ed74750 100644 --- a/docs/demo.rst +++ b/docs/demo.rst @@ -1,7 +1,9 @@ Demo project ============ -To run demo project (ideally in virtualenv): +The idea of creating demo project was to show how you can potentially use +django-rest-auth app with jQuery on frontend. +Do these steps to make it running (ideally in virtualenv). .. code-block:: python diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 0000000..6e098b8 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,17 @@ +FAQ +=== + +1. Why account_confirm_email url is defined but it is not usable? + + In /rest_auth/registration/urls.py we can find something like this: + + .. code-block:: python + + url(r'^account-confirm-email/(?P\w+)/$', TemplateView.as_view(), + name='account_confirm_email'), + + This url is used by django-allauth. Empty TemplateView is defined just to allow reverse() call inside app - when email with verification link is being sent. + + You should override this view/url to handle it in your API client somehow and then, send post to /verify-email/ endpoint with proper key. + If you don't want to use API on that step, then just use ConfirmEmailView view from: + djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190 diff --git a/docs/index.rst b/docs/index.rst index 6f9547e..2044cb6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,6 +22,7 @@ Contents API endpoints Configuration Demo project + FAQ Changelog diff --git a/docs/introduction.rst b/docs/introduction.rst index 88da8f3..191abb5 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -14,7 +14,21 @@ Features * Password reset via e-mail * Social Media authentication + Apps structure -------------- + * ``rest_auth`` has basic auth functionality like login, logout, password reset and password change * ``rest_auth.registration`` has logic related with registration and social media authentication + + +Angular app +----------- + +- Tivix has also created angular module which uses API endpoints from this app - `angular-django-registration-auth `_ + + +Demo project +------------ + +- You can also check our :doc:`Demo Project ` which is using jQuery on frontend. diff --git a/rest_auth/app_settings.py b/rest_auth/app_settings.py index 349864d..d868175 100644 --- a/rest_auth/app_settings.py +++ b/rest_auth/app_settings.py @@ -37,5 +37,3 @@ PasswordChangeSerializer = import_callable( serializers.get('PASSWORD_CHANGE_SERIALIZER', DefaultPasswordChangeSerializer) ) - - diff --git a/rest_auth/registration/urls.py b/rest_auth/registration/urls.py index f9df934..b01c067 100644 --- a/rest_auth/registration/urls.py +++ b/rest_auth/registration/urls.py @@ -7,7 +7,7 @@ urlpatterns = patterns('', url(r'^$', Register.as_view(), name='rest_register'), url(r'^verify-email/$', VerifyEmail.as_view(), name='rest_verify_email'), - # These two views are used in django-allauth and empty TemplateView were + # This url is used by django-allauth and empty TemplateView is # defined just to allow reverse() call inside app, for example when email # with verification link is being sent, then it's required to render email # content. @@ -18,8 +18,6 @@ urlpatterns = patterns('', # If you don't want to use API on that step, then just use ConfirmEmailView # view from: # djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190 - url(r'^account-email-verification-sent/$', TemplateView.as_view(), - name='account_email_verification_sent'), url(r'^account-confirm-email/(?P\w+)/$', TemplateView.as_view(), name='account_confirm_email'), ) diff --git a/rest_auth/registration/views.py b/rest_auth/registration/views.py index 6ffa776..7fb2631 100644 --- a/rest_auth/registration/views.py +++ b/rest_auth/registration/views.py @@ -16,6 +16,13 @@ class Register(APIView, SignupView): permission_classes = (AllowAny,) user_serializer_class = UserDetailsSerializer + allowed_methods = ('POST', 'OPTIONS', 'HEAD') + + def get(self, *args, **kwargs): + return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) + + def put(self, *args, **kwargs): + return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) def form_valid(self, form): self.user = form.save(self.request) @@ -45,6 +52,10 @@ class Register(APIView, SignupView): class VerifyEmail(APIView, ConfirmEmailView): permission_classes = (AllowAny,) + allowed_methods = ('POST', 'OPTIONS', 'HEAD') + + def get(self, *args, **kwargs): + return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) def post(self, request, *args, **kwargs): self.kwargs['key'] = self.request.DATA.get('key', '') diff --git a/rest_auth/serializers.py b/rest_auth/serializers.py index 0fdc5f4..24dedef 100644 --- a/rest_auth/serializers.py +++ b/rest_auth/serializers.py @@ -121,14 +121,31 @@ class PasswordResetConfirmSerializer(serializers.Serializer): class PasswordChangeSerializer(serializers.Serializer): + old_password = serializers.CharField(max_length=128) new_password1 = serializers.CharField(max_length=128) new_password2 = serializers.CharField(max_length=128) set_password_form_class = SetPasswordForm + def __init__(self, *args, **kwargs): + self.old_password_field_enabled = getattr(settings, + 'OLD_PASSWORD_FIELD_ENABLED', False) + super(PasswordChangeSerializer, self).__init__(*args, **kwargs) + + if not self.old_password_field_enabled: + self.fields.pop('old_password') + + self.request = self.context.get('request') + self.user = self.request.user + + def validate_old_password(self, attrs, source): + if self.old_password_field_enabled and \ + not self.user.check_password(attrs.get(source, '')): + raise serializers.ValidationError('Invalid password') + return attrs + def validate(self, attrs): - request = self.context.get('request') - self.set_password_form = self.set_password_form_class(user=request.user, + self.set_password_form = self.set_password_form_class(user=self.user, data=attrs) if not self.set_password_form.is_valid(): diff --git a/rest_auth/tests.py b/rest_auth/tests.py index 4f66124..1134940 100644 --- a/rest_auth/tests.py +++ b/rest_auth/tests.py @@ -248,6 +248,40 @@ class APITestCase1(TestCase, BaseAPITestCase): # send empty payload self.post(self.password_change_url, data={}, status_code=400) + @override_settings(OLD_PASSWORD_FIELD_ENABLED=True) + def test_password_change_with_old_password(self): + login_payload = { + "username": self.USERNAME, + "password": self.PASS + } + User.objects.create_user(self.USERNAME, '', self.PASS) + self.post(self.login_url, data=login_payload, status_code=200) + self.token = self.response.json['key'] + + new_password_payload = { + "old_password": "%s!" % self.PASS, # wrong password + "new_password1": "new_person", + "new_password2": "new_person" + } + self.post(self.password_change_url, data=new_password_payload, + status_code=400) + + new_password_payload = { + "old_password": self.PASS, + "new_password1": "new_person", + "new_password2": "new_person" + } + self.post(self.password_change_url, data=new_password_payload, + status_code=200) + + # user should not be able to login using old password + self.post(self.login_url, data=login_payload, status_code=400) + + # new password should work + login_payload['password'] = new_password_payload['new_password1'] + self.post(self.login_url, data=login_payload, status_code=200) + + def test_password_reset(self): user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASS) diff --git a/rest_auth/views.py b/rest_auth/views.py index a27c61d..0f18fd5 100644 --- a/rest_auth/views.py +++ b/rest_auth/views.py @@ -6,8 +6,6 @@ from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.generics import GenericAPIView from rest_framework.permissions import IsAuthenticated, AllowAny -from rest_framework.authentication import SessionAuthentication, \ - TokenAuthentication from rest_framework.authtoken.models import Token from rest_framework.generics import RetrieveUpdateAPIView @@ -32,10 +30,6 @@ class Login(GenericAPIView): token_model = Token response_serializer = TokenSerializer - def get_serializer(self): - return self.serializer_class(data=self.request.DATA, - context={'request': self.request, 'view': self}) - def login(self): self.user = self.serializer.object['user'] self.token, created = self.token_model.objects.get_or_create( @@ -52,7 +46,7 @@ class Login(GenericAPIView): status=status.HTTP_400_BAD_REQUEST) def post(self, request, *args, **kwargs): - self.serializer = self.get_serializer() + self.serializer = self.get_serializer(data=self.request.DATA) if not self.serializer.is_valid(): return self.get_error_response() self.login() @@ -67,7 +61,7 @@ class Logout(APIView): Accepts/Returns nothing. """ - permissions_classes = (AllowAny,) + permission_classes = (AllowAny,) def post(self, request): try: