diff --git a/lemonauth/jinja2/lemonauth/tokens.html b/lemonauth/jinja2/lemonauth/tokens.html new file mode 100644 index 0000000..e5bd32c --- /dev/null +++ b/lemonauth/jinja2/lemonauth/tokens.html @@ -0,0 +1,68 @@ +{% extends 'lemoncurry/layout.html' %} + +{% block main %} +
+
+ {% for _, c in clients | dictsort %} +
+ {% if c.app.logo %} + {{ c.app.name[0] }} + + {% endif %} +
+
+ {{ c.app.name[0] if c.app else (c.id | friendly_url) }} + + {{ c.count }} + {{ 'tokens' if c.count > 1 else 'token' }} + +
+ +
+ {{ c.id }} +
+ +

this client has access to the scopes:

+
+ +
    + {% for scope in c.scopes %} +
  • {{ scope }}
  • + {% endfor %} +
+ + +
+ {% endfor %} +
+
+{% endblock %} + +{% block foot %} + +{% endblock %} diff --git a/lemonauth/urls.py b/lemonauth/urls.py index 0e4d9a7..e98672a 100644 --- a/lemonauth/urls.py +++ b/lemonauth/urls.py @@ -8,4 +8,6 @@ urlpatterns = [ path('indie', views.IndieView.as_view(), name='indie'), path('indie/approve', views.indie_approve, name='indie_approve'), path('token', views.TokenView.as_view(), name='token'), + path('tokens', views.TokensListView.as_view(), name='tokens'), + path('tokens/', views.TokensRevokeView.as_view(), name='tokens_revoke'), ] diff --git a/lemonauth/views/__init__.py b/lemonauth/views/__init__.py index 776cc79..1a73e02 100644 --- a/lemonauth/views/__init__.py +++ b/lemonauth/views/__init__.py @@ -2,3 +2,4 @@ from .login import login from .logout import logout from .indie import IndieView, approve as indie_approve from .token import TokenView +from .tokens import TokensListView, TokensRevokeView diff --git a/lemonauth/views/tokens/__init__.py b/lemonauth/views/tokens/__init__.py new file mode 100644 index 0000000..cfcdba5 --- /dev/null +++ b/lemonauth/views/tokens/__init__.py @@ -0,0 +1,2 @@ +from .list import TokensListView +from .revoke import TokensRevokeView diff --git a/lemonauth/views/tokens/list.py b/lemonauth/views/tokens/list.py new file mode 100644 index 0000000..dba4eb3 --- /dev/null +++ b/lemonauth/views/tokens/list.py @@ -0,0 +1,41 @@ +from django.contrib.auth.mixins import LoginRequiredMixin +from django.views.generic import TemplateView +from typing import Dict, Optional, Set +from lemoncurry.requests import mf2 + + +class ClientsDict(dict): + def __missing__(self, client_id): + self[client_id] = Client(client_id) + return self[client_id] + + +class Client: + id: str + count: int + scopes: Set[str] + app: Optional[Dict[str, str]] + + def __init__(self, client_id): + self.id = client_id + self.count = 0 + self.scopes = set() + apps = mf2(self.id).to_dict(filter_by_type='h-x-app') + try: + self.app = apps[0]['properties'] + except IndexError: + self.app = None + + +class TokensListView(LoginRequiredMixin, TemplateView): + template_name = 'lemonauth/tokens.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + clients = ClientsDict() + for token in self.request.user.token_set.all(): + client = clients[token.client_id] + client.count += 1 + client.scopes |= set(token.scope.split(' ')) + context.update({'clients': clients, 'title': 'tokens'}) + return context diff --git a/lemonauth/views/tokens/revoke.py b/lemonauth/views/tokens/revoke.py new file mode 100644 index 0000000..e581b4a --- /dev/null +++ b/lemonauth/views/tokens/revoke.py @@ -0,0 +1,11 @@ +from django.http import HttpResponse +from django.contrib.auth.mixins import LoginRequiredMixin +from django.views import View + +from ...models import Token + + +class TokensRevokeView(LoginRequiredMixin, View): + def delete(self, request, client_id: str): + Token.objects.filter(client_id=client_id).delete() + return HttpResponse(status=204) diff --git a/lemoncurry/jinja2/lemoncurry/layout.html b/lemoncurry/jinja2/lemoncurry/layout.html index e2c9769..09800e4 100644 --- a/lemoncurry/jinja2/lemoncurry/layout.html +++ b/lemoncurry/jinja2/lemoncurry/layout.html @@ -61,6 +61,12 @@