Ver Fonte

feat(api): add logout endpoint (for current token)

Peter Thomassen há 5 anos atrás
pai
commit
65d0cdd8ee

+ 15 - 0
api/desecapi/tests/test_user_management.py

@@ -47,6 +47,9 @@ class UserManagementClient(APIClient):
             'password': password,
         })
 
+    def logout(self, token):
+        return self.post(reverse('v1:logout'), HTTP_AUTHORIZATION=f'Token {token}')
+
     def reset_password(self, email):
         return self.post(reverse('v1:account-reset-password'), {
             'email': email,
@@ -98,6 +101,9 @@ class UserManagementTestCase(DesecTestCase, PublicSuffixMockMixin):
         token = response.data.get('token')
         return token, response
 
+    def logout(self, token):
+        return self.client.logout(token)
+
     def reset_password(self, email):
         return self.client.reset_password(email)
 
@@ -214,6 +220,9 @@ class UserManagementTestCase(DesecTestCase, PublicSuffixMockMixin):
             status_code=status.HTTP_200_OK
         )
 
+    def assertLogoutSuccessResponse(self, response):
+        return self.assertStatus(response, status.HTTP_204_NO_CONTENT)
+
     def assertRegistrationFailurePasswordRequiredResponse(self, response):
         self.assertContains(
             response=response,
@@ -418,6 +427,11 @@ class UserManagementTestCase(DesecTestCase, PublicSuffixMockMixin):
         self.assertLoginSuccessResponse(response)
         return token
 
+    def _test_logout(self):
+        response = self.logout(self.token)
+        self.assertLogoutSuccessResponse(response)
+        return response
+
     def _test_reset_password(self, email, new_password=None, **kwargs):
         new_password = new_password or self.random_password()
         self.assertResetPasswordSuccessResponse(self.reset_password(email))
@@ -455,6 +469,7 @@ class UserLifeCycleTestCase(UserManagementTestCase):
         mail.outbox = []
         self.token = self._test_login()
         email = self._test_change_email()
+        self._test_logout()
         self._test_delete_account(email, self.password)
 
 

+ 1 - 0
api/desecapi/urls/version_1.py

@@ -14,6 +14,7 @@ auth_urls = [
     path('account/change-email/', views.AccountChangeEmailView.as_view(), name='account-change-email'),
     path('account/reset-password/', views.AccountResetPasswordView.as_view(), name='account-reset-password'),
     path('login/', views.AccountLoginView.as_view(), name='login'),
+    path('logout/', views.AccountLogoutView.as_view(), name='logout'),
 
     # Token management
     path('tokens/', include(tokens_router.urls)),

+ 13 - 0
api/desecapi/views.py

@@ -205,6 +205,7 @@ class Root(APIView):
                     'change-email': reverse('account-change-email', request=request),
                     'reset-password': reverse('account-reset-password', request=request),
                 },
+                'logout': reverse('logout', request=request),
                 'tokens': reverse('token-list', request=request),
                 'domains': reverse('domain-list', request=request),
             }
@@ -451,6 +452,18 @@ class AccountLoginView(generics.GenericAPIView):
         return Response(data)
 
 
+class AccountLogoutView(generics.GenericAPIView, mixins.DestroyModelMixin):
+    authentication_classes = (auth.TokenAuthentication,)
+    permission_classes = (IsAuthenticated,)
+
+    def get_object(self):
+        # self.request.auth contains the hashed key as it is stored in the database
+        return models.Token.objects.get(key=self.request.auth)
+
+    def post(self, request, *args, **kwargs):
+        return self.destroy(request, *args, **kwargs)
+
+
 class AccountChangeEmailView(generics.GenericAPIView):
     authentication_classes = (auth.EmailPasswordPayloadAuthentication,)
     permission_classes = (IsAuthenticated,)

+ 12 - 3
docs/authentication.rst

@@ -285,7 +285,13 @@ Conflict`` and not delete your account.
 Log Out
 ```````
 
-To invalidate an authentication token (log out), please see `Delete Tokens`_.
+To invalidate an authentication token (log out), send a POST request to the
+the log out endpoint::
+
+    curl -X POST https://desec.io/api/v1/auth/logout/ \
+        --header "Authorization: Token i-T3b1h_OI-H9ab8tRS98stGtURe"
+
+To delete other tokens based on their ID, see `Delete Tokens`_.
 
 
 Security Considerations
@@ -425,7 +431,7 @@ will reply with ``201 Created`` and the created token in the response body::
 Delete Tokens
 `````````````
 
-To delete an existing token via the token management endpoints, issue a
+To delete an existing token by its ID via the token management endpoints, issue a
 ``DELETE`` request on the token's endpoint, replacing ``:id`` with the
 token ``id`` value::
 
@@ -434,8 +440,11 @@ token ``id`` value::
 
 The server will reply with ``204 No Content``, even if the token was not found.
 
+If you do not have the token UUID, but you do have the token value itself, you
+can use the `Log Out`_ endpoint to delete it.
+
 Note that, for now, all tokens have equal power -- every token can authorize
-any action. We may implement specialized tokens in the future.
+any action. We are planning to implement scoped tokens in the future.
 
 
 Token Security Considerations

+ 2 - 0
docs/endpoint-reference.rst

@@ -19,6 +19,8 @@ for `User Registration and Management`_.
 +------------------------------------------------+------------+---------------------------------------------+
 | ...\ ``/auth/login/``                          | ``POST``   | Log in and request authentication token     |
 +------------------------------------------------+------------+---------------------------------------------+
+| ...\ ``/auth/logout/``                         | ``POST``   | Log out (= delete current token)            |
++------------------------------------------------+------------+---------------------------------------------+
 | ...\ ``/auth/tokens/``                         | ``GET``    | Retrieve all current tokens                 |
 |                                                +------------+---------------------------------------------+
 |                                                | ``POST``   | Create new token                            |