From 22d932fafc3fa9af5a8f5eeab908688eaeb177ea Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 28 Jun 2020 21:17:30 -0700 Subject: [PATCH] /-/logout page for logging out of ds_actor cookie Refs #840 --- datasette/app.py | 4 ++++ datasette/templates/logout.html | 25 +++++++++++++++++++++++++ datasette/views/special.py | 17 +++++++++++++++++ docs/authentication.rst | 8 ++++++++ tests/test_auth.py | 29 +++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+) create mode 100644 datasette/templates/logout.html diff --git a/datasette/app.py b/datasette/app.py index 90abc373..ceaf36f2 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -33,6 +33,7 @@ from .views.special import ( JsonDataView, PatternPortfolioView, AuthTokenView, + LogoutView, PermissionsDebugView, MessagesDebugView, ) @@ -853,6 +854,9 @@ class Datasette: add_route( AuthTokenView.as_view(self), r"/-/auth-token$", ) + add_route( + LogoutView.as_view(self), r"/-/logout$", + ) add_route( PermissionsDebugView.as_view(self), r"/-/permissions$", ) diff --git a/datasette/templates/logout.html b/datasette/templates/logout.html new file mode 100644 index 00000000..08141962 --- /dev/null +++ b/datasette/templates/logout.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block title %}Log out{% endblock %} + +{% block nav %} +

+ home +

+ {{ super() }} +{% endblock %} + +{% block content %} + +

Log out

+ +

You are logged in as {{ actor.id or actor }}

+ +
+
+ + +
+
+ +{% endblock %} diff --git a/datasette/views/special.py b/datasette/views/special.py index 6c378995..374ca9f2 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -72,6 +72,23 @@ class AuthTokenView(BaseView): return Response("Invalid token", status=403) +class LogoutView(BaseView): + name = "logout" + + def __init__(self, datasette): + self.ds = datasette + + async def get(self, request): + if not request.actor: + return Response.redirect("/") + return await self.render(["logout.html"], request, {"actor": request.actor},) + + async def post(self, request): + response = Response.redirect("/") + response.set_cookie("ds_actor", "", expires=0, max_age=0) + return response + + class PermissionsDebugView(BaseView): name = "permissions_debug" diff --git a/docs/authentication.rst b/docs/authentication.rst index 2a6fa9bc..a2b1276b 100644 --- a/docs/authentication.rst +++ b/docs/authentication.rst @@ -404,6 +404,14 @@ The resulting cookie will encode data that looks something like this: "e": "1jjSji" } + +.. _LogoutView: + +The /-/logout page +------------------ + +The page at ``/-/logout`` provides the ability to log out of a ``ds_actor`` cookie authentication session. + .. _permissions: Built-in permissions diff --git a/tests/test_auth.py b/tests/test_auth.py index bb4bee4b..96a8bef9 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -47,3 +47,32 @@ def test_actor_cookie_that_expires(app_client, offset, expected): ) response = app_client.get("/", cookies={"ds_actor": cookie}) assert expected == app_client.ds._last_request.scope["actor"] + + +def test_logout(app_client): + response = app_client.get( + "/-/logout", cookies={"ds_actor": app_client.actor_cookie({"id": "test"})} + ) + assert 200 == response.status + assert "

You are logged in as test

" in response.text + # Actors without an id get full serialization + response2 = app_client.get( + "/-/logout", cookies={"ds_actor": app_client.actor_cookie({"name2": "bob"})} + ) + assert 200 == response2.status + assert ( + "

You are logged in as {'name2': 'bob'}

" + in response2.text + ) + # If logged out you get a redirect to / + response3 = app_client.get("/-/logout", allow_redirects=False) + assert 302 == response3.status + # A POST to that page should log the user out + response4 = app_client.post( + "/-/logout", + csrftoken_from=True, + cookies={"ds_actor": app_client.actor_cookie({"id": "test"})}, + allow_redirects=False, + ) + assert {"ds_actor": ""} == response4.cookies + assert 302 == response4.status